From 7b286eca6dc9030060c56d5f8498df103162295b Mon Sep 17 00:00:00 2001 From: "shu.nie" Date: Mon, 20 Apr 2026 12:03:30 +0800 Subject: [PATCH 1/6] feat(ui): status bar icon reflects ascii_mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an NSStatusItem that shows "中" when Squirrel is in Chinese input mode and "A" when ascii_mode is toggled on (e.g. via Shift). The icon auto-hides when the active input source is not Squirrel, driven by a kTISNotifySelectedKeyboardInputSourceChanged observer. The Rime notification handler is refactored to extract optionName so the ascii_mode toggle can drive the status icon update, without disturbing the existing schema / option notification paths. No changes to IMKInputController, its right-click menu, or system input source enablement. Co-Authored-By: Claude Opus 4.7 --- sources/InputSource.swift | 6 ++ sources/SquirrelApplicationDelegate.swift | 74 +++++++++++++++++++---- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/sources/InputSource.swift b/sources/InputSource.swift index a0c80add3..8fd8daf41 100644 --- a/sources/InputSource.swift +++ b/sources/InputSource.swift @@ -91,6 +91,12 @@ final class SquirrelInstaller { } } + static func currentInputSourceID() -> String? { + let source = TISCopyCurrentKeyboardInputSource().takeRetainedValue() + let idRef = TISGetInputSourceProperty(source, kTISPropertyInputSourceID) + return unsafeBitCast(idRef, to: CFString?.self) as String? + } + func disable(modes: [InputMode] = []) { let modesToDisable = modes.isEmpty ? InputMode.allCases : modes for (mode, inputSource) in getInputSource(modes: modesToDisable) { diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index c60376040..c71d687a8 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -8,6 +8,7 @@ import UserNotifications import Sparkle import AppKit +import InputMethodKit final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUStandardUserDriverDelegate, UNUserNotificationCenterDelegate { static let rimeWikiURL = URL(string: "https://github.com/rime/home/wiki")! @@ -18,6 +19,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta var config: SquirrelConfig? var panel: SquirrelPanel? var enableNotifications = false + var statusItem: NSStatusItem? let updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) var supportsGentleScheduledUpdateReminders: Bool { true @@ -54,6 +56,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta func applicationWillFinishLaunching(_ notification: Notification) { panel = SquirrelPanel(position: .zero) + setupStatusItem() addObservers() } @@ -62,6 +65,16 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta NotificationCenter.default.removeObserver(self) DistributedNotificationCenter.default().removeObserver(self) panel?.hide() + if let item = statusItem { + NSStatusBar.system.removeStatusItem(item) + statusItem = nil + } + } + + func updateStatusIcon(asciiMode: Bool) { + DispatchQueue.main.async { [weak self] in + self?.applyStatusIcon(asciiMode: asciiMode) + } } func deploy() { @@ -225,6 +238,9 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta let notifCenter = DistributedNotificationCenter.default() notifCenter.addObserver(forName: .init("SquirrelReloadNotification"), object: nil, queue: nil, using: rimeNeedsReload) notifCenter.addObserver(forName: .init("SquirrelSyncNotification"), object: nil, queue: nil, using: rimeNeedsSync) + notifCenter.addObserver(forName: .init(kTISNotifySelectedKeyboardInputSourceChanged as String), object: nil, queue: .main) { [weak self] _ in + self?.updateStatusItemVisibility() + } } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -253,20 +269,23 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio } return } - // off - if !delegate.enableNotifications { - return - } - 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" { + if messageType == "option" { let state = messageValue?.first != "!" - let optionName = if state { - messageValue + let optionName: String? + if state { + optionName = messageValue + } else if let value = messageValue { + optionName = String(value[value.index(after: value.startIndex)...]) } else { - String(messageValue![messageValue!.index(after: messageValue!.startIndex)...]) + optionName = nil + } + if optionName == "ascii_mode" { + delegate.updateStatusIcon(asciiMode: state) + } + // off + if !delegate.enableNotifications { + return } if let optionName = optionName { optionName.withCString { name in @@ -277,6 +296,17 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio delegate.showStatusMessage(msgTextLong: longLabel, msgTextShort: shortLabel) } } + return + } + + // off + if !delegate.enableNotifications { + return + } + + if messageType == "schema", let messageValue = messageValue, let schemaName = try? /^[^\/]*\/(.*)$/.firstMatch(in: messageValue)?.output.1 { + delegate.showStatusMessage(msgTextLong: String(schemaName), msgTextShort: String(schemaName)) + return } } @@ -287,6 +317,28 @@ private extension SquirrelApplicationDelegate { } } + func setupStatusItem() { + let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + if let button = item.button { + button.font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold) + button.toolTip = NSLocalizedString("Squirrel", comment: "") + } + statusItem = item + applyStatusIcon(asciiMode: false) + updateStatusItemVisibility() + } + + func updateStatusItemVisibility() { + guard let statusItem = statusItem else { return } + let id = SquirrelInstaller.currentInputSourceID() ?? "" + statusItem.isVisible = id.hasPrefix("im.rime.inputmethod.Squirrel") + } + + func applyStatusIcon(asciiMode: Bool) { + guard let button = statusItem?.button else { return } + button.title = asciiMode ? "A" : "中" + } + func shutdownRime() { config?.close() rimeAPI.finalize() From 3dad5247e643587e1c28d3a4103d34fd2766e530 Mon Sep 17 00:00:00 2001 From: "shu.nie" Date: Tue, 21 Apr 2026 14:21:17 +0800 Subject: [PATCH 2/6] feat(ui): allow disabling status bar icon via show_status_icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new top-level boolean key in squirrel.yaml (default true) so users can hide the 中/A menu-bar icon with a custom patch: patch: show_status_icon: false Re-reads the value in loadSettings() and creates / removes the NSStatusItem via refreshStatusItem(), so "Squirrel --reload" picks up the toggle without relaunching. Co-Authored-By: Claude Opus 4.7 --- data/squirrel.yaml | 4 ++++ sources/SquirrelApplicationDelegate.swift | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/data/squirrel.yaml b/data/squirrel.yaml index 40227deb2..5bbcb6a91 100644 --- a/data/squirrel.yaml +++ b/data/squirrel.yaml @@ -15,6 +15,10 @@ chord_duration: 0.1 # seconds # options: always | never | appropriate show_notifications_when: appropriate +# whether to show a menu bar icon indicating input mode +# (中 for Chinese, A for ascii_mode / English). Set to false for a clean menu bar. +show_status_icon: true + style: color_scheme: native # Optional: define both light and dark color schemes to match system appearance diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index c71d687a8..336fbb754 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -19,6 +19,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta var config: SquirrelConfig? var panel: SquirrelPanel? var enableNotifications = false + var showStatusIcon: Bool = true var statusItem: NSStatusItem? let updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) var supportsGentleScheduledUpdateReminders: Bool { @@ -56,7 +57,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta func applicationWillFinishLaunching(_ notification: Notification) { panel = SquirrelPanel(position: .zero) - setupStatusItem() + refreshStatusItem() addObservers() } @@ -175,6 +176,8 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } enableNotifications = config!.getString("show_notifications_when") != "never" + showStatusIcon = config!.getBool("show_status_icon") ?? true + refreshStatusItem() if let panel = panel, let config = self.config { panel.load(config: config, forDarkMode: false) panel.load(config: config, forDarkMode: true) @@ -317,6 +320,17 @@ private extension SquirrelApplicationDelegate { } } + func refreshStatusItem() { + if showStatusIcon { + if statusItem == nil { + setupStatusItem() + } + } else if let item = statusItem { + NSStatusBar.system.removeStatusItem(item) + statusItem = nil + } + } + func setupStatusItem() { let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) if let button = item.button { From 049cab62ba6b22ed80960df0c6a3536c51098fc9 Mon Sep 17 00:00:00 2001 From: "shu.nie" Date: Tue, 21 Apr 2026 23:43:25 +0800 Subject: [PATCH 3/6] feat(ui): use schema state label for menu-bar icon, with user overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the ascii_mode menu-bar icon hardcoded "A" / "中", ignoring each schema's own states: declaration (e.g. luna_pinyin_simp defines [中文, 西文]). This commit: 1. Calls get_state_label_abbreviated in the option-notification path and reuses the short label for the icon, so the icon respects the active schema's convention by default. 2. Adds two optional yaml keys, status_icon/ascii and status_icon/chinese, that let users pin compact single-character glyphs when the schema's label is too wide for the menu bar. Resolution order for the button title: user override -> schema short label -> hardcoded "A"/"中" fallback. Restructures the option-notification handler so the label fetch is shared between the icon and the status bubble -- the bubble remains generic (fires for any option when enableNotifications is on), while the icon is still gated to ascii_mode. Co-Authored-By: Claude Opus 4.7 --- data/squirrel.yaml | 6 ++++ sources/SquirrelApplicationDelegate.swift | 40 ++++++++++++++--------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/data/squirrel.yaml b/data/squirrel.yaml index 5bbcb6a91..cd3de68c7 100644 --- a/data/squirrel.yaml +++ b/data/squirrel.yaml @@ -19,6 +19,12 @@ show_notifications_when: appropriate # (中 for Chinese, A for ascii_mode / English). Set to false for a clean menu bar. show_status_icon: true +# Menu-bar status-icon text. Overrides the schema's short state label for ascii_mode. +# Useful when the schema label is too wide for the menu bar (e.g. "中文" / "西文" → "中" / "A"). +# Unset → uses the schema's short `states:` label, then falls back to "A" / "中". +# status_icon/ascii: A # shown when ascii_mode is on (English input) +# status_icon/chinese: 中 # shown when ascii_mode is off (Chinese input) + style: color_scheme: native # Optional: define both light and dark color schemes to match system appearance diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index 336fbb754..80867e9d1 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -20,6 +20,8 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta var panel: SquirrelPanel? var enableNotifications = false var showStatusIcon: Bool = true + var statusIconAsciiLabel: String? // nil → use schema label, then "A" + var statusIconChineseLabel: String? // nil → use schema label, then "中" var statusItem: NSStatusItem? let updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) var supportsGentleScheduledUpdateReminders: Bool { @@ -72,9 +74,9 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } } - func updateStatusIcon(asciiMode: Bool) { + func updateStatusIcon(asciiMode: Bool, schemaLabel: String?) { DispatchQueue.main.async { [weak self] in - self?.applyStatusIcon(asciiMode: asciiMode) + self?.applyStatusIcon(asciiMode: asciiMode, schemaLabel: schemaLabel) } } @@ -177,6 +179,8 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta enableNotifications = config!.getString("show_notifications_when") != "never" showStatusIcon = config!.getBool("show_status_icon") ?? true + statusIconAsciiLabel = config!.getString("status_icon/ascii") + statusIconChineseLabel = config!.getString("status_icon/chinese") refreshStatusItem() if let panel = panel, let config = self.config { panel.load(config: config, forDarkMode: false) @@ -283,20 +287,18 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio } else { optionName = nil } - if optionName == "ascii_mode" { - delegate.updateStatusIcon(asciiMode: state) - } - // off - if !delegate.enableNotifications { - return - } if let optionName = optionName { optionName.withCString { name in - let stateLabelLong = delegate.rimeAPI.get_state_label_abbreviated(sessionId, name, state, false) + 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 longLabel = stateLabelLong.str .map { String(cString: $0) } let shortLabel = stateLabelShort.str.map { String(cString: $0) } - delegate.showStatusMessage(msgTextLong: longLabel, msgTextShort: shortLabel) + if optionName == "ascii_mode" { + delegate.updateStatusIcon(asciiMode: state, schemaLabel: shortLabel) + } + if delegate.enableNotifications { + delegate.showStatusMessage(msgTextLong: longLabel, msgTextShort: shortLabel) + } } } return @@ -338,7 +340,7 @@ private extension SquirrelApplicationDelegate { button.toolTip = NSLocalizedString("Squirrel", comment: "") } statusItem = item - applyStatusIcon(asciiMode: false) + applyStatusIcon(asciiMode: false, schemaLabel: nil) updateStatusItemVisibility() } @@ -348,9 +350,17 @@ private extension SquirrelApplicationDelegate { statusItem.isVisible = id.hasPrefix("im.rime.inputmethod.Squirrel") } - func applyStatusIcon(asciiMode: Bool) { + func applyStatusIcon(asciiMode: Bool, schemaLabel: String?) { guard let button = statusItem?.button else { return } - button.title = asciiMode ? "A" : "中" + let override = asciiMode ? statusIconAsciiLabel : statusIconChineseLabel + let fallback = asciiMode ? "A" : "中" + if let override = override, !override.isEmpty { + button.title = override + } else if let schemaLabel = schemaLabel, !schemaLabel.isEmpty { + button.title = schemaLabel + } else { + button.title = fallback + } } func shutdownRime() { From 8480b2558bc0918e5f2990d523a96ce5b9d4470b Mon Sep 17 00:00:00 2001 From: "shu.nie" Date: Tue, 21 Apr 2026 23:52:33 +0800 Subject: [PATCH 4/6] refactor(ui): group status-icon keys under status_icon/ subtree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, the three status-icon settings had inconsistent styles: show_status_icon: true (flat, underscored) status_icon/ascii: A (subtree, slashed) status_icon/chinese: 中 (subtree, slashed) Unify under one status_icon/ subtree, matching Rime's convention for grouped settings (ascii_composer/switch_key, key_binder/bindings, ...): status_icon: show: true ascii: A chinese: 中 show_status_icon was only introduced in the previous commit on this feature branch (3dad524) and has not been released, so the rename is a pure pre-merge cleanup with no user migration needed outside this PR. Co-Authored-By: Claude Opus 4.7 --- data/squirrel.yaml | 19 ++++++++++--------- sources/SquirrelApplicationDelegate.swift | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/data/squirrel.yaml b/data/squirrel.yaml index cd3de68c7..15d3f1d5f 100644 --- a/data/squirrel.yaml +++ b/data/squirrel.yaml @@ -15,15 +15,16 @@ chord_duration: 0.1 # seconds # options: always | never | appropriate show_notifications_when: appropriate -# whether to show a menu bar icon indicating input mode -# (中 for Chinese, A for ascii_mode / English). Set to false for a clean menu bar. -show_status_icon: true - -# Menu-bar status-icon text. Overrides the schema's short state label for ascii_mode. -# Useful when the schema label is too wide for the menu bar (e.g. "中文" / "西文" → "中" / "A"). -# Unset → uses the schema's short `states:` label, then falls back to "A" / "中". -# status_icon/ascii: A # shown when ascii_mode is on (English input) -# status_icon/chinese: 中 # shown when ascii_mode is off (Chinese input) +# Menu-bar status icon. +# show — whether to show the icon at all; set to false for a clean menu bar +# ascii — label when ascii_mode is on (English input) +# chinese — label when ascii_mode is off (Chinese input) +# When ascii/chinese are unset, the current schema's short `states:` label is used +# (e.g. "西文" / "中文"), falling back to "A" / "中" when the schema defines no label. +status_icon: + show: true + # ascii: A + # chinese: 中 style: color_scheme: native diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index 80867e9d1..745bef629 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -178,7 +178,7 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } enableNotifications = config!.getString("show_notifications_when") != "never" - showStatusIcon = config!.getBool("show_status_icon") ?? true + showStatusIcon = config!.getBool("status_icon/show") ?? true statusIconAsciiLabel = config!.getString("status_icon/ascii") statusIconChineseLabel = config!.getString("status_icon/chinese") refreshStatusItem() From 150b3c96ddd69401a71ef2058da14a469de390d2 Mon Sep 17 00:00:00 2001 From: "shu.nie" Date: Wed, 22 Apr 2026 01:59:59 +0800 Subject: [PATCH 5/6] fix(ui): honor RimeStringSlice.length when reading state labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_state_label_abbreviated returns a RimeStringSlice whose .str points to a null-terminated C string but whose .length may be shorter — for abbreviated=true without an explicit `abbrev:` field, librime clips .length to the first Unicode character (see switches.cc first_unicode_byte_length). The Swift side was using String(cString: slice.str), which ignores .length and reads to the null terminator. As a result: - The "short" label for ascii_mode on luna_pinyin_simp (states: [中文, 西文]) came through as "中文" / "西文" instead of the intended "中" / "西". - The menu-bar icon, which defaults to this short label when no user override is set, showed "中文" / "西文" taking up far more menu-bar space than a status glyph should. Add a private RimeStringSlice.asString that constructs a Swift String from Data(bytes:count:) using the slice's own length, and switch both notificationHandler conversions (long + short) to use it. The long path is unchanged in behavior (long always has full length) but now goes through the same helper for consistency. After this fix the icon, with default config on luna_pinyin_simp, will correctly show "中" / "西" — no user override needed. Co-Authored-By: Claude Opus 4.7 --- sources/SquirrelApplicationDelegate.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index 745bef629..d72a30901 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -258,6 +258,18 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } +private extension RimeStringSlice { + /// Bridge the slice's pointer + length to a Swift String, honoring `.length`. + /// librime clips `.length` to the first Unicode character for abbreviated labels + /// when no explicit `abbrev:` field is defined, so reading past `.length` (e.g. with + /// `String(cString:)`) would incorrectly return the full `states:` value. + var asString: String? { + guard let ptr = str else { return nil } + let data = Data(bytes: UnsafeRawPointer(ptr), count: Int(length)) + return String(data: data, encoding: .utf8) + } +} + private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer?, messageValueC: UnsafePointer?) { let delegate: SquirrelApplicationDelegate = Unmanaged.fromOpaque(contextObject!).takeUnretainedValue() @@ -291,8 +303,8 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio 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) } + let longLabel = stateLabelLong.asString + let shortLabel = stateLabelShort.asString if optionName == "ascii_mode" { delegate.updateStatusIcon(asciiMode: state, schemaLabel: shortLabel) } From a207eeeff890637a97af36c3c767e484dd036eb8 Mon Sep 17 00:00:00 2001 From: "shu.nie" Date: Thu, 7 May 2026 15:39:29 +0800 Subject: [PATCH 6/6] refactor(ui): simplify status-icon per review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reviewer asked to drop the schema-label complexity in favour of hardcoded glyphs, since mainstream schemas don't yet declare `abbrev:` for ascii_mode and so `get_state_label_abbreviated` returns wide labels like "中文" / "西文" that are unsuitable for a menu-bar glyph. Take a middle path that satisfies the simplification request without giving up forward compatibility: - Drop the user-facing `status_icon/ascii` and `status_icon/chinese` config keys (and the cached instance properties that backed them). - Drop the `RimeStringSlice.asString` length-aware helper from the prior commit. `String(cString:)` reads `.str` to its null terminator, which gives the correct abbrev for schemas that declare it (the reviewer's referenced `librime/data/minimal/luna_pinyin.schema.yaml` has `abbrev: [中, A]` -> `.str` is "中\0" -> reads "中") and the full `states:` value otherwise. No client-side slicing necessary. - Hardcode the fallback at full-width "中" / "A" (per reviewer suggestion) for schemas that don't define `states:` at all. - Keep the `status_icon/show` subtree key untouched. Net effect: schemas that declare `abbrev: [中, A]` get compact glyphs for free; schemas that don't see their `states:` value as-is and can opt into compactness by adding `abbrev:` themselves; the codebase no longer carries the per-state config knobs the reviewer rejected. Co-Authored-By: Claude Opus 4.7 --- data/squirrel.yaml | 12 ++++------ sources/SquirrelApplicationDelegate.swift | 28 ++++------------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/data/squirrel.yaml b/data/squirrel.yaml index 15d3f1d5f..b684b47ca 100644 --- a/data/squirrel.yaml +++ b/data/squirrel.yaml @@ -16,15 +16,13 @@ chord_duration: 0.1 # seconds show_notifications_when: appropriate # Menu-bar status icon. -# show — whether to show the icon at all; set to false for a clean menu bar -# ascii — label when ascii_mode is on (English input) -# chinese — label when ascii_mode is off (Chinese input) -# When ascii/chinese are unset, the current schema's short `states:` label is used -# (e.g. "西文" / "中文"), falling back to "A" / "中" when the schema defines no label. +# show — whether to show the icon at all; set to false for a clean menu bar. +# The icon's text is the schema's short state label for ascii_mode (via +# get_state_label_abbreviated). Schemas declaring `abbrev: [中, A]` get +# compact glyphs; otherwise the schema's `states:` value is used as-is. +# Falls back to "中" / "A" when no `states:` is defined. status_icon: show: true - # ascii: A - # chinese: 中 style: color_scheme: native diff --git a/sources/SquirrelApplicationDelegate.swift b/sources/SquirrelApplicationDelegate.swift index d72a30901..10d58d05f 100644 --- a/sources/SquirrelApplicationDelegate.swift +++ b/sources/SquirrelApplicationDelegate.swift @@ -20,8 +20,6 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta var panel: SquirrelPanel? var enableNotifications = false var showStatusIcon: Bool = true - var statusIconAsciiLabel: String? // nil → use schema label, then "A" - var statusIconChineseLabel: String? // nil → use schema label, then "中" var statusItem: NSStatusItem? let updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) var supportsGentleScheduledUpdateReminders: Bool { @@ -179,8 +177,6 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta enableNotifications = config!.getString("show_notifications_when") != "never" showStatusIcon = config!.getBool("status_icon/show") ?? true - statusIconAsciiLabel = config!.getString("status_icon/ascii") - statusIconChineseLabel = config!.getString("status_icon/chinese") refreshStatusItem() if let panel = panel, let config = self.config { panel.load(config: config, forDarkMode: false) @@ -258,18 +254,6 @@ final class SquirrelApplicationDelegate: NSObject, NSApplicationDelegate, SPUSta } -private extension RimeStringSlice { - /// Bridge the slice's pointer + length to a Swift String, honoring `.length`. - /// librime clips `.length` to the first Unicode character for abbreviated labels - /// when no explicit `abbrev:` field is defined, so reading past `.length` (e.g. with - /// `String(cString:)`) would incorrectly return the full `states:` value. - var asString: String? { - guard let ptr = str else { return nil } - let data = Data(bytes: UnsafeRawPointer(ptr), count: Int(length)) - return String(data: data, encoding: .utf8) - } -} - private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer?, messageValueC: UnsafePointer?) { let delegate: SquirrelApplicationDelegate = Unmanaged.fromOpaque(contextObject!).takeUnretainedValue() @@ -303,8 +287,8 @@ private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessio 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.asString - let shortLabel = stateLabelShort.asString + let longLabel = stateLabelLong.str .map { String(cString: $0) } + let shortLabel = stateLabelShort.str.map { String(cString: $0) } if optionName == "ascii_mode" { delegate.updateStatusIcon(asciiMode: state, schemaLabel: shortLabel) } @@ -364,14 +348,10 @@ private extension SquirrelApplicationDelegate { func applyStatusIcon(asciiMode: Bool, schemaLabel: String?) { guard let button = statusItem?.button else { return } - let override = asciiMode ? statusIconAsciiLabel : statusIconChineseLabel - let fallback = asciiMode ? "A" : "中" - if let override = override, !override.isEmpty { - button.title = override - } else if let schemaLabel = schemaLabel, !schemaLabel.isEmpty { + if let schemaLabel = schemaLabel, !schemaLabel.isEmpty { button.title = schemaLabel } else { - button.title = fallback + button.title = asciiMode ? "A" : "中" } }