Skip to content

Instantly share code, notes, and snippets.

@ShikiSuen
Last active March 24, 2024 07:29
Show Gist options
  • Save ShikiSuen/73b7a55526c9fadd2da2a16d94ec5b49 to your computer and use it in GitHub Desktop.
Save ShikiSuen/73b7a55526c9fadd2da2a16d94ec5b49 to your computer and use it in GitHub Desktop.
Let's talk about what InputMethodKits needs to improve.

Related sample project: https://github.com/vChewing/vChewing-macOS/tree/3.4.9

It seems that individual bug reports doesn't work at all. Besides, the entire InputMethodKit needs a renovation.

This thread will be sent to Apple by certain special approaches after gathering enough usable information.

Let's talk about what InputMethodKits needs to improve. Here's my conclusion. If Apple think there's already an API, then it might be either mulfunctioning or not exposed to Swift.

  1. An official Swift-friendly wrapper with neither "!" nor "?" in the parameters of all provided APIs.

  2. Provide effective official exposed Swift access to:

    1. { get set } WindowLevel of the IMKCandidates, making sure that it won't get covered by NSMenu and Spotlight by default.
    2. { get set } Default candidate fonts as NSFont
    3. { get set } Highlight background color of the IMKCandidate panel
    4. { get set } Window background color tint of the IMKCandidate panel, allowing developers to enable or disable aero-glass transparency.
    5. { get set } Candidate keys definable in both ways: charCodes and keyCodes.
    6. { get set } Get the currently-highlighted TOTAL INDEX of the chosen candidate in the current candidate window.
    7. { get } Add IMKTextInput.isWritingContextVertical.
    8. { get } Add currentMarkedRangeOrigin (NSPoint), currentMarkedRangeTopLeft (NSPoint), currentMarkedRangeTopRight (NSPoint), currentCursorOrigin (NSPoint), currentCursorTopPoint (NSPoint).
  3. IMKInputController.Candidates() can send (Candidate, Reading, Annotation) triplet to the IMKCandidates. Also, IMKInputController.candidateSelected() and IMKInputController.candidateSelectionChanged() retrieves both Candidate and its total index number in the candidate window.

  4. Add Home / End key support in the IMKCandidate window.

  5. Provide a clear and unified Swift API for sending key NSEvents to IMKCandidates, making sure that these sent events are handled there. At least, - (BOOL)handleKeyboardEvent:(NSEvent *)event API_AVAILABLE(macosx(10.14)); needs to be officially exposed to developers.

  6. Add official NSEvent support of detecting whether Shift key has been pressed as a single function key. This is for helping IME developers to provide their own in-method input mode switch. This feature is crucial for users who already got accustomed to how Windows input methods behave. Reasons:

    1. Ergonomics: "Ctrl" / "CapsLock" / "Fn" key are less friendly than "Shift" key to one's pinkle finger.
    2. macOS built-in CapsLock IME toggle can have 1-second latency on certain user's computers with totally unknown reasons.
    3. Most Chinese IME users are from Windows, inheriting their preferences which are uncompromisable at all, period.
    4. IME-Developer-implemented Shift-key alphanumerical mode toggle can provide a fast way of inputting ASCII alphanumerals without moving the pinkle finger away from the Shift key.
  7. Provide a USABLE way of showing a description text at the bottom of the IMKCandidate window. This feature can be used for telling users what this candidate window is prepared for.

  8. Official TouchBar IMKCandidates API for 3rd-party input method developers.

  9. IMKCandidate window instances need to be separated for each InputMethodController. Reason: The IMKController session of an app sometimes deactivates after the one of the another client app gets activated, leading to the mess of handling input states. Imagine that you switched to another app and immediately want to call the symbol menu (shown through IMKCandidates), but the symbol menu window gets disappeared due to deactivateServer() triggered by another client app.

  10. Please state clearly in the documentation of certain APIs (like (de)activateServer() and setValue()) if they are expected to be mission-critical (e.g. expected to finish their processings as fast as possible). Otherwise, this can cause responsiveness issues of an input method if it is using Swift and is not using Grand Central Dispatch.

  11. An official workable solution (with precise sample project) demonstrating how to implement the IME settings pane of the System Preferences / Settings window:

image

  1. The documentation of InputMethodKit needs update regarding how to make sure annotations are shown in IMKCandidates. A Swift-only example is warmly welcomed.

  2. Different IMKInputController session instances might interfere each other: When activating a new instance of such in Safari new tab, the previous instance may deactivate later than the current one. However, this deactivation process may close the IMKCandidate window called by the current instance, confusing the user who are using the current input session because he / she can't tell which input state it currently is: It's actually a candidate state, but the candidate window is closed by the previous deactivated input session.

  3. The menu item "Edit Text Substitutions…" in the input source menu has been mistranslated as "Edit User Dictionary" which is extremely misleading to 3rd-party input method users.

  4. macOS 14 Sonoma needs to provide an official instructive documentation regarding how the icons of 3rd-party input methods should be designed to make sure they are stylisticly unified with system built-in input methods.

  5. The documentation of InputMethodKit needs to be updated to ask developers to change their InputMethodConnectionName to $(PRODUCT_BUNDLE_IDENTIFIER)_Connection in order not to let their input methods occasionally greyed out in the input source menu.

According to WWDC 2006 presentation "Input Method Kit Overview" held by Lee Collins, the InputMethodConnectionName in the info.plist of an input method should "Do not include spaces in the name or periods." However, in recent macOS, it can be occasionally observed that the right answer is $(PRODUCT_BUNDLE_IDENTIFIER)_Connection for sandboxed 3rd-party input methods. Even if you have set your InputMethodConnectionName to something else, in rare cases (mostly after your laptop gets woke up in macOS 13.4 Ventura) the system might dismiss your specified connection name and use $(PRODUCT_BUNDLE_IDENTIFIER)_Connection to make a connection instead, hence refusal by (possibly) Sandbox. At least, in such occasion, you can only see the affected input methods gray out in the input source menu before your mac reboots.

Therefore, it is necessary to update the following page:

https://developer.apple.com/documentation/appkit/nstextinputcontext/1529156-keyboardinputsources

... to add the recommended value for InputMethodConnectionName as $(PRODUCT_BUNDLE_IDENTIFIER)_Connection instead of the one suggested in WWDC2006.

Additional notes: Since macOS 10.14 till now, in order to use IMKCandidates, these following APIs have to be exposed through bridging-header:

@interface IMKCandidates(PROJECT_TARGET_NAME) {}

- (unsigned long long)windowLevel API_AVAILABLE(macosx(10.14));
- (void)setWindowLevel:(unsigned long long)level API_AVAILABLE(macosx(10.14));
- (BOOL)handleKeyboardEvent:(NSEvent *)event API_AVAILABLE(macosx(10.14));
- (void)setFontSize:(double)fontSize API_AVAILABLE(macosx(10.14));

@end

Finally, please allow input methods sellable on Mac App Store. A macOS input method app can be installed in the ~/Library/Input Methods/ and is okay to be Sandboxed (e.g. vChewing since v2.3.0). Therefore, there should be no privacy concerns anymore.

@ShikiSuen
Copy link
Author

ShikiSuen commented Nov 9, 2022

WWDC 2023 Apple Office Hour

  1. What should we do in order to let IMKCandidates stay in front of NSMenu and Spotlight without force-exposing the internal API (unsigned long long)windowLevel and (void)setWindowLevel:(unsigned long long)level since macOS 10.14?

  2. What should we do in order to let the key inputs (especially selection keys) responded by IMKCandidates without force-exposing the internal API (BOOL)handleKeyboardEvent:(NSEvent *)event since macOS 10.14?

  3. What should we do in order to let the text size of IMKCandidates be customizable without force-exposing the internal API (void)setFontSize:(double)fontSize since macOS 10.14?

  4. What should we do in order to let the input method preferences pane shows correctly in System Properties / System Settings since macOS 10.15? It does work with macOS 10.14 and earlier, but not anymore since macOS 10.15.

@ShikiSuen
Copy link
Author

关联专案: https://github.com/vChewing/vChewing-macOS/tree/3.4.9

似乎单独的 BugReport 并不能起多少及时的作用,且整个 InputMethodKit 可能需要大翻新。

该讨论串会在筹集到足够的资讯的时候借由特殊渠道递交给 Apple。

先聊一下 InputMethodKit 有哪些需要改善的地方吧。下面是我的结论。如果 Apple 认为已经有对应 API 的话,要么这些 API 要么罢工了、要么没曝露给 Swift。

  1. InputMethodKit 需要针对 Swift 重新包装一下,不要出现任何被强制拆包(force-wrapped)的参数。因为 macOS 真的会给参数喂食 nil。典型例子就是 macOS 内建的 Emoji 符号输入面板,此时会给当前的输入法的 handle(event:) 递交一个实际上是 nil 的 NSEvent!,然后输入法就炸了。
  2. 官方提供下述 Swift API:
    1. IMKCandidates 专用的 var windowLevel: Double { get set },确保 IMK 选字窗不会被 NSMenu 和 Spotlight 遮住。这个毛病始于 macOS 10.14,令人怀疑 IMK 的单元测试的设计是否足够健全。
    2. IMKCandidates 专用的 var defaultCandidateFonts: NSFont { get set }。这是选字窗字型。目前的 API 用了没有任何效果。实际需求就是用 CTFontCreateUIFontForLanguage(.system, size, locale as CFString) 指定使用哪个语种的系统字型来显示候选字。
    3. IMKCandidates 专用的 var highlightBackgroundColor: NSColor { get set } 用来指定候选字高亮背景色。顺便一提:macOS 内建的注音输入法的 IMK 选字窗的候选字高亮背景色是会跟随客体应用的 ThemeColor 自动调整的,要是给第三方输入法用的 IMKCandidates 也能这样就好了。
    4. IMKCandidates 专用的 var windowBackgroundColor: NSColor,允许第三方输入法开发者启用或者停用 Windows Aero 玻璃背景特效。
    5. IMKCandidates 专用的 var candidateKeysOfChars: [Character] { get set }var candidateKeys: [KeyCode] { get set } 允许第三方输入法开发者既可以用 String 字符、又可以用 KeyCode 定义选字键。// 目前的 IMKCandidates 任何与选字键定义有关的 API 都是失效的。
    6. IMKCandidates 专用可读写变数:获取当前高亮候选字「相对于当前选字窗所有分页加起来的整个候选字词阵列池而言的」顺序编号(以 0 起算)。
    7. IMKTextInput Client 专用只读变数:isWritingContextVertical,告知输入法当前客体打字上下文排版是纵向书写还是横向书写。// 可能这个功能还要针对自右向左的语言进行扩充,比如阿拉伯语就是从右往左。
    8. IMKTextInput Client 专用只读变数:currentMarkedRangeOrigin (NSPoint)currentMarkedRangeTopLeft (NSPoint)currentMarkedRangeTopRight (NSPoint)currentCursorOrigin (NSPoint)currentCursorTopPoint (NSPoint)
  3. IMKInputController.Candidates() 可以送出「候选字, 读音, 注解」(Candidate, Reading, Annotation) 这个 triplet 类型、给 IMKCandidates 处理,以简化 Annotation 的显示调度流程。同时,IMKInputController.candidateSelected()以及IMKInputController.candidateSelectionChanged()不只得拿到当前的候选字词、还得拿到其在候选字词阵列池当中的顺序编号(以 0 起算)。
  4. 给 IMKCandidates 选字窗新增 Home / End 按键响应支持。
  5. 给定一个清晰可用的 API 来保证 NSEvents 可以借由 IMKInputController.handle(event:) 手动传递给 IMKCandidates。至少,- (BOOL)handleKeyboardEvent:(NSEvent *)event API_AVAILABLE(macosx(10.14));这个 macOS 10.14 开始才有的 API 得曝露给第三方输入法开发者。
  6. 新增官方的「Shift 键有没有单独敲击过」的 NSEvent 判定方法,且这个判定方法得在包括 NSMenu 等所有场合生效。关于为什么需要这个 API:这可以方便第三方输入法开发者们实作「Shift 切换中英文」。该特性对从 Windows 迁移到 macOS 的使用者群体而言极为至关重要,原因:
    1. 人因工程: "Ctrl" / "CapsLock" / "Fn" 这些按键都没有 Shift 键离左手小指最近。平时打字的时候,左手小指可以在超过三分之一的时间内一直放在左手 Shift 键上方。
    2. macOS 内建的 CapsLock 中英文输入法切换功能在一定数量的使用者的电脑上存在着大约一秒的固有延迟,且该延迟用尽几乎任何手段均无法彻底消除,严重影响使用者的中英文混合打字节奏(特别是注音输入法与仓颉输入法的用户)。
    3. 很多中文输入法使用者(特别是台澎金马用户)从 Windows 迁移过来,改不了他们在 Windows 电脑上已经形成的十几年的肌肉记忆。让他们改这个习惯,几乎等于要了他们的命。对 Shift 中英文切换的特性需求,是他们的硬需求,毫无妥协之余地。macOS 不内建这种功能的时候,他们会毫无疑虑地用脚投票、去寻求任何可能的第三方解决方案。《威注音输入法》以及《业火五笔输入法》均引入了「对 NSEvent 事件的上下文分析」实现了对「Shift 按键有没有敲过」的判定,但这个判定在诸如 NSMenu 以及「开启/保存档案」视窗内是失效的(除非要监控系统全局的 NSEvent 键盘事件,这样得向使用者请求权限、会引发使用者的资讯安全疑虑),所以还是得需要 Apple 官方亲自实现这个特性。
  7. 提供一个可用的 Swift API 来允许使用者自订 IMK 选字窗的底部显示的说明文字。这样可以实现一个用途:可以让使用者明白当前的选字窗是用来做什么的。比如说威注音(逐字选字联想词模式)、小麦注音、OpenVanilla 会在联想词选字窗出现的时候提示使用者要用「Shift+选字键」来选取联想字词。
  8. 提供官方的 TouchBar IMKCandidates 选字窗互动 API、且给出详细的说明与 Xcode 范例专案(纯 Swift)。
  9. IMK 选字窗目前的设计是:一个共有的 IMKCandidates 副本(instance)给所有的 IMKInputController 副本使用。这有个坏处:IMKInputController 的新的副本在 activateServer 之后、前一个 IMKInputController 副本才会 deactivateServer,此时的举动可能会影响到当前 IMKInputController 副本已经叫出的选字窗(比如某些输入法可能会用选字窗制作可以即时呼叫的符号选单),然后导致当前 IMKInputController 副本变成这样:明明是 Candidate 状态,选字窗却消失了,让使用者在那里鬼打墙(因为按键逻辑都是根据输入法的状态判定、而有所不同)。建议贵团队考虑给每个 IMKInputController 副本分配一个 IMKCandidates 视窗副本。
  10. 请在 IMK 的说明文件(documentation)当中指出有哪些 API 是有必要讲究 mission-critical 的(比如 (de)activateServer()setValue())。不然的话,如果是用 Swift 写输入法、且没有充分利用 Grand Central Dispatch 的话,某些 API 实作的运作效率没有足够的优化,会让输入法变得略显迟钝。
  11. 给出一个纯 Swift 的 Xcode 范例专案、来详尽演示一下「一款第三方输入法该怎样实作『用以镶嵌显示在系统(偏好)设定内的』输入法设定画面」:

image

  1. 整个 InputMethodKit 的说明文件都得需要更新,以指引使用者如何在 IMKCandidates 当中显示 annotation。最好有一个纯 Swift 的 Xcode 范例专案。

  2. 不同的 IMKInputController 会话副本会影响彼此:当你 activate 了一个新的副本的时候(比如说 Safari 的 new tab),之前的旧副本往往会在此之后才会 deactivate。与此有关的选字窗显示的问题,我已经在上文吐槽过了。但目前这一条是想建议 Apple 在 InputMethodKit 的说明文件当中仔细说明该特性、来指导第三方输入法开发者们该怎样正确处理。

  3. 输入法选单的「Edit Text Substitutions…」被讹译成「编辑使用者辞典…」,非常误导第三方输入法的使用者。身为威注音输入法的开发者,我到现在已经好几次被新手问到「点了『编辑使用者辞典…』无法修改威注音输入法的使用者辞典」,我只能把威注音输入法的「编辑自订语汇」改名为「编辑威注音自订语汇」。

  4. macOS 14 Sonoma 需要一套官方的「第三方输入法选单图示设计指南」,仔细阐明(macOS 13 开始的)「宽版输入法选单图示」与(macOS 14 开始才有的)「上下文输入法指示器」该怎样设计与实装、才能使其视觉风格表现与 macOS 内建输入法彼此统一。

  5. 目前的 InputMethodKit 的说明文件还需要更新,劝诫第三方输入法开发者统一将输入法的 info.plist 当中的 InputMethodConnectionName 以及沙盒 Entitlements 当中的 com.apple.security.temporary-exception.mach-register.global-name 都改成 $(PRODUCT_BUNDLE_IDENTIFIER)_Connection。不然的话,在某些情况下,macOS 会无视开发者指定的 connection name,而是直接以 $(PRODUCT_BUNDLE_IDENTIFIER)_Connection 试图启动 IMKServer 连线,然后输入法就会在输入法选单当中一直保持灰色不可选的状态、直至使用者再次重新开机。这一点与 WWDC 2006 的北京输入法讲座的内容有出入(那个讲座不鼓励在 connection name 当中使用小数点)。

补记:从 macOS 10.14 Mojave 开始,为了让 IMKCandidates 至少「能用」,下述四个 API 必须借由 bridging-header 强制曝露给 Swift:

@interface IMKCandidates(PROJECT_TARGET_NAME) {}

- (unsigned long long)windowLevel API_AVAILABLE(macosx(10.14));
- (void)setWindowLevel:(unsigned long long)level API_AVAILABLE(macosx(10.14));
- (BOOL)handleKeyboardEvent:(NSEvent *)event API_AVAILABLE(macosx(10.14));
- (void)setFontSize:(double)fontSize API_AVAILABLE(macosx(10.14));

@end

最后,请允许第三方输入法开发者将 macOS 平台的输入法在 Mac App Store 贩售。第三方输入法可以安装在「~/Library/Input Methods」使用者目录内,可以沙盒化(比如《威注音输入法》),理应不会再有任何的资安疑虑。

WWDC 2023 Apple Lab Office Hour

  1. 该怎样才能在不强制曝露 (unsigned long long)windowLevel(void)setWindowLevel:(unsigned long long)level 这两个 API 的情况下、在 macOS 10.14 开始的系统内将 IMKCandidates 选字窗 显示在任何 NSMenu 以及 Spotlight 的上方(不被遮住)?

  2. 该怎样才能(在 macOS 10.14 开始的系统内)让按键输入(特别是选字键按键输入)被 IMKCandidates 正确受理应对、而无须强制曝露系统 API (BOOL)handleKeyboardEvent:(NSEvent *)event

  3. 该怎样才能(在 macOS 10.14 开始的系统内)自订 IMKCandidates 的候选字词字号大小、而无须强制曝露系统 API (void)setFontSize:(double)fontSize

  4. 该怎样在 macOS 10.15 开始的系统内让输入法的 Preferences 设定面板正确地镶嵌显示在系统偏好设定当中?该功能是我按照 Mizuno Hiroki(在他加入 Apple 之前写)的 InputMethodKit 教材当中的指引来实作的,对 macOS 10.13 和 10.14 有效。我现在拿这个问题请教 Mizuno Hiroki 本人,但他却受限于 Apple 公司内部的资安保密限制、什么都不能讲。

@ShikiSuen
Copy link
Author

ShikiSuen commented Nov 20, 2023

  • FB13259791 // Need instructions on how to customize the IME icon used in the input source indicator.

  • FB13392636 // Please allow Sandboxed input methods to be enabled when SecureEventInput is enabled (except those which have security-concerned entitlements).

  • FB13392574 // Need an official Swift-friendly wrapper with neither "!" nor "?" in the parameters of all provided APIs.

  • FB13392573 // InputMethodKit needs some new Swift APIs exposed to the 3rd-party developers.

  • FB13392580 // Extend what IMKInputController can send / receive to IMKCandidates.

  • FB13392583 // Add Home / End key support in the IMKCandidate window.

  • FB13392588 // Provide a clear and unified Swift API for sending key NSEvents to IMKCandidates.

  • FB13392591 // Add official NSEvent support of detecting whether Shift key has been pressed as a single function key.

  • FB13392595 // Provide a usable way of showing a description text at the bottom of the IMKCandidate window.

  • FB13392597 // Provide official TouchBar IMKCandidates API for 3rd-party input method developers.

  • FB13392598 // IMKCandidate window instances need to be separated for each InputMethodController.

  • FB13392599 // Please state clearly in the documentation of certain APIs (like (de)activateServer() and setValue()) if they are expected to be mission-critical (e.g. expected to finish their processings as fast as possible).

  • FB13392602 // InputMethodKit needs an official workable solution (with precise sample project) demonstrating how to implement the IME settings pane of the System Preferences / Settings window.

  • FB13392604 // The documentation of InputMethodKit needs update regarding how to make sure annotations are shown in IMKCandidates.

  • FB13392607 // Please stop letting IMKInputController sessions interfere each other.

  • FB13392610 // The documentation of InputMethodKit needs to be updated to ask developers to change their InputMethodConnectionName to $(PRODUCT_BUNDLE_IDENTIFIER)_Connection in order not to let their input methods occasionally greyed out in the input source menu.

  • FB13392624 // Please clearly document what a Sandboxed input method shall do in the entitlements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment