Merge branch 'forward-port-mr-991-to-gtk4-2' into 'main'

Use native Windows API for converting keystrokes to characters

Closes #2944

See merge request GNOME/gtk!4986
This commit is contained in:
Luca Bacci 2022-10-17 19:20:08 +00:00
commit 995f00d23f
13 changed files with 128 additions and 222 deletions

View File

@ -262,7 +262,8 @@ _gdk_broadway_events_got_input (GdkDisplay *display,
message->key.state, message->key.state,
FALSE, FALSE,
&translated, &translated,
&translated); &translated,
NULL);
node = _gdk_event_queue_append (display, event); node = _gdk_event_queue_append (display, event);
_gdk_windowing_got_event (display, node, event, message->base.serial); _gdk_windowing_got_event (display, node, event, message->base.serial);

View File

@ -1517,6 +1517,16 @@ gdk_button_event_get_button (GdkEvent *event)
* An event related to a key-based device. * An event related to a key-based device.
*/ */
static void
gdk_key_event_finalize (GdkEvent *event)
{
GdkKeyEvent *self = (GdkKeyEvent *) event;
g_free (self->compose_sequence);
GDK_EVENT_SUPER (event)->finalize (event);
}
static GdkModifierType static GdkModifierType
gdk_key_event_get_state (GdkEvent *event) gdk_key_event_get_state (GdkEvent *event)
{ {
@ -1528,7 +1538,7 @@ gdk_key_event_get_state (GdkEvent *event)
static const GdkEventTypeInfo gdk_key_event_info = { static const GdkEventTypeInfo gdk_key_event_info = {
sizeof (GdkKeyEvent), sizeof (GdkKeyEvent),
NULL, NULL,
NULL, gdk_key_event_finalize,
gdk_key_event_get_state, gdk_key_event_get_state,
NULL, NULL,
NULL, NULL,
@ -1552,6 +1562,10 @@ GDK_DEFINE_EVENT_TYPE (GdkKeyEvent, gdk_key_event,
* @is_modifier: whether the event is a modifiers only event * @is_modifier: whether the event is a modifiers only event
* @translated: the translated key data for the given @state * @translated: the translated key data for the given @state
* @no_lock: the translated key data without the given @state * @no_lock: the translated key data without the given @state
* @compose_sequence: (transfer none) (nullable):
* The compose sequence string, either partial or only the
* final composed string, if that can be determined at event
* creation time. Used by selected IM modules.
* *
* Creates a new `GdkKeyEvent`. * Creates a new `GdkKeyEvent`.
* *
@ -1566,7 +1580,8 @@ gdk_key_event_new (GdkEventType type,
GdkModifierType state, GdkModifierType state,
gboolean is_modifier, gboolean is_modifier,
GdkTranslatedKey *translated, GdkTranslatedKey *translated,
GdkTranslatedKey *no_lock) GdkTranslatedKey *no_lock,
char *compose_sequence)
{ {
g_return_val_if_fail (type == GDK_KEY_PRESS || g_return_val_if_fail (type == GDK_KEY_PRESS ||
type == GDK_KEY_RELEASE, NULL); type == GDK_KEY_RELEASE, NULL);
@ -1579,6 +1594,7 @@ gdk_key_event_new (GdkEventType type,
self->key_is_modifier = is_modifier; self->key_is_modifier = is_modifier;
self->translated[0] = *translated; self->translated[0] = *translated;
self->translated[1] = *no_lock; self->translated[1] = *no_lock;
self->compose_sequence = g_strdup (compose_sequence);
return event; return event;
} }
@ -1609,6 +1625,26 @@ gdk_key_event_get_translated_key (GdkEvent *event,
return &(self->translated[0]); return &(self->translated[0]);
} }
/*< private >
* gdk_key_event_get_compose_sequence:
* @event: (type GdkKeyEvent): a key event
*
* Extracts the compose sequence string from a key event.
*
* Returns: (transfer none): the compose sequence string
*/
char *
gdk_key_event_get_compose_sequence (GdkEvent *event)
{
GdkKeyEvent *self = (GdkKeyEvent *) event;
g_return_val_if_fail (GDK_IS_EVENT (event), 0);
g_return_val_if_fail (GDK_IS_EVENT_TYPE (event, GDK_KEY_PRESS) ||
GDK_IS_EVENT_TYPE (event, GDK_KEY_RELEASE), FALSE);
return self->compose_sequence;
}
/** /**
* gdk_key_event_get_keyval: * gdk_key_event_get_keyval:
* @event: (type GdkKeyEvent): a key event * @event: (type GdkKeyEvent): a key event

View File

@ -259,6 +259,9 @@ typedef struct {
* @keycode: the raw code of the key that was pressed or released. * @keycode: the raw code of the key that was pressed or released.
* @translated: the result of translating @keycode. First with the full * @translated: the result of translating @keycode. First with the full
* @state, then while ignoring Caps Lock. * @state, then while ignoring Caps Lock.
* @compose_sequence: optional string for use by selected IM modules.
* Contains either partial compose sequences or the final composed
* string of the keystroke sequence.
* *
* Describes a key press or key release event. * Describes a key press or key release event.
*/ */
@ -270,6 +273,7 @@ struct _GdkKeyEvent
guint32 keycode; guint32 keycode;
gboolean key_is_modifier; gboolean key_is_modifier;
GdkTranslatedKey translated[2]; GdkTranslatedKey translated[2];
char *compose_sequence;
}; };
/* /*
@ -470,7 +474,8 @@ GdkEvent * gdk_key_event_new (GdkEventType type,
GdkModifierType modifiers, GdkModifierType modifiers,
gboolean is_modifier, gboolean is_modifier,
GdkTranslatedKey *translated, GdkTranslatedKey *translated,
GdkTranslatedKey *no_lock); GdkTranslatedKey *no_lock,
char *compose_sequence);
GdkEvent * gdk_focus_event_new (GdkSurface *surface, GdkEvent * gdk_focus_event_new (GdkSurface *surface,
GdkDevice *device, GdkDevice *device,
@ -597,6 +602,8 @@ GdkEvent * gdk_grab_broken_event_new (GdkSurface *surface,
GdkTranslatedKey * gdk_key_event_get_translated_key (GdkEvent *event, GdkTranslatedKey * gdk_key_event_get_translated_key (GdkEvent *event,
gboolean no_lock); gboolean no_lock);
char * gdk_key_event_get_compose_sequence (GdkEvent *event);
typedef enum typedef enum
{ {
/* Following flag is set for events on the event queue during /* Following flag is set for events on the event queue during
@ -626,7 +633,6 @@ void _gdk_event_queue_flush (GdkDisplay *display);
double * gdk_event_dup_axes (GdkEvent *event); double * gdk_event_dup_axes (GdkEvent *event);
G_END_DECLS G_END_DECLS
#endif /* __GDK_EVENTS_PRIVATE_H__ */ #endif /* __GDK_EVENTS_PRIVATE_H__ */

View File

@ -431,7 +431,8 @@ fill_key_event (GdkMacosDisplay *display,
state, state,
is_modifier, is_modifier,
&translated, &translated,
&no_lock); &no_lock,
NULL);
} }
static GdkEvent * static GdkEvent *

View File

@ -944,7 +944,8 @@ _gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self)
0, 0,
FALSE, FALSE,
&translated, &translated,
&no_lock); &no_lock,
NULL);
_gdk_event_queue_append (display, event); _gdk_event_queue_append (display, event);
} }

View File

@ -2205,7 +2205,8 @@ deliver_key_event (GdkWaylandSeat *seat,
device_get_modifiers (seat->logical_pointer), device_get_modifiers (seat->logical_pointer),
_gdk_wayland_keymap_key_is_modifier (keymap, key), _gdk_wayland_keymap_key_is_modifier (keymap, key),
&translated, &translated,
&no_lock); &no_lock,
NULL);
_gdk_wayland_display_deliver_event (seat->display, event); _gdk_wayland_display_deliver_event (seat->display, event);

View File

@ -1857,7 +1857,8 @@ gdk_event_translate (MSG *msg,
0, 0,
FALSE, FALSE,
&translated, &translated,
&translated); &translated,
NULL);
_gdk_win32_append_event (event); _gdk_win32_append_event (event);
} }
break; break;
@ -1906,8 +1907,12 @@ gdk_event_translate (MSG *msg,
GdkTranslatedKey translated; GdkTranslatedKey translated;
GdkTranslatedKey no_lock; GdkTranslatedKey no_lock;
BYTE key_state[256]; BYTE key_state[256];
wchar_t wbuf[100]; GArray *translation;
int ccount = 0; MSG msg2;
int level = 0;
int effective_group = 0;
GdkModifierType consumed = 0;
char *composed = NULL;
/* Ignore key messages intended for the IME */ /* Ignore key messages intended for the IME */
if (msg->wParam == VK_PROCESSKEY || in_ime_composition) if (msg->wParam == VK_PROCESSKEY || in_ime_composition)
@ -1929,34 +1934,41 @@ gdk_event_translate (MSG *msg,
API_CALL (GetKeyboardState, (key_state)); API_CALL (GetKeyboardState, (key_state));
ccount = 0;
if (msg->wParam == VK_PACKET)
{
ccount = ToUnicode (VK_PACKET, HIWORD (msg->lParam), key_state, wbuf, 1, 0);
if (ccount == 1)
{
if (wbuf[0] >= 0xD800 && wbuf[0] < 0xDC00)
{
if (msg->message == WM_KEYDOWN)
impl->leading_surrogate_keydown = wbuf[0];
else
impl->leading_surrogate_keyup = wbuf[0];
/* don't emit an event */
return_val = TRUE;
break;
}
else
{
/* wait until an event is created */;
}
}
}
keyval = GDK_KEY_VoidSymbol; keyval = GDK_KEY_VoidSymbol;
keycode = msg->wParam; 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) if (HIWORD (msg->lParam) & KF_EXTENDED)
{ {
switch (msg->wParam) switch (msg->wParam)
@ -1985,61 +1997,20 @@ gdk_event_translate (MSG *msg,
state = build_key_event_state (key_state); state = build_key_event_state (key_state);
group = get_active_group (); group = get_active_group ();
if (msg->wParam == VK_PACKET && ccount == 1) gdk_keymap_translate_keyboard_state ((GdkKeymap*) win32_keymap, keycode, state, group,
{ &keyval, &effective_group, &level, &consumed);
if (wbuf[0] >= 0xD800 && wbuf[0] < 0xDC00) translated.keyval = keyval;
{ translated.consumed = consumed;
g_assert_not_reached (); translated.layout = effective_group;
} translated.level = level;
else if (wbuf[0] >= 0xDC00 && wbuf[0] < 0xE000)
{
wchar_t leading;
if (msg->message == WM_KEYDOWN) gdk_keymap_translate_keyboard_state ((GdkKeymap*) win32_keymap, keycode,
leading = impl->leading_surrogate_keydown; state & ~GDK_LOCK_MASK, group, &keyval,
else &effective_group, &level, &consumed);
leading = impl->leading_surrogate_keyup; no_lock.keyval = keyval;
no_lock.consumed = consumed;
keyval = gdk_unicode_to_keyval ((leading - 0xD800) * 0x400 + wbuf[0] - 0xDC00 + 0x10000); no_lock.layout = effective_group;
} no_lock.level = level;
else
{
keyval = gdk_unicode_to_keyval (wbuf[0]);
}
translated.keyval = keyval;
translated.consumed = 0;
translated.layout = 0;
translated.level = 0;
no_lock = translated;
}
else
{
int level = 0;
int effective_group = 0;
GdkModifierType consumed = 0;
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;
}
if (msg->message == WM_KEYDOWN)
impl->leading_surrogate_keydown = 0;
else
impl->leading_surrogate_keyup = 0;
/* Only one release key event is fired when both shift keys are pressed together /* 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 and then released. In order to send the missing event, press events for shift
@ -2087,10 +2058,12 @@ gdk_event_translate (MSG *msg,
state, state,
is_modifier, is_modifier,
&translated, &translated,
&no_lock); &no_lock,
composed);
_gdk_win32_append_event (event); _gdk_win32_append_event (event);
g_free (composed);
return_val = TRUE; return_val = TRUE;
} }
break; break;
@ -2169,7 +2142,8 @@ gdk_event_translate (MSG *msg,
build_key_event_state (key_state), build_key_event_state (key_state),
FALSE, FALSE,
&translated, &translated,
&translated); &translated,
NULL);
_gdk_win32_append_event (event); _gdk_win32_append_event (event);
@ -2182,7 +2156,8 @@ gdk_event_translate (MSG *msg,
build_key_event_state (key_state), build_key_event_state (key_state),
FALSE, FALSE,
&translated, &translated,
&translated); &translated,
NULL);
_gdk_win32_append_event (event); _gdk_win32_append_event (event);
} }

View File

@ -235,14 +235,6 @@ struct _GdkWin32Surface
/* The cursor that GDK set for this window via GdkDevice */ /* The cursor that GDK set for this window via GdkDevice */
GdkWin32HCursor *cursor; GdkWin32HCursor *cursor;
/* When VK_PACKET sends us a leading surrogate, it's stashed here.
* Later, when another VK_PACKET sends a tailing surrogate, we make up
* a full unicode character from them, or discard the leading surrogate,
* if the next key is not a tailing surrogate.
*/
wchar_t leading_surrogate_keydown;
wchar_t leading_surrogate_keyup;
/* Window size hints */ /* Window size hints */
int hint_flags; int hint_flags;
GdkGeometry hints; GdkGeometry hints;

View File

@ -1595,7 +1595,8 @@ gdk_x11_device_manager_xi2_translate_event (GdkEventTranslator *translator,
state, state,
gdk_x11_keymap_key_is_modifier (keymap, xev->detail), gdk_x11_keymap_key_is_modifier (keymap, xev->detail),
&translated, &translated,
&no_lock); &no_lock,
NULL);
if (ev->evtype == XI_KeyPress) if (ev->evtype == XI_KeyPress)
set_user_time (event); set_user_time (event);

View File

@ -607,7 +607,8 @@ gtk_im_context_filter_key (GtkIMContext *context,
state, state,
FALSE, /* FIXME */ FALSE, /* FIXME */
&translated, &translated,
&no_lock); &no_lock,
NULL);
ret = GTK_IM_CONTEXT_GET_CLASS (context)->filter_keypress (context, key); ret = GTK_IM_CONTEXT_GET_CLASS (context)->filter_keypress (context, key);

View File

@ -34,6 +34,7 @@
#include "imm-extra.h" #include "imm-extra.h"
#include "gdk/gdkkeysyms.h" #include "gdk/gdkkeysyms.h"
#include "gdk/gdkeventsprivate.h"
#include "gdk/win32/gdkwin32.h" #include "gdk/win32/gdkwin32.h"
#include "gtk/gtkimmodule.h" #include "gtk/gtkimmodule.h"
#include "gtk/deprecated/gtkstylecontextprivate.h" #include "gtk/deprecated/gtkstylecontextprivate.h"
@ -61,9 +62,6 @@ typedef enum {
GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW, GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW,
} GtkWin32IMEFocusBehavior; } GtkWin32IMEFocusBehavior;
#define IS_DEAD_KEY(k) \
((k) >= GDK_KEY_dead_grave && (k) <= (GDK_KEY_dead_dasia+1))
struct _GtkIMContextIMEPrivate struct _GtkIMContextIMEPrivate
{ {
/* When pretend_empty_preedit is set to TRUE, /* When pretend_empty_preedit is set to TRUE,
@ -81,7 +79,6 @@ struct _GtkIMContextIMEPrivate
* https://gitlab.gnome.org/GNOME/gtk/commit/c255ba68fc2c918dd84da48a472e7973d3c00b03 * https://gitlab.gnome.org/GNOME/gtk/commit/c255ba68fc2c918dd84da48a472e7973d3c00b03
*/ */
gboolean pretend_empty_preedit; gboolean pretend_empty_preedit;
guint32 dead_key_keyval;
GtkWin32IMEFocusBehavior focus_behavior; GtkWin32IMEFocusBehavior focus_behavior;
}; };
@ -277,134 +274,26 @@ gtk_im_context_ime_set_client_widget (GtkIMContext *context,
context_ime->client_surface = surface; context_ime->client_surface = surface;
} }
static gunichar
_gtk_im_context_ime_dead_key_unichar (guint keyval,
gboolean spacing)
{
switch (keyval)
{
#define CASE(keysym, unicode, spacing_unicode) \
case GDK_KEY_dead_##keysym: return (spacing) ? spacing_unicode : unicode;
CASE (grave, 0x0300, 0x0060);
CASE (acute, 0x0301, 0x00b4);
CASE (circumflex, 0x0302, 0x005e);
CASE (tilde, 0x0303, 0x007e); /* Also used with perispomeni, 0x342. */
CASE (macron, 0x0304, 0x00af);
CASE (breve, 0x0306, 0x02d8);
CASE (abovedot, 0x0307, 0x02d9);
CASE (diaeresis, 0x0308, 0x00a8);
CASE (hook, 0x0309, 0);
CASE (abovering, 0x030A, 0x02da);
CASE (doubleacute, 0x030B, 0x2dd);
CASE (caron, 0x030C, 0x02c7);
CASE (abovecomma, 0x0313, 0); /* Equivalent to psili */
CASE (abovereversedcomma, 0x0314, 0); /* Equivalent to dasia */
CASE (horn, 0x031B, 0); /* Legacy use for psili, 0x313 (or 0x343). */
CASE (belowdot, 0x0323, 0);
CASE (cedilla, 0x0327, 0x00b8);
CASE (ogonek, 0x0328, 0); /* Legacy use for dasia, 0x314.*/
CASE (iota, 0x0345, 0);
#undef CASE
default:
return 0;
}
}
static void
_gtk_im_context_ime_commit_unichar (GtkIMContextIME *context_ime,
gunichar c)
{
char utf8[10];
int len;
if (context_ime->priv->dead_key_keyval != 0)
{
gunichar combining;
combining =
_gtk_im_context_ime_dead_key_unichar (context_ime->priv->dead_key_keyval,
FALSE);
g_unichar_compose (c, combining, &c);
}
len = g_unichar_to_utf8 (c, utf8);
utf8[len] = 0;
g_signal_emit_by_name (context_ime, "commit", utf8);
context_ime->priv->dead_key_keyval = 0;
}
static gboolean static gboolean
gtk_im_context_ime_filter_keypress (GtkIMContext *context, gtk_im_context_ime_filter_keypress (GtkIMContext *context,
GdkEvent *event) GdkEvent *event)
{ {
GtkIMContextIME *context_ime; GtkIMContextIME *context_ime;
gboolean retval = FALSE; char *compose_sequence = NULL;
guint32 c;
GdkModifierType state, consumed_modifiers, no_text_input_mask;
guint keyval;
g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context), FALSE); g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context), FALSE);
g_return_val_if_fail (event, FALSE); g_return_val_if_fail (event, FALSE);
if (gdk_event_get_event_type ((GdkEvent *) event) == GDK_KEY_RELEASE)
return FALSE;
no_text_input_mask = GDK_ALT_MASK|GDK_CONTROL_MASK;
state = gdk_event_get_modifier_state ((GdkEvent *) event);
consumed_modifiers = gdk_key_event_get_consumed_modifiers (event);
if (state & no_text_input_mask & ~consumed_modifiers)
return FALSE;
context_ime = GTK_IM_CONTEXT_IME (context); context_ime = GTK_IM_CONTEXT_IME (context);
if (!context_ime->focus) compose_sequence = gdk_key_event_get_compose_sequence (event);
return FALSE; if (compose_sequence)
if (!GDK_IS_SURFACE (context_ime->client_surface))
return FALSE;
keyval = gdk_key_event_get_keyval ((GdkEvent *) event);
if (keyval == GDK_KEY_space &&
context_ime->priv->dead_key_keyval != 0)
{ {
c = _gtk_im_context_ime_dead_key_unichar (context_ime->priv->dead_key_keyval, TRUE); g_signal_emit_by_name (context_ime, "commit", compose_sequence);
context_ime->priv->dead_key_keyval = 0;
_gtk_im_context_ime_commit_unichar (context_ime, c);
return TRUE; return TRUE;
} }
c = gdk_keyval_to_unicode (keyval); return FALSE;
if (c && !g_unichar_iscntrl(c))
{
_gtk_im_context_ime_commit_unichar (context_ime, c);
retval = TRUE;
}
else if (IS_DEAD_KEY (keyval))
{
gunichar dead_key;
dead_key = _gtk_im_context_ime_dead_key_unichar (keyval, FALSE);
/* Emulate double input of dead keys */
if (dead_key && keyval == context_ime->priv->dead_key_keyval)
{
c = _gtk_im_context_ime_dead_key_unichar (context_ime->priv->dead_key_keyval, TRUE);
context_ime->priv->dead_key_keyval = 0;
_gtk_im_context_ime_commit_unichar (context_ime, c);
_gtk_im_context_ime_commit_unichar (context_ime, c);
}
else
context_ime->priv->dead_key_keyval = keyval;
}
return retval;
} }

View File

@ -663,7 +663,8 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
gdk_event_get_modifier_state (event), gdk_event_get_modifier_state (event),
FALSE, FALSE,
&translated, &translated,
&translated); &translated,
NULL);
gtk_im_context_filter_keypress (context, tmp_event); gtk_im_context_filter_keypress (context, tmp_event);
gdk_event_unref (tmp_event); gdk_event_unref (tmp_event);

View File

@ -1084,7 +1084,8 @@ rewrite_event_for_toplevel (GdkEvent *event)
gdk_key_event_get_keycode (event), gdk_key_event_get_keycode (event),
gdk_event_get_modifier_state (event), gdk_event_get_modifier_state (event),
gdk_key_event_is_modifier (event), gdk_key_event_is_modifier (event),
key, key_no_lock); key, key_no_lock,
gdk_key_event_get_compose_sequence (event));
} }
static gboolean static gboolean