/
SDL_ibus.c
671 lines (534 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
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];
230
231
const char *host;
char *disp_num, *screen_num;
232
233
if (ibus_addr_file) {
234
235
236
return SDL_strdup(ibus_addr_file);
}
237
dbus = SDL_DBus_GetContext();
238
if (!dbus) {
239
240
241
242
return NULL;
}
/* Use this environment variable if it exists. */
243
addr = SDL_getenv("IBUS_ADDRESS");
244
if (addr && *addr) {
245
246
247
248
249
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. */
250
251
disp_env = SDL_getenv("DISPLAY");
252
if (!disp_env || !*disp_env) {
253
254
255
256
257
display = SDL_strdup(":0.0");
} else {
display = SDL_strdup(disp_env);
}
258
259
260
host = display;
disp_num = SDL_strrchr(display, ':');
screen_num = SDL_strrchr(display, '.');
261
262
if (!disp_num) {
263
264
265
266
267
268
269
SDL_free(display);
return NULL;
}
*disp_num = 0;
disp_num++;
270
if (screen_num) {
271
272
273
*screen_num = 0;
}
274
if (!*host) {
275
276
277
278
279
host = "unix";
}
SDL_memset(config_dir, 0, sizeof(config_dir));
280
conf_env = SDL_getenv("XDG_CONFIG_HOME");
281
if (conf_env && *conf_env) {
282
283
284
SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
} else {
const char *home_env = SDL_getenv("HOME");
285
if (!home_env || !*home_env) {
286
287
288
289
290
291
SDL_free(display);
return NULL;
}
SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
}
292
293
key = dbus->get_local_machine_id();
294
295
296
297
298
299
300
301
302
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);
}
303
304
305
306
307
308
309
310
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();
311
if (IBus_CheckConnection(dbus)) {
312
313
314
315
316
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
"SetCapabilities");
317
if (msg) {
318
Uint32 caps = IBUS_CAP_FOCUS;
319
if (!(internal_editing && *internal_editing == '1')) {
320
321
322
323
324
325
326
327
caps |= IBUS_CAP_PREEDIT_TEXT;
}
dbus->message_append_args(msg,
DBUS_TYPE_UINT32, &caps,
DBUS_TYPE_INVALID);
}
328
329
if (msg) {
if (dbus->connection_send(ibus_conn, msg, NULL)) {
330
331
332
333
334
335
336
337
dbus->connection_flush(ibus_conn);
}
dbus->message_unref(msg);
}
}
}
338
339
340
341
342
static SDL_bool
IBus_SetupConnection(SDL_DBusContext *dbus, const char* addr)
{
const char *path = NULL;
SDL_bool result = SDL_FALSE;
343
344
DBusMessage *msg;
345
346
ibus_conn = dbus->connection_open_private(addr, NULL);
347
if (!ibus_conn) {
348
349
350
351
352
return SDL_FALSE;
}
dbus->connection_flush(ibus_conn);
353
if (!dbus->bus_register(ibus_conn, NULL)) {
354
355
356
357
358
359
ibus_conn = NULL;
return SDL_FALSE;
}
dbus->connection_flush(ibus_conn);
360
msg = dbus->message_new_method_call(IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext");
361
if (msg) {
362
363
364
365
366
367
const char *client_name = "SDL2_Application";
dbus->message_append_args(msg,
DBUS_TYPE_STRING, &client_name,
DBUS_TYPE_INVALID);
}
368
if (msg) {
369
370
371
DBusMessage *reply;
reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 1000, NULL);
372
373
if (reply) {
if (dbus->message_get_args(reply, NULL,
374
DBUS_TYPE_OBJECT_PATH, &path,
375
376
DBUS_TYPE_INVALID)) {
if (input_ctx_path) {
377
378
379
380
381
382
383
384
385
386
SDL_free(input_ctx_path);
}
input_ctx_path = SDL_strdup(path);
result = SDL_TRUE;
}
dbus->message_unref(reply);
}
dbus->message_unref(msg);
}
387
if (result) {
388
SDL_AddHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL);
389
390
391
392
393
394
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);
}
395
SDL_IBus_SetFocus(SDL_GetKeyboardFocus() != NULL);
396
397
398
399
400
401
402
403
SDL_IBus_UpdateTextRect(NULL);
return result;
}
static SDL_bool
IBus_CheckConnection(SDL_DBusContext *dbus)
{
404
if (!dbus) return SDL_FALSE;
405
406
if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
407
408
409
return SDL_TRUE;
}
410
if (inotify_fd > 0 && inotify_wd > 0) {
411
412
char buf[1024];
ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
413
if (readsize > 0) {
414
415
416
417
char *p;
SDL_bool file_updated = SDL_FALSE;
418
for (p = buf; p < buf + readsize; /**/) {
419
struct inotify_event *event = (struct inotify_event*) p;
420
if (event->len > 0) {
421
char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
422
if (!addr_file_no_path) return SDL_FALSE;
423
424
if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
425
426
427
428
429
430
431
432
file_updated = SDL_TRUE;
break;
}
}
p += sizeof(struct inotify_event) + event->len;
}
433
if (file_updated) {
434
char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
435
if (addr) {
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
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();
453
if (dbus) {
454
char *addr_file = IBus_GetDBusAddressFilename();
455
456
457
char *addr;
char *addr_file_dir;
458
if (!addr_file) {
459
460
461
462
463
return SDL_FALSE;
}
ibus_addr_file = SDL_strdup(addr_file);
464
addr = IBus_ReadAddressFromFile(addr_file);
465
466
if (inotify_fd < 0) {
467
468
469
inotify_fd = inotify_init();
fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
}
470
471
addr_file_dir = SDL_strrchr(addr_file, '/');
472
if (addr_file_dir) {
473
474
475
*addr_file_dir = 0;
}
476
inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
477
478
479
480
481
482
483
484
485
486
487
488
SDL_free(addr_file);
result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
}
return result;
}
void
SDL_IBus_Quit(void)
{
489
490
SDL_DBusContext *dbus;
491
if (input_ctx_path) {
492
493
494
495
SDL_free(input_ctx_path);
input_ctx_path = NULL;
}
496
if (ibus_addr_file) {
497
498
499
500
SDL_free(ibus_addr_file);
ibus_addr_file = NULL;
}
501
dbus = SDL_DBus_GetContext();
502
503
if (dbus && ibus_conn) {
504
505
506
507
dbus->connection_close(ibus_conn);
dbus->connection_unref(ibus_conn);
}
508
if (inotify_fd > 0 && inotify_wd > 0) {
509
510
511
512
inotify_rm_watch(inotify_fd, inotify_wd);
inotify_wd = -1;
}
513
SDL_DelHintCallback(SDL_HINT_IME_INTERNAL_EDITING, &IBus_SetCapabilities, NULL);
514
515
516
517
518
519
520
521
522
SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
}
static void
IBus_SimpleMessage(const char *method)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
523
if (IBus_CheckConnection(dbus)) {
524
525
526
527
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
method);
528
529
if (msg) {
if (dbus->connection_send(ibus_conn, msg, NULL)) {
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
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();
556
if (IBus_CheckConnection(dbus)) {
557
558
559
560
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
"ProcessKeyEvent");
561
if (msg) {
562
563
564
565
566
567
568
569
Uint32 mods = IBus_ModState();
dbus->message_append_args(msg,
DBUS_TYPE_UINT32, &keysym,
DBUS_TYPE_UINT32, &keycode,
DBUS_TYPE_UINT32, &mods,
DBUS_TYPE_INVALID);
}
570
if (msg) {
571
572
573
DBusMessage *reply;
reply = dbus->connection_send_with_reply_and_block(ibus_conn, msg, 300, NULL);
574
575
if (reply) {
if (!dbus->message_get_args(reply, NULL,
576
DBUS_TYPE_BOOLEAN, &result,
577
DBUS_TYPE_INVALID)) {
578
579
580
581
582
583
584
585
586
result = SDL_FALSE;
}
dbus->message_unref(reply);
}
dbus->message_unref(msg);
}
}
587
588
SDL_IBus_UpdateTextRect(NULL);
589
590
591
592
593
594
return result;
}
void
SDL_IBus_UpdateTextRect(SDL_Rect *rect)
{
595
596
SDL_Window *focused_win;
SDL_SysWMinfo info;
597
598
599
int x = 0, y = 0;
SDL_DBusContext *dbus;
600
if (rect) {
601
602
603
SDL_memcpy(&ibus_cursor_rect, rect, sizeof(ibus_cursor_rect));
}
604
focused_win = SDL_GetKeyboardFocus();
605
606
607
608
if (!focused_win) {
return;
}
609
SDL_VERSION(&info.version);
610
611
612
if (!SDL_GetWindowWMInfo(focused_win, &info)) {
return;
}
613
614
SDL_GetWindowPosition(focused_win, &x, &y);
615
616
#if SDL_VIDEO_DRIVER_X11
617
618
if (info.subsystem == SDL_SYSWM_X11) {
SDL_DisplayData *displaydata = (SDL_DisplayData *) SDL_GetDisplayForWindow(focused_win)->driverdata;
619
620
621
622
623
624
Display *x_disp = info.info.x11.display;
Window x_win = info.info.x11.window;
int x_screen = displaydata->screen;
Window unused;
625
X11_XTranslateCoordinates(x_disp, x_win, RootWindow(x_disp, x_screen), 0, 0, &x, &y, &unused);
626
627
628
}
#endif
629
630
631
x += ibus_cursor_rect.x;
y += ibus_cursor_rect.y;
632
dbus = SDL_DBus_GetContext();
633
634
if (IBus_CheckConnection(dbus)) {
635
636
637
638
DBusMessage *msg = dbus->message_new_method_call(IBUS_SERVICE,
input_ctx_path,
IBUS_INPUT_INTERFACE,
"SetCursorLocation");
639
if (msg) {
640
641
642
643
644
645
646
647
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);
}
648
649
if (msg) {
if (dbus->connection_send(ibus_conn, msg, NULL)) {
650
651
652
653
654
655
656
657
658
659
660
661
dbus->connection_flush(ibus_conn);
}
dbus->message_unref(msg);
}
}
}
void
SDL_IBus_PumpEvents(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
662
if (IBus_CheckConnection(dbus)) {
663
664
dbus->connection_read_write(ibus_conn, 0);
665
while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
666
667
668
669
670
671
/* Do nothing, actual work happens in IBus_MessageFilter */
}
}
}
#endif