forked from 12to11/12to11

* text_input.c (EncodeIMString): * xdata.c (GetConversionCallback, PostReceiveConversion): Pacify GCC suspecting memory leaks after iconv_open returns -1.
3762 lines
94 KiB
C
3762 lines
94 KiB
C
/* Wayland compositor running on top of an X server.
|
||
|
||
Copyright (C) 2022 to various contributors.
|
||
|
||
This file is part of 12to11.
|
||
|
||
12to11 is free software: you can redistribute it and/or modify it
|
||
under the terms of the GNU General Public License as published by the
|
||
Free Software Foundation, either version 3 of the License, or (at your
|
||
option) any later version.
|
||
|
||
12to11 is distributed in the hope that it will be useful, but WITHOUT
|
||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with 12to11. If not, see <https://www.gnu.org/licenses/>. */
|
||
|
||
#include <string.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
|
||
#include <sys/errno.h>
|
||
|
||
#include <iconv.h>
|
||
#include <locale.h>
|
||
#include <langinfo.h>
|
||
#include <ctype.h>
|
||
|
||
#include "compositor.h"
|
||
#include "text-input-unstable-v3.h"
|
||
|
||
#include <X11/extensions/XInput2.h>
|
||
#include <X11/XKBlib.h>
|
||
|
||
/* X Input Method (XIM) support.
|
||
|
||
The X input method has a client-server architecture; the connection
|
||
between the client and server is abstracted away by Xlib, and
|
||
always results in an XIM object being produced. The connection
|
||
itself can take many forms: the IM server could be linked into the
|
||
X library, another X client on the same server, running on another
|
||
machine over a TCP/IP connection, or even a DECnet node.
|
||
|
||
The XIM object will be assigned an arbitrary seat (usually the
|
||
virtual core keyboard), which will be the only seat that can
|
||
utilize input methods.
|
||
|
||
Each text input will have a corresponding input context (XIC) for
|
||
every focused window. The XIC handles state logically associated
|
||
with a single text entry area, such as currently composed text, the
|
||
currently focused surface, the position of the cursor, and text
|
||
surrounding the cursor.
|
||
|
||
When the previously assigned seat's focus moves to a surface with
|
||
an associated XIC, and the text input is enabled, focus is given to
|
||
the XIC. Subsequent extension key events are translated into core
|
||
ones, then sent to the input context; then, should the input
|
||
context chose to discard the event, the event is simply discarded.
|
||
Otherwise, XmbLookupString is called on the event, and any keysym
|
||
or string returned is looked up and committed or sent to the
|
||
surface.
|
||
|
||
The X library synthesizes fake client-side events to represent
|
||
XIM_COMMIT events, and saves the committed text for XmbLookupString
|
||
to return. These events do not contain the information necessary
|
||
to determine the XIC that sent the event which caused it to be
|
||
generated. Similarly, events sent with XIM_FORWARD_EVENT do not
|
||
contain enough information to attribute them to the XIC that sent
|
||
them.
|
||
|
||
That means it is impossible to attribute forwarded events or
|
||
committed text to the correct XIC, and thus it is impossible to
|
||
look up which seat's TextInput resource an event is actually bound
|
||
for. If one day we move to our own implementation of the XIM
|
||
protocol, then it will become possible to properly support
|
||
multi-seat setups, with one XIC per-client and per-seat.
|
||
|
||
Further more, the XIM has its own locale, which is not guaranteed
|
||
to have the same coded character set as the character set used in
|
||
the Wayland protocol, namely UTF-8. During XIM creation, its
|
||
locale's coded character set is obtained and used to create a
|
||
character conversion context. All text obtained from the XIM
|
||
callbacks is then converted with that context, and character
|
||
indices provided by the XIM are converted to byte indices into the
|
||
converted string before being sent to the client.
|
||
|
||
This code has many inherent race conditions, just like the
|
||
zwp_text_input_v3 protocol itself. And as described above, it only
|
||
supports one seat due to limitations of the Xlib XIM wrapper. */
|
||
|
||
typedef struct _TextInputClientInfo TextInputClientInfo;
|
||
typedef struct _TextInput TextInput;
|
||
typedef struct _TextInputState TextInputState;
|
||
typedef struct _TextPosition TextPosition;
|
||
typedef struct _PreeditBuffer PreeditBuffer;
|
||
typedef struct _KeycodeMap KeycodeMap;
|
||
|
||
typedef enum _XimStyleKind XimStyleKind;
|
||
|
||
enum _XimStyleKind
|
||
{
|
||
XimStyleNone,
|
||
XimOverTheSpot,
|
||
XimOffTheSpot,
|
||
XimRootWindow,
|
||
XimOnTheSpot,
|
||
};
|
||
|
||
enum
|
||
{
|
||
PendingEnabled = 1,
|
||
PendingCursorRectangle = (1 << 1),
|
||
PendingSurroundingText = (1 << 2),
|
||
};
|
||
|
||
struct _KeycodeMap
|
||
{
|
||
/* Packed map between keycodes specified in KeyPress events and
|
||
keycodes that were actually sent to applications. */
|
||
KeyCode *keycodes;
|
||
|
||
/* The keycodes that were computed from keysyms and actually sent to
|
||
applications. */
|
||
KeyCode *keysyms;
|
||
|
||
/* The number of keycodes and used keycodes in this map. */
|
||
int key_count;
|
||
};
|
||
|
||
struct _PreeditBuffer
|
||
{
|
||
/* Buffer data. */
|
||
char *buffer;
|
||
|
||
/* The locale. */
|
||
char *locale;
|
||
|
||
/* Buffer size in bytes. */
|
||
size_t size;
|
||
|
||
/* Buffer size in characters. */
|
||
int total_characters;
|
||
};
|
||
|
||
struct _TextPosition
|
||
{
|
||
/* Byte position. */
|
||
ptrdiff_t bytepos;
|
||
|
||
/* Character position. */
|
||
int charpos;
|
||
};
|
||
|
||
struct _TextInputState
|
||
{
|
||
/* What is defined; alternatively, what is pending. */
|
||
int pending;
|
||
|
||
/* Whether or not this text input is enabled. */
|
||
Bool enabled;
|
||
|
||
/* Cursor rectangle. */
|
||
int cursor_x, cursor_y, cursor_width, cursor_height;
|
||
|
||
/* Surrounding text. This is allocated with XLMalloc and is made
|
||
NULL upon commit in the pending state. */
|
||
char *surrounding_text;
|
||
|
||
/* Character and byte positions of the cursor in the surrounding
|
||
text. */
|
||
TextPosition cursor;
|
||
};
|
||
|
||
struct _TextInput
|
||
{
|
||
/* The TextInputClientInfo associated with this text input. */
|
||
TextInputClientInfo *client_info;
|
||
|
||
/* The wl_resource associated with this text input. */
|
||
struct wl_resource *resource;
|
||
|
||
/* The next and last TextInputs. */
|
||
TextInput *next, *last;
|
||
|
||
/* The XIC associated with this text input. */
|
||
XIC xic;
|
||
|
||
/* The current pre-edit buffer, or NULL. */
|
||
PreeditBuffer *buffer;
|
||
|
||
/* The position of the preedit caret in characters. */
|
||
int caret;
|
||
|
||
/* The style of the caret. */
|
||
XIMCaretStyle caret_style;
|
||
|
||
/* The pending state. */
|
||
TextInputState pending_state;
|
||
|
||
/* The current state. */
|
||
TextInputState current_state;
|
||
|
||
/* Number of commit requests performed. */
|
||
uint32_t serial;
|
||
|
||
/* Map between keys currently held down and keysyms they looked up
|
||
to. */
|
||
KeycodeMap keysym_map;
|
||
};
|
||
|
||
/* Structure describing a list of TextInput resources associated with
|
||
a given client. */
|
||
struct _TextInputClientInfo
|
||
{
|
||
/* The next and last objects in this list. */
|
||
TextInputClientInfo *next, *last;
|
||
|
||
/* The associated seat. */
|
||
Seat *seat;
|
||
|
||
/* The key associated with the seat destruction callback. */
|
||
void *seat_key;
|
||
|
||
/* The associated Wayland client. */
|
||
struct wl_client *client;
|
||
|
||
/* The list of Wayland client info objects. */
|
||
TextInput inputs;
|
||
|
||
/* The current focused surface. */
|
||
Surface *focus_surface;
|
||
};
|
||
|
||
/* List of all TextInputClientInfos. */
|
||
static TextInputClientInfo all_client_infos;
|
||
|
||
/* The text input manager global. */
|
||
static struct wl_global *text_input_manager_global;
|
||
|
||
/* The IM fontset. */
|
||
static XFontSet im_fontset;
|
||
|
||
#if defined DEBUG
|
||
#define DebugPrint(format, args...) \
|
||
fprintf (stderr, "%s: " format "\n", __FUNCTION__, ## args)
|
||
#else
|
||
#define DebugPrint(fmt, ...) ((void) 0)
|
||
#endif
|
||
|
||
/* The XIM currently in use, or NULL. */
|
||
static XIM current_xim;
|
||
|
||
/* The conversion context for that XIM. */
|
||
static iconv_t current_cd;
|
||
|
||
/* The preferred XIM style. */
|
||
static XIMStyle xim_style;
|
||
|
||
/* The order in which XIM input styles will be searched for. */
|
||
static XimStyleKind xim_style_order[5];
|
||
|
||
|
||
|
||
static void
|
||
ClearKeycodeMap (KeycodeMap *map)
|
||
{
|
||
XLFree (map->keycodes);
|
||
XLFree (map->keysyms);
|
||
map->keycodes = NULL;
|
||
map->keysyms = NULL;
|
||
map->key_count = 0;
|
||
}
|
||
|
||
static void
|
||
InsertKeycode (KeycodeMap *map, KeyCode keycode, KeyCode keycode_used)
|
||
{
|
||
int i;
|
||
|
||
/* Insert keycode_used into map, under keycode. See if map already
|
||
contains the given keycode. */
|
||
|
||
for (i = 0; i < map->key_count; ++i)
|
||
{
|
||
if (map->keycodes[i] == keycode)
|
||
{
|
||
map->keysyms[i] = keycode_used;
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Otherwise, add it to the end. */
|
||
map->key_count++;
|
||
map->keycodes
|
||
= XLRealloc (map->keycodes,
|
||
map->key_count * sizeof *map->keycodes);
|
||
map->keysyms
|
||
= XLRealloc (map->keysyms,
|
||
map->key_count * sizeof *map->keysyms);
|
||
map->keycodes[map->key_count - 1] = keycode;
|
||
map->keysyms[map->key_count - 1] = keycode_used;
|
||
}
|
||
|
||
static void
|
||
RemoveKeysym (KeycodeMap *map, KeyCode keycode)
|
||
{
|
||
int i;
|
||
|
||
/* Find where the keycode is located in the map. */
|
||
|
||
for (i = 0; i < map->key_count; ++i)
|
||
{
|
||
if (map->keycodes[i] == keycode)
|
||
{
|
||
/* Remove the keycode from the list and shrink it. */
|
||
memcpy (&map->keycodes[i], &map->keycodes[i + 1],
|
||
(map->key_count - (i + 1)) * sizeof *map->keycodes);
|
||
memcpy (&map->keysyms[i], &map->keysyms[i + 1],
|
||
(map->key_count - (i + 1)) * sizeof *map->keysyms);
|
||
|
||
map->key_count -= 1;
|
||
map->keycodes
|
||
= XLRealloc (map->keycodes,
|
||
map->key_count * sizeof *map->keycodes);
|
||
map->keysyms
|
||
= XLRealloc (map->keysyms,
|
||
map->key_count * sizeof *map->keysyms);
|
||
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
static KeyCode
|
||
GetKeycode (KeycodeMap *map, KeyCode keycode)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < map->key_count; ++i)
|
||
{
|
||
if (map->keycodes[i] == keycode)
|
||
return map->keysyms[i];
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
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. */
|
||
|
||
static int
|
||
CountOctets (char byte)
|
||
{
|
||
/* Given the start of a UTF-8 sequence, return how many following
|
||
bytes are in the current sequence. */
|
||
return (!(byte & 0x80) ? 1
|
||
: !(byte & 0x20) ? 2
|
||
: !(byte & 0x10) ? 3
|
||
: !(byte & 0x08) ? 4
|
||
: 5);
|
||
}
|
||
|
||
static TextPosition
|
||
TextPositionFromBytePosition (const char *string, size_t length,
|
||
ptrdiff_t byte_position)
|
||
{
|
||
const char *start;
|
||
TextPosition position;
|
||
|
||
start = string;
|
||
position.charpos = 0;
|
||
position.bytepos = byte_position;
|
||
|
||
if (!byte_position)
|
||
return position;
|
||
|
||
if (byte_position > length)
|
||
goto invalid;
|
||
|
||
while (start < string + length)
|
||
{
|
||
if (start + CountOctets (*start) > string + length)
|
||
goto invalid;
|
||
|
||
start += CountOctets (*start);
|
||
position.charpos++;
|
||
position.bytepos = start - string;
|
||
|
||
if (position.bytepos == byte_position)
|
||
return position;
|
||
else if (position.bytepos > byte_position)
|
||
goto invalid;
|
||
}
|
||
|
||
invalid:
|
||
/* Return the invalid text position. */
|
||
position.bytepos = -1;
|
||
position.charpos = -1;
|
||
return position;
|
||
}
|
||
|
||
static TextPosition
|
||
TextPositionFromCharPosition (const char *string, size_t length,
|
||
int char_position)
|
||
{
|
||
const char *start;
|
||
TextPosition position;
|
||
|
||
start = string;
|
||
position.charpos = 0;
|
||
position.bytepos = 0;
|
||
|
||
if (!char_position)
|
||
return position;
|
||
|
||
while (position.charpos < char_position)
|
||
{
|
||
if (start + CountOctets (*start) > string + length)
|
||
goto invalid;
|
||
|
||
start += CountOctets (*start);
|
||
position.charpos++;
|
||
position.bytepos = start - string;
|
||
}
|
||
|
||
/* Return the resulting text position. */
|
||
return position;
|
||
|
||
invalid:
|
||
/* Return the invalid text position. */
|
||
position.bytepos = -1;
|
||
position.charpos = -1;
|
||
return position;
|
||
}
|
||
|
||
|
||
|
||
/* Forward declaration. */
|
||
static void CreateIC (TextInput *);
|
||
|
||
static void
|
||
DestroyTextInput (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
wl_resource_destroy (resource);
|
||
}
|
||
|
||
static void
|
||
Enable (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = wl_resource_get_user_data (resource);
|
||
|
||
/* If there is already a string, free it, as the pending flag will
|
||
be clobbered below. */
|
||
if (input->pending_state.surrounding_text)
|
||
XLFree (input->pending_state.surrounding_text);
|
||
input->pending_state.surrounding_text = NULL;
|
||
|
||
/* Set the pending state. */
|
||
input->pending_state.pending = PendingEnabled;
|
||
input->pending_state.enabled = True;
|
||
}
|
||
|
||
static void
|
||
Disable (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = wl_resource_get_user_data (resource);
|
||
|
||
/* If there is already a string, free it, as the pending flag will
|
||
be clobbered below. */
|
||
if (input->pending_state.surrounding_text)
|
||
XLFree (input->pending_state.surrounding_text);
|
||
input->pending_state.surrounding_text = NULL;
|
||
|
||
/* Set the pending state. */
|
||
input->pending_state.pending = PendingEnabled;
|
||
input->pending_state.enabled = False;
|
||
}
|
||
|
||
static void
|
||
SetSurroundingText (struct wl_client *client, struct wl_resource *resource,
|
||
const char *text, int cursor, int anchor)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = wl_resource_get_user_data (resource);
|
||
|
||
/* If there is already a string, free it. */
|
||
if (input->pending_state.surrounding_text)
|
||
XLFree (input->pending_state.surrounding_text);
|
||
|
||
/* Set the surrounding text and cursor position. */
|
||
input->pending_state.surrounding_text = XLStrdup (text);
|
||
input->pending_state.cursor
|
||
= TextPositionFromBytePosition (text, strlen (text),
|
||
cursor);
|
||
input->pending_state.pending |= PendingSurroundingText;
|
||
}
|
||
|
||
static void
|
||
SetTextChangeCause (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t cause)
|
||
{
|
||
/* Not supported. */
|
||
}
|
||
|
||
static void
|
||
SetContentType (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t hint, uint32_t purpose)
|
||
{
|
||
/* Not supported. */
|
||
}
|
||
|
||
static void
|
||
SetCursorRectangle (struct wl_client *client, struct wl_resource *resource,
|
||
int32_t x, int32_t y, int32_t width, int32_t height)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = wl_resource_get_user_data (resource);
|
||
|
||
if ((input->current_state.pending & PendingCursorRectangle
|
||
/* PendingEnabled will clear the current state's cursor
|
||
rectangle. */
|
||
&& !(input->pending_state.pending & PendingEnabled))
|
||
&& x == input->current_state.cursor_x
|
||
&& y == input->current_state.cursor_y
|
||
&& width == input->current_state.cursor_width
|
||
&& height == input->current_state.cursor_height)
|
||
/* Nothing changed, return. */
|
||
return;
|
||
|
||
input->pending_state.pending |= PendingCursorRectangle;
|
||
input->pending_state.cursor_x = x;
|
||
input->pending_state.cursor_y = y;
|
||
input->pending_state.cursor_width = width;
|
||
input->pending_state.cursor_height = height;
|
||
}
|
||
|
||
static TextInput *
|
||
FindEnabledTextInput (TextInputClientInfo *info)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = info->inputs.next;
|
||
while (input != &info->inputs)
|
||
{
|
||
if (input->current_state.enabled)
|
||
return input;
|
||
|
||
input = input->next;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static void
|
||
FitRect (XRectangle *input, int view_width, int view_height,
|
||
int caret_x, int caret_y, int caret_width, int caret_height)
|
||
{
|
||
XRectangle r1, r2, copy;
|
||
|
||
copy = *input;
|
||
|
||
/* Try to fit the given dimensions into the view. First,
|
||
start with the width:
|
||
|
||
right edge of view
|
||
^ |------suggested size-------|
|
||
^ caret X, Y + HEIGHT ^ left edge of view
|
||
|
||
If the suggested size does not fit, like so:
|
||
|
||
^ |------suggested size------^--|
|
||
^ caret X, Y + HEIGHT
|
||
|
||
Move the caret to X 0 so it does, if the suggested size is wider
|
||
than half the view:
|
||
|
||
^|------suggested size---------|^
|
||
^ start X, Y + HEIGHT
|
||
|
||
Otherwise, move the rectangle leftwards until it fits.
|
||
|
||
If it still does not fit, limit the width to that of the
|
||
the view.
|
||
|
||
Next, fit the height. Start by placing the rectangle
|
||
below the caret. If that is too small, try placing the
|
||
rectangle so that its bottom touches the caret. If that
|
||
still does not fit, then use the tallest of the following
|
||
two rectangles:
|
||
|
||
CARET_Y + CARET_HEIGHT by (BOTTOM_X - CARET_X + CARET_HEIGHT + 1)
|
||
0 by CARET_Y. */
|
||
|
||
/* Do the width. Input should already be placed at the bottom right
|
||
corner of the caret. */
|
||
|
||
if (input->x + input->width >= view_width)
|
||
{
|
||
if (input->width > view_width / 2)
|
||
/* Flip x to 0. */
|
||
input->x = 0;
|
||
else
|
||
/* Move the rect left until it fits. */
|
||
input->x -= (input->x + input->width - 1
|
||
- view_width);
|
||
|
||
/* If it still doesn't fit, set x to 0 and width to
|
||
view_width. */
|
||
if (input->x + input->width >= view_width)
|
||
{
|
||
input->x = 0;
|
||
input->width = view_width;
|
||
}
|
||
}
|
||
|
||
/* Do the height. */
|
||
if (input->y + input->height >= view_height)
|
||
{
|
||
/* Flip the caret upwards, so the last scanline of the preedit
|
||
area is immediately above the first scanline of the
|
||
caret. */
|
||
input->y = caret_y - input->height;
|
||
|
||
/* If the rectangle is still too small, use the rectangle formed
|
||
between the top of the view and the top of the caret, or that
|
||
between the bottom of the view and the bottom of the caret,
|
||
whichever is larger. */
|
||
if (input->y < 0 || input->y + input->height >= view_height)
|
||
{
|
||
r1.y = 0;
|
||
r1.height = caret_y;
|
||
|
||
r2.y = caret_y + caret_height;
|
||
r2.height = view_height - r2.y;
|
||
|
||
if (r1.height > r2.height)
|
||
{
|
||
input->y = r1.y;
|
||
input->height = r1.height;
|
||
}
|
||
else
|
||
{
|
||
input->y = r2.y;
|
||
input->height = r2.height;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If the rectangle is still invalid, just fall back to the old
|
||
one. */
|
||
if (input->width <= 0 || input->height <= 0)
|
||
*input = copy;
|
||
}
|
||
|
||
static void
|
||
DoGeometryAllocation (TextInput *input)
|
||
{
|
||
XPoint spot;
|
||
XRectangle area, *needed;
|
||
XVaNestedList attr;
|
||
View *view;
|
||
char *rc;
|
||
|
||
DebugPrint ("doing geometry allocation for %p", input);
|
||
|
||
if (!input->xic)
|
||
return;
|
||
|
||
XLAssert (input->client_info->focus_surface != NULL);
|
||
view = input->client_info->focus_surface->view;
|
||
|
||
if (xim_style & XIMPreeditPosition)
|
||
{
|
||
DebugPrint ("IM wants spot values for preedit window");
|
||
|
||
if (input->current_state.pending & PendingCursorRectangle)
|
||
{
|
||
spot.x = CurrentCursorX (input);
|
||
spot.y = (CurrentCursorY (input)
|
||
+ CurrentCursorHeight (input));
|
||
}
|
||
else
|
||
{
|
||
spot.x = 0;
|
||
spot.y = 1;
|
||
}
|
||
|
||
DebugPrint ("using spot: %d, %d", spot.x, spot.y);
|
||
attr = XVaCreateNestedList (0, XNSpotLocation, &spot, NULL);
|
||
XSetICValues (input->xic, XNPreeditAttributes, attr, NULL);
|
||
XFree (attr);
|
||
}
|
||
else if (xim_style & XIMPreeditArea)
|
||
{
|
||
DebugPrint ("IM wants geometry negotiation");
|
||
|
||
/* Suggest no size to the input method. */
|
||
area.x = area.y = area.width = area.height = 0;
|
||
|
||
attr = XVaCreateNestedList (0, XNAreaNeeded, &area, NULL);
|
||
XSetICValues (input->xic, XNPreeditAttributes, attr, NULL);
|
||
XFree (attr);
|
||
|
||
/* Get the size from the input method. */
|
||
attr = XVaCreateNestedList (0, XNAreaNeeded, &needed, NULL);
|
||
rc = XGetICValues (input->xic, XNPreeditAttributes, attr, NULL);
|
||
XFree (attr);
|
||
|
||
if (!rc)
|
||
{
|
||
DebugPrint ("IM suggested the given size: %d %d",
|
||
needed->width, needed->height);
|
||
|
||
/* Place the rectangle below and to the right of the
|
||
caret. */
|
||
|
||
if (input->current_state.pending & PendingCursorRectangle)
|
||
{
|
||
needed->x = (CurrentCursorX (input)
|
||
+ CurrentCursorWidth (input));
|
||
needed->y = (CurrentCursorY (input)
|
||
+ CurrentCursorHeight (input));
|
||
|
||
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);
|
||
}
|
||
else
|
||
{
|
||
/* No caret was specified... Place the preedit window on
|
||
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);
|
||
}
|
||
|
||
/* Set the geometry. */
|
||
attr = XVaCreateNestedList (0, XNArea, needed, NULL);
|
||
XSetICValues (input->xic, XNPreeditAttributes, attr, NULL);
|
||
XFree (attr);
|
||
|
||
/* Free the rectangle returned. */
|
||
XFree (needed);
|
||
}
|
||
}
|
||
|
||
if (xim_style & XIMStatusArea)
|
||
{
|
||
DebugPrint ("IM wants geometry negotiation for status area");
|
||
|
||
/* Suggest no size to the input method. */
|
||
area.x = area.y = area.width = area.height = 0;
|
||
|
||
attr = XVaCreateNestedList (0, XNAreaNeeded, &area, NULL);
|
||
XSetICValues (input->xic, XNStatusAttributes, attr, NULL);
|
||
XFree (attr);
|
||
|
||
/* Get the size from the input method. */
|
||
attr = XVaCreateNestedList (0, XNAreaNeeded, &needed, NULL);
|
||
rc = XGetICValues (input->xic, XNStatusAttributes, attr, NULL);
|
||
XFree (attr);
|
||
|
||
if (!rc)
|
||
{
|
||
DebugPrint ("IM suggested the given size: %d %d",
|
||
needed->width, needed->height);
|
||
|
||
/* Place the rectangle at the bottom of the window. */
|
||
needed->x = ViewWidth (view) - needed->width;
|
||
needed->y = ViewHeight (view) - 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);
|
||
XSetICValues (input->xic, XNStatusAttributes, attr, NULL);
|
||
XFree (attr);
|
||
|
||
/* Free the needed rectangle. */
|
||
XFree (needed);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
Commit (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
TextInput *input, *enabled;
|
||
|
||
input = wl_resource_get_user_data (resource);
|
||
input->serial++;
|
||
|
||
if (!input->client_info)
|
||
/* The text input has no more associated seat. */
|
||
return;
|
||
|
||
if (!input->client_info->focus_surface)
|
||
/* The text input has no more associated surface. */
|
||
return;
|
||
|
||
if (input->pending_state.pending & PendingEnabled)
|
||
{
|
||
if (input->pending_state.enabled)
|
||
{
|
||
/* Check if there is another enabled text input in the same
|
||
client info structure. */
|
||
enabled = FindEnabledTextInput (input->client_info);
|
||
|
||
if (enabled && enabled != input)
|
||
/* Return, as the spec says we should ignore requests to
|
||
enable a text input. */
|
||
return;
|
||
}
|
||
|
||
/* Free any surrounding text in the current state. */
|
||
if (input->current_state.surrounding_text)
|
||
XLFree (input->current_state.surrounding_text);
|
||
|
||
/* Copy the pending state wholesale. */
|
||
input->current_state = input->pending_state;
|
||
|
||
/* Clear the surrounding text. */
|
||
input->pending_state.surrounding_text = NULL;
|
||
input->pending_state.pending = 0;
|
||
|
||
if (input->current_state.surrounding_text)
|
||
DebugPrint ("surrounding text early change: %s[%d]",
|
||
input->current_state.surrounding_text,
|
||
input->current_state.cursor.charpos);
|
||
|
||
if (input->current_state.enabled)
|
||
{
|
||
DebugPrint ("text input %p enabled, state: %2b", input,
|
||
(unsigned int) input->current_state.pending);
|
||
|
||
/* Maybe create or reset and then focus the IC. */
|
||
if (!input->xic)
|
||
CreateIC (input);
|
||
else
|
||
XFree (XmbResetIC (input->xic));
|
||
|
||
/* Perform geometry/position allocation on the IC. */
|
||
DoGeometryAllocation (input);
|
||
|
||
if (input->xic)
|
||
XSetICFocus (input->xic);
|
||
}
|
||
else
|
||
{
|
||
DebugPrint ("text input %p disabled", input);
|
||
|
||
if (input->xic)
|
||
XUnsetICFocus (input->xic);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Apply the pending state piecemeal. */
|
||
if (input->pending_state.pending & PendingCursorRectangle)
|
||
{
|
||
DebugPrint ("cursor rectangle changed to: %d %d %d %d",
|
||
input->pending_state.cursor_x,
|
||
input->pending_state.cursor_y,
|
||
input->pending_state.cursor_width,
|
||
input->pending_state.cursor_height);
|
||
|
||
input->current_state.cursor_x
|
||
= input->pending_state.cursor_x;
|
||
input->current_state.cursor_y
|
||
= input->pending_state.cursor_y;
|
||
input->current_state.cursor_width
|
||
= input->pending_state.cursor_width;
|
||
input->current_state.cursor_height
|
||
= input->pending_state.cursor_height;
|
||
|
||
input->current_state.pending |= PendingCursorRectangle;
|
||
|
||
if (input->current_state.enabled && input->xic)
|
||
/* Perform geometry/position allocation on the IC. */
|
||
DoGeometryAllocation (input);
|
||
}
|
||
|
||
if (input->pending_state.pending & PendingSurroundingText)
|
||
{
|
||
DebugPrint ("surrounding text changed to: %s[%d]",
|
||
input->pending_state.surrounding_text,
|
||
input->pending_state.cursor.charpos);
|
||
|
||
if (input->current_state.surrounding_text)
|
||
XLFree (input->current_state.surrounding_text);
|
||
|
||
/* Surrounding text changed. Move the surrounding text and
|
||
cursor position over. */
|
||
input->current_state.surrounding_text
|
||
= input->pending_state.surrounding_text;
|
||
input->current_state.cursor = input->pending_state.cursor;
|
||
|
||
/* Clear the surrounding text on the pending state. */
|
||
input->pending_state.surrounding_text = NULL;
|
||
|
||
/* And add the flag to the current state. */
|
||
input->current_state.pending |= PendingSurroundingText;
|
||
}
|
||
|
||
/* Clear the pending state mask. */
|
||
input->pending_state.pending = 0;
|
||
}
|
||
}
|
||
|
||
static const struct zwp_text_input_v3_interface input_impl =
|
||
{
|
||
.destroy = DestroyTextInput,
|
||
.enable = Enable,
|
||
.disable = Disable,
|
||
.set_surrounding_text = SetSurroundingText,
|
||
.set_text_change_cause = SetTextChangeCause,
|
||
.set_content_type = SetContentType,
|
||
.set_cursor_rectangle = SetCursorRectangle,
|
||
.commit = Commit,
|
||
};
|
||
|
||
/* Forward declarations. */
|
||
static void FreePreeditBuffer (PreeditBuffer *);
|
||
static void UpdatePreedit (TextInput *);
|
||
|
||
static void
|
||
HandleICDestroyed (TextInput *input)
|
||
{
|
||
/* Destroy the preedit buffer and update the preedit state. */
|
||
if (input->buffer)
|
||
{
|
||
FreePreeditBuffer (input->buffer);
|
||
input->buffer = NULL;
|
||
|
||
/* Send changes to the client. */
|
||
UpdatePreedit (input);
|
||
}
|
||
}
|
||
|
||
static void
|
||
InputDoLeave (TextInput *input, Surface *old_surface)
|
||
{
|
||
/* Destroy any XIC that was created. */
|
||
if (input->xic)
|
||
{
|
||
XDestroyIC (input->xic);
|
||
input->xic = NULL;
|
||
HandleICDestroyed (input);
|
||
}
|
||
|
||
/* Clear the input state. */
|
||
|
||
if (input->current_state.surrounding_text)
|
||
XLFree (input->current_state.surrounding_text);
|
||
|
||
/* Clear the keycode-keycode table. Correlating key release with
|
||
key press events is no longer important, as a leave event has
|
||
been sent to the seat. */
|
||
ClearKeycodeMap (&input->keysym_map);
|
||
|
||
memset (&input->current_state, 0, sizeof input->current_state);
|
||
}
|
||
|
||
static void
|
||
InputDoEnter (TextInput *input, Surface *new_surface)
|
||
{
|
||
/* If there is still a preedit buffer, destroy it. */
|
||
if (input->buffer)
|
||
{
|
||
FreePreeditBuffer (input->buffer);
|
||
|
||
/* Set it to NULL. */
|
||
input->buffer = NULL;
|
||
UpdatePreedit (input);
|
||
}
|
||
}
|
||
|
||
static void
|
||
HandleResourceDestroy (struct wl_resource *resource)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = wl_resource_get_user_data (resource);
|
||
|
||
/* Now, if the client info is attached, unlink the text input. */
|
||
if (input->client_info)
|
||
{
|
||
input->last->next = input->next;
|
||
input->next->last = input->last;
|
||
|
||
/* If the client info is now empty, destroy the client info as
|
||
well. */
|
||
if (input->client_info->inputs.next
|
||
== &input->client_info->inputs)
|
||
{
|
||
XLSeatCancelDestroyListener (input->client_info->seat_key);
|
||
input->client_info->last->next = input->client_info->next;
|
||
input->client_info->next->last = input->client_info->last;
|
||
|
||
XLFree (input->client_info);
|
||
}
|
||
}
|
||
|
||
/* If an XIC still exists, destroy it. */
|
||
if (input->xic)
|
||
XDestroyIC (input->xic);
|
||
|
||
/* If there is a surrounding text string, free it. */
|
||
if (input->pending_state.surrounding_text)
|
||
XLFree (input->pending_state.surrounding_text);
|
||
if (input->current_state.surrounding_text)
|
||
XLFree (input->current_state.surrounding_text);
|
||
|
||
/* If there is still a preedit buffer, destroy it. */
|
||
if (input->buffer)
|
||
FreePreeditBuffer (input->buffer);
|
||
|
||
/* Destroy the map of pressed keycodes to keycodes. */
|
||
ClearKeycodeMap (&input->keysym_map);
|
||
|
||
/* Free the text input itself. */
|
||
XLFree (input);
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
HandleSeatDestroyed (void *data)
|
||
{
|
||
TextInputClientInfo *info;
|
||
TextInput *input;
|
||
|
||
/* The seat associated with the given TextInputClientInfo was
|
||
destroyed. Detach every TextInput object. */
|
||
info = data;
|
||
input = info->inputs.next;
|
||
|
||
while (input != &info->inputs)
|
||
{
|
||
input->client_info = NULL;
|
||
|
||
/* client_info is now NULL, meaning this text input is inert.
|
||
So destroy the XIC, as it's not being destroyed later. */
|
||
if (input->xic)
|
||
{
|
||
XDestroyIC (input->xic);
|
||
input->xic = NULL;
|
||
HandleICDestroyed (input);
|
||
}
|
||
|
||
input = input->next;
|
||
}
|
||
|
||
/* Next, unlink and free the client info. */
|
||
info->last->next = info->next;
|
||
info->next->last = info->last;
|
||
XLFree (info);
|
||
}
|
||
|
||
static void
|
||
NoticeEnter (TextInputClientInfo *info, Surface *surface)
|
||
{
|
||
TextInput *input;
|
||
|
||
DebugPrint ("client info: %p, surface: %p",
|
||
info, surface);
|
||
|
||
if (info->focus_surface == surface)
|
||
/* The focus surface did not change. */
|
||
return;
|
||
|
||
input = info->inputs.next;
|
||
while (input != &info->inputs)
|
||
{
|
||
/* If a previous surface exists, also send a leave event. */
|
||
if (info->focus_surface)
|
||
{
|
||
DebugPrint ("sending leave to text input %p", input);
|
||
|
||
XLAssert (info->focus_surface->resource != NULL);
|
||
zwp_text_input_v3_send_leave (input->resource,
|
||
info->focus_surface->resource);
|
||
|
||
InputDoLeave (input, info->focus_surface);
|
||
}
|
||
|
||
DebugPrint ("sending enter to text input %p", input);
|
||
|
||
/* Send the enter event to each text input. */
|
||
zwp_text_input_v3_send_enter (input->resource,
|
||
surface->resource);
|
||
InputDoEnter (input, surface);
|
||
|
||
input = input->next;
|
||
}
|
||
|
||
/* Record the focus surface. Note that this surface should always
|
||
be removed by ClearFocusSurface in the seat upon destruction, so
|
||
there is no need for a callback to be registered here as well!
|
||
|
||
If that invariant is broken, strange bugs will follow. */
|
||
info->focus_surface = surface;
|
||
}
|
||
|
||
static void
|
||
NoticeLeave (TextInputClientInfo *info)
|
||
{
|
||
TextInput *input;
|
||
|
||
/* If there is already no focus surface, return. */
|
||
if (!info->focus_surface)
|
||
return;
|
||
|
||
DebugPrint ("client info: %p", info);
|
||
|
||
input = info->inputs.next;
|
||
while (input != &info->inputs)
|
||
{
|
||
DebugPrint ("sending leave to text input %p", input);
|
||
|
||
/* Otherwise, if info->focus_surface->resource is still
|
||
there, send the leave event to each text input. */
|
||
if (info->focus_surface->resource)
|
||
/* Send the enter event to each text input. */
|
||
zwp_text_input_v3_send_leave (input->resource,
|
||
info->focus_surface->resource);
|
||
InputDoLeave (input, info->focus_surface);
|
||
|
||
input = input->next;
|
||
}
|
||
|
||
/* And clear the focus surface. */
|
||
info->focus_surface = NULL;
|
||
}
|
||
|
||
static TextInputClientInfo *
|
||
GetClientInfo (struct wl_client *client, Seat *seat, Bool create)
|
||
{
|
||
TextInputClientInfo *info;
|
||
|
||
/* First, look through the list of client infos. */
|
||
info = all_client_infos.next;
|
||
|
||
while (info != &all_client_infos)
|
||
{
|
||
if (info->seat == seat && info->client == client)
|
||
return info;
|
||
|
||
info = info->next;
|
||
}
|
||
|
||
if (!create)
|
||
return NULL;
|
||
|
||
/* If none was found, create one and link it onto the list. */
|
||
info = XLCalloc (1, sizeof *info);
|
||
info->seat = seat;
|
||
info->client = client;
|
||
info->next = all_client_infos.next;
|
||
info->last = &all_client_infos;
|
||
all_client_infos.next->last = info;
|
||
all_client_infos.next = info;
|
||
|
||
/* Then, attach the seat destruction listener and initialize the
|
||
list of text input objects. */
|
||
info->seat_key
|
||
= XLSeatRunOnDestroy (seat, HandleSeatDestroyed, info);
|
||
info->inputs.next = &info->inputs;
|
||
|
||
/* And return info. */
|
||
return info;
|
||
}
|
||
|
||
static void
|
||
Destroy (struct wl_client *client, struct wl_resource *resource)
|
||
{
|
||
wl_resource_destroy (resource);
|
||
}
|
||
|
||
/* Forward declaration. */
|
||
static void FocusInCallback (Seat *, Surface *);
|
||
|
||
static void
|
||
GetTextInput (struct wl_client *client, struct wl_resource *resource,
|
||
uint32_t id, struct wl_resource *seat_resource)
|
||
{
|
||
Seat *seat;
|
||
struct wl_resource *dummy;
|
||
TextInput *input;
|
||
TextInputClientInfo *info;
|
||
|
||
seat = wl_resource_get_user_data (seat_resource);
|
||
|
||
/* If the seat is inert, we cannot rely on destroy callbacks being
|
||
run. In that case, we make a dummy text input resource with no
|
||
data attached. */
|
||
if (XLSeatIsInert (seat))
|
||
{
|
||
dummy = wl_resource_create (client, &zwp_text_input_v3_interface,
|
||
wl_resource_get_version (resource), id);
|
||
|
||
if (!dummy)
|
||
wl_resource_post_no_memory (resource);
|
||
else
|
||
wl_resource_set_implementation (dummy, &input_impl, NULL, NULL);
|
||
|
||
return;
|
||
}
|
||
|
||
/* Create the text input. */
|
||
input = XLSafeMalloc (sizeof *input);
|
||
|
||
if (!input)
|
||
{
|
||
wl_resource_post_no_memory (resource);
|
||
return;
|
||
}
|
||
|
||
memset (input, 0, sizeof *input);
|
||
input->resource
|
||
= wl_resource_create (client, &zwp_text_input_v3_interface,
|
||
wl_resource_get_version (resource), id);
|
||
|
||
if (!input->resource)
|
||
{
|
||
XLFree (input);
|
||
wl_resource_post_no_memory (resource);
|
||
return;
|
||
}
|
||
|
||
/* Obtain the client info. */
|
||
info = GetClientInfo (client, seat, True);
|
||
|
||
/* Set the implementation. N.B. that HandleResourceDestroy will
|
||
free the client info structure once all references are gone. */
|
||
wl_resource_set_implementation (input->resource, &input_impl,
|
||
input, HandleResourceDestroy);
|
||
|
||
/* Initialize and link the text input. */
|
||
input->client_info = info;
|
||
input->next = info->inputs.next;
|
||
input->last = &info->inputs;
|
||
info->inputs.next->last = input;
|
||
info->inputs.next = input;
|
||
|
||
/* If there is already a focused surface on the seat belonging to
|
||
the client, focus it now. */
|
||
if (info->focus_surface)
|
||
{
|
||
DebugPrint ("focusing newly created text input %p", input);
|
||
|
||
/* The info already existed and already has a focus surface
|
||
set. */
|
||
zwp_text_input_v3_send_enter (input->resource,
|
||
info->focus_surface->resource);
|
||
InputDoEnter (input, info->focus_surface);
|
||
}
|
||
else if (XLSeatIsClientFocused (seat, client))
|
||
{
|
||
DebugPrint ("focusing newly created text input with info %p", input);
|
||
|
||
/* The info did not previously exist, but the client created a
|
||
surface that is the seat's input focus. */
|
||
FocusInCallback (seat, XLSeatGetFocus (seat));
|
||
}
|
||
}
|
||
|
||
static const struct zwp_text_input_manager_v3_interface manager_impl =
|
||
{
|
||
.destroy = Destroy,
|
||
.get_text_input = GetTextInput,
|
||
};
|
||
|
||
static void
|
||
HandleBind (struct wl_client *client, void *data, uint32_t version,
|
||
uint32_t id)
|
||
{
|
||
struct wl_resource *resource;
|
||
|
||
resource = wl_resource_create (client,
|
||
&zwp_text_input_manager_v3_interface,
|
||
version, id);
|
||
|
||
if (!resource)
|
||
{
|
||
wl_client_post_no_memory (client);
|
||
return;
|
||
}
|
||
|
||
wl_resource_set_implementation (resource, &manager_impl,
|
||
NULL, NULL);
|
||
}
|
||
|
||
|
||
|
||
static PreeditBuffer *
|
||
MakePreeditBuffer (const char *locale)
|
||
{
|
||
PreeditBuffer *buffer;
|
||
|
||
buffer = XLCalloc (1, sizeof *buffer);
|
||
buffer->locale = XLStrdup (locale);
|
||
|
||
return buffer;
|
||
}
|
||
|
||
static void
|
||
FreePreeditBuffer (PreeditBuffer *buffer)
|
||
{
|
||
XLFree (buffer->buffer);
|
||
XLFree (buffer->locale);
|
||
XLFree (buffer);
|
||
}
|
||
|
||
static Bool
|
||
PreeditDeleteChars (PreeditBuffer *buffer, int start_char,
|
||
int length)
|
||
{
|
||
wchar_t wc;
|
||
char *oldlocale, *start, *end;
|
||
int rc, chars, old_chars;
|
||
|
||
/* Note that preedit buffers operate on text encoded with the IM
|
||
locale's charset. Record the old locale. */
|
||
oldlocale = XLStrdup (setlocale (LC_CTYPE, NULL));
|
||
|
||
/* Switch to the new locale. */
|
||
if (!setlocale (LC_CTYPE, buffer->locale))
|
||
{
|
||
XLFree (oldlocale);
|
||
return False;
|
||
}
|
||
|
||
start = buffer->buffer;
|
||
chars = 0;
|
||
|
||
/* Increase start until we reach start_char. */
|
||
while (chars < start_char)
|
||
{
|
||
if (start >= buffer->buffer + buffer->size)
|
||
{
|
||
DebugPrint ("start %p out of bounds %p",
|
||
start, buffer->buffer + buffer->size);
|
||
|
||
/* start is out of bounds. */
|
||
goto failure;
|
||
}
|
||
|
||
/* After this, rc should either be -1 (meaning failure) or the
|
||
number of bytes read. */
|
||
rc = mbtowc (&wc, start, buffer->buffer + buffer->size - start);
|
||
chars++;
|
||
|
||
DebugPrint ("mbtowc gave (calculating start) %d", rc);
|
||
|
||
/* If rc is not -1, move start forward by that much. */
|
||
if (rc != -1)
|
||
start += rc;
|
||
else
|
||
goto failure;
|
||
}
|
||
|
||
DebugPrint ("chars: %d, start, %p", chars, start);
|
||
|
||
/* Now, start is the first byte of the area we want to delete.
|
||
Count forward by length. */
|
||
end = start;
|
||
old_chars = chars;
|
||
|
||
while (chars < old_chars + length)
|
||
{
|
||
if (end >= buffer->buffer + buffer->size)
|
||
{
|
||
DebugPrint ("end %p out of bounds %p",
|
||
end, buffer->buffer + buffer->size);
|
||
|
||
/* end is out of bounds. */
|
||
goto failure;
|
||
}
|
||
|
||
/* After this, rc should either be -1 (meaning failure) or the
|
||
number of bytes read. */
|
||
rc = mbtowc (&wc, end, buffer->buffer + buffer->size - end);
|
||
chars++;
|
||
|
||
DebugPrint ("mbtowc gave (calculating end) %d", rc);
|
||
|
||
/* If rc is not -1, move start forward by that much. */
|
||
if (rc != -1)
|
||
end += rc;
|
||
else
|
||
goto failure;
|
||
}
|
||
|
||
DebugPrint ("chars: %d, end, %p", chars, end);
|
||
|
||
/* Now, delete the area between start and end, by moving the bytes
|
||
between end and the end of the buffer to start. */
|
||
memmove (start, end, buffer->buffer + buffer->size - end);
|
||
|
||
/* Resize the buffer. */
|
||
buffer->size -= end - start;
|
||
buffer->total_characters -= length;
|
||
XLAssert (buffer->size >= 0);
|
||
|
||
buffer->buffer = XLRealloc (buffer->buffer,
|
||
buffer->size);
|
||
|
||
/* Restore the locale and return success. */
|
||
setlocale (LC_CTYPE, oldlocale);
|
||
XLFree (oldlocale);
|
||
|
||
/* Reset the shift state. */
|
||
mbtowc (NULL, NULL, 0);
|
||
return True;
|
||
|
||
failure:
|
||
setlocale (LC_CTYPE, oldlocale);
|
||
XLFree (oldlocale);
|
||
|
||
/* Reset the shift state. */
|
||
mbtowc (NULL, NULL, 0);
|
||
return False;
|
||
}
|
||
|
||
static Bool
|
||
PreeditInsertChars (PreeditBuffer *buffer, int start_char,
|
||
const char *string, size_t length,
|
||
int char_length)
|
||
{
|
||
wchar_t wc;
|
||
char *oldlocale, *start;
|
||
int rc, chars;
|
||
|
||
/* Note that preedit buffers operate on text encoded with the IM
|
||
locale's charset. Record the old locale. */
|
||
oldlocale = XLStrdup (setlocale (LC_CTYPE, NULL));
|
||
|
||
/* Switch to the new locale. */
|
||
if (!setlocale (LC_CTYPE, buffer->locale))
|
||
{
|
||
XLFree (oldlocale);
|
||
return False;
|
||
}
|
||
|
||
/* Resize the buffer accordingly. */
|
||
buffer->buffer = XLRealloc (buffer->buffer,
|
||
buffer->size + length);
|
||
|
||
start = buffer->buffer;
|
||
chars = 0;
|
||
|
||
/* Increase start until we reach start_char. */
|
||
while (chars < start_char)
|
||
{
|
||
if (start >= buffer->buffer + buffer->size)
|
||
/* start is out of bounds. */
|
||
goto failure;
|
||
|
||
/* After this, rc should either be -1 (meaning failure) or the
|
||
number of bytes read. */
|
||
rc = mbtowc (&wc, start, buffer->buffer + buffer->size - start);
|
||
chars++;
|
||
|
||
/* If rc is not -1, move start forward by that much. */
|
||
if (rc != -1)
|
||
start += rc;
|
||
else
|
||
goto failure;
|
||
}
|
||
|
||
/* Move everything past start length away. */
|
||
memmove (start + length, start,
|
||
buffer->buffer + buffer->size - start);
|
||
buffer->size += length;
|
||
buffer->total_characters += char_length;
|
||
|
||
/* Copy the text onto start. */
|
||
memcpy (start, string, length);
|
||
|
||
setlocale (LC_CTYPE, oldlocale);
|
||
XLFree (oldlocale);
|
||
|
||
/* Reset the shift state. */
|
||
mbtowc (NULL, NULL, 0);
|
||
return True;
|
||
|
||
failure:
|
||
setlocale (LC_CTYPE, oldlocale);
|
||
XLFree (oldlocale);
|
||
|
||
/* Reset the shift state. */
|
||
mbtowc (NULL, NULL, 0);
|
||
return False;
|
||
}
|
||
|
||
/* Forward declarations. */
|
||
static char *ConvertString (char *, size_t, size_t *);
|
||
static void PreeditString (TextInput *, const char *, size_t, ptrdiff_t);
|
||
|
||
static void
|
||
UpdatePreedit (TextInput *input)
|
||
{
|
||
char *buffer;
|
||
size_t new_text_size;
|
||
TextPosition caret;
|
||
|
||
if (input->buffer)
|
||
{
|
||
/* Convert the preedit text. */
|
||
buffer = ConvertString (input->buffer->buffer,
|
||
input->buffer->size,
|
||
&new_text_size);
|
||
DebugPrint ("updated buffer %p", buffer);
|
||
|
||
if (!buffer)
|
||
goto no_buffer;
|
||
|
||
/* Obtain the caret position. */
|
||
|
||
if (input->caret_style != XIMIsInvisible)
|
||
caret = TextPositionFromCharPosition (buffer, new_text_size,
|
||
input->caret);
|
||
else
|
||
/* The caret is hidden, so don't send any caret position. */
|
||
caret.bytepos = -1, caret.charpos = -1;
|
||
|
||
DebugPrint ("caret position is: char %d, byte: %td",
|
||
caret.charpos, caret.bytepos);
|
||
|
||
PreeditString (input, buffer, new_text_size,
|
||
/* caret.bytepos will be -1 if obtaining the
|
||
position failed or the caret is hidden. */
|
||
caret.bytepos);
|
||
XLFree (buffer);
|
||
}
|
||
else
|
||
{
|
||
no_buffer:
|
||
DebugPrint ("no buffer");
|
||
|
||
/* Clear the preedit string. */
|
||
zwp_text_input_v3_send_preedit_string (input->resource, NULL,
|
||
-1, -1);
|
||
zwp_text_input_v3_send_done (input->resource, input->serial);
|
||
}
|
||
}
|
||
|
||
static int
|
||
PreeditStartCallback (XIC ic, XPointer client_data, XPointer call_data)
|
||
{
|
||
TextInput *input;
|
||
const char *locale;
|
||
|
||
XLAssert (current_xim != NULL);
|
||
|
||
input = (TextInput *) client_data;
|
||
locale = XLocaleOfIM (current_xim);
|
||
|
||
DebugPrint ("text input: %p; locale: %s", input, locale);
|
||
|
||
if (input->buffer)
|
||
FreePreeditBuffer (input->buffer);
|
||
|
||
/* Create the preedit buffer. */
|
||
input->buffer = MakePreeditBuffer (locale);
|
||
|
||
/* Set the default caret style. */
|
||
input->caret_style = XIMIsPrimary;
|
||
|
||
/* There should be no limit on the number of bytes in a preedit
|
||
string. We make the string fit in 4000 bytes ourselves. */
|
||
return -1;
|
||
}
|
||
|
||
static void
|
||
PreeditDoneCallback (XIC ic, XPointer client_data, XPointer call_data)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = (TextInput *) client_data;
|
||
DebugPrint ("text input: %p", input);
|
||
|
||
if (input->buffer)
|
||
FreePreeditBuffer (input->buffer);
|
||
input->buffer = NULL;
|
||
|
||
/* Send change to the client. */
|
||
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);
|
||
|
||
if (!input->buffer)
|
||
return;
|
||
|
||
DebugPrint ("chg_first: %d, chg_length: %d",
|
||
call_data->chg_first,
|
||
call_data->chg_length);
|
||
|
||
/* Delete text between chg_first and chg_first + chg_length. */
|
||
if (call_data->chg_length
|
||
&& !PreeditDeleteChars (input->buffer, call_data->chg_first,
|
||
call_data->chg_length))
|
||
{
|
||
DebugPrint ("text deletion failed");
|
||
return;
|
||
}
|
||
|
||
if (call_data->text)
|
||
{
|
||
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,
|
||
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. */
|
||
input->caret = call_data->caret;
|
||
|
||
DebugPrint ("buffer text is now: %.*s, with the caret at %d",
|
||
(int) input->buffer->size, input->buffer->buffer,
|
||
input->caret);
|
||
|
||
/* Send change to the client. */
|
||
UpdatePreedit (input);
|
||
}
|
||
|
||
static void
|
||
PreeditCaretCallback (XIC ic, XPointer client_data,
|
||
XIMPreeditCaretCallbackStruct *call_data)
|
||
{
|
||
TextInput *input;
|
||
|
||
input = (TextInput *) client_data;
|
||
|
||
if (!input->buffer)
|
||
return;
|
||
|
||
DebugPrint ("text input: %p; direction: %u", input,
|
||
call_data->direction);
|
||
|
||
switch (call_data->direction)
|
||
{
|
||
case XIMAbsolutePosition:
|
||
input->caret = call_data->position;
|
||
break;
|
||
|
||
case XIMForwardChar:
|
||
input->caret = MIN (input->caret + 1,
|
||
input->buffer->total_characters);
|
||
break;
|
||
|
||
case XIMBackwardChar:
|
||
input->caret = MAX (input->caret - 1, 0);
|
||
break;
|
||
|
||
/* The rest cannot be implemented under Wayland as the text
|
||
input protocol is too limited. */
|
||
default:
|
||
DebugPrint ("unsupported movement direction");
|
||
}
|
||
|
||
/* Return the caret position. */
|
||
call_data->position = input->caret;
|
||
|
||
/* Set the caret style. */
|
||
input->caret_style = call_data->style;
|
||
|
||
/* Send change to the client. */
|
||
UpdatePreedit (input);
|
||
}
|
||
|
||
static TextPosition
|
||
ScanForwardWord (const char *string, size_t string_size,
|
||
TextPosition caret, int factor)
|
||
{
|
||
const char *start;
|
||
Bool punct_found;
|
||
TextPosition caret_before;
|
||
|
||
start = string + caret.bytepos;
|
||
|
||
/* Skip initial whitespace. */
|
||
while (start < string + string_size
|
||
/* Make sure the character has 0 trailing bytes. */
|
||
&& *start < 127 && *start >= 0
|
||
&& (isspace (*start) || ispunct (*start)))
|
||
{
|
||
start++;
|
||
caret.charpos++;
|
||
caret.bytepos++;
|
||
}
|
||
|
||
while (start < string + string_size)
|
||
{
|
||
punct_found = False;
|
||
caret_before = caret;
|
||
|
||
start += CountOctets (*start);
|
||
|
||
if (start >= string + string_size)
|
||
{
|
||
/* The string is too big. */
|
||
caret.bytepos = -1;
|
||
caret.charpos = -1;
|
||
|
||
return caret;
|
||
}
|
||
else
|
||
caret.bytepos = start - string;
|
||
|
||
caret.charpos++;
|
||
|
||
/* Eat all punctuation. */
|
||
while (isspace (*start) || ispunct (*start))
|
||
{
|
||
punct_found = True;
|
||
|
||
if (++start >= string + string_size)
|
||
/* We are now at the end of the string, so just return the
|
||
position of caret_before, which should be before this
|
||
extraneous punctuation. */
|
||
return caret_before;
|
||
|
||
/* Move the caret forward. */
|
||
caret.charpos++;
|
||
caret.bytepos++;
|
||
}
|
||
|
||
if (punct_found && !(--factor))
|
||
{
|
||
/* Punctuation was seen and factor is now 0. Return the
|
||
caret before the punctuation. */
|
||
DebugPrint ("returning caret_before: char: %d byte: %td",
|
||
caret_before.charpos, caret_before.bytepos);
|
||
return caret_before;
|
||
}
|
||
|
||
/* Simply return the current position at the end of the
|
||
string. */
|
||
if (start == string + string_size - 1)
|
||
{
|
||
DebugPrint ("returning caret_before at end of string: char: %d byte: %td",
|
||
caret_before.charpos, caret_before.bytepos);
|
||
|
||
return caret_before;
|
||
}
|
||
}
|
||
|
||
return caret;
|
||
}
|
||
|
||
static Bool
|
||
IsLeading (char c)
|
||
{
|
||
return (((unsigned char) c) & 0b11000000
|
||
|| !(c >> 7));
|
||
}
|
||
|
||
static TextPosition
|
||
ScanBackwardWord (const char *string, size_t string_size,
|
||
TextPosition caret, int factor)
|
||
{
|
||
TextPosition original, caret_before;
|
||
const char *start;
|
||
Bool punct_found;
|
||
|
||
/* Record the original caret position. */
|
||
original = caret;
|
||
|
||
if (!string_size)
|
||
{
|
||
/* The string is empty, so simply return the start of the
|
||
string. */
|
||
caret.charpos = 0;
|
||
caret.bytepos = 0;
|
||
return caret;
|
||
}
|
||
|
||
/* First, skip all whitespace. */
|
||
start = string + caret.bytepos;
|
||
while (start >= string
|
||
/* Make sure the character has 0 trailing bytes. */
|
||
&& *start < 127 && *start >= 0
|
||
&& (isspace (*start) || ispunct (*start)))
|
||
{
|
||
start--;
|
||
caret.charpos--;
|
||
caret.bytepos--;
|
||
|
||
if (caret.charpos <= 0 || caret.bytepos <= 0)
|
||
return original;
|
||
}
|
||
|
||
/* Next, look backwards. Every time whitespace is encountered,
|
||
gobble it up, and decrease factor. Once factor is 0, return the
|
||
caret position before the first whitespace character. Otherwise,
|
||
repeat. */
|
||
|
||
while (start >= string)
|
||
{
|
||
caret_before = caret;
|
||
|
||
do
|
||
{
|
||
if (--start < string)
|
||
{
|
||
/* Invalid UTF-8 data was found in STRING. Just return
|
||
the start of the string in this case. */
|
||
caret.charpos = 0;
|
||
caret.bytepos = 0;
|
||
return caret;
|
||
}
|
||
|
||
caret.bytepos--;
|
||
}
|
||
while (!IsLeading (*start));
|
||
|
||
caret.charpos--;
|
||
|
||
DebugPrint ("caret_before: char: %d byte: %td, new char: %c",
|
||
caret_before.charpos, caret_before.bytepos,
|
||
*start);
|
||
|
||
/* We are now at the start of the last character. If it is
|
||
whitespace, eat the whitespace and decrease factor. */
|
||
|
||
punct_found = False;
|
||
|
||
while (isspace (*start) || ispunct (*start))
|
||
{
|
||
do
|
||
{
|
||
if (--start < string)
|
||
{
|
||
/* Invalid UTF-8 data was found in STRING. Just return
|
||
the start of the string in this case. */
|
||
caret.charpos = 0;
|
||
caret.bytepos = 0;
|
||
return caret;
|
||
}
|
||
|
||
caret.bytepos--;
|
||
}
|
||
while (!IsLeading (*start));
|
||
caret.charpos--;
|
||
|
||
punct_found = True;
|
||
}
|
||
|
||
if (punct_found && !(--factor))
|
||
{
|
||
/* Punctuation was seen and the factor is now 0. Return the
|
||
caret before the punctuation. */
|
||
DebugPrint ("returning caret_before: char: %d byte: %td",
|
||
caret_before.charpos, caret_before.bytepos);
|
||
return caret_before;
|
||
}
|
||
}
|
||
|
||
return caret;
|
||
}
|
||
|
||
static void
|
||
FindTextSections (const char *string, size_t string_size,
|
||
TextPosition caret, XIMCaretDirection direction,
|
||
int factor, TextPosition *start_return,
|
||
TextPosition *end_return)
|
||
{
|
||
TextPosition end;
|
||
const char *found;
|
||
|
||
switch (direction)
|
||
{
|
||
case XIMForwardChar:
|
||
/* Move forward by factor. */
|
||
end = TextPositionFromCharPosition (string, string_size,
|
||
caret.charpos + factor);
|
||
break;
|
||
|
||
case XIMBackwardChar:
|
||
/* Move backwards by factor. */
|
||
end = TextPositionFromCharPosition (string, string_size,
|
||
MAX (0, caret.charpos - factor));
|
||
break;
|
||
|
||
case XIMForwardWord:
|
||
/* Move forwards by factor words. */
|
||
end = ScanForwardWord (string, string_size, caret, factor);
|
||
break;
|
||
|
||
case XIMBackwardWord:
|
||
/* Move backwards by factor words. */
|
||
end = ScanBackwardWord (string, string_size, caret, factor);
|
||
break;
|
||
|
||
case XIMLineStart:
|
||
/* Scan backwards for factor newline characters. */
|
||
|
||
found = string + caret.bytepos;
|
||
DebugPrint ("start: found %p, found-string %td",
|
||
found, found - string);
|
||
|
||
while (factor)
|
||
{
|
||
found = memrchr (string, '\n', found - string);
|
||
DebugPrint ("LineStart processing found %p %td", found,
|
||
found ? found - string : 0);
|
||
|
||
if (!found)
|
||
{
|
||
/* Use the beginning of the string. */
|
||
found = string - 1;
|
||
|
||
/* Exit the loop too. */
|
||
goto end_line_start;
|
||
}
|
||
|
||
factor--;
|
||
}
|
||
|
||
end_line_start:
|
||
DebugPrint ("found %p string %p found+1-string %td",
|
||
found, string, found + 1 - string);
|
||
end = TextPositionFromBytePosition (string, string_size,
|
||
found + 1 - string);
|
||
break;
|
||
|
||
case XIMLineEnd:
|
||
/* Scan forwards for factor newline characters. */
|
||
found = string + caret.bytepos;
|
||
|
||
while (factor)
|
||
{
|
||
found = memchr (found + 1, '\n',
|
||
(string + string_size - 1) - found + 1);
|
||
|
||
if (!found)
|
||
{
|
||
/* Use the end of the string. */
|
||
found = string + string_size - 1;
|
||
goto end_line_end;
|
||
}
|
||
|
||
factor--;
|
||
}
|
||
|
||
end_line_end:
|
||
end = TextPositionFromBytePosition (string, string_size,
|
||
found - 1 - string);
|
||
break;
|
||
|
||
default:
|
||
DebugPrint ("unsuported string conversion direction: %u",
|
||
direction);
|
||
end.bytepos = 0;
|
||
end.charpos = 0;
|
||
}
|
||
|
||
DebugPrint ("end: char: %d byte: %td", end.charpos,
|
||
end.bytepos);
|
||
|
||
if (caret.charpos > end.charpos)
|
||
*start_return = end, *end_return = caret;
|
||
else
|
||
*start_return = caret, *end_return = end;
|
||
}
|
||
|
||
static Bool
|
||
MoveCaret (TextPosition *caret, const char *buffer, size_t buffer_size,
|
||
int by)
|
||
{
|
||
const char *end, *start;
|
||
int octets;
|
||
|
||
XLAssert (caret->bytepos <= buffer_size);
|
||
|
||
if (by > 0)
|
||
{
|
||
end = buffer + buffer_size;
|
||
buffer += caret->bytepos;
|
||
|
||
while (by && buffer < end)
|
||
{
|
||
octets = CountOctets (*buffer);
|
||
|
||
/* Move the buffer and text position forwards. */
|
||
buffer += octets;
|
||
caret->bytepos += octets;
|
||
caret->charpos++;
|
||
by--;
|
||
}
|
||
|
||
/* If caret->bytepos is too large, return failure. */
|
||
if (buffer > end)
|
||
return False;
|
||
}
|
||
else if (by < 0)
|
||
{
|
||
/* Move the buffer and text position backwards. */
|
||
|
||
start = buffer + caret->bytepos;
|
||
while (by && start >= buffer)
|
||
{
|
||
do
|
||
{
|
||
start--;
|
||
caret->bytepos -= 1;
|
||
|
||
if (start < buffer)
|
||
return False;
|
||
}
|
||
while (!IsLeading (*start));
|
||
|
||
caret->charpos--;
|
||
by--;
|
||
}
|
||
}
|
||
|
||
return True;
|
||
}
|
||
|
||
static char *
|
||
EncodeIMString (const char *input, size_t input_size, int *chars)
|
||
{
|
||
iconv_t cd;
|
||
char *oldlocale, *locale;
|
||
size_t rc;
|
||
ptrdiff_t size;
|
||
char *outbuf, *outptr, *end;
|
||
size_t outsize, outbytesleft;
|
||
int nchars, num_chars_read;
|
||
wchar_t wc;
|
||
char *inbuf;
|
||
|
||
/* Encode the given input string in the IM coding system, and then
|
||
return a NULL terminated buffer and the number of characters, or
|
||
NULL if the conversion failed. */
|
||
DebugPrint ("encoding string %.*s", (int) input_size, input);
|
||
|
||
/* Switch to the input method locale. */
|
||
locale = XLocaleOfIM (current_xim);
|
||
oldlocale = XLStrdup (setlocale (LC_CTYPE, NULL));
|
||
|
||
if (!setlocale (LC_CTYPE, locale))
|
||
{
|
||
/* Switching to the new locale failed. */
|
||
XLFree (oldlocale);
|
||
return NULL;
|
||
}
|
||
|
||
/* First, try creating a conversion descriptor. */
|
||
cd = iconv_open (nl_langinfo (CODESET), "UTF-8");
|
||
|
||
/* If creating the cd failed, bail out. */
|
||
if (cd == (iconv_t) -1)
|
||
{
|
||
#ifdef __GNUC__
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
|
||
#endif
|
||
/* Restore the old locale. */
|
||
if (!setlocale (LC_CTYPE, oldlocale))
|
||
abort ();
|
||
|
||
XLFree (oldlocale);
|
||
return NULL;
|
||
#ifdef __GNUC__
|
||
#pragma GCC diagnostic pop
|
||
#endif
|
||
}
|
||
|
||
/* Otherwise, start converting. */
|
||
outbuf = XLMalloc (BUFSIZ + 1);
|
||
outptr = outbuf;
|
||
outsize = BUFSIZ;
|
||
outbytesleft = outsize;
|
||
inbuf = (char *) input;
|
||
|
||
while (input_size > 0)
|
||
{
|
||
rc = iconv (cd, &inbuf, &input_size, &outptr,
|
||
&outbytesleft);
|
||
DebugPrint ("iconv gave: %zu", rc);
|
||
|
||
if (rc == (size_t) -1)
|
||
{
|
||
/* See what went wrong. */
|
||
if (errno == E2BIG)
|
||
{
|
||
/* Reallocate the output buffer. */
|
||
outbuf = XLRealloc (outbuf, outsize + BUFSIZ + 1);
|
||
|
||
/* Move the outptr to the right location in the new
|
||
outbuf. */
|
||
outptr = outbuf + outsize - outbytesleft;
|
||
|
||
/* Expand outsize and outbytesleft. */
|
||
outsize += BUFSIZ;
|
||
outbytesleft += BUFSIZ;
|
||
|
||
DebugPrint ("expanding outsize to %zu, outbytesleft now %zu",
|
||
outsize, outbytesleft);
|
||
}
|
||
else
|
||
{
|
||
/* An error occured while encoding the string.
|
||
Normally, this is not such a big deal, but the number
|
||
of characters in the string is later counted with
|
||
mbtowc. So, simply bail out. */
|
||
DebugPrint ("iconv failed: %s", strerror (errno));
|
||
XLFree (outbuf);
|
||
iconv_close (cd);
|
||
/* Restore the old locale. */
|
||
if (!setlocale (LC_CTYPE, oldlocale))
|
||
abort ();
|
||
|
||
XLFree (oldlocale);
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* The conversion finished. */
|
||
DebugPrint ("conversion finished, size_out %zu",
|
||
outsize - outbytesleft);
|
||
|
||
/* Now, count the number of multibyte characters. */
|
||
nchars = 0;
|
||
end = outbuf;
|
||
size = outsize - outbytesleft;
|
||
|
||
while (end < outbuf + size)
|
||
{
|
||
num_chars_read = mbtowc (&wc, end, outbuf + size - end);
|
||
nchars++;
|
||
|
||
if (num_chars_read != -1)
|
||
end += num_chars_read;
|
||
else
|
||
{
|
||
DebugPrint ("mbtowc failed");
|
||
|
||
XLFree (outbuf);
|
||
iconv_close (cd);
|
||
/* Restore the old locale. */
|
||
if (!setlocale (LC_CTYPE, oldlocale))
|
||
abort ();
|
||
|
||
XLFree (oldlocale);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/* Reset the shift state and return the number of characters. */
|
||
mbtowc (NULL, NULL, 0);
|
||
*chars = nchars;
|
||
|
||
/* Close the cd. */
|
||
iconv_close (cd);
|
||
|
||
/* Restore the old locale. */
|
||
if (!setlocale (LC_CTYPE, oldlocale))
|
||
abort ();
|
||
XLFree (oldlocale);
|
||
|
||
/* Return the output buffer. */
|
||
return outbuf;
|
||
}
|
||
|
||
static void
|
||
StringConversionCallback (XIC ic, XPointer client_data,
|
||
XIMStringConversionCallbackStruct *call_data)
|
||
{
|
||
TextInput *input;
|
||
TextPosition start, end, caret;
|
||
short position;
|
||
size_t length;
|
||
char *buffer;
|
||
int num_characters;
|
||
int bytes_before, bytes_after;
|
||
|
||
input = (TextInput *) client_data;
|
||
|
||
/* Clear some members of the returned text structure. */
|
||
call_data->text->feedback = NULL;
|
||
call_data->text->encoding_is_wchar = False;
|
||
|
||
if (!(input->current_state.pending & PendingSurroundingText))
|
||
return;
|
||
|
||
DebugPrint ("string conversion; position: %d, factor: %d"
|
||
" operation: %u", (short) call_data->position,
|
||
call_data->factor, call_data->operation);
|
||
|
||
/* Obtain the actual caret position. */
|
||
caret = input->current_state.cursor;
|
||
DebugPrint ("current caret position: char: %d, byte: %td",
|
||
caret.charpos, caret.bytepos);
|
||
|
||
if (caret.charpos < 0 || caret.bytepos < 0)
|
||
goto failure;
|
||
|
||
/* This is unsigned short in Xlib.h but the spec says it should be
|
||
signed. */
|
||
position = (short) call_data->position;
|
||
|
||
/* Move the caret by position. */
|
||
length = strlen (input->current_state.surrounding_text);
|
||
|
||
/* If the string is too small, just fail. */
|
||
if (!length)
|
||
goto failure;
|
||
|
||
if (!MoveCaret (&caret, input->current_state.surrounding_text,
|
||
length, position))
|
||
{
|
||
DebugPrint ("failed to move caret position");
|
||
goto failure;
|
||
}
|
||
|
||
if (call_data->factor < 1)
|
||
goto failure;
|
||
|
||
DebugPrint ("new caret position: char %d, byte: %td",
|
||
caret.charpos, caret.bytepos);
|
||
|
||
/* Now, obtain the start and end of the text to return. */
|
||
FindTextSections (input->current_state.surrounding_text,
|
||
length, caret, call_data->direction,
|
||
call_data->factor, &start, &end);
|
||
|
||
DebugPrint ("start: %d, %td, end: %d, %td",
|
||
start.charpos, start.bytepos, end.charpos,
|
||
end.bytepos);
|
||
|
||
/* If either of those positions are invalid, signal failure. */
|
||
if (start.charpos < 0 || start.bytepos < 0
|
||
|| end.charpos < 0 || end.bytepos < 0)
|
||
goto failure;
|
||
|
||
/* Verify that some assumptions hold. */
|
||
XLAssert (start.bytepos <= end.bytepos && end.bytepos < length);
|
||
|
||
/* Extract and encode the contents of the string. */
|
||
buffer = EncodeIMString ((input->current_state.surrounding_text
|
||
+ start.bytepos),
|
||
end.bytepos - start.bytepos + 1,
|
||
&num_characters);
|
||
|
||
/* Return those characters. */
|
||
|
||
if (buffer)
|
||
{
|
||
call_data->text->length = MIN (USHRT_MAX, num_characters);
|
||
call_data->text->string.mbs = buffer;
|
||
}
|
||
else
|
||
goto failure;
|
||
|
||
DebugPrint ("returned text: %s", buffer);
|
||
|
||
if (call_data->operation == XIMStringConversionSubstitution)
|
||
{
|
||
/* Also tell the client to delete the extracted part of the
|
||
buffer. First, calculate how much start extends behind the
|
||
cursor. This is an approximation; it assumes that the
|
||
portion of text to change always contains the caret, which is
|
||
not guaranteed to be the case if the IM specified an offset,
|
||
direction, and factor that resulted in none of the text
|
||
between start and end containing the caret. */
|
||
caret = input->current_state.cursor;
|
||
|
||
if (start.bytepos < caret.bytepos)
|
||
bytes_before = caret.bytepos - start.bytepos;
|
||
else
|
||
bytes_before = 0;
|
||
|
||
if (end.bytepos > caret.bytepos)
|
||
bytes_after = end.bytepos - caret.bytepos;
|
||
else
|
||
bytes_after = 0;
|
||
|
||
DebugPrint ("deleting: %d %d", bytes_before, bytes_after);
|
||
|
||
zwp_text_input_v3_send_delete_surrounding_text (input->resource,
|
||
bytes_before,
|
||
bytes_after);
|
||
zwp_text_input_v3_send_done (input->resource, input->serial);
|
||
}
|
||
|
||
return;
|
||
|
||
failure:
|
||
/* Return a string of length 0. This assumes XFree is able to free
|
||
data allocated with our malloc wrapper. */
|
||
call_data->text->length = 0;
|
||
call_data->text->string.mbs = XLMalloc (0);
|
||
}
|
||
|
||
static void
|
||
CreateIC (TextInput *input)
|
||
{
|
||
XVaNestedList status_attr, preedit_attr;
|
||
XPoint spot;
|
||
XRectangle rect;
|
||
Window window;
|
||
XIMCallback preedit_start_callback;
|
||
XIMCallback preedit_draw_callback;
|
||
XIMCallback preedit_done_callback;
|
||
XIMCallback preedit_caret_callback;
|
||
XIMCallback string_conversion_callback;
|
||
unsigned long additional_events;
|
||
|
||
if (!current_xim)
|
||
return;
|
||
|
||
if (!input->client_info)
|
||
return;
|
||
|
||
if (!input->client_info->focus_surface)
|
||
return;
|
||
|
||
window = XLWindowFromSurface (input->client_info->focus_surface);
|
||
|
||
if (!window)
|
||
return;
|
||
|
||
XLAssert (!input->xic);
|
||
|
||
DebugPrint ("creating XIC for text input %p and window 0x%lx", input,
|
||
window);
|
||
|
||
status_attr = NULL;
|
||
preedit_attr = NULL;
|
||
|
||
/* Create an XIC for the given text input. */
|
||
if (xim_style & XIMPreeditPosition)
|
||
{
|
||
DebugPrint ("IM wants spot values for preedit window");
|
||
|
||
if (input->current_state.pending & PendingCursorRectangle)
|
||
{
|
||
spot.x = CurrentCursorX (input);
|
||
spot.y = (CurrentCursorY (input)
|
||
+ CurrentCursorHeight (input));
|
||
}
|
||
else
|
||
{
|
||
spot.x = 0;
|
||
spot.y = 1;
|
||
}
|
||
|
||
DebugPrint ("using spot: %d, %d", spot.x, spot.y);
|
||
preedit_attr = XVaCreateNestedList (0, XNSpotLocation, &spot,
|
||
XNFontSet, im_fontset, NULL);
|
||
}
|
||
else if (xim_style & XIMPreeditArea)
|
||
{
|
||
DebugPrint ("IM wants geometry negotiation");
|
||
|
||
/* Use some dummy values, and then negotiate geometry after the
|
||
XIC is created. */
|
||
rect.x = 0;
|
||
rect.y = 0;
|
||
rect.height = 1;
|
||
rect.width = 1;
|
||
|
||
preedit_attr = XVaCreateNestedList (0, XNArea, &rect, XNFontSet,
|
||
im_fontset, NULL);
|
||
}
|
||
else if (xim_style & XIMPreeditCallbacks)
|
||
{
|
||
DebugPrint ("IM wants preedit callbacks");
|
||
|
||
preedit_start_callback.client_data = (XPointer) input;
|
||
preedit_done_callback.client_data = (XPointer) input;
|
||
preedit_draw_callback.client_data = (XPointer) input;
|
||
preedit_caret_callback.client_data = (XPointer) input;
|
||
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||
preedit_start_callback.callback = (XIMProc) PreeditStartCallback;
|
||
preedit_done_callback.callback = (XIMProc) PreeditDoneCallback;
|
||
preedit_draw_callback.callback = (XIMProc) PreeditDrawCallback;
|
||
preedit_caret_callback.callback = (XIMProc) PreeditCaretCallback;
|
||
#pragma GCC diagnostic pop
|
||
|
||
preedit_attr = XVaCreateNestedList (0, XNPreeditStartCallback,
|
||
&preedit_start_callback,
|
||
XNPreeditDoneCallback,
|
||
&preedit_done_callback,
|
||
XNPreeditDrawCallback,
|
||
&preedit_draw_callback,
|
||
XNPreeditCaretCallback,
|
||
&preedit_caret_callback,
|
||
NULL);
|
||
}
|
||
|
||
if (xim_style & XIMStatusArea)
|
||
{
|
||
DebugPrint ("IM wants geometry negotiation for status area");
|
||
|
||
/* Use some dummy values, and then negotiate geometry after the
|
||
XIC is created. */
|
||
rect.x = 0;
|
||
rect.y = 0;
|
||
rect.height = 1;
|
||
rect.width = 1;
|
||
|
||
status_attr = XVaCreateNestedList (0, XNArea, &rect, XNFontSet,
|
||
im_fontset, NULL);
|
||
}
|
||
|
||
DebugPrint ("preedit attr: %p, status attr: %p",
|
||
preedit_attr, status_attr);
|
||
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||
string_conversion_callback.client_data = (XPointer) input;
|
||
string_conversion_callback.callback = (XIMProc) StringConversionCallback;
|
||
#pragma GCC diagnostic pop
|
||
|
||
if (preedit_attr && status_attr)
|
||
input->xic = XCreateIC (current_xim, XNInputStyle, xim_style,
|
||
XNClientWindow, window, XNFocusWindow,
|
||
window, XNStatusAttributes, status_attr,
|
||
XNPreeditAttributes, preedit_attr,
|
||
XNStringConversionCallback,
|
||
&string_conversion_callback,
|
||
NULL);
|
||
else if (preedit_attr)
|
||
input->xic = XCreateIC (current_xim, XNInputStyle, xim_style,
|
||
XNClientWindow, window, XNFocusWindow,
|
||
window, XNPreeditAttributes, preedit_attr,
|
||
XNStringConversionCallback,
|
||
&string_conversion_callback,
|
||
NULL);
|
||
else if (status_attr)
|
||
input->xic = XCreateIC (current_xim, XNInputStyle, xim_style,
|
||
XNClientWindow, window, XNFocusWindow,
|
||
window, XNStatusAttributes, status_attr,
|
||
XNStringConversionCallback,
|
||
&string_conversion_callback,
|
||
NULL);
|
||
else
|
||
input->xic = XCreateIC (current_xim, XNInputStyle, xim_style,
|
||
XNClientWindow, window, XNFocusWindow,
|
||
window, XNStringConversionCallback,
|
||
&string_conversion_callback,
|
||
NULL);
|
||
|
||
/* Select for additional events should the IC have been successfully
|
||
created. Note that we do not deselect for the extra event mask
|
||
anywhere; the events an input method makes us select for should
|
||
be benign enough. */
|
||
|
||
if (input->xic)
|
||
{
|
||
additional_events = NoEventMask;
|
||
|
||
if (!XGetICValues (input->xic, XNFilterEvents,
|
||
&additional_events, NULL)
|
||
&& additional_events)
|
||
{
|
||
DebugPrint ("selecting for additional event mask: %lx",
|
||
additional_events);
|
||
|
||
XLSurfaceSelectExtraEvents (input->client_info->focus_surface,
|
||
additional_events);
|
||
}
|
||
}
|
||
|
||
/* Free the nested lists. */
|
||
if (status_attr)
|
||
XFree (status_attr);
|
||
|
||
if (preedit_attr)
|
||
XFree (preedit_attr);
|
||
|
||
DebugPrint ("created IC %p", input->xic);
|
||
}
|
||
|
||
static void
|
||
IMDestroyCallback (XIM im, XPointer client_data, XPointer call_data)
|
||
{
|
||
TextInputClientInfo *info;
|
||
TextInput *input;
|
||
|
||
DebugPrint ("XIM %p destroyed", im);
|
||
|
||
if (im != current_xim)
|
||
/* Is this even possible? */
|
||
return;
|
||
|
||
/* The XIM was destroyed, and all XICs have been freed. Clear all
|
||
fields still referencing XICs or XIMs. */
|
||
|
||
current_xim = NULL;
|
||
|
||
/* Close the cd. */
|
||
if (current_cd != (iconv_t) -1)
|
||
iconv_close (current_cd);
|
||
current_cd = (iconv_t) -1;
|
||
|
||
/* Clear the XIC field of each input. */
|
||
|
||
info = all_client_infos.next;
|
||
while (info != &all_client_infos)
|
||
{
|
||
input = info->inputs.next;
|
||
while (input != &info->inputs)
|
||
{
|
||
/* Destroy the XIC of this one input. */
|
||
if (input->xic)
|
||
{
|
||
input->xic = NULL;
|
||
|
||
/* Handle IC destruction. */
|
||
HandleICDestroyed (input);
|
||
}
|
||
|
||
/* Move to the next input. */
|
||
input = input->next;
|
||
}
|
||
|
||
/* Move to the next client info. */
|
||
info = info->next;
|
||
}
|
||
|
||
DebugPrint ("finished XIM destruction");
|
||
}
|
||
|
||
static XIMStyle
|
||
CheckStyle (XIMStyles *styles, XIMStyle preedit_style,
|
||
XIMStyle status_style)
|
||
{
|
||
int i;
|
||
|
||
/* Is this preedit & status style combination supported? */
|
||
for (i = 0; i < styles->count_styles; i++)
|
||
{
|
||
if ((styles->supported_styles[i] & preedit_style)
|
||
&& (styles->supported_styles[i] & status_style))
|
||
return styles->supported_styles[i];
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
CheckStyles (XIM xim)
|
||
{
|
||
XIMStyles *styles;
|
||
XIMStyle style;
|
||
int i;
|
||
|
||
/* Pick a supported XIM style from the current input method. The
|
||
following input styles are supported:
|
||
|
||
over-the-spot, where the preedit is displayed in a window at a
|
||
given position.
|
||
|
||
off-the-spot, where the preedit is displayed in a window
|
||
somewhere inside the application window.
|
||
|
||
root-window, where the preedit is displayed is displayed in a
|
||
window that is a child of the root window.
|
||
|
||
on-the-spot, where the preedit is displayed inside the
|
||
application window. */
|
||
|
||
if (XGetIMValues (xim, XNQueryInputStyle, &styles, NULL))
|
||
{
|
||
/* An error occured; default to none. */
|
||
xim_style = XIMPreeditNone | XIMStatusNone;
|
||
return;
|
||
}
|
||
|
||
/* Otherwise, find the best style in our order of preference. */
|
||
for (i = 0; xim_style_order[i] != XimStyleNone; ++i)
|
||
{
|
||
DebugPrint ("considering style: %d", (int) xim_style_order[i]);
|
||
|
||
switch (xim_style_order[i])
|
||
{
|
||
case XimOverTheSpot:
|
||
DebugPrint ("checking for over-the-spot");
|
||
style = CheckStyle (styles, XIMPreeditPosition,
|
||
XIMStatusArea | XIMStatusNothing | XIMStatusNone);
|
||
if (style)
|
||
goto done;
|
||
break;
|
||
|
||
case XimOffTheSpot:
|
||
DebugPrint ("checking for off-the-spot");
|
||
style = CheckStyle (styles, XIMPreeditArea,
|
||
XIMStatusArea | XIMStatusNothing | XIMStatusNone);
|
||
if (style)
|
||
goto done;
|
||
break;
|
||
|
||
case XimRootWindow:
|
||
DebugPrint ("checking for root-window");
|
||
style = CheckStyle (styles, XIMPreeditNothing,
|
||
XIMStatusNothing | XIMStatusNone);
|
||
if (style)
|
||
goto done;
|
||
break;
|
||
|
||
case XimOnTheSpot:
|
||
DebugPrint ("checking for on-the-spot");
|
||
style = CheckStyle (styles, XIMPreeditCallbacks,
|
||
XIMStatusArea | XIMStatusNothing | XIMStatusNone);
|
||
if (style)
|
||
goto done;
|
||
break;
|
||
|
||
case XimStyleNone:
|
||
/* This shouldn't happen. */
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
DebugPrint ("checking for input method styles failed");
|
||
/* No style could be found, so fall back to XIMPreeditNone and
|
||
XIMStatusNone. */
|
||
style = XIMPreeditNone | XIMStatusNone;
|
||
done:
|
||
DebugPrint ("set styles to: %lu", (unsigned long) style);
|
||
XFree (styles);
|
||
xim_style = style;
|
||
}
|
||
|
||
static void
|
||
HandleNewIM (XIM xim)
|
||
{
|
||
TextInputClientInfo *info;
|
||
TextInput *input;
|
||
const char *locale;
|
||
char *oldlocale, *coding;
|
||
iconv_t cd;
|
||
XIMCallback destroy_callback;
|
||
|
||
/* A new input method is available; destroy the XIC of every text
|
||
input. */
|
||
info = all_client_infos.next;
|
||
while (info != &all_client_infos)
|
||
{
|
||
input = info->inputs.next;
|
||
while (input != &info->inputs)
|
||
{
|
||
/* Destroy the XIC of this one input. */
|
||
if (input->xic)
|
||
{
|
||
XDestroyIC (input->xic);
|
||
input->xic = NULL;
|
||
|
||
/* Handle IC destruction. */
|
||
HandleICDestroyed (input);
|
||
}
|
||
|
||
/* Move to the next input. */
|
||
input = input->next;
|
||
}
|
||
|
||
/* Move to the next client info. */
|
||
info = info->next;
|
||
}
|
||
|
||
/* Now, it is okay to delete the current XIM. */
|
||
if (current_xim)
|
||
XCloseIM (current_xim);
|
||
current_xim = NULL;
|
||
|
||
/* And its cd. */
|
||
if (current_cd != (iconv_t) -1)
|
||
iconv_close (current_cd);
|
||
current_cd = (iconv_t) -1;
|
||
|
||
/* Obtain the locale of the new input method. */
|
||
locale = XLocaleOfIM (xim);
|
||
|
||
/* Temporarily switch to the new locale to determine its coded
|
||
character set. */
|
||
oldlocale = XLStrdup (setlocale (LC_ALL, NULL));
|
||
|
||
if (!setlocale (LC_ALL, locale))
|
||
{
|
||
/* The locale specified by the input method couldn't be set. */
|
||
XLFree (oldlocale);
|
||
goto bad_locale;
|
||
}
|
||
|
||
/* Now we are in the input method locale. Obtain the codeset. */
|
||
coding = XLStrdup (nl_langinfo (CODESET));
|
||
|
||
/* Switch back to the new locale. */
|
||
if (!setlocale (LC_ALL, oldlocale))
|
||
abort ();
|
||
|
||
DebugPrint ("input method coding system is %s", coding);
|
||
|
||
/* Create a character conversion context for input data. */
|
||
cd = iconv_open ("UTF-8", coding);
|
||
|
||
/* Free the new data. */
|
||
XLFree (oldlocale);
|
||
XLFree (coding);
|
||
|
||
/* If cd creation failed, assume it isn't supported. */
|
||
if (cd == (iconv_t) -1)
|
||
goto bad_locale;
|
||
|
||
DebugPrint ("conversion descriptor created to UTF-8");
|
||
|
||
/* Now enable the input method and create XICs for all text inputs.
|
||
Then, restore previous state. */
|
||
current_xim = xim;
|
||
current_cd = cd;
|
||
|
||
/* Attach the destroy callback to the XIM. */
|
||
destroy_callback.client_data = NULL;
|
||
destroy_callback.callback = IMDestroyCallback;
|
||
XSetIMValues (xim, XNDestroyCallback, &destroy_callback,
|
||
NULL);
|
||
|
||
/* Initialize the styles supported by this input method. */
|
||
CheckStyles (xim);
|
||
|
||
/* A new input method is available; destroy the XIC of every text
|
||
input. */
|
||
info = all_client_infos.next;
|
||
while (info != &all_client_infos)
|
||
{
|
||
input = info->inputs.next;
|
||
while (input != &info->inputs)
|
||
{
|
||
/* Try to create the IC for this one input. */
|
||
if (input->current_state.enabled
|
||
/* If this is NULL, then the IC will only be created
|
||
upon the next commit after the focus is actually
|
||
transferred to the text input. */
|
||
&& input->client_info->focus_surface)
|
||
{
|
||
CreateIC (input);
|
||
|
||
/* Focus the IC and do geometry allocation. */
|
||
if (input->xic)
|
||
XSetICFocus (input->xic);
|
||
DoGeometryAllocation (input);
|
||
}
|
||
|
||
/* Move to the next input. */
|
||
input = input->next;
|
||
}
|
||
|
||
/* Move to the next client info. */
|
||
info = info->next;
|
||
}
|
||
|
||
return;
|
||
|
||
#ifdef __GNUC__
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
|
||
#endif
|
||
|
||
bad_locale:
|
||
XCloseIM (xim);
|
||
|
||
#ifdef __GNUC__
|
||
#pragma GCC diagnostic pop
|
||
#endif
|
||
}
|
||
|
||
static void
|
||
IMInstantiateCallback (Display *display, XPointer client_data,
|
||
XPointer call_data)
|
||
{
|
||
XIM newim;
|
||
|
||
DebugPrint ("input method instantiated");
|
||
|
||
/* Open the input method. */
|
||
newim = XOpenIM (compositor.display,
|
||
XrmGetDatabase (compositor.display),
|
||
(char *) compositor.resource_name,
|
||
(char *) compositor.app_name);
|
||
|
||
/* Obtain its locale. */
|
||
if (newim)
|
||
{
|
||
DebugPrint ("created input method with locale: %s",
|
||
XLocaleOfIM (newim));
|
||
HandleNewIM (newim);
|
||
}
|
||
else
|
||
DebugPrint ("input method creation failed");
|
||
}
|
||
|
||
static void
|
||
FocusInCallback (Seat *seat, Surface *surface)
|
||
{
|
||
TextInputClientInfo *info, *start;
|
||
|
||
DebugPrint ("seat %p, surface %p", seat, surface);
|
||
|
||
info = GetClientInfo (wl_resource_get_client (surface->resource),
|
||
seat, False);
|
||
|
||
if (info)
|
||
{
|
||
DebugPrint ("found seat client info; sending events");
|
||
NoticeEnter (info, surface);
|
||
}
|
||
|
||
start = info ? info : &all_client_infos;
|
||
info = start->next;
|
||
|
||
/* Now, leave all of the other infos on the same seat. */
|
||
while (info != start)
|
||
{
|
||
if (info->seat == seat)
|
||
NoticeLeave (info);
|
||
|
||
/* Note that info->seat will be NULL for the sentinel node, so
|
||
the above comparison can never be true. */
|
||
info = info->next;
|
||
}
|
||
}
|
||
|
||
static void
|
||
FocusOutCallback (Seat *seat)
|
||
{
|
||
TextInputClientInfo *info;
|
||
|
||
DebugPrint ("seat %p", seat);
|
||
|
||
info = all_client_infos.next;
|
||
while (info != &all_client_infos)
|
||
{
|
||
/* Leave the info if this is the same seat. */
|
||
if (info->seat == seat)
|
||
NoticeLeave (info);
|
||
|
||
info = info->next;
|
||
}
|
||
}
|
||
|
||
static void
|
||
ConvertKeyEvent (XIDeviceEvent *xev, XEvent *event)
|
||
{
|
||
/* Input methods cannot understand extension events, so filter an
|
||
equivalent core event instead. */
|
||
|
||
memset (event, 0, sizeof *event);
|
||
|
||
if (xev->evtype == XI_KeyPress)
|
||
event->xkey.type = KeyPress;
|
||
else
|
||
event->xkey.type = KeyRelease;
|
||
|
||
event->xkey.serial = xev->serial;
|
||
event->xkey.send_event = xev->send_event;
|
||
event->xkey.display = compositor.display;
|
||
event->xkey.window = xev->event;
|
||
event->xkey.root = xev->root;
|
||
event->xkey.subwindow = xev->child;
|
||
event->xkey.time = xev->time;
|
||
event->xkey.state = ((xev->mods.effective & ~(1 << 13 | 1 << 14))
|
||
| (xev->group.effective << 13));
|
||
event->xkey.keycode = xev->detail;
|
||
event->xkey.x = xev->event_x;
|
||
event->xkey.y = xev->event_y;
|
||
event->xkey.x_root = xev->root_x;
|
||
event->xkey.y_root = xev->root_y;
|
||
|
||
if (xev->root == DefaultRootWindow (compositor.display))
|
||
event->xkey.same_screen = True;
|
||
|
||
/* Wayland clients don't expect to receive repeated key events,
|
||
while input methods do. However, there is no way to stuff the
|
||
XIKeyRepeat flag into a core event. Our saving graces are that:
|
||
|
||
- the high two bits of a valid XID are not set.
|
||
|
||
- event->xkey.subwindow is unused by all input methods.
|
||
|
||
- it cannot be valid to actually query information from the
|
||
subwindow, since it may no longer exist by the time the event
|
||
is forwarded to the input method.
|
||
|
||
As a result, it becomes possible to record that information by
|
||
setting the high bit of the event subwindow for repeated key
|
||
events. */
|
||
|
||
if (xev->flags & XIKeyRepeat)
|
||
event->xkey.subwindow |= (1U << 31);
|
||
}
|
||
|
||
static char *
|
||
ConvertString (char *buffer, size_t nbytes, size_t *size_out)
|
||
{
|
||
char *outbuf, *outptr;
|
||
size_t outsize, outbytesleft, rc;
|
||
|
||
outbuf = XLMalloc (BUFSIZ + 1);
|
||
outptr = outbuf;
|
||
outsize = BUFSIZ;
|
||
outbytesleft = outsize;
|
||
|
||
DebugPrint ("converting string of size %zu", nbytes);
|
||
|
||
/* Reset the cd state. */
|
||
iconv (current_cd, NULL, NULL, &outptr, &outbytesleft);
|
||
|
||
/* Start converting. */
|
||
while (nbytes > 0)
|
||
{
|
||
rc = iconv (current_cd, &buffer, &nbytes,
|
||
&outptr, &outbytesleft);
|
||
|
||
DebugPrint ("iconv gave: %zu", rc);
|
||
|
||
if (rc == (size_t) -1)
|
||
{
|
||
/* See what went wrong. */
|
||
if (errno == E2BIG)
|
||
{
|
||
/* Reallocate the output buffer. */
|
||
outbuf = XLRealloc (outbuf, outsize + BUFSIZ + 1);
|
||
|
||
/* Move the outptr to the right location in the new
|
||
outbuf. */
|
||
outptr = outbuf + outsize - outbytesleft;
|
||
|
||
/* Expand outsize and outbytesleft. */
|
||
outsize += BUFSIZ;
|
||
outbytesleft += BUFSIZ;
|
||
|
||
DebugPrint ("expanding outsize to %zu, outbytesleft now %zu",
|
||
outsize, outbytesleft);
|
||
}
|
||
else
|
||
goto finish;
|
||
}
|
||
}
|
||
|
||
finish:
|
||
DebugPrint ("conversion finished, size_out %zu",
|
||
outsize - outbytesleft);
|
||
|
||
/* Return outbuf and the number of bytes put in it. */
|
||
if (size_out)
|
||
*size_out = outsize - outbytesleft;
|
||
|
||
/* NULL-terminate the string. */
|
||
outbuf[outsize - outbytesleft] = '\0';
|
||
|
||
return outbuf;
|
||
}
|
||
|
||
static void
|
||
PreeditString (TextInput *input, const char *buffer,
|
||
size_t buffer_size, ptrdiff_t cursor)
|
||
{
|
||
char chunk[4000];
|
||
const char *start, *end;
|
||
int skip;
|
||
const char *buffer_end;
|
||
int cursor_pos;
|
||
|
||
start = buffer;
|
||
buffer_end = buffer + buffer_size;
|
||
|
||
/* The Wayland protocol limits strings to 4000 bytes (including the
|
||
terminating NULL). Send the text as valid substrings consisting
|
||
of less than 4000 bytes each. */
|
||
|
||
while (start < buffer_end)
|
||
{
|
||
end = start;
|
||
|
||
while (true)
|
||
{
|
||
skip = CountOctets (*end);
|
||
|
||
DebugPrint ("skip %d (%p+%d)", skip, end, skip);
|
||
|
||
if (end + skip - start >= 3998)
|
||
break;
|
||
|
||
if (end >= buffer_end)
|
||
break;
|
||
|
||
end += skip;
|
||
}
|
||
|
||
DebugPrint ("end-start (%p-%p): %td", end, start,
|
||
end - start);
|
||
|
||
/* Now, start to end contain a UTF-8 sequence less than 4000
|
||
bytes in length. */
|
||
XLAssert (end - start < 3998);
|
||
memcpy (chunk, start, end - start);
|
||
|
||
/* NULL-terminate the buffer. */
|
||
chunk[end - start] = '\0';
|
||
DebugPrint ("sending buffered string %s", chunk);
|
||
|
||
/* Calculate the cursor position and whether or not it is in
|
||
this chunk. */
|
||
|
||
if (cursor == -1)
|
||
cursor_pos = -1;
|
||
else
|
||
cursor_pos = cursor - (start - buffer);
|
||
|
||
if (cursor_pos < 0)
|
||
cursor_pos = -1;
|
||
|
||
/* Send the sequence. */
|
||
zwp_text_input_v3_send_preedit_string (input->resource, chunk,
|
||
cursor_pos, cursor_pos);
|
||
|
||
start = end;
|
||
}
|
||
|
||
/* Finish sending it. */
|
||
zwp_text_input_v3_send_done (input->resource, input->serial);
|
||
}
|
||
|
||
static void
|
||
CommitString (TextInput *input, const char *buffer,
|
||
size_t buffer_size)
|
||
{
|
||
char chunk[4000];
|
||
const char *start, *end;
|
||
int skip;
|
||
const char *buffer_end;
|
||
|
||
start = buffer;
|
||
buffer_end = buffer + buffer_size;
|
||
|
||
/* The Wayland protocol limits strings to 4000 bytes (including the
|
||
terminating NULL). Send the text as valid substrings consisting
|
||
of less than 4000 bytes each. */
|
||
|
||
while (start < buffer_end)
|
||
{
|
||
end = start;
|
||
|
||
while (true)
|
||
{
|
||
skip = CountOctets (*end);
|
||
|
||
DebugPrint ("skip %d (%p+%d)", skip, end, skip);
|
||
|
||
if (end + skip - start >= 3998)
|
||
break;
|
||
|
||
if (end >= buffer_end)
|
||
break;
|
||
|
||
end += skip;
|
||
}
|
||
|
||
DebugPrint ("end-start (%p-%p): %td", end, start,
|
||
end - start);
|
||
|
||
/* Now, start to end contain a UTF-8 sequence less than 4000
|
||
bytes in length. */
|
||
XLAssert (end - start < 3998);
|
||
memcpy (chunk, start, end - start);
|
||
|
||
/* NULL-terminate the buffer. */
|
||
chunk[end - start] = '\0';
|
||
DebugPrint ("sending buffered string %s", chunk);
|
||
|
||
/* Send the sequence. */
|
||
zwp_text_input_v3_send_commit_string (input->resource, chunk);
|
||
|
||
start = end;
|
||
}
|
||
|
||
/* Finish sending it. */
|
||
zwp_text_input_v3_send_done (input->resource, input->serial);
|
||
}
|
||
|
||
static Bool
|
||
LookupString (TextInput *input, XEvent *event, KeySym *keysym_return)
|
||
{
|
||
char *buffer;
|
||
size_t nbytes, buffer_size;
|
||
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,
|
||
buffer, 256, &keysym, &status);
|
||
DebugPrint ("looked up %zu", nbytes);
|
||
|
||
if (status == XBufferOverflow)
|
||
{
|
||
DebugPrint ("overflow to %zu", nbytes);
|
||
|
||
/* Handle overflow by growing the buffer. */
|
||
buffer = alloca (nbytes + 1);
|
||
nbytes = XmbLookupString (input->xic, &event->xkey,
|
||
buffer, nbytes + 1,
|
||
&keysym, &status);
|
||
}
|
||
|
||
DebugPrint ("status is: %d", (int) status);
|
||
|
||
/* If no string was returned, return False. Otherwise, convert the
|
||
string to UTF-8 and commit it. However, use the keysym if both a
|
||
keysym and string were looked up, as a keysym allows modifiers to
|
||
be correctly dispatched to the seat and avoids doing potentially
|
||
expensive character conversion. */
|
||
if (status != XLookupChars)
|
||
{
|
||
if ((status == XLookupKeySym
|
||
|| status == XLookupBoth) && keysym_return)
|
||
/* Return the keysym if it was looked up. */
|
||
*keysym_return = keysym;
|
||
|
||
return False;
|
||
}
|
||
|
||
DebugPrint ("converting buffer of %zu", nbytes);
|
||
|
||
/* current_xim should not be NULL. */
|
||
XLAssert (current_xim != NULL);
|
||
|
||
/* Convert the string. */
|
||
buffer = ConvertString (buffer, nbytes, &buffer_size);
|
||
|
||
if (buffer)
|
||
CommitString (input, buffer, buffer_size);
|
||
XFree (buffer);
|
||
|
||
return True;
|
||
}
|
||
|
||
static KeyCode
|
||
CalculateKeycodeForEvent (XEvent *event, KeySym keysym)
|
||
{
|
||
KeySym sym_return;
|
||
unsigned int mods_return;
|
||
|
||
/* Calculate the keycode to actually send clients along with an
|
||
event, given the keysym specified by the input method. Return 0
|
||
if no special treatment is required. */
|
||
|
||
if (!keysym)
|
||
return 0;
|
||
|
||
/* If looking up the event keycode also results in the keysym,
|
||
then just use the keycode specified in the event. This is
|
||
because French keyboard layouts have multiple keycodes that
|
||
decode to the same keysym, which causes problems later on
|
||
when Wayland clients keep repeating the "a" key, as a keysym
|
||
was looked up for the key press but not for the corresponding
|
||
key release. */
|
||
if (XkbLookupKeySym (compositor.display, event->xkey.keycode,
|
||
event->xkey.state, &mods_return, &sym_return)
|
||
&& keysym == sym_return)
|
||
return 0;
|
||
|
||
/* Otherwise, convert the keysym to a keycode and use that. */
|
||
return XLKeysymToKeycode (keysym, event);
|
||
}
|
||
|
||
static Bool
|
||
FilterInputCallback (Seat *seat, Surface *surface, void *event,
|
||
KeyCode *keycode)
|
||
{
|
||
XIDeviceEvent *xev;
|
||
XEvent xkey;
|
||
TextInputClientInfo *info;
|
||
TextInput *input;
|
||
KeySym keysym;
|
||
|
||
xev = event;
|
||
keysym = 0;
|
||
|
||
DebugPrint ("seat %p, surface %p, detail: %d, event: %lx",
|
||
seat, surface, xev->detail, xev->event);
|
||
|
||
/* Find the client info. */
|
||
info = GetClientInfo (wl_resource_get_client (surface->resource),
|
||
seat, False);
|
||
|
||
/* Find the enabled text input. */
|
||
if (info)
|
||
{
|
||
input = FindEnabledTextInput (info);
|
||
|
||
/* If there is an enabled text input, start filtering the
|
||
event. */
|
||
if (input && input->xic)
|
||
{
|
||
DebugPrint ("found enabled text input %p on client-seat info %p",
|
||
input, info);
|
||
|
||
/* Convert the extension event into a fake core event that
|
||
the input method can understand. */
|
||
ConvertKeyEvent (xev, &xkey);
|
||
|
||
/* And return the result of filtering the event. */
|
||
if (XFilterEvent (&xkey, XLWindowFromSurface (surface)))
|
||
return True;
|
||
|
||
/* Otherwise, call XmbLookupString. If a keysym is
|
||
returned, return False. Otherwise, commit the string
|
||
looked up and return True. */
|
||
if (LookupString (input, &xkey, &keysym))
|
||
return True;
|
||
|
||
/* Look up the right keycode for the event. */
|
||
|
||
if (!keysym && xev->type == XI_KeyRelease)
|
||
*keycode = GetKeycode (&input->keysym_map, xev->detail);
|
||
else
|
||
*keycode = CalculateKeycodeForEvent (&xkey, keysym);
|
||
|
||
if (xev->type == XI_KeyRelease)
|
||
/* Remove the keycode from the keycode-keysym map. */
|
||
RemoveKeysym (&input->keysym_map, xev->detail);
|
||
}
|
||
}
|
||
|
||
/* Otherwise, do nothing. */
|
||
return False;
|
||
}
|
||
|
||
|
||
|
||
/* Seat input callbacks. */
|
||
static TextInputFuncs input_funcs =
|
||
{
|
||
.focus_in = FocusInCallback,
|
||
.focus_out = FocusOutCallback,
|
||
.filter_input = FilterInputCallback,
|
||
};
|
||
|
||
void
|
||
XLTextInputDispatchCoreEvent (Surface *surface, XEvent *event)
|
||
{
|
||
Seat *im_seat;
|
||
TextInputClientInfo *info;
|
||
TextInput *input;
|
||
KeySym keysym;
|
||
KeyCode effective_keycode;
|
||
|
||
DebugPrint ("dispatching core event to surface %p:\n"
|
||
"\ttype: %d\n"
|
||
"\tserial: %lu\n"
|
||
"\tsend_event: %d\n"
|
||
"\twindow: %lx\n"
|
||
"\troot: %lx\n"
|
||
"\tsubwindow: %lx\n"
|
||
"\ttime: %lu\n"
|
||
"\tstate: %x\n"
|
||
"\tkeycode: %x", surface,
|
||
event->xkey.type,
|
||
event->xkey.serial, event->xkey.send_event,
|
||
event->xkey.window, event->xkey.subwindow,
|
||
event->xkey.subwindow, event->xkey.time,
|
||
event->xkey.state, event->xkey.keycode);
|
||
|
||
keysym = 0;
|
||
|
||
/* Some of the events we want here are rather special. They are put
|
||
back on the event queue by the X internationalization library in
|
||
response to an XIM_COMMIT event being received. Other events are
|
||
put back on the event queue in response to XIM_FORWARD_EVENT.
|
||
First, find out which seat is the input method seat. */
|
||
|
||
im_seat = XLSeatGetInputMethodSeat ();
|
||
|
||
if (!im_seat)
|
||
return;
|
||
|
||
/* Next, find the client info associated with the surface for that
|
||
seat. */
|
||
info = GetClientInfo (wl_resource_get_client (surface->resource),
|
||
im_seat, False);
|
||
|
||
if (!info)
|
||
return;
|
||
|
||
if (info->focus_surface != surface)
|
||
{
|
||
/* The surface is no longer focused. */
|
||
DebugPrint ("incorrect focus surface (focus surface is %p, "
|
||
"surface is %p)", info->focus_surface, surface);
|
||
return;
|
||
}
|
||
|
||
if (info)
|
||
{
|
||
/* And look for an enabled text input. */
|
||
input = FindEnabledTextInput (info);
|
||
|
||
if (input)
|
||
{
|
||
DebugPrint ("found enabled input %p on info %p", input, info);
|
||
|
||
/* Now, try to dispatch the core event. First, look up the
|
||
text string. */
|
||
if (!input->xic || LookupString (input, event, &keysym))
|
||
return;
|
||
|
||
if (event->xkey.subwindow & (1U << 31))
|
||
DebugPrint ("lookup failed; not dispatching event because"
|
||
" this is a key repeat");
|
||
else
|
||
{
|
||
/* Since that failed, dispatch the event to the seat. */
|
||
DebugPrint ("lookup failed; dispatching event to seat; "
|
||
"keysym is: %lu", keysym);
|
||
|
||
/* First, clear effective_keycode. */
|
||
effective_keycode = 0;
|
||
|
||
/* If the event is a KeyPress event and a keysym was
|
||
looked up, calculate a keycode for the keysym, and
|
||
record in the text input's keycode-keycode table, so
|
||
the correct keycode can be looked up upon the next
|
||
KeyRelease event. X input methods tend not to filter
|
||
the KeyRelease events, so the KeyRelease event for an
|
||
event that changed the keysym will be sent with the
|
||
wrong keycode, which does not matter much with X
|
||
programs, but leads to Wayland programs constantly
|
||
autorepeating the keycode for which a KeyPress event
|
||
was sent. */
|
||
|
||
if (event->xkey.type == KeyPress && keysym)
|
||
{
|
||
/* Compute the keycode. */
|
||
effective_keycode
|
||
= CalculateKeycodeForEvent (event, keysym);
|
||
|
||
DebugPrint ("inserting keycode %lu into map under %u",
|
||
effective_keycode, event->xkey.keycode);
|
||
InsertKeycode (&input->keysym_map, event->xkey.keycode,
|
||
effective_keycode);
|
||
}
|
||
else if (event->xkey.type == KeyRelease)
|
||
{
|
||
/* If no keysym was committed by the input method,
|
||
use the keysym provided in the last KeyPress
|
||
event for the keycode. Otherwise, the input
|
||
method probably knows better than us, so use the
|
||
keysym provided by the input method. */
|
||
|
||
if (!keysym)
|
||
{
|
||
effective_keycode
|
||
= GetKeycode (&input->keysym_map,
|
||
event->xkey.keycode);
|
||
DebugPrint ("obtained keycode %lu for keycode %u"
|
||
" while processing KeyRelease event",
|
||
keysym, event->xkey.keycode);
|
||
}
|
||
else
|
||
effective_keycode
|
||
= CalculateKeycodeForEvent (event, keysym);
|
||
|
||
DebugPrint ("removing keycode %u from map",
|
||
event->xkey.keycode);
|
||
|
||
/* Remove the keycode from the keycode-keysym
|
||
map. */
|
||
RemoveKeysym (&input->keysym_map, event->xkey.keycode);
|
||
}
|
||
|
||
/* Finally, if an effective keycode was calculated,
|
||
replace the keycode in the event with it. */
|
||
|
||
if (effective_keycode)
|
||
event->xkey.keycode = effective_keycode;
|
||
|
||
XLSeatDispatchCoreKeyEvent (im_seat, surface, event);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static Bool
|
||
InitFontset (void)
|
||
{
|
||
XrmDatabase rdb;
|
||
XrmName namelist[3];
|
||
XrmClass classlist[3];
|
||
XrmValue value;
|
||
XrmRepresentation type;
|
||
char **missing_charset_list, *def_string;
|
||
int missing_charset_count;
|
||
|
||
rdb = XrmGetDatabase (compositor.display);
|
||
|
||
if (!rdb)
|
||
return False;
|
||
|
||
DebugPrint ("initializing fontset");
|
||
|
||
namelist[1] = XrmStringToQuark ("ximFont");
|
||
namelist[0] = app_quark;
|
||
namelist[2] = NULLQUARK;
|
||
|
||
classlist[1] = XrmStringToQuark ("XimFont");
|
||
classlist[0] = resource_quark;
|
||
classlist[2] = NULLQUARK;
|
||
|
||
if (XrmQGetResource (rdb, namelist, classlist,
|
||
&type, &value)
|
||
&& type == QString)
|
||
{
|
||
DebugPrint ("XIM fontset: %s", value.addr);
|
||
|
||
im_fontset = XCreateFontSet (compositor.display,
|
||
(char *) value.addr,
|
||
&missing_charset_list,
|
||
&missing_charset_count,
|
||
&def_string);
|
||
|
||
if (missing_charset_count)
|
||
XFreeStringList (missing_charset_list);
|
||
return True;
|
||
}
|
||
|
||
return False;
|
||
}
|
||
|
||
static void
|
||
InitInputStyles (void)
|
||
{
|
||
XrmDatabase rdb;
|
||
XrmName namelist[3];
|
||
XrmClass classlist[3];
|
||
XrmValue value;
|
||
XrmRepresentation type;
|
||
int i;
|
||
char *string, *end, *sep, *buffer;
|
||
|
||
rdb = XrmGetDatabase (compositor.display);
|
||
|
||
if (!rdb)
|
||
return;
|
||
|
||
DebugPrint ("initializing input styles");
|
||
|
||
namelist[1] = XrmStringToQuark ("ximStyles");
|
||
namelist[0] = app_quark;
|
||
namelist[2] = NULLQUARK;
|
||
|
||
classlist[1] = XrmStringToQuark ("XimStyles");
|
||
classlist[0] = resource_quark;
|
||
classlist[2] = NULLQUARK;
|
||
|
||
if (XrmQGetResource (rdb, namelist, classlist,
|
||
&type, &value)
|
||
&& type == QString)
|
||
{
|
||
DebugPrint ("XIM styles: %s", value.addr);
|
||
string = value.addr;
|
||
end = string + strlen (string);
|
||
i = 0;
|
||
|
||
while (string < end)
|
||
{
|
||
/* Find the next comma. */
|
||
sep = strchr (string, ',');
|
||
|
||
if (!sep)
|
||
sep = end;
|
||
|
||
/* Copy the text between string and sep into buffer. */
|
||
buffer = alloca (sep - string + 1);
|
||
memcpy (buffer, string, sep - string);
|
||
buffer[sep - string] = '\0';
|
||
|
||
/* If the comparison is successful, populate the list. */
|
||
DebugPrint ("considering: %s", buffer);
|
||
|
||
if (!strcmp (buffer, "overTheSpot"))
|
||
xim_style_order[i++] = XimOverTheSpot;
|
||
else if (!strcmp (buffer, "offTheSpot"))
|
||
xim_style_order[i++] = XimOffTheSpot;
|
||
else if (!strcmp (buffer, "rootWindow"))
|
||
xim_style_order[i++] = XimRootWindow;
|
||
else if (!strcmp (buffer, "onTheSpot"))
|
||
xim_style_order[i++] = XimOnTheSpot;
|
||
else
|
||
{
|
||
/* Invalid value encountered; stop parsing. */
|
||
DebugPrint ("invalid value: %s", buffer);
|
||
return;
|
||
}
|
||
|
||
/* Return if i is now 4. */
|
||
if (i == 4)
|
||
return;
|
||
|
||
string = sep + 1;
|
||
}
|
||
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
/* Set up default values. */
|
||
|
||
xim_style_order[0] = XimOverTheSpot;
|
||
xim_style_order[1] = XimOffTheSpot;
|
||
xim_style_order[2] = XimRootWindow;
|
||
xim_style_order[3] = XimOnTheSpot;
|
||
|
||
DebugPrint ("set up default values for XIM style order");
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
void
|
||
XLInitTextInput (void)
|
||
{
|
||
const char *modifiers;
|
||
char **missing_charset_list, *def_string;
|
||
int missing_charset_count;
|
||
|
||
current_cd = (iconv_t) -1;
|
||
|
||
if (!XSupportsLocale ())
|
||
{
|
||
DebugPrint ("not initializing text input because the"
|
||
" locale is not supported by the X library");
|
||
return;
|
||
}
|
||
|
||
/* Append the contents of XMODIFIERS to the locale modifiers
|
||
list. */
|
||
modifiers = XSetLocaleModifiers ("");
|
||
DebugPrint ("locale modifiers are: %s", modifiers);
|
||
|
||
/* Prevent -Wunused-but-set-variable when not debug. */
|
||
((void) modifiers);
|
||
|
||
all_client_infos.next = &all_client_infos;
|
||
all_client_infos.last = &all_client_infos;
|
||
|
||
text_input_manager_global
|
||
= wl_global_create (compositor.wl_display,
|
||
&zwp_text_input_manager_v3_interface,
|
||
1, NULL, HandleBind);
|
||
|
||
/* Initialize the IM fontset. */
|
||
if (!InitFontset ())
|
||
{
|
||
im_fontset = XCreateFontSet (compositor.display,
|
||
"-*-*-*-R-*-*-*-120-*-*-*-*",
|
||
&missing_charset_list,
|
||
&missing_charset_count,
|
||
&def_string);
|
||
if (missing_charset_count)
|
||
XFreeStringList (missing_charset_list);
|
||
}
|
||
|
||
/* Initialize input styles. */
|
||
InitInputStyles ();
|
||
|
||
if (im_fontset == NULL)
|
||
fprintf (stderr, "Unable to load any usable fontset for input methods\n");
|
||
|
||
/* Register the IM callback. */
|
||
XRegisterIMInstantiateCallback (compositor.display,
|
||
XrmGetDatabase (compositor.display),
|
||
(char *) compositor.resource_name,
|
||
(char *) compositor.app_name,
|
||
IMInstantiateCallback, NULL);
|
||
|
||
/* Register the text input functions. */
|
||
XLSeatSetTextInputFuncs (&input_funcs);
|
||
}
|