diff --git a/Fire.xcodeproj/project.pbxproj b/Fire.xcodeproj/project.pbxproj index eaebcbd..32f2376 100644 --- a/Fire.xcodeproj/project.pbxproj +++ b/Fire.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 451A556A2AF9F92D00AF57C1 /* InputModeCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451A55692AF9F92D00AF57C1 /* InputModeCache.swift */; }; 45B76CA92AEA6042009AFABD /* PunctuationConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B76CA82AEA6042009AFABD /* PunctuationConversion.swift */; }; 6753419E2AB54A3A00757F76 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6753419D2AB54A3A00757F76 /* main.cpp */; }; 675341A72AB54AEA00757F76 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = 675341A52AB54AEA00757F76 /* sqlite3.c */; }; @@ -68,6 +69,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 451A55692AF9F92D00AF57C1 /* InputModeCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputModeCache.swift; sourceTree = ""; }; 45B76CA82AEA6042009AFABD /* PunctuationConversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PunctuationConversion.swift; sourceTree = ""; }; 6753419B2AB54A3A00757F76 /* TableBuilder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TableBuilder; sourceTree = BUILT_PRODUCTS_DIR; }; 6753419D2AB54A3A00757F76 /* main.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; @@ -187,7 +189,6 @@ 67C9A02D2AB53ED3000B5281 /* CandidatesWindow.swift */, 67C9A01D2AB53ED2000B5281 /* DictManager.swift */, 67C9A0142AB53ED2000B5281 /* en.lproj */, - 67C9A01A2AB53ED2000B5281 /* FireMenu.swift */, 67C9A0192AB53ED2000B5281 /* Info.plist */, 67C9A0242AB53ED3000B5281 /* Preferences */, 67C9A01E2AB53ED3000B5281 /* Resources */, @@ -196,8 +197,9 @@ 67C9A0112AB53EB0000B5281 /* Table */, 67C9A0052AB53E83000B5281 /* fire.pdf */, 67C9A0062AB53E83000B5281 /* Fire.swift */, - 67C9A0222AB53ED3000B5281 /* FireInputServer.swift */, 67C9A0072AB53E83000B5281 /* FireInputController.swift */, + 67C9A0222AB53ED3000B5281 /* FireInputServer.swift */, + 67C9A01A2AB53ED2000B5281 /* FireMenu.swift */, 45B76CA82AEA6042009AFABD /* PunctuationConversion.swift */, 67C9A0022AB53E83000B5281 /* InputSource.swift */, 67C9A0012AB53E83000B5281 /* MainMenu.xib */, @@ -207,6 +209,7 @@ 67C99FF82AB53E62000B5281 /* Utils */, 67C99FEB2AB53DB2000B5281 /* AppDelegate.swift */, 67C99FF22AB53DB4000B5281 /* Fire.entitlements */, + 451A55692AF9F92D00AF57C1 /* InputModeCache.swift */, ); path = Fire; sourceTree = ""; @@ -442,6 +445,7 @@ 67C9A0412AB53ED3000B5281 /* ThemePane.swift in Sources */, 67C9A03E2AB53ED3000B5281 /* UserDictPane.swift in Sources */, 67C9A03F2AB53ED3000B5281 /* GeneralPane.swift in Sources */, + 451A556A2AF9F92D00AF57C1 /* InputModeCache.swift in Sources */, 67C9A00E2AB53E83000B5281 /* Fire.swift in Sources */, 67C9A0102AB53E83000B5281 /* types.swift in Sources */, 67C99FFF2AB53E62000B5281 /* TipsWindow.swift in Sources */, diff --git a/Fire/FireInputController.swift b/Fire/FireInputController.swift index 993d28d..fd39ba5 100644 --- a/Fire/FireInputController.swift +++ b/Fire/FireInputController.swift @@ -186,7 +186,7 @@ class FireInputController: IMKInputController { // 获取输入的字符 let string = event.characters! - guard let reg = try? NSRegularExpression(pattern: "^[a-z]+$") else { + guard let reg = try? NSRegularExpression(pattern: "^[a-zA-Z]+$") else { return nil } let match = reg.firstMatch( diff --git a/Fire/FireInputServer.swift b/Fire/FireInputServer.swift index b6793e0..fdce668 100644 --- a/Fire/FireInputServer.swift +++ b/Fire/FireInputServer.swift @@ -9,8 +9,6 @@ import Foundation import Defaults -private var inputModeCache: [String: InputMode] = [:] - extension FireInputController { /** * 根据当前输入的应用改变输入模式 @@ -25,7 +23,7 @@ extension FireInputController { return currentMode != Fire.shared.inputMode } // 启用APP缓存设置 - if Defaults[.keepAppInputMode], let mode = inputModeCache[identifier] { + if Defaults[.keepAppInputMode], let mode = InputModeCache.shared.get(identifier) { NSLog("[FireInputController] activeClientInputMode from cache: \(identifier), \(mode)") Fire.shared.toggleInputMode(mode, showTip: false) return currentMode != Fire.shared.inputMode @@ -34,9 +32,11 @@ extension FireInputController { } private func savePreviousClientInputMode() { - if let identifier = CandidatesWindow.shared.inputController?.client()?.bundleIdentifier() { + if Defaults[.keepAppInputMode], + let identifier = CandidatesWindow.shared.inputController?.client()?.bundleIdentifier(), + Defaults[.appSettings][identifier] == nil { // 缓存当前输入模式 - inputModeCache.updateValue(inputMode, forKey: identifier) + InputModeCache.shared.put(identifier, inputMode) } } @@ -63,6 +63,7 @@ extension FireInputController { } } override func deactivateServer(_ sender: Any!) { + insertOriginText() clean() NSLog("[FireInputController] deactivate server: \(client()?.bundleIdentifier() ?? "no client deactivate")") } diff --git a/Fire/FireMenu.swift b/Fire/FireMenu.swift index 574b192..a586807 100644 --- a/Fire/FireMenu.swift +++ b/Fire/FireMenu.swift @@ -9,6 +9,7 @@ import Foundation import AppKit import Sparkle +import Defaults extension FireInputController { /* -- menu actions start -- */ @@ -29,14 +30,48 @@ extension FireInputController { NSApp.setActivationPolicy(.accessory) FirePreferencesController.shared.showPane("用户词库") } + @objc func setAppicationMode(_ sender: Any!) { + if let menuWrapper = sender as? [String: Any], + let menuItem = menuWrapper["IMKCommandMenuItem"] as? NSMenuItem, + let dict = menuItem.representedObject as? [String: Any], + let bundleID = dict["bundleID"] as? String, + let mode = dict["mode"] as? InputMode { + NSLog("[FireInputController] setApplicationMode, \(bundleID), \(mode)") + var appSettings = Defaults[.appSettings] + appSettings[bundleID] = ApplicationSettingItem(bundleId: bundleID, inputMs: mode == .zhhans ? .zhhans : .enUS) + Defaults[.appSettings] = appSettings + } + } override func menu() -> NSMenu! { + NSLog("[FireInputController] menu") let menu = NSMenu() menu.items = [ NSMenuItem(title: "首选项", action: #selector(showPreferences(_:)), keyEquivalent: ""), NSMenuItem(title: "用户词库", action: #selector(showUserDictPrefs(_:)), keyEquivalent: ""), + ] + if !Defaults[.disableEnMode], + let controller = CandidatesWindow.shared.inputController, + let bundleID = controller.client()?.bundleIdentifier() { + var displayName = bundleID + if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) { + displayName = FileManager.default.displayName(atPath: url.path) + } + let title = "设置“\(displayName)”的预设为\(Fire.shared.inputMode == .zhhans ? "中文" : "英文")" + let menuItem = NSMenuItem(title: title, action: #selector(setAppicationMode(_:)), keyEquivalent: "") + menuItem.representedObject = [ + "bundleID": bundleID, + "mode": Fire.shared.inputMode + ] + menu.items.append(contentsOf: [ + NSMenuItem.separator(), + menuItem, + ]) + } + menu.items.append(contentsOf: [ + NSMenuItem.separator(), NSMenuItem(title: "检查更新", action: #selector(checkForUpdates(_:)), keyEquivalent: ""), NSMenuItem(title: "关于业火输入法", action: #selector(openAbout(_:)), keyEquivalent: "") - ] + ]) return menu } } diff --git a/Fire/InputModeCache.swift b/Fire/InputModeCache.swift new file mode 100644 index 0000000..7bedc10 --- /dev/null +++ b/Fire/InputModeCache.swift @@ -0,0 +1,59 @@ +// +// InputModeCache.swift +// Fire +// +// Created by qwertyyb on 2023/11/7. +// +import Defaults + +class InputModeCache { + let capacity = 100 + private var cache: [String: InputMode] = [:] + private var keys: [String] = [] + + private init() { + loadFromUserDefaults() + } + + func get(_ key: String) -> InputMode? { + if let value = cache[key] { + updateKeyOrder(key) + return value + } + return nil + } + + func put(_ key: String, _ value: InputMode) { + if cache[key] == nil { + if keys.count >= capacity { + let oldestKey = keys.removeFirst() + cache[oldestKey] = nil + } + keys.append(key) + } else { + updateKeyOrder(key) + } + cache[key] = value + saveToUserDefaults() + } + + private func updateKeyOrder(_ key: String) { + if let index = keys.firstIndex(of: key) { + keys.remove(at: index) + keys.append(key) + saveToUserDefaults() + } + } + + private func saveToUserDefaults() { + Defaults[.keepAppInputMode_keys] = keys + Defaults[.keepAppInputMode_cache] = cache + } + + private func loadFromUserDefaults() { + keys = Defaults[.keepAppInputMode_keys] + cache = Defaults[.keepAppInputMode_cache] + } + + static let shared = InputModeCache() +} diff --git a/Fire/Preferences/ApplicationPane.swift b/Fire/Preferences/ApplicationPane.swift index df7ad50..a32a0e9 100644 --- a/Fire/Preferences/ApplicationPane.swift +++ b/Fire/Preferences/ApplicationPane.swift @@ -16,15 +16,18 @@ struct ApplicationSettingItemView: View { let onChange: () -> Void private func getDisplayName(_ identifier: String) -> String { - guard let info = Bundle.main.localizedInfoDictionary ?? Bundle.main.infoDictionary else { return identifier } - guard let displayName = ( - info["CFBundleDisplayName"] ?? - info["CFBundleName"]) as? String else { return identifier } + guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: identifier) else { + return identifier + } + let displayName = FileManager.default.displayName(atPath: url.path) return "\(displayName)(\(identifier))" } private func getIcon(_ identifier: String) -> NSImage { - return NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) + guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: identifier) else { + return NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) + } + return NSWorkspace.shared.icon(forFile: url.path) } var body: some View { @@ -106,10 +109,16 @@ struct ApplicationPane: View { HStack { Text("自动切换") Toggle("保持应用最后使用的输入模式", isOn: $keepAppInputMode) - .padding(.leading, 12) + .padding(.leading, 10) + } + HStack { + Text("仅保留最近使用的\(InputModeCache.shared.capacity)个应用的输入模式") + .font(.footnote) + .padding(.leading, 70) } HStack { - Picker("显示提示", selection: $appInputModeTipShowTime) { + Text("显示提示") + Picker("", selection: $appInputModeTipShowTime) { Text("仅在变化时显示").tag(AppInputModeTipShowTime.onlyChanged) Text("总是显示").tag(AppInputModeTipShowTime.always) Text("不显示") @@ -127,7 +136,7 @@ struct ApplicationPane: View { Text("添加") } } - .padding(.leading, 12) + .padding(.leading, 8) } ScrollView(.vertical) { if appSettings.count > 0 { diff --git a/Fire/PunctuationConversion.swift b/Fire/PunctuationConversion.swift index 047d85a..eda280c 100644 --- a/Fire/PunctuationConversion.swift +++ b/Fire/PunctuationConversion.swift @@ -2,7 +2,7 @@ // PunctuationConversion.swift // Fire // -// Created by 杨永榜 on 2023/10/26. +// Created by qwertyyb on 2023/10/26. // import Foundation diff --git a/Fire/Utils/ModifierKeyUpChecker.swift b/Fire/Utils/ModifierKeyUpChecker.swift index 85b2950..6039d4c 100644 --- a/Fire/Utils/ModifierKeyUpChecker.swift +++ b/Fire/Utils/ModifierKeyUpChecker.swift @@ -58,7 +58,7 @@ class ModifierKeyUpChecker { } } - private let delayInterval = 0.3 + private let delayInterval = 0.2 private var lastTime: Date = Date() diff --git a/Fire/types.swift b/Fire/types.swift index 4c4fd5a..4100a9a 100644 --- a/Fire/types.swift +++ b/Fire/types.swift @@ -119,11 +119,11 @@ extension Defaults.Keys { // 应用输入配置 static let keepAppInputMode = Key("keepAppInputMode", default: true) + static let keepAppInputMode_keys = Key<[String]>("keepAppInputMode_keys", default: []) + static let keepAppInputMode_cache = Key<[String: InputMode]>("keepAppInputMode_cache", default: [:]) + static let appInputModeTipShowTime = Key("appInputModeTipShowTime", default: .onlyChanged) - static let appSettings = Key<[String: ApplicationSettingItem]>( - "AppSettings", - default: [:] - ) + static let appSettings = Key<[String: ApplicationSettingItem]>("AppSettings", default: [:]) // 标点符号配置 static let punctuationMode = Key("punctuationMode", default: PunctuationMode.zhhans) static let customPunctuationSettings = Key<[String: String]>("customPunctuationSettings", default: punctuation) @@ -145,7 +145,7 @@ extension Defaults.Keys { // Key Type UserDefaults name Default value } -enum InputMode: String { +enum InputMode: String, Defaults.Serializable { case zhhans case enUS }