From 694952b7f7793b70e9bccc8b5d109b0a51d05526 Mon Sep 17 00:00:00 2001 From: oldosfan Date: Fri, 7 Oct 2022 02:08:41 +0000 Subject: [PATCH] Minor fixes to input method handling * compositor.h (struct _TextInputFuncs): Make filter_input return a KeySym. (ConfigureWidth, ConfigureHeight): Remove macros. * seat.c (LookupKeysym): New function. (DispatchKey): Handle keysym return from filter input function. * text_input.c (CurrentCursorX, CurrentCursorY, CurrentCursorWidth) (CurrentCursorHeight): New functions. (DoGeometryAllocation, Commit): Handle scaling correctly. (ConvertWcharString, PreeditDrawCallback): Handle wide character strings. (CreateIC): Do geometry allocation before setting the focus, not after. (LookupString): Only prefer keysym if control chars were returned. (FilterInputCallback): Return keysym. (XLTextInputDispatchCoreEvent): Improve debugging code. * xdg_toplevel.c (HandleConfigureEvent): Fix scaling of event coordinates during resize. --- compositor.h | 5 +- seat.c | 49 ++++++++- text_input.c | 271 +++++++++++++++++++++++++++++++++++-------------- xdg_toplevel.c | 15 ++- 4 files changed, 252 insertions(+), 88 deletions(-) diff --git a/compositor.h b/compositor.h index e1abbe4..e2a40fe 100644 --- a/compositor.h +++ b/compositor.h @@ -1275,7 +1275,7 @@ struct _TextInputFuncs void (*focus_out) (Seat *); /* The last argument is actually an XIDeviceEvent *. */ - Bool (*filter_input) (Seat *, Surface *, void *); + Bool (*filter_input) (Seat *, Surface *, void *, KeySym *); }; extern void XLTextInputDispatchCoreEvent (Surface *, XEvent *); @@ -1518,6 +1518,3 @@ extern void XLInitSinglePixelBuffer (void); #define IntSubtractWrapv(a, b, r) __builtin_sub_overflow (a, b, r) #define IntMultiplyWrapv(a, b, r) __builtin_mul_overflow (a, b, r) -#define ConfigureWidth(event) ((event)->xconfigure.width / global_scale_factor) -#define ConfigureHeight(event) ((event)->xconfigure.height / global_scale_factor) - diff --git a/seat.c b/seat.c index 812347b..ab693df 100644 --- a/seat.c +++ b/seat.c @@ -4017,10 +4017,31 @@ DispatchButton (Subcompositor *subcompositor, XIDeviceEvent *xev) xev->event_x, xev->event_y); } +static KeySym +LookupKeysym (XIDeviceEvent *xev, KeyCode keycode) +{ + unsigned int state, mods_return; + KeySym keysym; + + /* Convert the state to a core state mask. */ + state = ((xev->mods.effective & ~(1 << 13 | 1 << 14)) + | (xev->group.effective << 13)); + keysym = 0; + + /* Look up the keysym. */ + XkbLookupKeySym (compositor.display, keycode, + state, &mods_return, &keysym); + + /* Return the keysym. */ + return keysym; +} + static void DispatchKey (XIDeviceEvent *xev) { Seat *seat; + KeySym keysym; + KeyCode keycode; seat = XLLookUpAssoc (seats, xev->deviceid); @@ -4033,10 +4054,12 @@ DispatchKey (XIDeviceEvent *xev) if (seat->focus_surface) { + keysym = 0; + if (input_funcs && seat->flags & IsTextInputSeat && input_funcs->filter_input (seat, seat->focus_surface, - xev)) + xev, &keysym)) /* The input method decided to filter the key. */ return; @@ -4044,13 +4067,33 @@ DispatchKey (XIDeviceEvent *xev) if (xev->flags & XIKeyRepeat) return; + keycode = xev->detail; + + /* If the input method specified a keysym to use, use it. */ + + if (keysym) + { + /* If looking up the event keycode also results in the + keysym, then just use the keycode specified in the + event. */ + if (LookupKeysym (xev, keycode) == keysym) + keycode = xev->detail; + else + keycode = XKeysymToKeycode (compositor.display, keysym); + + /* But if no corresponding keycode could be found, use the + keycode provided in the event. */ + if (!keycode) + keycode = xev->detail; + } + if (xev->evtype == XI_KeyPress) SendKeyboardKey (seat, seat->focus_surface, - xev->time, WaylandKeycode (xev->detail), + xev->time, WaylandKeycode (keycode), WL_KEYBOARD_KEY_STATE_PRESSED); else SendKeyboardKey (seat, seat->focus_surface, - xev->time, WaylandKeycode (xev->detail), + xev->time, WaylandKeycode (keycode), WL_KEYBOARD_KEY_STATE_RELEASED); } } diff --git a/text_input.c b/text_input.c index ef6d8e4..d87a0a1 100644 --- a/text_input.c +++ b/text_input.c @@ -241,6 +241,70 @@ static XIMStyle xim_style; /* The order in which XIM input styles will be searched for. */ static XimStyleKind xim_style_order[5]; +static int +CurrentCursorX (TextInput *input) +{ + int x, y; + + XLAssert (input->client_info->focus_surface != NULL); + + /* Scale these coordinates into window coordinates. */ + TruncateSurfaceToWindow (input->client_info->focus_surface, + input->current_state.cursor_x, + input->current_state.cursor_y, + &x, &y); + + return x; +} + +static int +CurrentCursorY (TextInput *input) +{ + int x, y; + + XLAssert (input->client_info->focus_surface != NULL); + + /* Scale these coordinates into window coordinates. */ + TruncateSurfaceToWindow (input->client_info->focus_surface, + input->current_state.cursor_x, + input->current_state.cursor_y, + &x, &y); + + return y; +} + +static int +CurrentCursorWidth (TextInput *input) +{ + int width, height; + + XLAssert (input->client_info->focus_surface != NULL); + + /* Scale these coordinates into window coordinates. */ + TruncateScaleToWindow (input->client_info->focus_surface, + input->current_state.cursor_width, + input->current_state.cursor_height, + &width, &height); + + return width; +} + +static int +CurrentCursorHeight (TextInput *input) +{ + int width, height; + + XLAssert (input->client_info->focus_surface != NULL); + + /* Scale these coordinates into window coordinates. */ + TruncateScaleToWindow (input->client_info->focus_surface, + input->current_state.cursor_width, + input->current_state.cursor_height, + &width, &height); + + return height; +} + /* Byte-text position conversion. */ @@ -558,7 +622,7 @@ static void DoGeometryAllocation (TextInput *input) { XPoint spot; - XRectangle area, needed; + XRectangle area, *needed; XVaNestedList attr; View *view; char *rc; @@ -577,9 +641,9 @@ DoGeometryAllocation (TextInput *input) if (input->current_state.pending & PendingCursorRectangle) { - spot.x = input->current_state.cursor_x; - spot.y = (input->current_state.cursor_y - + input->current_state.cursor_height - 1); + spot.x = CurrentCursorX (input); + spot.y = (CurrentCursorY (input) + + CurrentCursorHeight (input)); } else { @@ -611,44 +675,46 @@ DoGeometryAllocation (TextInput *input) if (!rc) { DebugPrint ("IM suggested the given size: %d %d", - needed.width, needed.height); + needed->width, needed->height); /* Place the rectangle below and to the right of the caret. */ if (input->current_state.pending & PendingCursorRectangle) { - needed.x = (input->current_state.cursor_x - + input->current_state.cursor_width); - needed.y = (input->current_state.cursor_y - + input->current_state.cursor_height); + needed->x = (CurrentCursorX (input) + + CurrentCursorWidth (input)); + needed->y = (CurrentCursorY (input) + + CurrentCursorHeight (input)); - FitRect (&needed, ViewWidth (view), ViewHeight (view), - input->current_state.cursor_x, - input->current_state.cursor_y, - input->current_state.cursor_width, - input->current_state.cursor_height); + FitRect (needed, ViewWidth (view), ViewHeight (view), + CurrentCursorX (input), CurrentCursorY (input), + CurrentCursorWidth (input), + CurrentCursorHeight (input)); DebugPrint ("filled rectangle: %d %d %d %d", - needed.x, needed.y, needed.width, - needed.height); + needed->x, needed->y, needed->width, + needed->height); } else { /* No caret was specified... Place the preedit window on - the bottom right corner of the view. */ - needed.x = ViewWidth (view); - needed.y = ViewHeight (view); + the bottom left corner of the view. */ + needed->x = 0; + needed->y = ViewHeight (view) - needed->height; DebugPrint ("placed rectangle: %d %d %d %d", - needed.x, needed.y, needed.width, - needed.height); + needed->x, needed->y, needed->width, + needed->height); } /* Set the geometry. */ - attr = XVaCreateNestedList (0, XNArea, &needed, NULL); + attr = XVaCreateNestedList (0, XNArea, needed, NULL); XSetICValues (input->xic, XNPreeditAttributes, attr, NULL); XFree (attr); + + /* Free the rectangle returned. */ + XFree (needed); } } @@ -671,44 +737,23 @@ DoGeometryAllocation (TextInput *input) if (!rc) { DebugPrint ("IM suggested the given size: %d %d", - needed.width, needed.height); + needed->width, needed->height); - /* Place the rectangle below and to the right of the - caret. */ + /* Place the rectangle at the bottom of the window. */ + needed->x = ViewWidth (view) - needed->width; + needed->y = ViewHeight (view) - needed->height; - if (input->current_state.pending & PendingCursorRectangle) - { - needed.x = (input->current_state.cursor_x - + input->current_state.cursor_width); - needed.y = (input->current_state.cursor_y - + input->current_state.cursor_height); - - FitRect (&needed, ViewWidth (view), ViewHeight (view), - input->current_state.cursor_x, - input->current_state.cursor_y, - input->current_state.cursor_width, - input->current_state.cursor_height); - - DebugPrint ("filled rectangle: %d %d %d %d", - needed.x, needed.y, needed.width, - needed.height); - } - else - { - /* No caret was specified... Place the preedit window on - the bottom right corner of the view. */ - needed.x = ViewWidth (view); - needed.y = ViewHeight (view); - - DebugPrint ("placed rectangle: %d %d %d %d", - needed.x, needed.y, needed.width, - needed.height); - } + DebugPrint ("placed rectangle at bottom right: %d %d %d %d", + needed->x, needed->y, needed->width, + needed->height); /* Set the geometry. */ - attr = XVaCreateNestedList (0, XNArea, &needed, NULL); + attr = XVaCreateNestedList (0, XNArea, needed, NULL); XSetICValues (input->xic, XNStatusAttributes, attr, NULL); XFree (attr); + + /* Free the needed rectangle. */ + XFree (needed); } } } @@ -770,11 +815,11 @@ Commit (struct wl_client *client, struct wl_resource *resource) else XFree (XmbResetIC (input->xic)); - if (input->xic) - XSetICFocus (input->xic); - /* Perform geometry/position allocation on the IC. */ DoGeometryAllocation (input); + + if (input->xic) + XSetICFocus (input->xic); } else { @@ -1512,12 +1557,62 @@ PreeditDoneCallback (XIC ic, XPointer client_data, XPointer call_data) UpdatePreedit (input); } +static char * +ConvertWcharString (PreeditBuffer *buffer, const wchar_t *input, + size_t input_size, size_t *string_size) +{ + char *output, *oldlocale; + int rc; + size_t bytes; + + /* Since the text is intended for BUFFER, switch to BUFFER's + locale. */ + oldlocale = XLStrdup (setlocale (LC_CTYPE, NULL)); + + /* Switch to the new locale. */ + if (!setlocale (LC_CTYPE, buffer->locale)) + { + /* Setting the locale failed. Return an empty string. */ + XLFree (oldlocale); + *string_size = 0; + return NULL; + } + + output = XLCalloc (input_size + 1, MB_CUR_MAX); + bytes = 0; + + while (input_size) + { + input_size--; + rc = wctomb (output + bytes, *input++); + + if (rc == -1) + /* Invalid wide character code. */ + continue; + + /* Otherwise, move the string forward this much. */ + bytes += rc; + } + + /* Return the string and the number of bytes put in it. */ + *string_size = bytes; + + /* Clear shift state. */ + wctomb (NULL, L'\0'); + + /* Restore the old locale. */ + setlocale (LC_CTYPE, oldlocale); + XLFree (oldlocale); + return output; +} + static void PreeditDrawCallback (XIC ic, XPointer client_data, XIMPreeditDrawCallbackStruct *call_data) { TextInput *input; size_t string_size; + char *multi_byte_string; input = (TextInput *) client_data; DebugPrint ("text input: %p", input); @@ -1529,13 +1624,6 @@ PreeditDrawCallback (XIC ic, XPointer client_data, call_data->chg_first, call_data->chg_length); - if (call_data->text - && call_data->text->encoding_is_wchar) - { - DebugPrint ("wchar encoding not yet implemented"); - return; - } - /* Delete text between chg_first and chg_first + chg_length. */ if (call_data->chg_length && !PreeditDeleteChars (input->buffer, call_data->chg_first, @@ -1547,17 +1635,35 @@ PreeditDrawCallback (XIC ic, XPointer client_data, if (call_data->text) { - /* The multibyte string should be NULL terminated. */ - string_size = strlen (call_data->text->string.multi_byte); + if (call_data->text->encoding_is_wchar) + { + DebugPrint ("converting wide character string"); + + multi_byte_string + = ConvertWcharString (input->buffer, + call_data->text->string.wide_char, + call_data->text->length, + &string_size); + } + else + { + /* The multibyte string should be NULL terminated. */ + string_size = strlen (call_data->text->string.multi_byte); + multi_byte_string = call_data->text->string.multi_byte; + } DebugPrint ("inserting text of size %d, %zu", call_data->text->length, string_size); /* Now, insert whatever text was specified at chg_first. */ if (!PreeditInsertChars (input->buffer, call_data->chg_first, - call_data->text->string.multi_byte, - string_size, call_data->text->length)) + multi_byte_string, string_size, + call_data->text->length)) DebugPrint ("insertion failed"); + + if (call_data->text->encoding_is_wchar) + /* We must free the conversion results. */ + XLFree (multi_byte_string); } /* Now set the caret position. */ @@ -2279,9 +2385,9 @@ CreateIC (TextInput *input) if (input->current_state.pending & PendingCursorRectangle) { - spot.x = input->current_state.cursor_x; - spot.y = (input->current_state.cursor_y - + input->current_state.cursor_height - 1); + spot.x = CurrentCursorX (input); + spot.y = (CurrentCursorY (input) + + CurrentCursorHeight (input)); } else { @@ -3021,6 +3127,12 @@ LookupString (TextInput *input, XEvent *event, KeySym *keysym_return) Status status; KeySym keysym; + if (event->xkey.type != KeyPress) + { + DebugPrint ("ignoring key release event"); + return False; + } + /* First, do XmbLookupString with the default buffer size. */ buffer = alloca (256); nbytes = XmbLookupString (input->xic, &event->xkey, @@ -3059,11 +3171,12 @@ LookupString (TextInput *input, XEvent *event, KeySym *keysym_return) /* Convert the string. */ buffer = ConvertString (buffer, nbytes, &buffer_size); - /* If the string happens to consist of only 1 character and a keysym - was also found, give preference to the keysym. */ - if (buffer_size == 1 && status == XLookupBoth) + /* If the string happens to consist of only 1 control character and + a keysym was also found, give preference to the keysym. */ + if (buffer_size == 1 && status == XLookupBoth + && buffer[0] > 0 && buffer[0] < 32) { - DebugPrint ("using keysym in preference to single char"); + DebugPrint ("using keysym in preference to single control char"); XFree (buffer); @@ -3081,7 +3194,8 @@ LookupString (TextInput *input, XEvent *event, KeySym *keysym_return) } static Bool -FilterInputCallback (Seat *seat, Surface *surface, void *event) +FilterInputCallback (Seat *seat, Surface *surface, void *event, + KeySym *keysym) { XIDeviceEvent *xev; XEvent xkey; @@ -3118,7 +3232,7 @@ FilterInputCallback (Seat *seat, Surface *surface, void *event) /* Otherwise, call XmbLookupString. If a keysym is returned, return False. Otherwise, commit the string looked up and return True. */ - return LookupString (input, &xkey, NULL); + return LookupString (input, &xkey, keysym); } /* Otherwise, do nothing. */ @@ -3204,7 +3318,8 @@ XLTextInputDispatchCoreEvent (Surface *surface, XEvent *event) else { /* Since that failed, dispatch the event to the seat. */ - DebugPrint ("lookup failed; dispatching event to seat"); + DebugPrint ("lookup failed; dispatching event to seat; " + "keysym is: %lu", keysym); XLSeatDispatchCoreKeyEvent (im_seat, surface, event, keysym); } diff --git a/xdg_toplevel.c b/xdg_toplevel.c index f485301..d73490c 100644 --- a/xdg_toplevel.c +++ b/xdg_toplevel.c @@ -1316,7 +1316,7 @@ RestoreStateTo (XdgToplevel *toplevel, int width, int height) static Bool HandleConfigureEvent (XdgToplevel *toplevel, XEvent *event) { - int width, height; + int width, height, configure_width, configure_height; if (event->xconfigure.send_event) /* Handle only synthetic events, since that's what the @@ -1357,9 +1357,18 @@ HandleConfigureEvent (XdgToplevel *toplevel, XEvent *event) if (!MaybePostDelayedConfigure (toplevel, StatePendingConfigureSize)) { + /* Scale the configure event width and height to the + surface. */ + TruncateScaleToSurface (toplevel->role->surface, + event->xconfigure.width, + event->xconfigure.height, + &configure_width, + &configure_height); + + /* Calculate the new window size. */ XLXdgRoleCalcNewWindowSize (toplevel->role, - ConfigureWidth (event), - ConfigureHeight (event), + configure_width, + configure_height, &width, &height); SendConfigure (toplevel, width, height);