Skip to content

Conversation

@ShikiSuen
Copy link
Contributor

@ShikiSuen ShikiSuen commented Nov 2, 2025

- On macOS, `NSTextInputClient::validAttributesForMarkedText` now automatically attaches a value
  called `_rust_winit_\(CARGO_PKG_VERSION)`.

  This allows IME devs to identify whether `winit` is used in the IMKTextInput client.
  If identified, IME devs can introduce compatibility behaviors against such IMKTextInput client.
  InputMethodKit casts `NSTextInputClient` to `IMKTextInput`, but `validAttributesForMarkedText`
  is shared between these two protocols and can all be parsed as `Array<NSString>`.

  `validAttributesForMarkedText` specifically returns an `NSArray<NSAttributedStringKey>` which
  are plain NSString*; Apple treats custom keys as legal, so slipping harmless metadata like
  `_rust_winit_\(CARGO_PKG_VERSION)` in there is the one slot where such data is piggybackable.
  • Tested on all platforms changed // I only tested the compilability.
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality // This requires 3rd-party input method devs to involve in after this PR gets merged.

The true reason for this PR is to allow IME devs to automatically enable their customized popup composition buffer window in lieu of preedit (inline composition buffer). This is at least better than waiting for a lot of months seeing some IME-related PR patches are not mergeable in this winit repository due to maintainers' concerns.

As a 3rd-party IME dev, I would really glad to blacklist all winit-based IMKTextInput clients in my IME by default to save both my time and the time of the devs of such kind of IMKTextInput clients: The blacklisted clients will automatically use my popup composition buffer instead.

@ShikiSuen ShikiSuen requested a review from madsmtm as a code owner November 2, 2025 13:15
@ShikiSuen ShikiSuen force-pushed the shiki/report-client-as-winit branch 2 times, most recently from 6736710 to 3cbe8f3 Compare November 2, 2025 13:30
@ShikiSuen ShikiSuen marked this pull request as draft November 2, 2025 13:49
@ShikiSuen
Copy link
Contributor Author

Seems that there are some compatibility issues with the latest CI environment. Lemme draft this PR until the compilation issue gets solved.

@kchibisov
Copy link
Member

This is at least better than waiting for a lot of months seeing some IME-related PR patches are not mergeable in this winit repository due to maintainers' concerns.

Can't winit just detect such cases? Like the issue with certain patches is that they route pretty much everything straight into the Ime::Commit. If IME just wants to insert something, wouldn't it make the scancode to be not present or something for such cases? I'm sure that cases when a -> あ could be detected without breaking regular keyboard input.

- This allows IME devs to identify whether `winit` is used in the IMKTextInput client. If identified, IME devs can introduce compatibility behaviors against such IMKTextInput client. // InputMethodKit casts `NSTextInputClient` to `IMKTextInput`.
@ShikiSuen ShikiSuen force-pushed the shiki/report-client-as-winit branch from 3cbe8f3 to 094eff9 Compare November 2, 2025 14:26
@ShikiSuen ShikiSuen marked this pull request as ready for review November 2, 2025 14:32
@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 2, 2025

Compilation issue with latest rust CI has been solved.
This PR now stopped its draft state.

@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 2, 2025

This is at least better than waiting for a lot of months seeing some IME-related PR patches are not mergeable in this winit repository due to maintainers' concerns.

Can't winit just detect such cases? Like the issue with certain patches is that they route pretty much everything straight into the Ime::Commit. If IME just wants to insert something, wouldn't it make the scancode to be not present or something for such cases? I'm sure that cases when a -> あ could be detected without breaking regular keyboard input.

I simply just do not want to participate any discussion related to certain winit-dependent repositories anymore. To avoid further friction, I decided to stop interacting with the devs of these repositories (nor mentioning their names here). My efforts have lack of respect but sometimes rude moderative responses by some of them, and every of my bone hate that.

@kchibisov
Copy link
Member

Well, if you decided to not communicate with me, then I'm not sure how to proceed with even reviewing things like that or merging them. If you want to just put code for observation then it's fine, I guess. I'm not even sure what was wrong with such a question, like I wanted to avoid having workarounds in IMEs. If such questions offend you, then, well...

@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 2, 2025

@kchibisov You did not offend me at all. I'm sorry for letting you feel confused. The winit repository itself is fine. What I meant is someone else who mods a repository which has dependency of winit, but I don't think it is good for the atmosphere to mention his name here 'cause this will escalate the issue to something ad-hominem.

I'm sure that cases when a -> あ could be detected without breaking regular keyboard input.

I mean punctuations like . -> 。. This is not an issue with my IME but an issue with almost all Sogou-style Pinyin IMEs (except macOS built-in Pinyin IME which has hacks in its dedicated Ukelele keylayout). However, some users of Sogou-style Pinyin IMEs attack me for advertising my own IME just because I failed from repeating their issues of punctuation inputs. I pissed off and spend hours investigating what's really going on, shared my investigation-based deductions. After that, I posted Codex reviews against my deduction but got marked as spam. I asked why, and the mod's response is extremely not professional against the quality of Codex's review. I stated that GitHub's GPT / Codex models are heavily trained with historical changes of AppKit, but my statement got marked as spam. That repository has no Code of Conduct, nor clear ban of AI-generated contents in CONTRIBUTING file.

@ShikiSuen
Copy link
Contributor Author

@kchibisov By the way, _rust_winit_\(CARGO_PKG_VERSION) has pkg version information. This allows IME devs to only blacklist certain buggy versions of winit, which should be beneficial to both IME devs and their users.

@kchibisov
Copy link
Member

However, some users of Sogou-style Pinyin IMEs attack me for advertising my own IME just because I failed from repeating their issues of punctuation inputs. I pissed off and spend hours investigating what's really going on, shared my investigation-based deductions. After that, I posted Codex reviews against my deduction but got marked as spam

Given that I'm also part of said repository, I'd say that while AI agent rule isn't written, but due to general tendency of people spamming with it, such reply could be given. In general, you should have been discussed not there but rather here, and even then, the larger the comment the less likely it'll be read fully.

@kchibisov
Copy link
Member

I mean punctuations like . -> 。. This is not an issue with my IME but an issue with almost all Sogou-style Pinyin IMEs (except macOS built-in Pinyin IME which has hacks in its dedicated Ukelele keylayout). However, some users of Sogou-style Pinyin IMEs attack me for advertising my own IME just because I failed from repeating their issues of punctuation inputs.

And what I try to understand here, is whether those patterns are detectable, since I feel like they could be detectable on winit side of things and we can route them properly?

Like consider this #4374

What I think is that if you try to compare insertText vs regular input, it'll differ for said IMEs, thus in such cases, we should send Commit, but for regular input, this won't be affected at all? Alternatively insertText result could be routed into the text of the key events in such cases?

@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 2, 2025

@kchibisov

is whether those patterns are detectable, since I feel like they could be detectable on winit side of things and we can route them properly.

Different IME devs may prefer different keycode-character mappings. Some of these IMEs even allow users to customize such mappings.

What I think is that if you try to compare insertText vs regular input, it'll differ for said IMEs, thus in such cases, we should send Commit, but for regular input, this won't be affected at all? Alternatively insertText result could be routed into the text of the key events in such cases?

My deduction: Maybe both Sogou IME and WeChat Input share some same method of using IMK-related handling APIs other than handleEvent, and that method has issues with winit. At least, my vChewing only uses handleEvent and vChewing works fine with the "said app". There are IMK APIs other than handleEvent which provides different places for handling input informations. Both Sogou and WeChat input are now Tencent products (Sogou was previously owned by Sohu), and it is well-known that Tencent products generally have lack of effective & responsive bug-reporting channels. Otherwise, I would like to have a talk with the engineers who work on these two IMEs to see how they use IMK APIs.

(It's about time for me to try RIME Squirrel using some Sogou-style Pinyin Schema to see whether the issue is repeatable. At least, RIME is open-source).

@kchibisov
Copy link
Member

Different IME devs may prefer different keycode-character mappings. Some of these IMEs even allow users to customize such mappings.

But does it really matter if we compare text for KeyDown and insertText? Or do we have insertText without KeyDown? From what I understood it's not really the case?

I'm expecting that for e.g. Qwerty, they'll match, but for said IMEs they won't and in such cases we can just a) Send it via Commit, or b) Send it via text on KeyDown.

IMEs doing something strange and no way to report bugs will just take time and energy, but testing my suggestion might be faster. I don't have macOS so try to delegate, but it's fine if you don't want to.

@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 2, 2025

@kchibisov

Short answers:

  • "do we have insertText without KeyDown?": Sometimes yes on macOS with Sogou/WeChat; more commonly we see both: a ReceivedCharacter '.' from the basic Ukelele layout and then an IME insertText/Commit '。'.

  • "From what I understood it's not really the case?": For plain QWERTY or any input method which only respond using its basic ukelele layout, KeyDown text and inserted text do match.

Rather than string-comparing KeyDown text vs insertText, I'd suggest following Cocoa's contract: if NSTextInputContext.handleEvent(_ event: NSEvent) (which bridges to handleEvent of the IMKInputController of an IME) returns true (IME handled), we should always suppress emitting ReceivedCharacter for that event and rely on marked/commit callbacks. If it returns false, let the raw character committed (emitted) as usual.

If that boolean isn't reliable in our glue, a practical fallback is to coalesce within the same runloop turn: when an insertText/Commit arrives for the same keypress, prefer it and drop the raw ReceivedCharacter (especially when they differ). This aligns with NSTextView behavior and avoids brittle comparisons.

Why this matters: with Sogou/WeChat punctuation we frequently see '.' (ReceivedCharacter) vs '。' (Commit). If we don't suppress the raw character, '.' wins and users can't input the intended punctuation.

@kchibisov
Copy link
Member

In general, I think both should work, it's just, in the way it was send in #4374 I think pretty much all the text will go into -> Ime::Commit which we don't want.

Why this matters: with Sogou/WeChat punctuation we frequently see '.' (ReceivedCharacter) vs '。' (Commit). If we don't suppress the raw character, '.' wins and users can't input the intended punctuation.

But that is covered by what I've proposed, isn't it? If they differ, we suppress regular KeyDown and send Commit (or KeyDown with altered text). I also think that those could have scancode set to 0, but I think that's irrelevant.

@ShikiSuen
Copy link
Contributor Author

@kchibisov

Thanks for the follow-up.

Regarding scancode == 0: This corresponds to NSEvent.keyCode on macOS. Since IME-generated text often has no corresponding KeyboardInput or shows up with scancode == 0, scancode isn't reliable for client-side text routing. I personally think that it is more appropriate to let the IME see the keyCode and do the decisions.

Instead of relying on scancode, here's a more targeted approach:

  • Core Policy: Call NSTextInputContext.handleEvent(event) on keyDown. This triggers IMKInputController.handleEvent. // I also strongly suggest you to send the keyUp to NSTextInputContext.handleEvent(event) as well, because some input methods (incl. vChewing) rely on comparing neighbored NSEvents (keyDown-keyUp) to detect whether Shift key is single-clicked.

    • If handleEvent returns true (IME handled), always suppress ReceivedCharacter and rely on Ime::Preedit/Commit callbacks.
    • If false, emit the raw ReceivedCharacter as usual.
  • Safety Net: When both a raw ReceivedCharacter and an Ime::Commit arrive in the same runloop tick with different content (e.g., '.' vs '。'), keep the Commit and discard the raw character.

    • Avoid synthesizing "KeyDown with altered text" to preserve IME semantics like replacement ranges.

Why This Works:

  • Handles Sogou/WeChat cases where '.' (raw) and '。' (commit) compete.
  • Maintains pure QWERTY/dead key paths untouched.
  • Matches Cocoa/NSTextView behavior without funneling all text through Commit.

This addresses the punctuation mismatch while keeping IME semantics intact.

@kchibisov
Copy link
Member

It seems like handleEvent works like filter event on X11?

let filtered = if event_type == xlib::KeyPress || event_type == xlib::KeyRelease {

So basically the logic would be pretty much the same?

@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 2, 2025

@kchibisov

Thanks for your response.

I have no knowledge against X11. However, intel from Codex suggests that the mental model matches X11:

  • X11: XFilterEvent(event) => filtered == true means “IME consumed this KeyPress/KeyRelease; don’t produce raw text for it.”
  • macOS: NSTextInputContext.handleEvent(event) => true means “IME consumed; don’t emit ReceivedCharacter; rely on preedit/commit.”

So the cross-backend invariant could be:

  1. If the backend reports “IME consumed” (X11: filtered, macOS: handleEvent == true), suppress raw text and prefer IME callbacks (preedit/commit).
  2. If not consumed, emit the raw character as usual.
  3. Safety net: if both raw text and an IME commit arrive for the same press and they differ (e.g., '.' vs '。'), drop the raw char and keep the commit.

@ShikiSuen
Copy link
Contributor Author

@kchibisov I amend my initial responses regarding the IMEs affected:

I just tested that the RIME Squirrel (the macOS version of RIME) is also affected when its default luna_pinyin scheme is loaded.

RIME only uses IMKInputController.handleEvent (which Swift API is handle(event: NSEvent?): https://github.com/rime/squirrel/blob/1d117436f306fd49db7b665f70266f52e28fb85e/sources/SquirrelInputController.swift#L33-L126

So it seems to be not necessary anymore to attempt to discuss with devs of SogouIME and WeChat_IME.

@ShikiSuen
Copy link
Contributor Author

I just found how Chromium implements NSTextInputClient protocol:

https://github.com/chromium/chromium/blob/69f55d3e23005b838eadb93288c5044df4941b22/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm#L2011-L2677

I guess this might be a good referential example of implementation.

@kchibisov
Copy link
Member

I looked into firefox yesterday as well, and it looked similar. Though, chromium looks easier to follow in that regard.

@ShikiSuen ShikiSuen requested a review from kchibisov as a code owner November 4, 2025 08:00
On macOS, make AppKit monitor handle tests derive their reference display IDs from
  `CGMainDisplayID`, preventing failures on systems where the primary monitor is not display `1`.
@ShikiSuen ShikiSuen force-pushed the shiki/report-client-as-winit branch from 6803619 to e6b4b33 Compare November 4, 2025 08:02
@ShikiSuen
Copy link
Contributor Author

ShikiSuen commented Nov 4, 2025

I have no clue how the review request to @kchibisov gets automatically initiated. Sorry for the inconveniences.

I'm drafting this PR now and'll fix the commit wording issues until CI passes.

@ShikiSuen ShikiSuen closed this Nov 4, 2025
@ShikiSuen
Copy link
Contributor Author

(My mistake. I closed this PR instead... Sigh... It's not reopenable now.)

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants