Skip to content

Isolated modifier key press events may not be sent to mozc_server on Windows #1415

@yukawa

Description

@yukawa

Description

While Mozc clients (TSF, IBus, IMKit ...) are not expected to send key-up events in general, one of notable exceptions is when a single modifier key (e.g. SHIFT) is pressed and released without any other keys.

Steps to reproduce

  1. Install Mozc
  2. Open Notepad
  3. Enable Mozc
  4. Down SHIFT -> Down M -> Up M -> SHIFT
  5. Down O -> Up O
  6. Down Z -> Up Z
  7. Down C -> Up C
  8. Down SHIFT -> Up SHIFT
  9. Down A -> Up A

Expected behavior

  • After the step 3, the mode indicator is half-alphanumeric mode.
  • After the step 4, the composing text is M, and the mode indicator is half-alphanumeric mode.
  • After the step 7, the composing text is Mozc, and the mode indicator is half-alphanumeric mode.
  • After the step 8, the composing text is Mozc, and the mode indicator is Hiragana mode.
  • After the step 9, the composing text is Mozcあ, and the mode indicator is Hiragana mode.

Actual behavior

  • After the step 3, the mode indicator is half-alphanumeric mode.
  • After the step 4, the composing text is M, and the mode indicator is half-alphanumeric mode.
  • After the step 7, the composing text is Mozc, and the mode indicator is half-alphanumeric mode.
  • After the step 8, the composing text is Mozc, and the mode indicator is half-alphanumeric mode.
  • After the step 9, the composing text is Mozca, and the mode indicator is half-alphanumeric mode.

Version or commit-id

f6786ca

Environment

  • OS: Windows 11 25H2
  • Related Applications: Notepad

Additional context

The actual condition is when ITfKeyEventSink::OnKeyDown and ITfKeyEventSink::OnKeyUp are called without ITfKeyEventSink::OnTestKeyDown and ITfKeyEventSink::OnTestKeyUp.

STDMETHODIMP OnTestKeyDown(ITfContext *context, WPARAM wparam, LPARAM lparam,
BOOL *eaten) override {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnTestKeyDown(this, context, wparam, lparam,
eaten);
}
// ITfKeyEventSink
STDMETHODIMP OnTestKeyUp(ITfContext *context, WPARAM wparam, LPARAM lparam,
BOOL *eaten) override {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnTestKeyUp(this, context, wparam, lparam,
eaten);
}
STDMETHODIMP OnKeyDown(ITfContext *context, WPARAM wparam, LPARAM lparam,
BOOL *eaten) override {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnKeyDown(this, context, wparam, lparam, eaten);
}
STDMETHODIMP OnKeyUp(ITfContext *context, WPARAM wparam, LPARAM lparam,
BOOL *eaten) override {
BOOL dummy_eaten = FALSE;
if (eaten == nullptr) {
eaten = &dummy_eaten;
}
*eaten = FALSE;
return TipKeyeventHandler::OnKeyUp(this, context, wparam, lparam, eaten);
}

This is because the following logic is called only through ITfKeyEventSink::OnTestKeyDown and ITfKeyEventSink::OnTestKeyUp.

// Although Mozc has not explicitly supported any key-up message, there exist
// some situations where the client has to send key message when it receives
// a key-up message. Currently we have following exceptions.
// - Shift/Control/Alt keys
// The Mozc protocol had originally allowed the client to ignore key-up
// events of these modifier keys but later has changed to expect the
// client to send a key message which contains only modifiers field and
// mode field to support b/2269058 and b/1995170.
if (is_key_down) {
// This is an ugly workaround to determine which key-up message for a
// modifier key should be sent to the server. Currently, the Mozc server
// expects the client to send such a key-up message only when a modifier
// key is released just after the same key is pressed, that is, any other
// key is not pressed between the key-down and key-up of a modifier key.
// Here are some examples, where [D] and [U] mean 'key down' and 'key up'.
// (1) [D]Shift -> [D]A -> [U]Shift -> [U]A
// In this case, only 'A' will be sent to the server
// (2) [D]Shift -> [U]Shift -> [D]A -> [U]A
// In this case, 'Shift' and 'A' will be sent to the server.
// (3) [D]Shift -> [D]Control -> [U]Shift -> [U]Control
// In this case, no key message will be sent to the server.
// (4) [D]Shift -> [D]Control -> [U]Control -> [U]Shift
// In this case, 'Control+Shift' will be sent to the server. Note
// that |KeyEvent::modifier_keys| will contain all the modifier keys
// when the client receives '[U]Control'.
// Unfortunately, it is currently client's responsibility to remember the
// key sequence to generate appropriate key messages as expected by the
// server. Strictly speaking, the Mozc client is actually stateful in
// this sense.
next_state->last_down_key = virtual_key;
}

As a result, ime_state.last_down_key in the following logic remains to be 0 when ITfKeyEventSink::OnTestKeyDown and ITfKeyEventSink::OnTestKeyUp are completely bypassed, which results in ignoring an isolated modifier key press event at the step 8

switch (virtual_key.virtual_key()) {
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
if (is_key_down) {
// Will not eat this message.
result.succeeded = true;
result.should_be_eaten = false;
result.should_be_sent_to_server = false;
return result;
}
if (ime_state.last_down_key.virtual_key() != virtual_key.virtual_key()) {
// Will not eat this message.
result.succeeded = true;
result.should_be_eaten = false;
result.should_be_sent_to_server = false;
return result;
}
// We will send this message to the server.
result.succeeded = true;
result.should_be_eaten = true;
result.should_be_sent_to_server = true;
return result;
break;
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions