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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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) } }