diff --git a/Demo/AppDelegate.swift b/Demo/AppDelegate.swift
new file mode 100644
index 000000000..159abc85e
--- /dev/null
+++ b/Demo/AppDelegate.swift
@@ -0,0 +1,43 @@
+//
+// AppDelegate.swift
+// Demo
+//
+// Created by mi on 2024/12/1.
+//
+
+import Cocoa
+import InputController
+
+@main
+class AppDelegate: NSObject, NSApplicationDelegate {
+ func applicationDidFinishLaunching(_ aNotification: Notification) {
+ // Insert code here to initialize your application
+
+ if GlobalContext.shared.problematicLaunchDetected() {
+ print("Problematic launch detected!")
+ let args = ["Problematic launch detected! Squirrel may be suffering a crash due to improper configuration. Revert previous modifications to see if the problem recurs."]
+ let task = Process()
+ task.executableURL = "/usr/bin/say".withCString { dir in
+ URL(fileURLWithFileSystemRepresentation: dir, isDirectory: false, relativeTo: nil)
+ }
+ task.arguments = args
+ try? task.run()
+ } else {
+ GlobalContext.shared.setupRime()
+ GlobalContext.shared.startRime(fullCheck: false)
+ GlobalContext.shared.loadSettings()
+ print("Squirrel reporting!")
+ }
+ }
+
+ func applicationWillTerminate(_ aNotification: Notification) {
+ // Insert code here to tear down your application
+ }
+
+ func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
+ return true
+ }
+
+
+}
+
diff --git a/Demo/Assets.xcassets/AccentColor.colorset/Contents.json b/Demo/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/Demo/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..3f00db43e
--- /dev/null
+++ b/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,58 @@
+{
+ "images" : [
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Demo/Assets.xcassets/Contents.json b/Demo/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/Demo/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Demo/Base.lproj/Main.storyboard b/Demo/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..bb3b5fe3a
--- /dev/null
+++ b/Demo/Base.lproj/Main.storyboard
@@ -0,0 +1,754 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demo/Demo.entitlements b/Demo/Demo.entitlements
new file mode 100644
index 000000000..0c67376eb
--- /dev/null
+++ b/Demo/Demo.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Demo/TextView+Unused.swift b/Demo/TextView+Unused.swift
new file mode 100644
index 000000000..4bbf8bee5
--- /dev/null
+++ b/Demo/TextView+Unused.swift
@@ -0,0 +1,67 @@
+//
+// Unused.swift
+// Jyutping
+//
+// Created by mi on 2024/11/3.
+//
+
+import Cocoa
+import InputMethodKit
+
+extension Client {
+ func validAttributesForMarkedText() -> [Any]! {
+ fatalError()
+ }
+
+ func selectedRange() -> NSRange {
+ fatalError()
+ }
+
+ func markedRange() -> NSRange {
+ fatalError()
+ }
+
+ func attributedSubstring(from range: NSRange) -> NSAttributedString! {
+ fatalError()
+ }
+
+ func length() -> Int {
+ fatalError()
+ }
+
+ func characterIndex(for point: NSPoint, tracking mappingMode: IMKLocationToOffsetMappingMode, inMarkedRange: UnsafeMutablePointer!) -> Int {
+ fatalError()
+ }
+
+ func overrideKeyboard(withKeyboardNamed keyboardUniqueName: String!) {
+ fatalError()
+ }
+
+ func selectMode(_ modeIdentifier: String!) {
+ fatalError()
+ }
+
+ func supportsUnicode() -> Bool {
+ fatalError()
+ }
+
+ func supportsProperty(_ property: TSMDocumentPropertyTag) -> Bool {
+ fatalError()
+ }
+
+ func string(from range: NSRange, actualRange: NSRangePointer!) -> String! {
+ fatalError()
+ }
+
+ func firstRect(forCharacterRange aRange: NSRange, actualRange: NSRangePointer!) -> NSRect {
+ fatalError()
+ }
+
+ func windowLevel() -> CGWindowLevel {
+ fatalError()
+ }
+
+ func uniqueClientIdentifierString() -> String! {
+ fatalError()
+ }
+}
diff --git a/Demo/TextView.swift b/Demo/TextView.swift
new file mode 100644
index 000000000..7eb190b8a
--- /dev/null
+++ b/Demo/TextView.swift
@@ -0,0 +1,97 @@
+//
+// TextView.swift
+// Demo
+//
+// Created by mi on 2024/11/3.
+//
+
+import Cocoa
+import InputMethodKit
+
+extension NSTextView {
+ func currentCursorRect() -> NSRect? {
+ guard let selectedRange = self.selectedRanges.first as? NSRange else {
+ return nil
+ }
+
+ var rect = NSRect.zero
+ self.layoutManager?.enumerateEnclosingRects(
+ forGlyphRange: selectedRange,
+ withinSelectedGlyphRange: selectedRange,
+ in: self.textContainer!,
+ using: { glyphRect, _ in
+ rect = glyphRect
+ }
+ )
+
+ return self.window?.convertToScreen(self.convert(rect, to: nil))
+ }
+}
+
+class Client: NSObject, IMKTextInput {
+ weak var textView: NSTextView?
+ init(textView: NSTextView?) {
+ self.textView = textView
+ }
+
+ func bundleIdentifier() -> String! {
+ Bundle.main.bundleIdentifier
+ }
+
+ func attributes(forCharacterIndex index: Int, lineHeightRectangle lineRect: UnsafeMutablePointer!) -> [AnyHashable : Any]! {
+ if let rect = textView?.currentCursorRect() {
+ lineRect.pointee = rect
+ }
+ return nil
+ }
+ var replacementRange: NSRange?
+ func setMarkedText(_ string: Any!, selectionRange: NSRange, replacementRange: NSRange) {
+ guard let textView else {
+ return
+ }
+ if let length = (string as? NSAttributedString)?.length, length == 0 {
+ if let replacementRange = self.replacementRange, replacementRange.length == 1 {
+ textView.insertText("", replacementRange: replacementRange)
+ }
+ self.replacementRange = nil
+ return
+ }
+
+ guard let firstRange = textView.selectedRanges.first?.rangeValue else {
+ return
+ }
+ if self.replacementRange == nil {
+ self.replacementRange = firstRange
+ }
+ guard let replacementRange = self.replacementRange else {
+ return
+ }
+
+ textView.setMarkedText(string as Any, selectedRange: selectionRange, replacementRange: replacementRange)
+ if let string = string as? String {
+ self.replacementRange?.length = string.count
+ } else if let string = string as? NSAttributedString {
+ self.replacementRange?.length = string.length
+ }
+ }
+
+ func insertText(_ string: Any!, replacementRange: NSRange) {
+ guard let string = string as? String, let textView = textView, let replacementRange = self.replacementRange else {
+ return
+ }
+ textView.insertText(string, replacementRange: replacementRange)
+ self.replacementRange = nil
+ }
+}
+
+class TextView: NSTextView {
+ lazy var client = Client(textView: self)
+ lazy var inputController = (NSClassFromString("SquirrelInputController") as! IMKInputController.Type).init(server: nil, delegate: nil, client: client)!
+
+ override func keyDown(with event: NSEvent) {
+ if inputController.handle(event, client: client) {
+ return
+ }
+ super.keyDown(with: event)
+ }
+}
diff --git a/Demo/ViewController.swift b/Demo/ViewController.swift
new file mode 100644
index 000000000..d3b125759
--- /dev/null
+++ b/Demo/ViewController.swift
@@ -0,0 +1,13 @@
+//
+// ViewController.swift
+// Demo
+//
+// Created by mi on 2024/11/10.
+//
+
+import Cocoa
+
+class ViewController: NSViewController {
+ @IBOutlet var textView: TextView!
+
+}
diff --git a/InputController/InputController.h b/InputController/InputController.h
new file mode 100644
index 000000000..4d95d61e1
--- /dev/null
+++ b/InputController/InputController.h
@@ -0,0 +1,19 @@
+//
+// InputController.h
+// InputController
+//
+// Created by mi on 2024/12/1.
+//
+
+#import
+
+//! Project version number for InputController.
+FOUNDATION_EXPORT double InputControllerVersionNumber;
+
+//! Project version string for InputController.
+FOUNDATION_EXPORT const unsigned char InputControllerVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import
+
+#import
+#import
diff --git a/Squirrel.xcodeproj/project.pbxproj b/Squirrel.xcodeproj/project.pbxproj
index 8f9f93f42..d6a0769b9 100644
--- a/Squirrel.xcodeproj/project.pbxproj
+++ b/Squirrel.xcodeproj/project.pbxproj
@@ -7,6 +7,20 @@
objects = {
/* Begin PBXBuildFile section */
+ 0DD1CB702CFBF51400E6C847 /* SquirrelConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39771242BED899F0093A49B /* SquirrelConfig.swift */; };
+ 0DD1CB712CFBF52200E6C847 /* SquirrelTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39771262BED9B250093A49B /* SquirrelTheme.swift */; };
+ 0DD1CB722CFBF52D00E6C847 /* SquirrelPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B397712A2BEE4C160093A49B /* SquirrelPanel.swift */; };
+ 0DD1CB732CFBF53A00E6C847 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DEE70632CFBF0FF0062CC96 /* FileManager+Extension.swift */; };
+ 0DD1CB742CFBF60700E6C847 /* SquirrelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39771282BEDAF4A0093A49B /* SquirrelView.swift */; };
+ 0DD1CB762CFBFB6500E6C847 /* librime.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 44CD640915E2633D0021234E /* librime.1.dylib */; };
+ 0DD1CBD12CFC00CE00E6C847 /* librime.1.dylib in Copy Shared Frameworks */ = {isa = PBXBuildFile; fileRef = 44CD640915E2633D0021234E /* librime.1.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+ 0DD1CBE92CFC013200E6C847 /* InputController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DEE704F2CFBEC6C0062CC96 /* InputController.framework */; };
+ 0DEE705C2CFBEDAC0062CC96 /* SquirrelInputController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38E9B942BEAFEFD0036ABEF /* SquirrelInputController.swift */; };
+ 0DEE705D2CFBEE170062CC96 /* BridgingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35D2FE72BF00839009D156B /* BridgingFunctions.swift */; };
+ 0DEE705E2CFBEE2D0062CC96 /* MacOSKeyCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B39771222BECEA150093A49B /* MacOSKeyCodes.swift */; };
+ 0DEE70642CFBF1080062CC96 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DEE70632CFBF0FF0062CC96 /* FileManager+Extension.swift */; };
+ 0DEE70662CFBF2070062CC96 /* GlobalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DEE70652CFBF2030062CC96 /* GlobalContext.swift */; };
+ 0DEE70672CFBF26E0062CC96 /* GlobalContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DEE70652CFBF2030062CC96 /* GlobalContext.swift */; };
2C6B9F9D2BCD086700E327DF /* librime-lua.dylib in Copy Rime plugins */ = {isa = PBXBuildFile; fileRef = 2C6B9F9A2BCD086700E327DF /* librime-lua.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
2C6B9F9E2BCD086700E327DF /* librime-octagram.dylib in Copy Rime plugins */ = {isa = PBXBuildFile; fileRef = 2C6B9F9B2BCD086700E327DF /* librime-octagram.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
2C6B9F9F2BCD086700E327DF /* librime-predict.dylib in Copy Rime plugins */ = {isa = PBXBuildFile; fileRef = 2C6B9F9C2BCD086700E327DF /* librime-predict.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
@@ -71,6 +85,13 @@
7B5488C01D2DACDF0056A1BE /* luna_pinyin.schema.yaml in Copy Shared Support Files */ = {isa = PBXBuildFile; fileRef = 7B5488321D2DAAD10056A1BE /* luna_pinyin.schema.yaml */; };
7B5488C11D2DACDF0056A1BE /* luna_quanpin.schema.yaml in Copy Shared Support Files */ = {isa = PBXBuildFile; fileRef = 7B5488331D2DAAD10056A1BE /* luna_quanpin.schema.yaml */; };
7B5488C91D2DACDF0056A1BE /* symbols.yaml in Copy Shared Support Files */ = {isa = PBXBuildFile; fileRef = 7B54883B1D2DAAD10056A1BE /* symbols.yaml */; };
+ 9168C20A2CFF446200348D56 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9168C2072CFF446200348D56 /* TextView.swift */; };
+ 9168C20B2CFF446200348D56 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9168C2092CFF446200348D56 /* ViewController.swift */; };
+ 9168C20C2CFF446200348D56 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9168C2012CFF446200348D56 /* AppDelegate.swift */; };
+ 9168C20D2CFF446200348D56 /* TextView+Unused.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9168C2082CFF446200348D56 /* TextView+Unused.swift */; };
+ 9168C20E2CFF446200348D56 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9168C2042CFF446200348D56 /* Main.storyboard */; };
+ 9168C20F2CFF446200348D56 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9168C2022CFF446200348D56 /* Assets.xcassets */; };
+ 9168C2102CFF44DF00348D56 /* InputController.h in Headers */ = {isa = PBXBuildFile; fileRef = 9168C2002CFF445500348D56 /* InputController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B3216E5C2BF438F800E292D2 /* rime.pdf in Resources */ = {isa = PBXBuildFile; fileRef = B3216E5B2BF438F800E292D2 /* rime.pdf */; };
B35D2FE82BF00839009D156B /* BridgingFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B35D2FE72BF00839009D156B /* BridgingFunctions.swift */; };
B38E9B912BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B38E9B902BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift */; };
@@ -90,6 +111,17 @@
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
+ 0DD1CBD02CFC00C600E6C847 /* Copy Shared Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 0DD1CBD12CFC00CE00E6C847 /* librime.1.dylib in Copy Shared Frameworks */,
+ );
+ name = "Copy Shared Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
2C6B9F992BCD083D00E327DF /* Copy Rime plugins */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -202,6 +234,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 0DD1CBD62CFC00ED00E6C847 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0DEE704F2CFBEC6C0062CC96 /* InputController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = InputController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0DEE70632CFBF0FF0062CC96 /* FileManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extension.swift"; sourceTree = ""; };
+ 0DEE70652CFBF2030062CC96 /* GlobalContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalContext.swift; sourceTree = ""; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; };
@@ -270,20 +306,28 @@
7B5488331D2DAAD10056A1BE /* luna_quanpin.schema.yaml */ = {isa = PBXFileReference; lastKnownFileType = text; name = luna_quanpin.schema.yaml; path = data/plum/luna_quanpin.schema.yaml; sourceTree = ""; };
7B54883B1D2DAAD10056A1BE /* symbols.yaml */ = {isa = PBXFileReference; lastKnownFileType = text; name = symbols.yaml; path = data/plum/symbols.yaml; sourceTree = ""; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = resources/Info.plist; sourceTree = ""; };
+ 9168C2002CFF445500348D56 /* InputController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InputController.h; sourceTree = ""; };
+ 9168C2012CFF446200348D56 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 9168C2022CFF446200348D56 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 9168C2032CFF446200348D56 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Main.storyboard; sourceTree = ""; };
+ 9168C2062CFF446200348D56 /* Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo.entitlements; sourceTree = ""; };
+ 9168C2072CFF446200348D56 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; };
+ 9168C2082CFF446200348D56 /* TextView+Unused.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextView+Unused.swift"; sourceTree = ""; };
+ 9168C2092CFF446200348D56 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
A4B8E1B20F645B870094E08B /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; };
B3216E5B2BF438F800E292D2 /* rime.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = rime.pdf; path = resources/rime.pdf; sourceTree = ""; };
B32B80772BE7FAA200FCF3BC /* Squirrel.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; name = Squirrel.entitlements; path = resources/Squirrel.entitlements; sourceTree = ""; };
- B35D2FE72BF00839009D156B /* BridgingFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BridgingFunctions.swift; path = sources/BridgingFunctions.swift; sourceTree = ""; };
- B38E9B8F2BE9AE1E0036ABEF /* Squirrel-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Squirrel-Bridging-Header.h"; path = "sources/Squirrel-Bridging-Header.h"; sourceTree = ""; };
- B38E9B902BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelApplicationDelegate.swift; path = sources/SquirrelApplicationDelegate.swift; sourceTree = ""; };
- B38E9B942BEAFEFD0036ABEF /* SquirrelInputController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelInputController.swift; path = sources/SquirrelInputController.swift; sourceTree = ""; };
- B39771222BECEA150093A49B /* MacOSKeyCodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MacOSKeyCodes.swift; path = sources/MacOSKeyCodes.swift; sourceTree = ""; };
- B39771242BED899F0093A49B /* SquirrelConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelConfig.swift; path = sources/SquirrelConfig.swift; sourceTree = ""; };
- B39771262BED9B250093A49B /* SquirrelTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelTheme.swift; path = sources/SquirrelTheme.swift; sourceTree = ""; };
- B39771282BEDAF4A0093A49B /* SquirrelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelView.swift; path = sources/SquirrelView.swift; sourceTree = ""; };
- B397712A2BEE4C160093A49B /* SquirrelPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SquirrelPanel.swift; path = sources/SquirrelPanel.swift; sourceTree = ""; };
- B397712C2BEEB39D0093A49B /* InputSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InputSource.swift; path = sources/InputSource.swift; sourceTree = ""; };
- B397712E2BEECBED0093A49B /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = sources/Main.swift; sourceTree = ""; };
+ B35D2FE72BF00839009D156B /* BridgingFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BridgingFunctions.swift; sourceTree = ""; };
+ B38E9B8F2BE9AE1E0036ABEF /* Squirrel-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Squirrel-Bridging-Header.h"; sourceTree = ""; };
+ B38E9B902BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquirrelApplicationDelegate.swift; sourceTree = ""; };
+ B38E9B942BEAFEFD0036ABEF /* SquirrelInputController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquirrelInputController.swift; sourceTree = ""; };
+ B39771222BECEA150093A49B /* MacOSKeyCodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacOSKeyCodes.swift; sourceTree = ""; };
+ B39771242BED899F0093A49B /* SquirrelConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquirrelConfig.swift; sourceTree = ""; };
+ B39771262BED9B250093A49B /* SquirrelTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquirrelTheme.swift; sourceTree = ""; };
+ B39771282BEDAF4A0093A49B /* SquirrelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquirrelView.swift; sourceTree = ""; };
+ B397712A2BEE4C160093A49B /* SquirrelPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquirrelPanel.swift; sourceTree = ""; };
+ B397712C2BEEB39D0093A49B /* InputSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSource.swift; sourceTree = ""; };
+ B397712E2BEECBED0093A49B /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; };
B39771302BEEE4540093A49B /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = InfoPlist.xcstrings; path = resources/InfoPlist.xcstrings; sourceTree = ""; };
B39771322BEEE4540093A49B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = resources/Localizable.xcstrings; sourceTree = ""; };
D26434542706A15100857391 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
@@ -292,6 +336,22 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 0DD1CBD32CFC00ED00E6C847 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0DD1CBE92CFC013200E6C847 /* InputController.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 0DEE704C2CFBEC6C0062CC96 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0DD1CB762CFBFB6500E6C847 /* librime.1.dylib in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8D11072E0486CEB800E47090 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -310,6 +370,7 @@
isa = PBXGroup;
children = (
B38E9B902BE9AE1E0036ABEF /* SquirrelApplicationDelegate.swift */,
+ 0DEE70652CFBF2030062CC96 /* GlobalContext.swift */,
B38E9B942BEAFEFD0036ABEF /* SquirrelInputController.swift */,
B39771222BECEA150093A49B /* MacOSKeyCodes.swift */,
B397712C2BEEB39D0093A49B /* InputSource.swift */,
@@ -319,9 +380,33 @@
B39771282BEDAF4A0093A49B /* SquirrelView.swift */,
B39771242BED899F0093A49B /* SquirrelConfig.swift */,
B35D2FE72BF00839009D156B /* BridgingFunctions.swift */,
+ 0DEE70632CFBF0FF0062CC96 /* FileManager+Extension.swift */,
B38E9B8F2BE9AE1E0036ABEF /* Squirrel-Bridging-Header.h */,
);
name = Sources;
+ path = sources;
+ sourceTree = "";
+ };
+ 0DD1CBD72CFC00ED00E6C847 /* Demo */ = {
+ isa = PBXGroup;
+ children = (
+ 9168C2012CFF446200348D56 /* AppDelegate.swift */,
+ 9168C2022CFF446200348D56 /* Assets.xcassets */,
+ 9168C2052CFF446200348D56 /* Base.lproj */,
+ 9168C2062CFF446200348D56 /* Demo.entitlements */,
+ 9168C2072CFF446200348D56 /* TextView.swift */,
+ 9168C2082CFF446200348D56 /* TextView+Unused.swift */,
+ 9168C2092CFF446200348D56 /* ViewController.swift */,
+ );
+ path = Demo;
+ sourceTree = "";
+ };
+ 0DEE70502CFBEC6C0062CC96 /* InputController */ = {
+ isa = PBXGroup;
+ children = (
+ 9168C2002CFF445500348D56 /* InputController.h */,
+ );
+ path = InputController;
sourceTree = "";
};
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
@@ -349,6 +434,8 @@
isa = PBXGroup;
children = (
44F1EB381431F8270015FD04 /* Squirrel.app */,
+ 0DEE704F2CFBEC6C0062CC96 /* InputController.framework */,
+ 0DD1CBD62CFC00ED00E6C847 /* Demo.app */,
);
name = Products;
sourceTree = "";
@@ -363,6 +450,8 @@
44DA7A4214DD598900C1ED3B /* SharedSupport */,
080E96DDFE201D6D7F000001 /* Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
+ 0DEE70502CFBEC6C0062CC96 /* InputController */,
+ 0DD1CBD72CFC00ED00E6C847 /* Demo */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
@@ -370,6 +459,7 @@
name = Squirrel;
sourceTree = "";
tabWidth = 2;
+ usesTabs = 0;
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
@@ -483,9 +573,70 @@
name = plum;
sourceTree = "";
};
+ 9168C2052CFF446200348D56 /* Base.lproj */ = {
+ isa = PBXGroup;
+ children = (
+ 9168C2042CFF446200348D56 /* Main.storyboard */,
+ );
+ path = Base.lproj;
+ sourceTree = "";
+ };
/* End PBXGroup section */
+/* Begin PBXHeadersBuildPhase section */
+ 0DEE704A2CFBEC6C0062CC96 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9168C2102CFF44DF00348D56 /* InputController.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
/* Begin PBXNativeTarget section */
+ 0DD1CBD52CFC00ED00E6C847 /* Demo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 0DD1CBE22CFC00EE00E6C847 /* Build configuration list for PBXNativeTarget "Demo" */;
+ buildPhases = (
+ 0DD1CBD22CFC00ED00E6C847 /* Sources */,
+ 0DD1CBD32CFC00ED00E6C847 /* Frameworks */,
+ 0DD1CBD42CFC00ED00E6C847 /* Resources */,
+ 44DA7A1614DD581B00C1ED3B /* Copy Shared Support Files */,
+ 4407F3CA14EC079A001329FE /* Copy opencc Files */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Demo;
+ packageProductDependencies = (
+ );
+ productName = Demo;
+ productReference = 0DD1CBD62CFC00ED00E6C847 /* Demo.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 0DEE704E2CFBEC6C0062CC96 /* InputController */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 0DEE705A2CFBEC6C0062CC96 /* Build configuration list for PBXNativeTarget "InputController" */;
+ buildPhases = (
+ 0DEE704A2CFBEC6C0062CC96 /* Headers */,
+ 0DEE704B2CFBEC6C0062CC96 /* Sources */,
+ 0DEE704C2CFBEC6C0062CC96 /* Frameworks */,
+ 0DEE704D2CFBEC6C0062CC96 /* Resources */,
+ 0DD1CBD02CFC00C600E6C847 /* Copy Shared Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = InputController;
+ packageProductDependencies = (
+ );
+ productName = InputController;
+ productReference = 0DEE704F2CFBEC6C0062CC96 /* InputController.framework */;
+ productType = "com.apple.product-type.framework";
+ };
8D1107260486CEB800E47090 /* Squirrel */ = {
isa = PBXNativeTarget;
buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Squirrel" */;
@@ -518,8 +669,16 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
+ LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1540;
TargetAttributes = {
+ 0DD1CBD52CFC00ED00E6C847 = {
+ CreatedOnToolsVersion = 16.0;
+ LastSwiftMigration = 1600;
+ };
+ 0DEE704E2CFBEC6C0062CC96 = {
+ CreatedOnToolsVersion = 16.0;
+ };
8D1107260486CEB800E47090 = {
LastSwiftMigration = 1530;
};
@@ -542,11 +701,29 @@
projectRoot = "";
targets = (
8D1107260486CEB800E47090 /* Squirrel */,
+ 0DEE704E2CFBEC6C0062CC96 /* InputController */,
+ 0DD1CBD52CFC00ED00E6C847 /* Demo */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 0DD1CBD42CFC00ED00E6C847 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9168C20E2CFF446200348D56 /* Main.storyboard in Resources */,
+ 9168C20F2CFF446200348D56 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 0DEE704D2CFBEC6C0062CC96 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8D1107290486CEB800E47090 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -563,10 +740,39 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 0DD1CBD22CFC00ED00E6C847 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9168C20A2CFF446200348D56 /* TextView.swift in Sources */,
+ 9168C20B2CFF446200348D56 /* ViewController.swift in Sources */,
+ 9168C20C2CFF446200348D56 /* AppDelegate.swift in Sources */,
+ 9168C20D2CFF446200348D56 /* TextView+Unused.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 0DEE704B2CFBEC6C0062CC96 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0DEE70672CFBF26E0062CC96 /* GlobalContext.swift in Sources */,
+ 0DD1CB742CFBF60700E6C847 /* SquirrelView.swift in Sources */,
+ 0DD1CB732CFBF53A00E6C847 /* FileManager+Extension.swift in Sources */,
+ 0DEE705C2CFBEDAC0062CC96 /* SquirrelInputController.swift in Sources */,
+ 0DEE705D2CFBEE170062CC96 /* BridgingFunctions.swift in Sources */,
+ 0DD1CB722CFBF52D00E6C847 /* SquirrelPanel.swift in Sources */,
+ 0DEE705E2CFBEE2D0062CC96 /* MacOSKeyCodes.swift in Sources */,
+ 0DD1CB712CFBF52200E6C847 /* SquirrelTheme.swift in Sources */,
+ 0DD1CB702CFBF51400E6C847 /* SquirrelConfig.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
8D11072C0486CEB800E47090 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 0DEE70662CFBF2070062CC96 /* GlobalContext.swift in Sources */,
+ 0DEE70642CFBF1080062CC96 /* FileManager+Extension.swift in Sources */,
B39771232BECEA150093A49B /* MacOSKeyCodes.swift in Sources */,
B39771292BEDAF4A0093A49B /* SquirrelView.swift in Sources */,
B38E9B952BEAFEFD0036ABEF /* SquirrelInputController.swift in Sources */,
@@ -582,7 +788,234 @@
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXVariantGroup section */
+ 9168C2042CFF446200348D56 /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 9168C2032CFF446200348D56 /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
/* Begin XCBuildConfiguration section */
+ 0DD1CBE32CFC00EE00E6C847 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INFOPLIST_KEY_NSMainStoryboardFile = Main;
+ INFOPLIST_KEY_NSPrincipalClass = NSApplication;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel.Demo;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 0DD1CBE42CFC00EE00E6C847 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INFOPLIST_KEY_NSMainStoryboardFile = Main;
+ INFOPLIST_KEY_NSPrincipalClass = NSApplication;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
+ MARKETING_VERSION = 1.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel.Demo;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 0DEE70572CFBEC6C0062CC96 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/lib",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel.InputController;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_INSTALL_OBJC_HEADER = NO;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 0DEE70582CFBEC6C0062CC96 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ ENABLE_NS_ASSERTIONS = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_KEY_NSHumanReadableCopyright = "";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/lib",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 15.0;
+ MARKETING_VERSION = 1.0;
+ MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+ MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel.InputController;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_INSTALL_OBJC_HEADER = NO;
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
C01FCF4B08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -628,6 +1061,7 @@
"$(OTHER_CPLUSPLUSFLAGS_QUOTED_FOR_TARGET_2)",
);
OTHER_LDFLAGS = "-lrime.1";
+ OTHER_SWIFT_FLAGS = "-D InputMethod";
PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel;
PRODUCT_NAME = Squirrel;
SDKROOT = macosx;
@@ -682,6 +1116,7 @@
"$(OTHER_CPLUSPLUSFLAGS_QUOTED_FOR_TARGET_2)",
);
OTHER_LDFLAGS = "-lrime.1";
+ OTHER_SWIFT_FLAGS = "-D InputMethod";
PRODUCT_BUNDLE_IDENTIFIER = im.rime.inputmethod.Squirrel;
PRODUCT_NAME = Squirrel;
SDKROOT = macosx;
@@ -811,6 +1246,24 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 0DD1CBE22CFC00EE00E6C847 /* Build configuration list for PBXNativeTarget "Demo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 0DD1CBE32CFC00EE00E6C847 /* Debug */,
+ 0DD1CBE42CFC00EE00E6C847 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 0DEE705A2CFBEC6C0062CC96 /* Build configuration list for PBXNativeTarget "InputController" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 0DEE70572CFBEC6C0062CC96 /* Debug */,
+ 0DEE70582CFBEC6C0062CC96 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Squirrel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/sources/FileManager+Extension.swift b/sources/FileManager+Extension.swift
new file mode 100644
index 000000000..88a4e9d6b
--- /dev/null
+++ b/sources/FileManager+Extension.swift
@@ -0,0 +1,20 @@
+//
+// FileManager+Extension.swift
+// Squirrel
+//
+// Created by mi on 2024/12/1.
+//
+
+import Foundation
+
+extension FileManager {
+ func createDirIfNotExist(path: URL) {
+ if !fileExists(atPath: path.path()) {
+ do {
+ try createDirectory(at: path, withIntermediateDirectories: true)
+ } catch {
+ print("Error creating user data directory: \(path.path())")
+ }
+ }
+ }
+}
diff --git a/sources/GlobalContext.swift b/sources/GlobalContext.swift
new file mode 100644
index 000000000..6f0e8f6db
--- /dev/null
+++ b/sources/GlobalContext.swift
@@ -0,0 +1,214 @@
+//
+// GlobalContext.swift
+// Squirrel
+//
+// Created by mi on 2024/12/1.
+//
+
+import Foundation
+import UserNotifications
+
+public final class GlobalContext {
+ public static let shared = GlobalContext()
+
+ let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee
+ var config: SquirrelConfig?
+ lazy var panel = SquirrelPanel(position: .zero)
+ var enableNotifications = false
+
+ static let notificationIdentifier = "SquirrelNotification"
+
+ enum Path {
+ static let userDir = if let pwuid = getpwuid(getuid()) {
+ URL(fileURLWithFileSystemRepresentation: pwuid.pointee.pw_dir, isDirectory: true, relativeTo: nil).appending(components: "Library", "Rime")
+ } else {
+ try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("Rime", isDirectory: true)
+ }
+ static let appDir = "/Library/Input Library/Squirrel.app".withCString { dir in
+ URL(fileURLWithFileSystemRepresentation: dir, isDirectory: false, relativeTo: nil)
+ }
+ static let logDir = FileManager.default.temporaryDirectory.appending(component: "rime.squirrel", directoryHint: .isDirectory)
+ }
+
+ public func setupRime() {
+ FileManager.default.createDirIfNotExist(path: Path.userDir)
+ FileManager.default.createDirIfNotExist(path: Path.logDir)
+ // swiftlint:disable identifier_name
+ let notification_handler: @convention(c) (UnsafeMutableRawPointer?, RimeSessionId, UnsafePointer?, UnsafePointer?) -> Void = notificationHandler
+ let context_object = Unmanaged.passUnretained(self).toOpaque()
+ // swiftlint:enable identifier_name
+ rimeAPI.set_notification_handler(notification_handler, context_object)
+
+ var squirrelTraits = RimeTraits.rimeStructInit()
+ squirrelTraits.setCString(Bundle.main.sharedSupportPath!, to: \.shared_data_dir)
+ squirrelTraits.setCString(Path.userDir.path(), to: \.user_data_dir)
+ squirrelTraits.setCString(Path.logDir.path(), to: \.log_dir)
+ squirrelTraits.setCString("Squirrel", to: \.distribution_code_name)
+ squirrelTraits.setCString("鼠鬚管", to: \.distribution_name)
+ squirrelTraits.setCString(Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String, to: \.distribution_version)
+ squirrelTraits.setCString("rime.squirrel", to: \.app_name)
+ rimeAPI.setup(&squirrelTraits)
+ }
+
+ public func startRime(fullCheck: Bool) {
+ print("Initializing la rime...")
+ rimeAPI.initialize(nil)
+ // check for configuration updates
+ if rimeAPI.start_maintenance(fullCheck) {
+ // update squirrel config
+ // print("[DEBUG] maintenance suceeds")
+ _ = rimeAPI.deploy_config_file("squirrel.yaml", "config_version")
+ } else {
+ // print("[DEBUG] maintenance fails")
+ }
+ }
+
+ public func loadSettings() {
+ config = SquirrelConfig()
+ if !config!.openBaseConfig() {
+ return
+ }
+
+ enableNotifications = config!.getString("show_notifications_when") != "never"
+ if let config = self.config {
+ panel.load(config: config, forDarkMode: false)
+ panel.load(config: config, forDarkMode: true)
+ }
+ }
+
+ func loadSettings(for schemaID: String) {
+ if schemaID.count == 0 || schemaID.first == "." {
+ return
+ }
+ let schema = SquirrelConfig()
+ if let config = self.config {
+ if schema.open(schemaID: schemaID, baseConfig: config) && schema.has(section: "style") {
+ panel.load(config: schema, forDarkMode: false)
+ panel.load(config: schema, forDarkMode: true)
+ } else {
+ panel.load(config: config, forDarkMode: false)
+ panel.load(config: config, forDarkMode: true)
+ }
+ }
+ schema.close()
+ }
+
+ func deploy() {
+ print("Start maintenance...")
+ self.shutdownRime()
+ self.startRime(fullCheck: true)
+ self.loadSettings()
+ }
+
+ // prevent freezing the system
+ public func problematicLaunchDetected() -> Bool {
+ var detected = false
+ let logFile = FileManager.default.temporaryDirectory.appendingPathComponent("squirrel_launch.json", conformingTo: .json)
+ // print("[DEBUG] archive: \(logFile)")
+ do {
+ let archive = try Data(contentsOf: logFile, options: [.uncached])
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = .millisecondsSince1970
+ let previousLaunch = try decoder.decode(Date.self, from: archive)
+ if previousLaunch.timeIntervalSinceNow >= -2 {
+ detected = true
+ }
+ } catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError {
+
+ } catch {
+ print("Error occurred during processing launch time archive: \(error.localizedDescription)")
+ return detected
+ }
+ do {
+ let encoder = JSONEncoder()
+ encoder.dateEncodingStrategy = .millisecondsSince1970
+ let record = try encoder.encode(Date.now)
+ try record.write(to: logFile)
+ } catch {
+ print("Error occurred during saving launch time to archive: \(error.localizedDescription)")
+ }
+ return detected
+ }
+
+ func showStatusMessage(msgTextLong: String?, msgTextShort: String?) {
+ if !(msgTextLong ?? "").isEmpty || !(msgTextShort ?? "").isEmpty {
+ panel.updateStatus(long: msgTextLong ?? "", short: msgTextShort ?? "")
+ }
+ }
+
+ func shutdownRime() {
+ config?.close()
+ rimeAPI.finalize()
+ }
+}
+
+
+func showMessage(msgText: String?) {
+ let center = UNUserNotificationCenter.current()
+ center.requestAuthorization(options: [.alert, .provisional]) { _, error in
+ if let error = error {
+ print("User notification authorization error: \(error.localizedDescription)")
+ }
+ }
+ center.getNotificationSettings { settings in
+ if (settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional) && settings.alertSetting == .enabled {
+ let content = UNMutableNotificationContent()
+ content.title = NSLocalizedString("Squirrel", comment: "")
+ if let msgText = msgText {
+ content.subtitle = msgText
+ }
+ content.interruptionLevel = .active
+ let request = UNNotificationRequest(identifier: GlobalContext.notificationIdentifier, content: content, trigger: nil)
+ center.add(request) { error in
+ if let error = error {
+ print("User notification request error: \(error.localizedDescription)")
+ }
+ }
+ }
+ }
+}
+
+private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer?, messageValueC: UnsafePointer?) {
+ let context: GlobalContext = Unmanaged.fromOpaque(contextObject!).takeUnretainedValue()
+
+ let messageType = messageTypeC.map { String(cString: $0) }
+ let messageValue = messageValueC.map { String(cString: $0) }
+ if messageType == "deploy" {
+ switch messageValue {
+ case "start":
+ showMessage(msgText: NSLocalizedString("deploy_start", comment: ""))
+ case "success":
+ showMessage(msgText: NSLocalizedString("deploy_success", comment: ""))
+ case "failure":
+ showMessage(msgText: NSLocalizedString("deploy_failure", comment: ""))
+ default:
+ break
+ }
+ return
+ }
+ // off
+ if !context.enableNotifications {
+ return
+ }
+
+ if messageType == "schema", let messageValue = messageValue, let schemaName = try? /^[^\/]*\/(.*)$/.firstMatch(in: messageValue)?.output.1 {
+ context.showStatusMessage(msgTextLong: String(schemaName), msgTextShort: String(schemaName))
+ return
+ } else if messageType == "option" {
+ let state = messageValue?.first != "!"
+ let optionName = if state {
+ messageValue
+ } else {
+ String(messageValue![messageValue!.index(after: messageValue!.startIndex)...])
+ }
+ if let optionName = optionName {
+ optionName.withCString { name in
+ let stateLabelLong = context.rimeAPI.get_state_label_abbreviated(sessionId, name, state, false)
+ let stateLabelShort = context.rimeAPI.get_state_label_abbreviated(sessionId, name, state, true)
+ let longLabel = stateLabelLong.str.map { String(cString: $0) }
+ let shortLabel = stateLabelShort.str.map { String(cString: $0) }
+ context.showStatusMessage(msgTextLong: longLabel, msgTextShort: shortLabel)
+ }
+ }
+ }
+}
diff --git a/sources/InputSource.swift b/sources/InputSource.swift
index a0c80add3..562bd6592 100644
--- a/sources/InputSource.swift
+++ b/sources/InputSource.swift
@@ -47,8 +47,8 @@ final class SquirrelInstaller {
// Already registered.
return
}
- TISRegisterInputSource(SquirrelApp.appDir as CFURL)
- print("Registered input source from \(SquirrelApp.appDir)")
+ TISRegisterInputSource(GlobalContext.Path.appDir as CFURL)
+ print("Registered input source from \(GlobalContext.Path.appDir)")
}
func enable(modes: [InputMode] = []) {
diff --git a/sources/Main.swift b/sources/Main.swift
index dca533ba3..27e209c98 100644
--- a/sources/Main.swift
+++ b/sources/Main.swift
@@ -10,15 +10,6 @@ import InputMethodKit
@main
struct SquirrelApp {
- static let userDir = if let pwuid = getpwuid(getuid()) {
- URL(fileURLWithFileSystemRepresentation: pwuid.pointee.pw_dir, isDirectory: true, relativeTo: nil).appending(components: "Library", "Rime")
- } else {
- try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("Rime", isDirectory: true)
- }
- static let appDir = "/Library/Input Library/Squirrel.app".withCString { dir in
- URL(fileURLWithFileSystemRepresentation: dir, isDirectory: false, relativeTo: nil)
- }
- static let logDir = FileManager.default.temporaryDirectory.appending(component: "rime.squirrel", directoryHint: .isDirectory)
// swiftlint:disable:next cyclomatic_complexity
static func main() {
@@ -69,7 +60,7 @@ struct SquirrelApp {
return true
case "--build":
// Notification
- SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_update", comment: ""))
+ showMessage(msgText: NSLocalizedString("deploy_update", comment: ""))
// Build all schemas in current directory
var builderTraits = RimeTraits.rimeStructInit()
builderTraits.setCString("rime.squirrel-builder", to: \.app_name)
@@ -108,7 +99,7 @@ struct SquirrelApp {
// opencc will be configured with relative dictionary paths
FileManager.default.changeCurrentDirectoryPath(main.sharedSupportPath!)
- if NSApp.squirrelAppDelegate.problematicLaunchDetected() {
+ if GlobalContext.shared.problematicLaunchDetected() {
print("Problematic launch detected!")
let args = ["Problematic launch detected! Squirrel may be suffering a crash due to improper configuration. Revert previous modifications to see if the problem recurs."]
let task = Process()
@@ -118,9 +109,9 @@ struct SquirrelApp {
task.arguments = args
try? task.run()
} else {
- NSApp.squirrelAppDelegate.setupRime()
- NSApp.squirrelAppDelegate.startRime(fullCheck: false)
- NSApp.squirrelAppDelegate.loadSettings()
+ GlobalContext.shared.setupRime()
+ GlobalContext.shared.startRime(fullCheck: false)
+ GlobalContext.shared.loadSettings()
print("Squirrel reporting!")
}
diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift
index c60376040..494ffd473 100644
--- a/sources/SquirrelApplicationDelegate.swift
+++ b/sources/SquirrelApplicationDelegate.swift
@@ -12,12 +12,7 @@ import AppKit
final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUStandardUserDriverDelegate, UNUserNotificationCenterDelegate {
static let rimeWikiURL = URL(string: "/~https://github.com/rime/home/wiki")!
static let updateNotificationIdentifier = "SquirrelUpdateNotification"
- static let notificationIdentifier = "SquirrelNotification"
- let rimeAPI: RimeApi_stdbool = rime_get_api_stdbool().pointee
- var config: SquirrelConfig?
- var panel: SquirrelPanel?
- var enableNotifications = false
let updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)
var supportsGentleScheduledUpdateReminders: Bool {
true
@@ -53,7 +48,6 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta
}
func applicationWillFinishLaunching(_ notification: Notification) {
- panel = SquirrelPanel(position: .zero)
addObservers()
}
@@ -61,158 +55,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta
// swiftlint:disable:next notification_center_detachment
NotificationCenter.default.removeObserver(self)
DistributedNotificationCenter.default().removeObserver(self)
- panel?.hide()
- }
-
- func deploy() {
- print("Start maintenance...")
- self.shutdownRime()
- self.startRime(fullCheck: true)
- self.loadSettings()
- }
-
- func syncUserData() {
- print("Sync user data")
- _ = rimeAPI.sync_user_data()
- }
-
- func openLogFolder() {
- NSWorkspace.shared.open(SquirrelApp.logDir)
- }
-
- func openRimeFolder() {
- NSWorkspace.shared.open(SquirrelApp.userDir)
- }
-
- func checkForUpdates() {
- if updateController.updater.canCheckForUpdates {
- print("Checking for updates")
- updateController.updater.checkForUpdates()
- } else {
- print("Cannot check for updates")
- }
- }
-
- func openWiki() {
- NSWorkspace.shared.open(Self.rimeWikiURL)
- }
-
- static func showMessage(msgText: String?) {
- let center = UNUserNotificationCenter.current()
- center.requestAuthorization(options: [.alert, .provisional]) { _, error in
- if let error = error {
- print("User notification authorization error: \(error.localizedDescription)")
- }
- }
- center.getNotificationSettings { settings in
- if (settings.authorizationStatus == .authorized || settings.authorizationStatus == .provisional) && settings.alertSetting == .enabled {
- let content = UNMutableNotificationContent()
- content.title = NSLocalizedString("Squirrel", comment: "")
- if let msgText = msgText {
- content.subtitle = msgText
- }
- content.interruptionLevel = .active
- let request = UNNotificationRequest(identifier: Self.notificationIdentifier, content: content, trigger: nil)
- center.add(request) { error in
- if let error = error {
- print("User notification request error: \(error.localizedDescription)")
- }
- }
- }
- }
- }
-
- func setupRime() {
- createDirIfNotExist(path: SquirrelApp.userDir)
- createDirIfNotExist(path: SquirrelApp.logDir)
- // swiftlint:disable identifier_name
- let notification_handler: @convention(c) (UnsafeMutableRawPointer?, RimeSessionId, UnsafePointer?, UnsafePointer?) -> Void = notificationHandler
- let context_object = Unmanaged.passUnretained(self).toOpaque()
- // swiftlint:enable identifier_name
- rimeAPI.set_notification_handler(notification_handler, context_object)
-
- var squirrelTraits = RimeTraits.rimeStructInit()
- squirrelTraits.setCString(Bundle.main.sharedSupportPath!, to: \.shared_data_dir)
- squirrelTraits.setCString(SquirrelApp.userDir.path(), to: \.user_data_dir)
- squirrelTraits.setCString(SquirrelApp.logDir.path(), to: \.log_dir)
- squirrelTraits.setCString("Squirrel", to: \.distribution_code_name)
- squirrelTraits.setCString("鼠鬚管", to: \.distribution_name)
- squirrelTraits.setCString(Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as! String, to: \.distribution_version)
- squirrelTraits.setCString("rime.squirrel", to: \.app_name)
- rimeAPI.setup(&squirrelTraits)
- }
-
- func startRime(fullCheck: Bool) {
- print("Initializing la rime...")
- rimeAPI.initialize(nil)
- // check for configuration updates
- if rimeAPI.start_maintenance(fullCheck) {
- // update squirrel config
- // print("[DEBUG] maintenance suceeds")
- _ = rimeAPI.deploy_config_file("squirrel.yaml", "config_version")
- } else {
- // print("[DEBUG] maintenance fails")
- }
- }
-
- func loadSettings() {
- config = SquirrelConfig()
- if !config!.openBaseConfig() {
- return
- }
-
- enableNotifications = config!.getString("show_notifications_when") != "never"
- if let panel = panel, let config = self.config {
- panel.load(config: config, forDarkMode: false)
- panel.load(config: config, forDarkMode: true)
- }
- }
-
- func loadSettings(for schemaID: String) {
- if schemaID.count == 0 || schemaID.first == "." {
- return
- }
- let schema = SquirrelConfig()
- if let panel = panel, let config = self.config {
- if schema.open(schemaID: schemaID, baseConfig: config) && schema.has(section: "style") {
- panel.load(config: schema, forDarkMode: false)
- panel.load(config: schema, forDarkMode: true)
- } else {
- panel.load(config: config, forDarkMode: false)
- panel.load(config: config, forDarkMode: true)
- }
- }
- schema.close()
- }
-
- // prevent freezing the system
- func problematicLaunchDetected() -> Bool {
- var detected = false
- let logFile = FileManager.default.temporaryDirectory.appendingPathComponent("squirrel_launch.json", conformingTo: .json)
- // print("[DEBUG] archive: \(logFile)")
- do {
- let archive = try Data(contentsOf: logFile, options: [.uncached])
- let decoder = JSONDecoder()
- decoder.dateDecodingStrategy = .millisecondsSince1970
- let previousLaunch = try decoder.decode(Date.self, from: archive)
- if previousLaunch.timeIntervalSinceNow >= -2 {
- detected = true
- }
- } catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError {
-
- } catch {
- print("Error occurred during processing launch time archive: \(error.localizedDescription)")
- return detected
- }
- do {
- let encoder = JSONEncoder()
- encoder.dateEncodingStrategy = .millisecondsSince1970
- let record = try encoder.encode(Date.now)
- try record.write(to: logFile)
- } catch {
- print("Error occurred during saving launch time to archive: \(error.localizedDescription)")
- }
- return detected
+ GlobalContext.shared.panel.hide()
}
// add an awakeFromNib item so that we can set the action method. Note that
@@ -229,98 +72,62 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
print("Squirrel is quitting.")
- rimeAPI.cleanup_all_sessions()
+ GlobalContext.shared.rimeAPI.cleanup_all_sessions()
return .terminateNow
}
-
}
-private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer?, messageValueC: UnsafePointer?) {
- let delegate: SquirrelApplicationDelegate = Unmanaged.fromOpaque(contextObject!).takeUnretainedValue()
+extension SquirrelApplicationDelegate: MenuActions {
+ func deploy() {
+ GlobalContext.shared.deploy()
+ }
- let messageType = messageTypeC.map { String(cString: $0) }
- let messageValue = messageValueC.map { String(cString: $0) }
- if messageType == "deploy" {
- switch messageValue {
- case "start":
- SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_start", comment: ""))
- case "success":
- SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_success", comment: ""))
- case "failure":
- SquirrelApplicationDelegate.showMessage(msgText: NSLocalizedString("deploy_failure", comment: ""))
- default:
- break
- }
- return
+ func syncUserData() {
+ print("Sync user data")
+ _ = GlobalContext.shared.rimeAPI.sync_user_data()
}
- // off
- if !delegate.enableNotifications {
- return
+
+ func openLogFolder() {
+ NSWorkspace.shared.open(GlobalContext.Path.logDir)
}
- if messageType == "schema", let messageValue = messageValue, let schemaName = try? /^[^\/]*\/(.*)$/.firstMatch(in: messageValue)?.output.1 {
- delegate.showStatusMessage(msgTextLong: String(schemaName), msgTextShort: String(schemaName))
- return
- } else if messageType == "option" {
- let state = messageValue?.first != "!"
- let optionName = if state {
- messageValue
- } else {
- String(messageValue![messageValue!.index(after: messageValue!.startIndex)...])
- }
- if let optionName = optionName {
- optionName.withCString { name in
- let stateLabelLong = delegate.rimeAPI.get_state_label_abbreviated(sessionId, name, state, false)
- let stateLabelShort = delegate.rimeAPI.get_state_label_abbreviated(sessionId, name, state, true)
- let longLabel = stateLabelLong.str.map { String(cString: $0) }
- let shortLabel = stateLabelShort.str.map { String(cString: $0) }
- delegate.showStatusMessage(msgTextLong: longLabel, msgTextShort: shortLabel)
- }
- }
+ func openRimeFolder() {
+ NSWorkspace.shared.open(GlobalContext.Path.userDir)
}
-}
-private extension SquirrelApplicationDelegate {
- func showStatusMessage(msgTextLong: String?, msgTextShort: String?) {
- if !(msgTextLong ?? "").isEmpty || !(msgTextShort ?? "").isEmpty {
- panel?.updateStatus(long: msgTextLong ?? "", short: msgTextShort ?? "")
+ func checkForUpdates() {
+ if updateController.updater.canCheckForUpdates {
+ print("Checking for updates")
+ updateController.updater.checkForUpdates()
+ } else {
+ print("Cannot check for updates")
}
}
- func shutdownRime() {
- config?.close()
- rimeAPI.finalize()
+ func openWiki() {
+ NSWorkspace.shared.open(SquirrelApplicationDelegate.rimeWikiURL)
}
+}
+private extension SquirrelApplicationDelegate {
func workspaceWillPowerOff(_: Notification) {
print("Finalizing before logging out.")
- self.shutdownRime()
+ GlobalContext.shared.shutdownRime()
}
func rimeNeedsReload(_: Notification) {
print("Reloading rime on demand.")
- self.deploy()
+ GlobalContext.shared.deploy()
}
func rimeNeedsSync(_: Notification) {
print("Sync rime on demand.")
self.syncUserData()
}
-
- func createDirIfNotExist(path: URL) {
- let fileManager = FileManager.default
- if !fileManager.fileExists(atPath: path.path()) {
- do {
- try fileManager.createDirectory(at: path, withIntermediateDirectories: true)
- } catch {
- print("Error creating user data directory: \(path.path())")
- }
- }
- }
}
extension NSApplication {
- var squirrelAppDelegate: SquirrelApplicationDelegate {
- self.delegate as! SquirrelApplicationDelegate
+ var squirrelAppDelegate: SquirrelApplicationDelegate? {
+ self.delegate as? SquirrelApplicationDelegate
}
}
diff --git a/sources/SquirrelInputController.swift b/sources/SquirrelInputController.swift
index b835f423f..4b3ca0a19 100644
--- a/sources/SquirrelInputController.swift
+++ b/sources/SquirrelInputController.swift
@@ -7,6 +7,16 @@
import InputMethodKit
+@objc protocol MenuActions {
+ func deploy()
+ func syncUserData()
+ func openLogFolder()
+ func openRimeFolder()
+ func checkForUpdates()
+ func openWiki()
+}
+
+@objc(SquirrelInputController)
final class SquirrelInputController: IMKInputController {
private static let keyRollOver = 50
private static var unknownAppCnt: UInt = 0
@@ -170,7 +180,7 @@ final class SquirrelInputController: IMKInputController {
override func activateServer(_ sender: Any!) {
self.client ?= sender as? IMKTextInput
// print("[DEBUG] activateServer:")
- var keyboardLayout = NSApp.squirrelAppDelegate.config?.getString("keyboard_layout") ?? ""
+ var keyboardLayout = GlobalContext.shared.config?.getString("keyboard_layout") ?? ""
if keyboardLayout == "last" || keyboardLayout == "" {
keyboardLayout = ""
} else if keyboardLayout == "default" {
@@ -187,7 +197,11 @@ final class SquirrelInputController: IMKInputController {
override init!(server: IMKServer!, delegate: Any!, client: Any!) {
self.client = client as? IMKTextInput
// print("[DEBUG] initWithServer: \(server ?? .init()) delegate: \(delegate ?? "nil") client:\(client ?? "nil")")
+#if InputMethod
super.init(server: server, delegate: delegate, client: client)
+#else
+ super.init()
+#endif
createSession()
}
@@ -199,7 +213,7 @@ final class SquirrelInputController: IMKInputController {
}
override func hidePalettes() {
- NSApp.squirrelAppDelegate.panel?.hide()
+ GlobalContext.shared.panel.hide()
super.hidePalettes()
}
@@ -226,18 +240,19 @@ final class SquirrelInputController: IMKInputController {
}
override func menu() -> NSMenu! {
- let deploy = NSMenuItem(title: NSLocalizedString("Deploy", comment: "Menu item"), action: #selector(deploy), keyEquivalent: "`")
+ // Since the action will be called from IMKInputController.doCommandBySelector, the target will/must be self
+ let deploy = NSMenuItem(title: NSLocalizedString("Deploy", comment: "Menu item"), action: #selector(MenuActions.deploy), keyEquivalent: "`")
deploy.target = self
deploy.keyEquivalentModifierMask = [.control, .option]
- let sync = NSMenuItem(title: NSLocalizedString("Sync user data", comment: "Menu item"), action: #selector(syncUserData), keyEquivalent: "")
+ let sync = NSMenuItem(title: NSLocalizedString("Sync user data", comment: "Menu item"), action: #selector(MenuActions.syncUserData), keyEquivalent: "")
sync.target = self
- let logDir = NSMenuItem(title: NSLocalizedString("Logs...", comment: "Menu item"), action: #selector(openLogFolder), keyEquivalent: "")
+ let logDir = NSMenuItem(title: NSLocalizedString("Logs...", comment: "Menu item"), action: #selector(MenuActions.openLogFolder), keyEquivalent: "")
logDir.target = self
- let setting = NSMenuItem(title: NSLocalizedString("Settings...", comment: "Menu item"), action: #selector(openRimeFolder), keyEquivalent: "")
+ let setting = NSMenuItem(title: NSLocalizedString("Settings...", comment: "Menu item"), action: #selector(MenuActions.openRimeFolder), keyEquivalent: "")
setting.target = self
- let wiki = NSMenuItem(title: NSLocalizedString("Rime Wiki...", comment: "Menu item"), action: #selector(openWiki), keyEquivalent: "")
+ let wiki = NSMenuItem(title: NSLocalizedString("Rime Wiki...", comment: "Menu item"), action: #selector(MenuActions.openWiki), keyEquivalent: "")
wiki.target = self
- let update = NSMenuItem(title: NSLocalizedString("Check for updates...", comment: "Menu item"), action: #selector(checkForUpdates), keyEquivalent: "")
+ let update = NSMenuItem(title: NSLocalizedString("Check for updates...", comment: "Menu item"), action: #selector(MenuActions.checkForUpdates), keyEquivalent: "")
update.target = self
let menu = NSMenu()
@@ -251,32 +266,37 @@ final class SquirrelInputController: IMKInputController {
return menu
}
- @objc func deploy() {
- NSApp.squirrelAppDelegate.deploy()
+ deinit {
+ destroySession()
}
+}
- @objc func syncUserData() {
- NSApp.squirrelAppDelegate.syncUserData()
+extension SquirrelInputController: MenuActions {
+ private var handler: MenuActions? {
+ NSApplication.shared.delegate as? MenuActions
+ }
+ func deploy() {
+ handler?.deploy()
}
- @objc func openLogFolder() {
- NSApp.squirrelAppDelegate.openLogFolder()
+ func syncUserData() {
+ handler?.syncUserData()
}
- @objc func openRimeFolder() {
- NSApp.squirrelAppDelegate.openRimeFolder()
+ func openLogFolder() {
+ handler?.openLogFolder()
}
- @objc func checkForUpdates() {
- NSApp.squirrelAppDelegate.checkForUpdates()
+ func openRimeFolder() {
+ handler?.openRimeFolder()
}
- @objc func openWiki() {
- NSApp.squirrelAppDelegate.openWiki()
+ func checkForUpdates() {
+ handler?.checkForUpdates()
}
- deinit {
- destroySession()
+ func openWiki() {
+ handler?.openWiki()
}
}
@@ -317,7 +337,7 @@ private extension SquirrelInputController {
timer.invalidate()
}
chordDuration = 0.1
- if let duration = NSApp.squirrelAppDelegate.config?.getDouble("chord_duration"), duration > 0 {
+ if let duration = GlobalContext.shared.config?.getDouble("chord_duration"), duration > 0 {
chordDuration = duration
}
chordTimer = Timer.scheduledTimer(withTimeInterval: chordDuration, repeats: false, block: onChordTimer)
@@ -352,7 +372,7 @@ private extension SquirrelInputController {
if currentApp == "" {
return
}
- if let appOptions = NSApp.squirrelAppDelegate.config?.getAppOptions(currentApp) {
+ if let appOptions = GlobalContext.shared.config?.getAppOptions(currentApp) {
for (key, value) in appOptions {
print("set app option: \(key) = \(value)")
rimeAPI.set_option(session, key, value)
@@ -373,14 +393,13 @@ private extension SquirrelInputController {
// TODO add special key event preprocessing here
// with linear candidate list, arrow keys may behave differently.
- if let panel = NSApp.squirrelAppDelegate.panel {
- if panel.linear != rimeAPI.get_option(session, "_linear") {
- rimeAPI.set_option(session, "_linear", panel.linear)
- }
- // with vertical text, arrow keys may behave differently.
- if panel.vertical != rimeAPI.get_option(session, "_vertical") {
- rimeAPI.set_option(session, "_vertical", panel.vertical)
- }
+ let panel = GlobalContext.shared.panel
+ if panel.linear != rimeAPI.get_option(session, "_linear") {
+ rimeAPI.set_option(session, "_linear", panel.linear)
+ }
+ // with vertical text, arrow keys may behave differently.
+ if panel.vertical != rimeAPI.get_option(session, "_vertical") {
+ rimeAPI.set_option(session, "_vertical", panel.vertical)
}
let handled = rimeAPI.process_key(session, Int32(rimeKeycode), Int32(rimeModifiers))
@@ -434,14 +453,13 @@ private extension SquirrelInputController {
// swiftlint:disable:next identifier_name
if let schema_id = status.schema_id, schemaId == "" || schemaId != String(cString: schema_id) {
schemaId = String(cString: schema_id)
- NSApp.squirrelAppDelegate.loadSettings(for: schemaId)
+ GlobalContext.shared.loadSettings(for: schemaId)
// inline preedit
- if let panel = NSApp.squirrelAppDelegate.panel {
- inlinePreedit = (panel.inlinePreedit && !rimeAPI.get_option(session, "no_inline")) || rimeAPI.get_option(session, "inline")
- inlineCandidate = panel.inlineCandidate && !rimeAPI.get_option(session, "no_inline")
- // if not inline, embed soft cursor in preedit string
- rimeAPI.set_option(session, "soft_cursor", !inlinePreedit)
- }
+ let panel = GlobalContext.shared.panel
+ inlinePreedit = (panel.inlinePreedit && !rimeAPI.get_option(session, "no_inline")) || rimeAPI.get_option(session, "inline")
+ inlineCandidate = panel.inlineCandidate && !rimeAPI.get_option(session, "no_inline")
+ // if not inline, embed soft cursor in preedit string
+ rimeAPI.set_option(session, "soft_cursor", !inlinePreedit)
}
_ = rimeAPI.free_status(&status)
}
@@ -557,11 +575,10 @@ private extension SquirrelInputController {
guard let client = client else { return }
var inputPos = NSRect()
client.attributes(forCharacterIndex: 0, lineHeightRectangle: &inputPos)
- if let panel = NSApp.squirrelAppDelegate.panel {
- panel.position = inputPos
- panel.inputController = self
- panel.update(preedit: preedit, selRange: selRange, caretPos: caretPos, candidates: candidates, comments: comments, labels: labels,
- highlighted: highlighted, page: page, lastPage: lastPage, update: true)
- }
+ let panel = GlobalContext.shared.panel
+ panel.position = inputPos
+ panel.inputController = self
+ panel.update(preedit: preedit, selRange: selRange, caretPos: caretPos, candidates: candidates, comments: comments, labels: labels,
+ highlighted: highlighted, page: page, lastPage: lastPage, update: true)
}
}