From 230a14e2f81103520ebfab732b232bba8401f8ad Mon Sep 17 00:00:00 2001 From: qwertyyb Date: Sat, 4 Jun 2022 14:30:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F=E6=8C=87=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Fire.xcodeproj/project.pbxproj | 4 ++ Fire/AppDelegate.swift | 1 + Fire/Fire.swift | 28 ++++++++-- Fire/FireInputController.swift | 15 ++++-- Fire/InputSource.swift | 6 +-- Fire/Preferences/GeneralPane.swift | 4 ++ Fire/StatusBar.swift | 82 ++++++++++++++++++++++++++++++ Fire/Utils/Statistics.swift | 13 +++-- Fire/types.swift | 1 + 9 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 Fire/StatusBar.swift diff --git a/Fire.xcodeproj/project.pbxproj b/Fire.xcodeproj/project.pbxproj index 78baf01..748958c 100644 --- a/Fire.xcodeproj/project.pbxproj +++ b/Fire.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 451E6056232E24A5007B0463 /* FireInputController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451E6055232E24A5007B0463 /* FireInputController.swift */; }; 451E605D232E3A3C007B0463 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 451E605C232E3A3C007B0463 /* MainMenu.xib */; }; 451E605F232E400B007B0463 /* Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451E605E232E400B007B0463 /* Fire.swift */; }; + 453377E52849E76A0064E4F2 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 453377E42849E76A0064E4F2 /* StatusBar.swift */; }; 45449B4C23535ED000C9EFEF /* InputMethodKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 451E6058232E2787007B0463 /* InputMethodKit.framework */; }; 45577E502543F03F0064325B /* build.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45577E4F2543F03F0064325B /* build.swift */; }; 45577E562543F4060064325B /* wb_table.txt in Resources */ = {isa = PBXBuildFile; fileRef = 45577E552543F4060064325B /* wb_table.txt */; }; @@ -83,6 +84,7 @@ 451E6058232E2787007B0463 /* InputMethodKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = InputMethodKit.framework; path = System/Library/Frameworks/InputMethodKit.framework; sourceTree = SDKROOT; }; 451E605C232E3A3C007B0463 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 451E605E232E400B007B0463 /* Fire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fire.swift; sourceTree = ""; }; + 453377E42849E76A0064E4F2 /* StatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; 45577E4F2543F03F0064325B /* build.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = build.swift; sourceTree = ""; }; 45577E552543F4060064325B /* wb_table.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wb_table.txt; sourceTree = ""; }; 45577E5F25442E240064325B /* TableBuilder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TableBuilder; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -195,6 +197,7 @@ 673A400B253D9FE70003901E /* InputSource.swift */, 45577EAF254575480064325B /* types.swift */, 45ACDE1A2841C52500658F46 /* Bridging-Header.h */, + 453377E42849E76A0064E4F2 /* StatusBar.swift */, ); path = Fire; sourceTree = ""; @@ -479,6 +482,7 @@ 45DCE62226A31F140009FED1 /* ApplicationSettingCache.swift in Sources */, 459DE990232EB26600A3ACD1 /* CandidatesView.swift in Sources */, 45DB6EC727E5B8FE00A39925 /* ThemeConfig.swift in Sources */, + 453377E52849E76A0064E4F2 /* StatusBar.swift in Sources */, 673C4178254697E400F462A3 /* TipsWindow.swift in Sources */, 677A0874254BD47D000B58D4 /* ToastWindow.swift in Sources */, ); diff --git a/Fire/AppDelegate.swift b/Fire/AppDelegate.swift index d18eca4..3b10fc6 100644 --- a/Fire/AppDelegate.swift +++ b/Fire/AppDelegate.swift @@ -14,6 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { var fire: Fire! var statistics: Statistics! + let statusBar = StatusBar.shared func installInputSource() { print("install input source") diff --git a/Fire/Fire.swift b/Fire/Fire.swift index 168027e..2fccc76 100644 --- a/Fire/Fire.swift +++ b/Fire/Fire.swift @@ -35,6 +35,7 @@ class Fire: NSObject { // 逻辑 static let candidateInserted = Notification.Name("Fire.candidateInserted") + static let inputModeChanged = Notification.Name("Fire.inputModeChanged") private var database: OpaquePointer? private var queryStatement: OpaquePointer? @@ -57,6 +58,23 @@ class Fire: NSObject { close() } + func toggleInputMode(_ nextInputMode: InputMode? = nil) { + if nextInputMode != nil, self.inputMode == nextInputMode { + return + } + let oldVal = self.inputMode + if let nextInputMode = nextInputMode, nextInputMode != self.inputMode { + self.inputMode = nextInputMode + } else { + self.inputMode = inputMode == .enUS ? .zhhans : .enUS + } + NotificationCenter.default.post(name: Fire.inputModeChanged, object: nil, userInfo: [ + "oldVal": oldVal, + "val": self.inputMode, + "label": self.inputMode == .enUS ? "英" : "中" + ]) + } + private func getStatementSql() -> String { let candidateCount = Defaults[.candidateCount] // 比显示的候选词数量多查一个,以此判断有没有下一页 @@ -96,7 +114,12 @@ class Fire: NSObject { } func prepareStatement() { - sqlite3_open_v2(getDatabaseURL().path, &database, SQLITE_OPEN_READWRITE, nil) + if database == nil { + sqlite3_open_v2(getDatabaseURL().path, &database, SQLITE_OPEN_READWRITE, nil) + } + if queryStatement != nil { + sqlite3_finalize(queryStatement) + } if sqlite3_prepare_v2(database, getStatementSql(), -1, &queryStatement, nil) == SQLITE_OK { print("prepare ok") print(sqlite3_bind_parameter_index(queryStatement, ":code")) @@ -145,8 +168,6 @@ class Fire: NSObject { sqlite3_bind_parameter_index(queryStatement, ":offset"), Int32((page - 1) * Defaults[.candidateCount]) ) - let strp = sqlite3_expanded_sql(queryStatement)! - print(String(cString: strp)) while sqlite3_step(queryStatement) == SQLITE_ROW { let code = String.init(cString: sqlite3_column_text(queryStatement, 0)) let text = String.init(cString: sqlite3_column_text(queryStatement, 1)) @@ -161,7 +182,6 @@ class Fire: NSObject { if candidates.isEmpty { candidates.append(Candidate(code: origin, text: origin, type: "wb", isPlaceholder: true)) } - return (candidates, hasNext: allCount > count) } diff --git a/Fire/FireInputController.swift b/Fire/FireInputController.swift index 8f0e991..a62ff84 100644 --- a/Fire/FireInputController.swift +++ b/Fire/FireInputController.swift @@ -103,7 +103,7 @@ class FireInputController: IMKInputController { // 把当前未上屏的原始code上屏处理 insertText(_originalString) - inputMode = inputMode == .zhhans ? InputMode.enUS : InputMode.zhhans + Fire.shared.toggleInputMode() let text = inputMode == .zhhans ? "中" : "英" @@ -336,7 +336,12 @@ class FireInputController: IMKInputController { } }), (Fire.prevPageBtnTapped, { _ in self.curPage = self.curPage > 1 ? self.curPage - 1 : 1 }), - (Fire.nextPageBtnTapped, { _ in self.curPage = self._hasNext ? self.curPage + 1 : self.curPage }) + (Fire.nextPageBtnTapped, { _ in self.curPage = self._hasNext ? self.curPage + 1 : self.curPage }), + (Fire.inputModeChanged, { notification in + if self._originalString.count > 0, notification.userInfo?["val"] as? InputMode == InputMode.enUS { + self.insertText(self._originalString) + } + }) ] } @@ -348,7 +353,7 @@ class FireInputController: IMKInputController { if let appSetting = Defaults[.appSettings][identifier], let mode = InputMode(rawValue: appSetting.inputModeSetting.rawValue) { print("[FireInputController] activeClientInputMode from setting : \(identifier), \(mode)") - inputMode = mode + Fire.shared.toggleInputMode(mode) return } if !Defaults[.keepAppInputMode] { return } @@ -356,7 +361,7 @@ class FireInputController: IMKInputController { if let appSetting = Fire.shared.appSettingCache.get(bundleIdentifier: identifier), let mode = InputMode(rawValue: appSetting.inputModeSetting.rawValue) { print("[FireInputController] activeClientInputMode from cache: \(identifier), \(mode)") - inputMode = mode + Fire.shared.toggleInputMode(mode) } } @@ -372,7 +377,7 @@ class FireInputController: IMKInputController { forName: observer.name, object: nil, queue: nil, using: observer.callback ))} if Defaults[.disableEnMode] { - inputMode = .zhhans + Fire.shared.toggleInputMode(.zhhans) return } diff --git a/Fire/InputSource.swift b/Fire/InputSource.swift index 3cb8be8..a551cf8 100644 --- a/Fire/InputSource.swift +++ b/Fire/InputSource.swift @@ -36,9 +36,9 @@ class InputSource { } private func findInputSource(forUsage: InputSourceUsage = .enable) -> (inputSource: TISInputSource, sourceID: NSString)? { - let sourceList = TISCreateInputSourceList(nil, true).takeUnretainedValue() + let sourceList = TISCreateInputSourceList(nil, true).takeRetainedValue() as NSArray - for index in 0...CFArrayGetCount(sourceList)-1 { + for index in 0...fromOpaque(CFArrayGetValueAtIndex( sourceList, index)).takeUnretainedValue() if let result = transformTargetSource(inputSource) { @@ -104,7 +104,7 @@ class InputSource { } func isSelected() -> Bool { - guard let result = findInputSource() else { + guard let result = findInputSource(forUsage: .selected) else { return false } let unsafeIsSelected = TISGetInputSourceProperty( diff --git a/Fire/Preferences/GeneralPane.swift b/Fire/Preferences/GeneralPane.swift index 888315b..79794ee 100644 --- a/Fire/Preferences/GeneralPane.swift +++ b/Fire/Preferences/GeneralPane.swift @@ -22,6 +22,7 @@ struct GeneralPane: View { @Default(.zKeyQuery) private var zKeyQuery @Default(.toggleInputModeKey) private var toggleInputModeKey @Default(.disableEnMode) private var disableEnMode + @Default(.showInputModeStatus) private var showInputModeStatus var body: some View { Preferences.Container(contentWidth: 450.0) { @@ -76,6 +77,9 @@ struct GeneralPane: View { } GroupBox(label: Text("中英文切换")) { VStack(alignment: .leading, spacing: 12) { + HStack { + Toggle("状态栏显示", isOn: $showInputModeStatus) + } HStack { Toggle("禁止切换英文", isOn: $disableEnMode) } diff --git a/Fire/StatusBar.swift b/Fire/StatusBar.swift new file mode 100644 index 0000000..e4fad74 --- /dev/null +++ b/Fire/StatusBar.swift @@ -0,0 +1,82 @@ +// +// StatusBar.swift +// Fire +// +// Created by 虚幻 on 2022/6/3. +// Copyright © 2022 qwertyyb. All rights reserved. +// + +import Foundation +import AppKit +import Carbon +import Combine +import Defaults + +class StatusBar { + static let shared = StatusBar() + + let statusItem: NSStatusItem + private var inputSourceChangedSubscription: AnyCancellable? + private var inputModeChangedSubscription: AnyCancellable? + private var showInputModeStatusSubscript: AnyCancellable? + private init() { + // 输入法变化时,根据当前选中状态切换显示 + statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + statusItem.button?.title = "中" + statusItem.button?.action = #selector(changeInputMode) + statusItem.button?.target = self + + refreshVisibleStatus() + + startEventsListener() + + showInputModeStatusSubscript = Defaults.publisher(.showInputModeStatus).sink { event in + if event.newValue { + self.startEventsListener() + } else { + self.stopEventListener() + } + self.refreshVisibleStatus() + } + } + + deinit { + stopEventListener() + showInputModeStatusSubscript?.cancel() + showInputModeStatusSubscript = nil + } + + private func startEventsListener() { + stopEventListener() + inputSourceChangedSubscription = DistributedNotificationCenter.default() + .publisher(for: Notification.Name(kTISNotifySelectedKeyboardInputSourceChanged as String)) + .sink { _ in + self.statusItem.isVisible = InputSource.shared.isSelected() + } + + // inputMode变化时,刷新标题 + inputModeChangedSubscription = NotificationCenter.default.publisher(for: Fire.inputModeChanged) + .sink { _ in + self.refreshTitle() + } + } + + private func stopEventListener() { + inputSourceChangedSubscription?.cancel() + inputModeChangedSubscription?.cancel() + inputModeChangedSubscription = nil + inputSourceChangedSubscription = nil + } + + @objc func changeInputMode() { + Fire.shared.toggleInputMode() + } + + private func refreshTitle() { + statusItem.button?.title = Fire.shared.inputMode == .zhhans ? "中" : "英" + } + + private func refreshVisibleStatus() { + statusItem.isVisible = Defaults[.showInputModeStatus] && InputSource.shared.isSelected() + } +} diff --git a/Fire/Utils/Statistics.swift b/Fire/Utils/Statistics.swift index 45018c6..6690a47 100644 --- a/Fire/Utils/Statistics.swift +++ b/Fire/Utils/Statistics.swift @@ -59,9 +59,13 @@ class Statistics { sqlite3_finalize(insertStatement) insertStatement = nil } else { + sqlite3_finalize(insertStatement) + insertStatement = nil print("errmsg: \(String(cString: sqlite3_errmsg(database)!))") } } else { + sqlite3_finalize(insertStatement) + insertStatement = nil print("prepare_errmsg: \(String(cString: sqlite3_errmsg(database)!))") } NotificationCenter.default.post(name: Statistics.updated, object: nil) @@ -130,10 +134,13 @@ class Statistics { private func getVersion() -> Int32 { let sql = "PRAGMA user_version" var stmt: OpaquePointer? - if sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK - && sqlite3_step(stmt) == SQLITE_ROW { - return sqlite3_column_int(stmt, 0) + if sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK, + sqlite3_step(stmt) == SQLITE_ROW { + let version = sqlite3_column_int(stmt, 0) + sqlite3_finalize(stmt) + return version } + sqlite3_finalize(stmt) return 0 } diff --git a/Fire/types.swift b/Fire/types.swift index f2dbcfd..116b0b9 100644 --- a/Fire/types.swift +++ b/Fire/types.swift @@ -85,6 +85,7 @@ extension Defaults.Keys { "inputModeTipWindowType", default: InputModeTipWindowType.centerScreen ) + static let showInputModeStatus = Key("showInputModeStatus", default: true) // 主题 static let themeConfig = Key("themeConfig", default: defaultThemeConfig)