Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 3 additions & 25 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,26 +1,4 @@
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
/kawa.xcarchive
Kawa.app
Carthage/
.DS_Store

# Carthage
#
Carthage/Checkouts
Carthage/Build
DerivedData/
kawa.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
1 change: 0 additions & 1 deletion Cartfile

This file was deleted.

1 change: 0 additions & 1 deletion Cartfile.resolved

This file was deleted.

19 changes: 5 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,15 @@ sources like [CJKV](https://en.wikipedia.org/wiki/CJK_characters).

## Development

We use [Carthage](https://github.com/Carthage/Carthage) as a dependency manager.
You can find the latest releases of Carthage [here](https://github.com/Carthage/Carthage/releases),
or just install it with [Homebrew](http://brew.sh).
Dependencies are fetched with Swift Package Manager. Open `kawa.xcodeproj` in
Xcode and it will resolve packages automatically, or resolve/build from the
command line:

```bash
$ brew update
$ brew install carthage
xcodebuild -resolvePackageDependencies
xcodebuild -scheme kawa -configuration Debug
```

To clone the Git repository of Kawa and install dependencies:

```bash
$ git clone git@github.com:utatti/kawa.git
$ carthage bootstrap
```

After dependency installation, open the project with Xcode.

## License

Kawa is released under the [MIT License](LICENSE).
53 changes: 31 additions & 22 deletions kawa.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,11 @@
CC6D8ED21B654099005682C0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CC6D8ED11B654099005682C0 /* Images.xcassets */; };
CC6D8ED51B654099005682C0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC6D8ED31B654099005682C0 /* Main.storyboard */; };
CC6D8EEB1B654143005682C0 /* InputSourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6D8EEA1B654143005682C0 /* InputSourceManager.swift */; };
CC9AAD2B1B71AA5400626F65 /* MASShortcut.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC9AAD2A1B71AA5400626F65 /* MASShortcut.framework */; };
CC9AAD2C1B71AA5400626F65 /* MASShortcut.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CC9AAD2A1B71AA5400626F65 /* MASShortcut.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CCB5F9A52C3A1B3E00D43F0A /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB5F9A42C3A1B3E00D43F0A /* NotificationManager.swift */; };
CCB5F9B62C3A2A8D00D43F0A /* MASShortcut in Frameworks */ = {isa = PBXBuildFile; productRef = CCB5F9B72C3A2A8D00D43F0A /* MASShortcut */; };
EE92E8FD1F6CC98E00559B7C /* TISInputSource+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE92E8FC1F6CC98E00559B7C /* TISInputSource+Additions.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
CC9AAD2D1B71AA5400626F65 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
CC9AAD2C1B71AA5400626F65 /* MASShortcut.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
CC1FEF6D1B685E9A005E9BC8 /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = "<group>"; };
CC1FEF6E1B685FD3005E9BC8 /* ShortcutCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutCellView.swift; sourceTree = "<group>"; };
Expand All @@ -49,8 +35,8 @@
CC6D8ECD1B654099005682C0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
CC6D8ED11B654099005682C0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
CC6D8EEA1B654143005682C0 /* InputSourceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSourceManager.swift; sourceTree = "<group>"; };
CC9AAD2A1B71AA5400626F65 /* MASShortcut.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MASShortcut.framework; path = Carthage/Build/Mac/MASShortcut.framework; sourceTree = "<group>"; };
DE8850D624B633EC002964E4 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/Main.storyboard; sourceTree = "<group>"; };
CCB5F9A42C3A1B3E00D43F0A /* NotificationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
EE92E8FC1F6CC98E00559B7C /* TISInputSource+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TISInputSource+Additions.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand All @@ -59,7 +45,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CC9AAD2B1B71AA5400626F65 /* MASShortcut.framework in Frameworks */,
CCB5F9B62C3A2A8D00D43F0A /* MASShortcut in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -71,7 +57,6 @@
children = (
CC6D8ECA1B654099005682C0 /* kawa */,
CC6D8EC91B654099005682C0 /* Products */,
CC9AAD2A1B71AA5400626F65 /* MASShortcut.framework */,
);
sourceTree = "<group>";
};
Expand All @@ -93,6 +78,7 @@
CC69E0771B66415300A92F58 /* ShortcutViewController.swift */,
CC1FEF6E1B685FD3005E9BC8 /* ShortcutCellView.swift */,
CC6D8EEA1B654143005682C0 /* InputSourceManager.swift */,
CCB5F9A42C3A1B3E00D43F0A /* NotificationManager.swift */,
CC4181671B6CB3CB007DA794 /* PreferencesViewController.swift */,
CC6D8ED11B654099005682C0 /* Images.xcassets */,
CC6D8ED31B654099005682C0 /* Main.storyboard */,
Expand Down Expand Up @@ -121,13 +107,15 @@
CC6D8EC41B654099005682C0 /* Sources */,
CC6D8EC51B654099005682C0 /* Frameworks */,
CC6D8EC61B654099005682C0 /* Resources */,
CC9AAD2D1B71AA5400626F65 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = kawa;
packageProductDependencies = (
CCB5F9B72C3A2A8D00D43F0A /* MASShortcut */,
);
productName = kawa;
productReference = CC6D8EC81B654099005682C0 /* Kawa.app */;
productType = "com.apple.product-type.application";
Expand Down Expand Up @@ -159,6 +147,9 @@
productRefGroup = CC6D8EC91B654099005682C0 /* Products */;
projectDirPath = "";
projectRoot = "";
packageReferences = (
CCB5F9B82C3A2A8D00D43F0A /* XCRemoteSwiftPackageReference "MASShortcut" */,
);
targets = (
CC6D8EC71B654099005682C0 /* kawa */,
);
Expand Down Expand Up @@ -189,6 +180,7 @@
CC6D8ECE1B654099005682C0 /* AppDelegate.swift in Sources */,
CC4065BE1B6D334B000E1E87 /* PermanentStorage.swift in Sources */,
CC6D8EEB1B654143005682C0 /* InputSourceManager.swift in Sources */,
CCB5F9A52C3A1B3E00D43F0A /* NotificationManager.swift in Sources */,
EE92E8FD1F6CC98E00559B7C /* TISInputSource+Additions.swift in Sources */,
CC69E0781B66415300A92F58 /* ShortcutViewController.swift in Sources */,
);
Expand All @@ -207,6 +199,25 @@
};
/* End PBXVariantGroup section */

/* Begin XCRemoteSwiftPackageReference section */
CCB5F9B82C3A2A8D00D43F0A /* XCRemoteSwiftPackageReference "MASShortcut" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/shpakovski/MASShortcut.git";
requirement = {
branch = master;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
CCB5F9B72C3A2A8D00D43F0A /* MASShortcut */ = {
isa = XCSwiftPackageProductDependency;
package = CCB5F9B82C3A2A8D00D43F0A /* XCRemoteSwiftPackageReference "MASShortcut" */;
productName = MASShortcut;
};
/* End XCSwiftPackageProductDependency section */

/* Begin XCBuildConfiguration section */
CC6D8EE21B654099005682C0 /* Debug */ = {
isa = XCBuildConfiguration;
Expand Down Expand Up @@ -321,7 +332,6 @@
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
);
INFOPLIST_FILE = kawa/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -344,7 +354,6 @@
COMBINE_HIDPI_IMAGES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Carthage/Build/Mac",
);
INFOPLIST_FILE = kawa/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
4 changes: 4 additions & 0 deletions kawa/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if PermanentStorage.launchedForTheFirstTime {
PermanentStorage.launchedForTheFirstTime = false
}

if PermanentStorage.showsNotification {
NotificationManager.requestAuthorizationIfNeeded { _ in }
}
}

func applicationDidBecomeActive(_ notification: Notification) {
Expand Down
2 changes: 1 addition & 1 deletion kawa/BridgingHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
#define kawa_BridgingHeader_h

#import <Cocoa/Cocoa.h>
#import <MASShortcut/Shortcut.h>
@import MASShortcut;

#endif
5 changes: 3 additions & 2 deletions kawa/InputSourceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ extension InputSource: Equatable {

extension InputSource {
static var sources: [InputSource] {
let inputSourceNSArray = TISCreateInputSourceList(nil, false).takeRetainedValue() as NSArray
let inputSourceList = inputSourceNSArray as! [TISInputSource]
guard let unmanagedList = TISCreateInputSourceList(nil, false) else { return [] }
let inputSourceNSArray = unmanagedList.takeRetainedValue() as NSArray
guard let inputSourceList = inputSourceNSArray as? [TISInputSource] else { return [] }

return inputSourceList
.filter {
Expand Down
67 changes: 67 additions & 0 deletions kawa/NotificationManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import AppKit
import Foundation
import UserNotifications

enum NotificationManager {
static func requestAuthorizationIfNeeded(completion: @escaping (Bool) -> Void) {
let center = UNUserNotificationCenter.current()
center.getNotificationSettings { settings in
switch settings.authorizationStatus {
case .authorized, .provisional, .ephemeral:
completion(true)
case .notDetermined:
center.requestAuthorization(options: [.alert, .sound]) { granted, _ in
completion(granted)
}
case .denied:
completion(false)
@unknown default:
completion(false)
}
}
}

static func deliver(_ message: String, icon: NSImage?) {
requestAuthorizationIfNeeded { granted in
guard granted else { return }

let content = UNMutableNotificationContent()
content.body = message

if let attachment = icon.flatMap(createAttachment(from:)) {
content.attachments = [attachment]
}

let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil
)

UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
}

private static func createAttachment(from image: NSImage) -> UNNotificationAttachment? {
guard let url = writeImageToTemporaryLocation(image) else { return nil }
return try? UNNotificationAttachment(identifier: "kawa-icon", url: url, options: nil)
}

private static func writeImageToTemporaryLocation(_ image: NSImage) -> URL? {
guard
let tiff = image.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: tiff),
let pngData = bitmap.representation(using: .png, properties: [:])
else { return nil }

let url = FileManager.default.temporaryDirectory
.appendingPathComponent("kawa-icon-\(UUID().uuidString).png")

do {
try pngData.write(to: url)
return url
} catch {
return nil
}
}
}
25 changes: 11 additions & 14 deletions kawa/PermanentStorage.swift
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import Cocoa

class PermanentStorage {
private static func object<T>(forKey key: StorageKey, withDefault defaultValue: T) -> T {
if let val = UserDefaults.standard.object(forKey: key.rawValue) as? T {
return val
} else {
return defaultValue
}
}

private static func set<T>(_ value: T, forKey key: StorageKey) {
UserDefaults.standard.set((value as AnyObject), forKey: key.rawValue)
UserDefaults.standard.synchronize()
}
private static let defaults = UserDefaults.standard

private enum StorageKey: String {
case showsNotification = "show-notification"
case launchedForTheFirstTime = "launched-for-the-first-time"
}

private static func bool(forKey key: StorageKey, default defaultValue: Bool) -> Bool {
return defaults.object(forKey: key.rawValue) as? Bool ?? defaultValue
}

private static func set(_ value: Bool, forKey key: StorageKey) {
defaults.set(value, forKey: key.rawValue)
}

static var showsNotification: Bool {
get {
return object(forKey: .showsNotification, withDefault: false)
return bool(forKey: .showsNotification, default: false)
}
set {
set(newValue, forKey: .showsNotification)
Expand All @@ -30,7 +27,7 @@ class PermanentStorage {

static var launchedForTheFirstTime: Bool {
get {
return object(forKey: .launchedForTheFirstTime, withDefault: true)
return bool(forKey: .launchedForTheFirstTime, default: true)
}
set {
set(newValue, forKey: .launchedForTheFirstTime)
Expand Down
7 changes: 6 additions & 1 deletion kawa/PreferencesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ class PreferencesViewController: NSViewController {
}

@IBAction func showNotification(_ sender: NSButton) {
PermanentStorage.showsNotification = sender.state.boolValue
let shouldShow = sender.state.boolValue
PermanentStorage.showsNotification = shouldShow

if shouldShow {
NotificationManager.requestAuthorizationIfNeeded { _ in }
}
}
}

Expand Down
Loading