gdkevents-win32.c: Split out keyboard and IME events

...from gdk_event_translate(), as that function is getting too big and
makes it harder to track things.
This commit is contained in:
Chun-wei Fan
2024-11-15 18:58:54 +08:00
parent 37fc7ec3af
commit 80b3a34dab

View File

@@ -1718,6 +1718,367 @@ _gdk_win32_surface_fill_min_max_info (GdkSurface *surface,
return TRUE;
}
static gboolean
handle_keyboard_event (GdkDisplay *display,
GdkSurface *surface,
MSG *msg,
int *ret_valp,
gboolean *goto_done)
{
GdkEvent *event;
GdkWin32Display *win32_display = GDK_WIN32_DISPLAY (display);
GdkWin32Keymap *win32_keymap;
GdkTranslatedKey translated;
gboolean return_val = FALSE;
switch (msg->message)
{
case WM_INPUTLANGCHANGE:
{
HKL input_locale;
win32_keymap = GDK_WIN32_KEYMAP (gdk_display_get_keymap (display));
input_locale = (HKL) msg->lParam;
gdk_win32_display_set_input_locale (win32_display, input_locale);
_gdk_win32_keymap_set_active_layout (win32_keymap, input_locale);
gdk_win32_display_increment_keymap_serial (win32_display);
GDK_NOTE (EVENTS,
g_print (" cs:%lu hkl:%p%s",
(gulong) msg->wParam,
(gpointer) msg->lParam,
gdk_win32_display_input_locale_is_ime (win32_display) ? " (IME)" : ""));
gdk_display_setting_changed (display, "gtk-im-module");
/* Generate a dummy key event to "nudge" IMContext */
translated.keyval = GDK_KEY_VoidSymbol;
translated.consumed = 0;
translated.layout = 0;
translated.level = 0;
event = gdk_key_event_new (GDK_KEY_PRESS,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
0,
0,
FALSE,
&translated,
&translated,
NULL);
_gdk_win32_append_event (event);
}
break;
case WM_SYSKEYUP:
case WM_SYSKEYDOWN:
GDK_NOTE (EVENTS,
g_print (" %s ch:%.02x %s",
_gdk_win32_key_to_string (msg->lParam),
(int) msg->wParam,
decode_key_lparam (msg->lParam)));
/* If posted without us having keyboard focus, ignore */
if ((msg->wParam != VK_F10 && msg->wParam != VK_MENU) &&
!(HIWORD (msg->lParam) & KF_ALTDOWN))
break;
/* Let the system handle Alt-Tab, Alt-Space and Alt-F4 unless
* the keyboard is grabbed.
*/
if (!_gdk_display_get_last_device_grab (display, win32_display->device_manager->core_keyboard) &&
(msg->wParam == VK_TAB ||
msg->wParam == VK_SPACE ||
msg->wParam == VK_F4))
break;
/* Jump to code in common with WM_KEYUP and WM_KEYDOWN */
G_GNUC_FALLTHROUGH;
case WM_KEYUP:
case WM_KEYDOWN:
{
GdkModifierType state;
guint keyval;
guint16 keycode;
guint8 group;
gboolean is_modifier;
GdkTranslatedKey no_lock;
BYTE key_state[256];
GArray *translation;
MSG msg2;
int level = 0;
int effective_group = 0;
GdkModifierType consumed = 0;
char *composed = NULL;
GdkWin32Surface *impl;
if (msg->message == WM_KEYUP || msg->message == WM_KEYDOWN)
{
GDK_NOTE (EVENTS,
g_print (" %s ch:%.02x %s",
_gdk_win32_key_to_string (msg->lParam),
(int) msg->wParam,
decode_key_lparam (msg->lParam)));
}
/* Ignore key messages intended for the IME */
if (msg->wParam == VK_PROCESSKEY || win32_display->event_record->in_ime_composition)
break;
/* Ignore autorepeats on modifiers */
if (msg->message == WM_KEYDOWN &&
(msg->wParam == VK_MENU || msg->wParam == VK_CONTROL || msg->wParam == VK_SHIFT) &&
((HIWORD(msg->lParam) & KF_REPEAT) >= 1))
break;
if (GDK_SURFACE_DESTROYED (surface))
break;
win32_keymap = GDK_WIN32_KEYMAP (gdk_display_get_keymap (display));
impl = GDK_WIN32_SURFACE (surface);
API_CALL (GetKeyboardState, (key_state));
keyval = GDK_KEY_VoidSymbol;
keycode = msg->wParam;
/* Get the WinAPI translation of the WM_KEY messages to characters.
The WM_CHAR messages are generated by a previous call to TranslateMessage() and always
follow directly after the corresponding WM_KEY* messages.
There could be 0 or more WM_CHAR messages following (for example dead keys don't generate
WM_CHAR messages - they generate WM_DEAD_CHAR instead, but we are not interested in those
messages). */
translation = g_array_sized_new (FALSE, FALSE, sizeof (gunichar2), 2);
while (PeekMessageW (&msg2, msg->hwnd, 0, 0, 0) && (msg2.message == WM_CHAR || msg2.message == WM_SYSCHAR))
{
/* The character is encoded in WPARAM as UTF-16. */
gunichar2 c = msg2.wParam;
/* Append character to translation string. */
g_array_append_val (translation, c);
/* Remove message from queue */
GetMessageW (&msg2, msg->hwnd, 0, 0);
}
if (translation->len > 0)
composed = g_utf16_to_utf8 ((gunichar2*)translation->data,
translation->len, NULL, NULL, NULL);
g_array_unref (translation);
translation = NULL;
/* Ignore control sequences like Backspace */
if (composed && g_unichar_iscntrl (g_utf8_get_char (composed)))
g_clear_pointer (&composed, g_free);
if (HIWORD (msg->lParam) & KF_EXTENDED)
{
switch (msg->wParam)
{
case VK_CONTROL:
keycode = VK_RCONTROL;
break;
case VK_SHIFT: /* Actually, KF_EXTENDED is not set
* for the right shift key.
*/
keycode = VK_RSHIFT;
break;
case VK_MENU:
keycode = VK_RMENU;
break;
}
}
else if (msg->wParam == VK_SHIFT &&
LOBYTE (HIWORD (msg->lParam)) == _gdk_win32_keymap_get_rshift_scancode (win32_keymap))
keycode = VK_RSHIFT;
is_modifier = (msg->wParam == VK_CONTROL ||
msg->wParam == VK_SHIFT ||
msg->wParam == VK_MENU);
state = build_key_event_state (display, key_state);
group = get_active_group (display);
gdk_keymap_translate_keyboard_state ((GdkKeymap*) win32_keymap, keycode, state, group,
&keyval, &effective_group, &level, &consumed);
translated.keyval = keyval;
translated.consumed = consumed;
translated.layout = effective_group;
translated.level = level;
gdk_keymap_translate_keyboard_state ((GdkKeymap*) win32_keymap, keycode,
state & ~GDK_LOCK_MASK, group, &keyval,
&effective_group, &level, &consumed);
no_lock.keyval = keyval;
no_lock.consumed = consumed;
no_lock.layout = effective_group;
no_lock.level = level;
/* Only one release key event is fired when both shift keys are pressed together
and then released. In order to send the missing event, press events for shift
keys are recorded and sent together when the release event occurs.
Other modifiers (e.g. ctrl, alt) don't have this problem. */
if (msg->message == WM_KEYDOWN && msg->wParam == VK_SHIFT)
{
int pressed_shift = msg->lParam & 0xffffff; /* mask shift modifier */
if (win32_display->event_record->both_shift_pressed[0] == 0)
win32_display->event_record->both_shift_pressed[0] = pressed_shift;
else if (win32_display->event_record->both_shift_pressed[0] != pressed_shift)
win32_display->event_record->both_shift_pressed[1] = pressed_shift;
}
if (msg->message == WM_KEYUP && msg->wParam == VK_SHIFT)
{
if (win32_display->event_record->both_shift_pressed[0] != 0 &&
win32_display->event_record->both_shift_pressed[1] != 0)
{
int tmp_retval;
MSG fake_release = *msg;
int pressed_shift = msg->lParam & 0xffffff;
if (win32_display->event_record->both_shift_pressed[0] == pressed_shift)
fake_release.lParam = win32_display->event_record->both_shift_pressed[1];
else
fake_release.lParam = win32_display->event_record->both_shift_pressed[0];
win32_display->event_record->both_shift_pressed[0] = win32_display->event_record->both_shift_pressed[1] = 0;
gdk_event_translate (&fake_release, &tmp_retval);
}
win32_display->event_record->both_shift_pressed[0] = win32_display->event_record->both_shift_pressed[1] = 0;
}
/* Reset ALT_MASK if it is the Alt key itself */
if (msg->wParam == VK_MENU)
state &= ~GDK_ALT_MASK;
event = gdk_key_event_new ((msg->message == WM_KEYDOWN || msg->message == WM_SYSKEYDOWN) ? GDK_KEY_PRESS : GDK_KEY_RELEASE,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
keycode,
state,
is_modifier,
&translated,
&no_lock,
composed);
_gdk_win32_append_event (event);
g_free (composed);
return_val = TRUE;
}
break;
case WM_SYSCHAR:
if (msg->wParam != VK_SPACE)
{
/* To prevent beeps, don't let DefWindowProcW() be called */
return_val = TRUE;
*goto_done = TRUE;
}
break;
case WM_IME_STARTCOMPOSITION:
win32_display->event_record->in_ime_composition = TRUE;
break;
case WM_IME_ENDCOMPOSITION:
win32_display->event_record->in_ime_composition = FALSE;
break;
case WM_IME_COMPOSITION:
{
BYTE key_state[256];
wchar_t wbuf[100];
int ccount = 0;
HIMC himc;
int i;
/* On Win2k WM_IME_CHAR doesn't work correctly for non-Unicode
* applications. Thus, handle WM_IME_COMPOSITION with
* GCS_RESULTSTR instead, fetch the Unicode chars from the IME
* with ImmGetCompositionStringW().
*
* See for instance
* http://groups.google.com/groups?selm=natX5.57%24g77.19788%40nntp2.onemain.com
* and
* http://groups.google.com/groups?selm=u2XfrXw5BHA.1628%40tkmsftngp02
* for comments by other people that seems to have the same
* experience. WM_IME_CHAR just gives question marks, apparently
* because of going through some conversion to the current code
* page.
*
* WM_IME_CHAR might work on NT4 or Win9x with ActiveIMM, but
* use WM_IME_COMPOSITION there, too, to simplify the code.
*/
GDK_NOTE (EVENTS, g_print (" %#lx", (long) msg->lParam));
if (!(msg->lParam & GCS_RESULTSTR))
break;
if (GDK_SURFACE_DESTROYED (surface))
break;
himc = ImmGetContext (msg->hwnd);
ccount = ImmGetCompositionStringW (himc, GCS_RESULTSTR,
wbuf, sizeof (wbuf));
ImmReleaseContext (msg->hwnd, himc);
ccount /= 2;
API_CALL (GetKeyboardState, (key_state));
for (i = 0; i < ccount; i++)
{
GdkTranslatedKey translated;
/* Build a key press event */
translated.keyval = gdk_unicode_to_keyval (wbuf[i]);
translated.consumed = 0;
translated.layout = get_active_group (display);
translated.level = 0;
event = gdk_key_event_new (GDK_KEY_PRESS,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
0,
build_key_event_state (display, key_state),
FALSE,
&translated,
&translated,
NULL);
_gdk_win32_append_event (event);
/* Build a key release event. */
event = gdk_key_event_new (GDK_KEY_RELEASE,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
0,
build_key_event_state (display, key_state),
FALSE,
&translated,
&translated,
NULL);
_gdk_win32_append_event (event);
}
return_val = TRUE;
}
break;
default:
g_warning ("Maybe this was reached because this is not a keyboard-related event");
g_assert_not_reached ();
}
return return_val;
}
#define GDK_ANY_BUTTON_MASK (GDK_BUTTON1_MASK | \
GDK_BUTTON2_MASK | \
GDK_BUTTON3_MASK | \
@@ -1732,7 +2093,6 @@ gdk_event_translate (MSG *msg,
POINT point;
MINMAXINFO *mmi;
HWND hwnd;
HIMC himc;
WINDOWPOS *hwndpos;
gboolean ignore_leave;
@@ -1754,8 +2114,7 @@ gdk_event_translate (MSG *msg,
int button;
gboolean return_val = FALSE;
int i;
gboolean goto_done = FALSE;
display = gdk_display_get_default ();
win32_display = GDK_WIN32_DISPLAY (display);
@@ -1811,346 +2170,19 @@ gdk_event_translate (MSG *msg,
switch (msg->message)
{
/* keyboard inpout related events (including IME events) */
case WM_INPUTLANGCHANGE:
{
GdkWin32Keymap *win32_keymap;
GdkTranslatedKey translated;
HKL input_locale;
win32_keymap = GDK_WIN32_KEYMAP (gdk_display_get_keymap (display));
input_locale = (HKL) msg->lParam;
gdk_win32_display_set_input_locale (win32_display, input_locale);
_gdk_win32_keymap_set_active_layout (win32_keymap, input_locale);
gdk_win32_display_increment_keymap_serial (win32_display);
GDK_NOTE (EVENTS,
g_print (" cs:%lu hkl:%p%s",
(gulong) msg->wParam,
(gpointer) msg->lParam,
gdk_win32_display_input_locale_is_ime (win32_display) ? " (IME)" : ""));
gdk_display_setting_changed (display, "gtk-im-module");
/* Generate a dummy key event to "nudge" IMContext */
translated.keyval = GDK_KEY_VoidSymbol;
translated.consumed = 0;
translated.layout = 0;
translated.level = 0;
event = gdk_key_event_new (GDK_KEY_PRESS,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
0,
0,
FALSE,
&translated,
&translated,
NULL);
_gdk_win32_append_event (event);
}
break;
case WM_SYSKEYUP:
case WM_SYSKEYDOWN:
GDK_NOTE (EVENTS,
g_print (" %s ch:%.02x %s",
_gdk_win32_key_to_string (msg->lParam),
(int) msg->wParam,
decode_key_lparam (msg->lParam)));
/* If posted without us having keyboard focus, ignore */
if ((msg->wParam != VK_F10 && msg->wParam != VK_MENU) &&
!(HIWORD (msg->lParam) & KF_ALTDOWN))
break;
/* Let the system handle Alt-Tab, Alt-Space and Alt-F4 unless
* the keyboard is grabbed.
*/
if (!keyboard_grab &&
(msg->wParam == VK_TAB ||
msg->wParam == VK_SPACE ||
msg->wParam == VK_F4))
break;
/* Jump to code in common with WM_KEYUP and WM_KEYDOWN */
goto keyup_or_down;
case WM_KEYUP:
case WM_KEYDOWN:
GDK_NOTE (EVENTS,
g_print (" %s ch:%.02x %s",
_gdk_win32_key_to_string (msg->lParam),
(int) msg->wParam,
decode_key_lparam (msg->lParam)));
keyup_or_down:
{
GdkWin32Keymap *win32_keymap;
GdkModifierType state;
guint keyval;
guint16 keycode;
guint8 group;
gboolean is_modifier;
GdkTranslatedKey translated;
GdkTranslatedKey no_lock;
BYTE key_state[256];
GArray *translation;
MSG msg2;
int level = 0;
int effective_group = 0;
GdkModifierType consumed = 0;
char *composed = NULL;
/* Ignore key messages intended for the IME */
if (msg->wParam == VK_PROCESSKEY || win32_display->event_record->in_ime_composition)
break;
/* Ignore autorepeats on modifiers */
if (msg->message == WM_KEYDOWN &&
(msg->wParam == VK_MENU ||
msg->wParam == VK_CONTROL ||
msg->wParam == VK_SHIFT) &&
((HIWORD(msg->lParam) & KF_REPEAT) >= 1))
break;
if (GDK_SURFACE_DESTROYED (surface))
break;
win32_keymap = GDK_WIN32_KEYMAP (gdk_display_get_keymap (display));
impl = GDK_WIN32_SURFACE (surface);
API_CALL (GetKeyboardState, (key_state));
keyval = GDK_KEY_VoidSymbol;
keycode = msg->wParam;
/* Get the WinAPI translation of the WM_KEY messages to characters.
The WM_CHAR messages are generated by a previous call to TranslateMessage() and always
follow directly after the corresponding WM_KEY* messages.
There could be 0 or more WM_CHAR messages following (for example dead keys don't generate
WM_CHAR messages - they generate WM_DEAD_CHAR instead, but we are not interested in those
messages). */
translation = g_array_sized_new (FALSE, FALSE, sizeof (gunichar2), 2);
while (PeekMessageW (&msg2, msg->hwnd, 0, 0, 0) && (msg2.message == WM_CHAR || msg2.message == WM_SYSCHAR))
{
/* The character is encoded in WPARAM as UTF-16. */
gunichar2 c = msg2.wParam;
/* Append character to translation string. */
g_array_append_val (translation, c);
/* Remove message from queue */
GetMessageW (&msg2, msg->hwnd, 0, 0);
}
if (translation->len > 0)
composed = g_utf16_to_utf8 ((gunichar2*)translation->data,
translation->len, NULL, NULL, NULL);
g_array_unref (translation);
translation = NULL;
/* Ignore control sequences like Backspace */
if (composed && g_unichar_iscntrl (g_utf8_get_char (composed)))
g_clear_pointer (&composed, g_free);
if (HIWORD (msg->lParam) & KF_EXTENDED)
{
switch (msg->wParam)
{
case VK_CONTROL:
keycode = VK_RCONTROL;
break;
case VK_SHIFT: /* Actually, KF_EXTENDED is not set
* for the right shift key.
*/
keycode = VK_RSHIFT;
break;
case VK_MENU:
keycode = VK_RMENU;
break;
}
}
else if (msg->wParam == VK_SHIFT &&
LOBYTE (HIWORD (msg->lParam)) == _gdk_win32_keymap_get_rshift_scancode (win32_keymap))
keycode = VK_RSHIFT;
is_modifier = (msg->wParam == VK_CONTROL ||
msg->wParam == VK_SHIFT ||
msg->wParam == VK_MENU);
state = build_key_event_state (display, key_state);
group = get_active_group (display);
gdk_keymap_translate_keyboard_state ((GdkKeymap*) win32_keymap, keycode, state, group,
&keyval, &effective_group, &level, &consumed);
translated.keyval = keyval;
translated.consumed = consumed;
translated.layout = effective_group;
translated.level = level;
gdk_keymap_translate_keyboard_state ((GdkKeymap*) win32_keymap, keycode,
state & ~GDK_LOCK_MASK, group, &keyval,
&effective_group, &level, &consumed);
no_lock.keyval = keyval;
no_lock.consumed = consumed;
no_lock.layout = effective_group;
no_lock.level = level;
/* Only one release key event is fired when both shift keys are pressed together
and then released. In order to send the missing event, press events for shift
keys are recorded and sent together when the release event occurs.
Other modifiers (e.g. ctrl, alt) don't have this problem. */
if (msg->message == WM_KEYDOWN && msg->wParam == VK_SHIFT)
{
int pressed_shift = msg->lParam & 0xffffff; /* mask shift modifier */
if (win32_display->event_record->both_shift_pressed[0] == 0)
win32_display->event_record->both_shift_pressed[0] = pressed_shift;
else if (win32_display->event_record->both_shift_pressed[0] != pressed_shift)
win32_display->event_record->both_shift_pressed[1] = pressed_shift;
}
if (msg->message == WM_KEYUP && msg->wParam == VK_SHIFT)
{
if (win32_display->event_record->both_shift_pressed[0] != 0 &&
win32_display->event_record->both_shift_pressed[1] != 0)
{
int tmp_retval;
MSG fake_release = *msg;
int pressed_shift = msg->lParam & 0xffffff;
if (win32_display->event_record->both_shift_pressed[0] == pressed_shift)
fake_release.lParam = win32_display->event_record->both_shift_pressed[1];
else
fake_release.lParam = win32_display->event_record->both_shift_pressed[0];
win32_display->event_record->both_shift_pressed[0] = win32_display->event_record->both_shift_pressed[1] = 0;
gdk_event_translate (&fake_release, &tmp_retval);
}
win32_display->event_record->both_shift_pressed[0] = win32_display->event_record->both_shift_pressed[1] = 0;
}
/* Reset ALT_MASK if it is the Alt key itself */
if (msg->wParam == VK_MENU)
state &= ~GDK_ALT_MASK;
event = gdk_key_event_new ((msg->message == WM_KEYDOWN || msg->message == WM_SYSKEYDOWN)
? GDK_KEY_PRESS
: GDK_KEY_RELEASE,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
keycode,
state,
is_modifier,
&translated,
&no_lock,
composed);
_gdk_win32_append_event (event);
g_free (composed);
return_val = TRUE;
}
break;
case WM_SYSCHAR:
if (msg->wParam != VK_SPACE)
{
/* To prevent beeps, don't let DefWindowProcW() be called */
return_val = TRUE;
goto done;
}
break;
case WM_IME_STARTCOMPOSITION:
win32_display->event_record->in_ime_composition = TRUE;
break;
case WM_IME_ENDCOMPOSITION:
win32_display->event_record->in_ime_composition = FALSE;
break;
case WM_IME_COMPOSITION:
{
BYTE key_state[256];
wchar_t wbuf[100];
int ccount = 0;
/* On Win2k WM_IME_CHAR doesn't work correctly for non-Unicode
* applications. Thus, handle WM_IME_COMPOSITION with
* GCS_RESULTSTR instead, fetch the Unicode chars from the IME
* with ImmGetCompositionStringW().
*
* See for instance
* http://groups.google.com/groups?selm=natX5.57%24g77.19788%40nntp2.onemain.com
* and
* http://groups.google.com/groups?selm=u2XfrXw5BHA.1628%40tkmsftngp02
* for comments by other people that seems to have the same
* experience. WM_IME_CHAR just gives question marks, apparently
* because of going through some conversion to the current code
* page.
*
* WM_IME_CHAR might work on NT4 or Win9x with ActiveIMM, but
* use WM_IME_COMPOSITION there, too, to simplify the code.
*/
GDK_NOTE (EVENTS, g_print (" %#lx", (long) msg->lParam));
if (!(msg->lParam & GCS_RESULTSTR))
break;
if (GDK_SURFACE_DESTROYED (surface))
break;
himc = ImmGetContext (msg->hwnd);
ccount = ImmGetCompositionStringW (himc, GCS_RESULTSTR,
wbuf, sizeof (wbuf));
ImmReleaseContext (msg->hwnd, himc);
ccount /= 2;
API_CALL (GetKeyboardState, (key_state));
for (i = 0; i < ccount; i++)
{
GdkTranslatedKey translated;
/* Build a key press event */
translated.keyval = gdk_unicode_to_keyval (wbuf[i]);
translated.consumed = 0;
translated.layout = get_active_group (display);
translated.level = 0;
event = gdk_key_event_new (GDK_KEY_PRESS,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
0,
build_key_event_state (display, key_state),
FALSE,
&translated,
&translated,
NULL);
_gdk_win32_append_event (event);
/* Build a key release event. */
event = gdk_key_event_new (GDK_KEY_RELEASE,
surface,
win32_display->device_manager->core_keyboard,
_gdk_win32_get_next_tick (msg->time),
0,
build_key_event_state (display, key_state),
FALSE,
&translated,
&translated,
NULL);
_gdk_win32_append_event (event);
}
return_val = TRUE;
}
return_val = handle_keyboard_event (display, surface, msg, ret_valp, &goto_done);
if (goto_done)
goto done;
break;
case WM_LBUTTONDOWN: