Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions data/squirrel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ chord_duration: 0.1 # seconds
# options: always | never | appropriate
show_notifications_when: appropriate

# Menu-bar status icon.
# 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以簡化爲 show_status_icon

誰知道會不會還有別的選項?
即使以後再加 status_icon 的詳細配置,把開關單寫出來也沒問題。


style:
color_scheme: native
# Optional: define both light and dark color schemes to match system appearance
Expand Down
6 changes: 6 additions & 0 deletions sources/InputSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
96 changes: 82 additions & 14 deletions sources/SquirrelApplicationDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")!
Expand All @@ -18,6 +19,8 @@
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 {
true
Expand Down Expand Up @@ -54,6 +57,7 @@

func applicationWillFinishLaunching(_ notification: Notification) {
panel = SquirrelPanel(position: .zero)
refreshStatusItem()
addObservers()
}

Expand All @@ -62,6 +66,16 @@
NotificationCenter.default.removeObserver(self)
DistributedNotificationCenter.default().removeObserver(self)
panel?.hide()
if let item = statusItem {
NSStatusBar.system.removeStatusItem(item)
statusItem = nil
}
}

func updateStatusIcon(asciiMode: Bool, schemaLabel: String?) {
DispatchQueue.main.async { [weak self] in
self?.applyStatusIcon(asciiMode: asciiMode, schemaLabel: schemaLabel)
}
}

func deploy() {
Expand Down Expand Up @@ -162,6 +176,8 @@
}

enableNotifications = config!.getString("show_notifications_when") != "never"
showStatusIcon = config!.getBool("status_icon/show") ?? true
refreshStatusItem()
if let panel = panel, let config = self.config {
panel.load(config: config, forDarkMode: false)
panel.load(config: config, forDarkMode: true)
Expand Down Expand Up @@ -225,6 +241,9 @@
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 {
Expand All @@ -235,7 +254,7 @@

}

private func notificationHandler(contextObject: UnsafeMutableRawPointer?, sessionId: RimeSessionId, messageTypeC: UnsafePointer<CChar>?, messageValueC: UnsafePointer<CChar>?) {

Check warning on line 257 in sources/SquirrelApplicationDelegate.swift

View workflow job for this annotation

GitHub Actions / build

Function should have complexity 10 or less; currently complexity is 13 (cyclomatic_complexity)
let delegate: SquirrelApplicationDelegate = Unmanaged<SquirrelApplicationDelegate>.fromOpaque(contextObject!).takeUnretainedValue()

let messageType = messageTypeC.map { String(cString: $0) }
Expand All @@ -253,30 +272,42 @@
}
return
}
// off
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code below needs cleanup.

The control flow is not easy to understand.

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 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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra space

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) }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra spaces

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
}

// off
if !delegate.enableNotifications {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}
}

Expand All @@ -287,6 +318,43 @@
}
}

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 {
button.font = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
button.toolTip = NSLocalizedString("Squirrel", comment: "")
}
statusItem = item
applyStatusIcon(asciiMode: false, schemaLabel: nil)
updateStatusItemVisibility()
}

func updateStatusItemVisibility() {
guard let statusItem = statusItem else { return }
let id = SquirrelInstaller.currentInputSourceID() ?? ""

Check warning on line 345 in sources/SquirrelApplicationDelegate.swift

View workflow job for this annotation

GitHub Actions / build

Variable name 'id' should be between 3 and 40 characters long (identifier_name)
statusItem.isVisible = id.hasPrefix("im.rime.inputmethod.Squirrel")
}

func applyStatusIcon(asciiMode: Bool, schemaLabel: String?) {
guard let button = statusItem?.button else { return }
if let schemaLabel = schemaLabel, !schemaLabel.isEmpty {
button.title = schemaLabel
} else {
button.title = asciiMode ? "A" : "中"
}
}

func shutdownRime() {
config?.close()
rimeAPI.finalize()
Expand Down