/
SDL_ibus.c
669 lines (531 loc) · 19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
Simple DirectMedia Layer
Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"
#ifdef HAVE_IBUS_IBUS_H
#include "SDL.h"
25
#include "SDL_syswm.h"
26
27
28
29
#include "SDL_ibus.h"
#include "SDL_dbus.h"
#include "../../video/SDL_sysvideo.h"
#include "../../events/SDL_keyboard_c.h"
30
31
32
33
34
#if SDL_VIDEO_DRIVER_X11
#include "../../video/x11/SDL_x11video.h"
#endif
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
static char *input_ctx_path = NULL;
static SDL_Rect ibus_cursor_rect = {0};
static DBusConnection *ibus_conn = NULL;
static char *ibus_addr_file = NULL;
48
int inotify_fd = -1, inotify_wd = -1;
49
50
51
52
53
54
55
56
static Uint32
IBus_ModState(void)
{
Uint32 ibus_mods = 0;
SDL_Keymod sdl_mods = SDL_GetModState();
/* Not sure about MOD3, MOD4 and HYPER mappings */
57
58
59
60
61
62
63
64
if (sdl_mods & KMOD_LSHIFT) ibus_mods |= IBUS_SHIFT_MASK;
if (sdl_mods & KMOD_CAPS) ibus_mods |= IBUS_LOCK_MASK;
if (sdl_mods & KMOD_LCTRL) ibus_mods |= IBUS_CONTROL_MASK;
if (sdl_mods & KMOD_LALT) ibus_mods |= IBUS_MOD1_MASK;
if (sdl_mods & KMOD_NUM) ibus_mods |= IBUS_MOD2_MASK;
if (sdl_mods & KMOD_MODE) ibus_mods |= IBUS_MOD5_MASK;
if (sdl_mods & KMOD_LGUI) ibus_mods |= IBUS_SUPER_MASK;
if (sdl_mods & KMOD_RGUI) ibus_mods |= IBUS_META_MASK;
65
66
67
68
69
70
71
72
73
return ibus_mods;
}
static const char *
IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
{
/* The text we need is nested weirdly, use dbus-monitor to see the structure better */
const char *text = NULL;
74
const char *struct_id = NULL;
75
76
DBusMessageIter sub1, sub2;
77
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
78
79
80
81
82
return NULL;
}
dbus->message_iter_recurse(iter, &sub1);
83
if (dbus->message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) {
84
85
86
87
88
return NULL;
}
dbus->message_iter_recurse(&sub1, &sub2);
89
if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
90
91
92
93
return NULL;
}
dbus->message_iter_get_basic(&sub2, &struct_id);
94
if (!struct_id || SDL_strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) {
95
96
97
98
99
100
return NULL;
}
dbus->message_iter_next(&sub2);
dbus->message_iter_next(&sub2);
101
if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) {
102
103
104
105
106
107
108
109
110
111
112
113
114
115
return NULL;
}
dbus->message_iter_get_basic(&sub2, &text);
return text;
}
static size_t
IBus_utf8_strlen(const char *str)
{
size_t utf8_len = 0;
const char *p;
116
117
for (p = str; *p; ++p) {
if (!((*p & 0x80) && !(*p & 0x40))) {
118
119
120
121
122
123
124
125
126
127
128
129
++utf8_len;
}
}
return utf8_len;
}
static DBusHandlerResult
IBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *user_data)
{
SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
130
if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "CommitText")) {
131
DBusMessageIter iter;
132
const char *text;
133
134
135
dbus->message_iter_init(msg, &iter);
136
text = IBus_GetVariantText(conn, &iter, dbus);
137
if (text && *text) {
138
139
140
char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
size_t text_bytes = SDL_strlen(text), i = 0;
141
while (i < text_bytes) {
142
143
144
145
146
147
148
149
150
151
size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
SDL_SendKeyboardText(buf);
i += sz;
}
}
return DBUS_HANDLER_RESULT_HANDLED;
}
152
if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "UpdatePreeditText")) {
153
DBusMessageIter iter;
154
155
const char *text;
156
dbus->message_iter_init(msg, &iter);
157
text = IBus_GetVariantText(conn, &iter, dbus);
158
159
if (text && *text) {
160
161
162
163
char buf[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
size_t text_bytes = SDL_strlen(text), i = 0;
size_t cursor = 0;
164
while (i < text_bytes) {
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
size_t sz = SDL_utf8strlcpy(buf, text+i, sizeof(buf));
size_t chars = IBus_utf8_strlen(buf);
SDL_SendEditingText(buf, cursor, chars);
i += sz;
cursor += chars;
}
}
SDL_IBus_UpdateTextRect(NULL);
return DBUS_HANDLER_RESULT_HANDLED;
}
180
181
182
if (dbus->message_is_signal(msg, IBUS_INPUT_INTERFACE, "HidePreeditText")) {
SDL_SendEditingText("", 0, 0);
return DBUS_HANDLER_RESULT_HANDLED;
183
184
}
185
186
187
188
189
190
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static char *
IBus_ReadAddressFromFile(const char *file_path)
{
191
192
193
194
195
char addr_buf[1024];
SDL_bool success = SDL_FALSE;
FILE *addr_file;
addr_file = fopen(file_path, "r");
196
if (!addr_file) {
197
198
199
return NULL;
}
200
201
while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {
if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
202
size_t sz = SDL_strlen(addr_buf);
203
204
if (addr_buf[sz-1] == '\n') addr_buf[sz-1] = 0;
if (addr_buf[sz-2] == '\r') addr_buf[sz-2] = 0;
205
206
207
208
209
210
211
success = SDL_TRUE;
break;
}
}
fclose(addr_file);
212
if (success) {
213
214
215
216
217
218
219
220
221
return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
} else {
return NULL;
}
}
static char *
IBus_GetDBusAddressFilename(void)
{
222
223
224
225
226
227
228
229
230
SDL_DBusContext *dbus;
const char *disp_env;
char config_dir[PATH_MAX];
char *display = NULL;
const char *addr;
const char *conf_env;
char *key;
char file_path[PATH_MAX];
231
if (ibus_addr_file) {
232
233
234
return SDL_strdup(ibus_addr_file);
}
235
dbus = SDL_DBus_GetContext();
236
if (!dbus) {
237
238
239
240
return NULL;
}
/* Use this environment variable if it exists. */
241
addr = SDL_getenv("IBUS_ADDRESS");
242
if (addr && *addr) {
243
244
245
246
247
return SDL_strdup(addr);
}
/* Otherwise, we have to get the hostname, display, machine id, config dir
and look up the address from a filepath using all those bits, eek. */
248
249
disp_env = SDL_getenv("DISPLAY");
250
if (!disp_env || !*disp_env) {
251
252
253
254
255
256
257
258
259
display = SDL_strdup(":0.0");
} else {
display = SDL_strdup(disp_env);
}
const char *host = display;
char *disp_num = SDL_strrchr(display, ':'),
*screen_num = SDL_strrchr(display, '.');
260
if (!disp_num) {
261
262
263
264
265
266
267
SDL_free(display);
return NULL;
}
*disp_num = 0;
disp_num++;
268
if (screen_num) {
269
270
271
*screen_num = 0;
}
272
if (!*host) {
273
274
275
276
277
host = "unix";
}
SDL_memset(config_dir, 0, sizeof(config_dir));
278
conf_env = SDL_getenv("XDG_CONFIG_HOME");
279
if (conf_env && *conf_env) {
280
281
282
SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
} else {
const char *home_env = SDL_getenv("HOME");
283
if (!home_env || !*home_env) {
284
285
286
287
288
289
SDL_free(display);
return NULL;
}
SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
}
290
291
key = dbus->get_local_machine_id();
292
293
294
295
296
297
298
299
300
SDL_memset(file_path, 0, sizeof(file_path));
SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",
config_dir, key, host, disp_num);
dbus->free(key);
SDL_free(display);
return SDL_strdup(file_path);
}
301
302
303
304
305
306
307
308
static SDL_bool IBus_CheckConnection(SDL_DBusContext *dbus);
static void
IBus_SetCapabilities(void *data, const char *name, const char *old_val,
const char *internal_editing)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
309
if (IBus_CheckConnection(dbus)) {
310
311
312
313
314
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
"SetCapabilities");
315
if (msg) {
316
Uint32 caps = IBUS_CAP_FOCUS;
317
if (!(internal_editing && *internal_editing == '1')) {
318
319
320
321
322
323
324
325
caps |= IBUS_CAP_PREEDIT_TEXT;
}
dbus->message_append_args(msg,
DBUS_TYPE_UINT32, &caps,
DBUS_TYPE_INVALID);
}
326
327
if (msg) {
if (dbus->connection_send(ibus_conn, msg, NULL)) {
328
329
330
331
332
333
334
335
dbus->connection_flush(ibus_conn);
}
dbus->message_unref(msg);
}
}
}
336
337
338
339
340
static SDL_bool
IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
{
const char *path = NULL;
SDL_bool result = SDL_FALSE;
341
342
DBusMessage *msg;
343
344
ibus_conn = dbus->connection_open_private(addr, NULL);
345
if (!ibus_conn) {
346
347
348
349
350
return SDL_FALSE;
}
dbus->connection_flush(ibus_conn);
351
if (!dbus->bus_register(ibus_conn, NULL)) {
352
353
354
355
356
357
ibus_conn = NULL;
return SDL_FALSE;
}
dbus->connection_flush(ibus_conn);
358
msg = dbus->message_new_method_call(IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext");
359
if (msg) {
360
361
362
363
364
365
const char *client_name = "SDL2_Application";
dbus->message_append_args(msg,
DBUS_TYPE_STRING, &client_name,
DBUS_TYPE_INVALID);
}
366
if (msg) {
367
368
369
DBusMessage *reply;
reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL);
370
371
if (reply) {
if (dbus->message_get_args(reply, NULL,
372
DBUS_TYPE_OBJECT_PATH, &path,
373
374
DBUS_TYPE_INVALID)) {
if (input_ctx_path) {
375
376
377
378
379
380
381
382
383
384
SDL_free(input_ctx_path);
}
input_ctx_path = SDL_strdup(path);
result = SDL_TRUE;
}
dbus->message_unref(reply);
}
dbus->message_unref(msg);
}
385
if (result) {
386
SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL);
387
388
389
390
391
392
dbus->bus_add_match(ibus_conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
dbus->connection_add_filter(ibus_conn, &IBus_MessageFilter, dbus, NULL);
dbus->connection_flush(ibus_conn);
}
393
SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
394
395
396
397
398
399
400
401
SDL_IBus_UpdateTextRect(NULL);
return result;
}
static SDL_bool
IBus_CheckConnection(SDL_DBusContext *dbus)
{
402
if (!dbus) return SDL_FALSE;
403
404
if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
405
406
407
return SDL_TRUE;
}
408
if (inotify_fd > 0 && inotify_wd > 0) {
409
410
char buf[1024];
ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
411
if (readsize > 0) {
412
413
414
415
char *p;
SDL_bool file_updated = SDL_FALSE;
416
for (p = buf; p < buf + readsize; /**/) {
417
struct inotify_event *event = (struct inotify_event*) p;
418
if (event->len > 0) {
419
char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
420
if (!addr_file_no_path) return SDL_FALSE;
421
422
if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
423
424
425
426
427
428
429
430
file_updated = SDL_TRUE;
break;
}
}
p += sizeof(struct inotify_event) + event->len;
}
431
if (file_updated) {
432
char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
433
if (addr) {
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
SDL_bool result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
return result;
}
}
}
}
return SDL_FALSE;
}
SDL_bool
SDL_IBus_Init(void)
{
SDL_bool result = SDL_FALSE;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
451
if (dbus) {
452
char *addr_file = IBus_GetDBusAddressFilename();
453
454
455
char *addr;
char *addr_file_dir;
456
if (!addr_file) {
457
458
459
460
461
return SDL_FALSE;
}
ibus_addr_file = SDL_strdup(addr_file);
462
addr = IBus_ReadAddressFromFile(addr_file);
463
464
if (inotify_fd < 0) {
465
466
467
inotify_fd = inotify_init();
fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
}
468
469
addr_file_dir = SDL_strrchr(addr_file, '/');
470
if (addr_file_dir) {
471
472
473
*addr_file_dir = 0;
}
474
inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
475
476
477
478
479
480
481
482
483
484
485
486
SDL_free(addr_file);
result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
}
return result;
}
void
SDL_IBus_Quit(void)
{
487
488
SDL_DBusContext *dbus;
489
if (input_ctx_path) {
490
491
492
493
SDL_free(input_ctx_path);
input_ctx_path = NULL;
}
494
if (ibus_addr_file) {
495
496
497
498
SDL_free(ibus_addr_file);
ibus_addr_file = NULL;
}
499
dbus = SDL_DBus_GetContext();
500
501
if (dbus && ibus_conn) {
502
503
504
505
dbus->connection_close(ibus_conn);
dbus->connection_unref(ibus_conn);
}
506
if (inotify_fd > 0 && inotify_wd > 0) {
507
508
509
510
inotify_rm_watch(inotify_fd, inotify_wd);
inotify_wd = -1;
}
511
SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL);
512
513
514
515
516
517
518
519
520
SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
}
static void
IBus_SimpleMessage(const char *method)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
521
if (IBus_CheckConnection(dbus)) {
522
523
524
525
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
method);
526
527
if (msg) {
if (dbus->connection_send(ibus_conn, msg, NULL)) {
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
dbus->connection_flush(ibus_conn);
}
dbus->message_unref(msg);
}
}
}
void
SDL_IBus_SetFocus(SDL_bool focused)
{
const char *method = focused ? "FocusIn" : "FocusOut";
IBus_SimpleMessage(method);
}
void
SDL_IBus_Reset(void)
{
IBus_SimpleMessage("Reset");
}
SDL_bool
SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode)
{
SDL_bool result = SDL_FALSE;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
554
if (IBus_CheckConnection(dbus)) {
555
556
557
558
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
"ProcessKeyEvent");
559
if (msg) {
560
561
562
563
564
565
566
567
Uint32 mods = IBus_ModState();
dbus->message_append_args(msg,
DBUS_TYPE_UINT32, &keysym,
DBUS_TYPE_UINT32, &keycode,
DBUS_TYPE_UINT32, &mods,
DBUS_TYPE_INVALID);
}
568
if (msg) {
569
570
571
DBusMessage *reply;
reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL);
572
573
if (reply) {
if (!dbus->message_get_args(reply, NULL,
574
DBUS_TYPE_BOOLEAN, &result,
575
DBUS_TYPE_INVALID)) {
576
577
578
579
580
581
582
583
584
result = SDL_FALSE;
}
dbus->message_unref(reply);
}
dbus->message_unref(msg);
}
}
585
586
SDL_IBus_UpdateTextRect(NULL);
587
588
589
590
591
592
return result;
}
void
SDL_IBus_UpdateTextRect(SDL_Rect *rect)
{
593
594
595
596
SDL_Window *focused_win = SDL_GetKeyboardFocus();
int x = 0, y = 0;
SDL_DBusContext *dbus;
597
if (rect) {
598
599
600
SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
}
601
602
603
604
if (!focused_win) {
return;
}
605
606
607
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
608
609
610
if (!SDL_GetWindowWMInfo(focused_win, &info)) {
return;
}
611
612
SDL_GetWindowPosition(focused_win, &x, &y);
613
614
#if SDL_VIDEO_DRIVER_X11
615
616
if (info.subsystem == SDL_SYSWM_X11) {
SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
617
618
619
620
621
622
Display *x_disp = info.info.x11.display;
Window x_win = info.info.x11.window;
int x_screen = displaydata->screen;
Window unused;
623
X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
624
625
626
}
#endif
627
628
629
x += ibus_cursor_rect.x;
y += ibus_cursor_rect.y;
630
dbus = SDL_DBus_GetContext();
631
632
if (IBus_CheckConnection(dbus)) {
633
634
635
636
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
"SetCursorLocation");
637
if (msg) {
638
639
640
641
642
643
644
645
dbus->message_append_args(msg,
DBUS_TYPE_INT32, &x,
DBUS_TYPE_INT32, &y,
DBUS_TYPE_INT32, &ibus_cursor_rect.w,
DBUS_TYPE_INT32, &ibus_cursor_rect.h,
DBUS_TYPE_INVALID);
}
646
647
if (msg) {
if (dbus->connection_send(ibus_conn, msg, NULL)) {
648
649
650
651
652
653
654
655
656
657
658
659
dbus->connection_flush(ibus_conn);
}
dbus->message_unref(msg);
}
}
}
void
SDL_IBus_PumpEvents(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
660
if (IBus_CheckConnection(dbus)) {
661
662
dbus->connection_read_write(ibus_conn, 0);
663
while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
664
665
666
667
668
669
/* Do nothing, actual work happens in IBus_MessageFilter */
}
}
}
#endif