client.c 34.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 
 * Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
 * Copyright (C) 2010-2011 Red Hat, Inc.
 * 
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */
Daiki Ueno's avatar
Daiki Ueno committed
18
19
20
21
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif  /* HAVE_CONFIG_H */

22
#include <libxklavier/xklavier.h>
Daiki Ueno's avatar
Daiki Ueno committed
23

Daiki Ueno's avatar
Daiki Ueno committed
24
25
26
27
#ifdef HAVE_ATSPI
#include <dbus/dbus.h>
#include <atspi/atspi.h>
#endif  /* HAVE_ATSPI */
Daiki Ueno's avatar
Daiki Ueno committed
28

29
30
#include <gdk/gdkx.h>

31
32
#ifdef HAVE_XTEST
#include <X11/extensions/XTest.h>
33
#include <X11/XKBlib.h>
34
#endif  /* HAVE_XTEST */
35

Daiki Ueno's avatar
Daiki Ueno committed
36
37
38
39
#ifdef HAVE_IBUS
#include <ibus.h>
#endif  /* HAVE_IBUS */

40
41
#include "eek/eek.h"
#include "eek/eek-xkl.h"
Daiki Ueno's avatar
Daiki Ueno committed
42
43
#include "eekboard/eekboard-client.h"
#include "eekboard/eekboard-xklutil.h"
44
#include "client.h"
45

46
47
#include <string.h>

48
49
50
51
52
53
#define CSW 640
#define CSH 480

enum {
    PROP_0,
    PROP_CONNECTION,
Daiki Ueno's avatar
Daiki Ueno committed
54
    PROP_EEKBOARD,
55
    PROP_CONTEXT,
56
    PROP_KEYBOARDS,
57
58
59
    PROP_LAST
};

Daiki Ueno's avatar
Daiki Ueno committed
60
typedef struct _ClientClass ClientClass;
61

Daiki Ueno's avatar
Daiki Ueno committed
62
struct _Client {
63
64
    GObject parent;

Daiki Ueno's avatar
Daiki Ueno committed
65
    EekboardClient *eekboard;
66
    EekboardContext *context;
67

Daiki Ueno's avatar
Daiki Ueno committed
68
    GSList *keyboards;
69
70
    GSList *keyboards_head;

71
72
73
    XklEngine *xkl_engine;
    XklConfigRegistry *xkl_config_registry;

74
75
    gulong xkl_config_changed_handler;
    gulong xkl_state_changed_handler;
76

77
    gulong key_activated_handler;
78

Daiki Ueno's avatar
Daiki Ueno committed
79
    gboolean follows_focus;
Daiki Ueno's avatar
Daiki Ueno committed
80
    guint hide_keyboard_timeout_id;
Daiki Ueno's avatar
Daiki Ueno committed
81

Daiki Ueno's avatar
Daiki Ueno committed
82
83
84
85
#ifdef HAVE_ATSPI
    AtspiAccessible *acc;
    AtspiDeviceListener *keystroke_listener;
#endif  /* HAVE_ATSPI */
Daiki Ueno's avatar
Daiki Ueno committed
86

Daiki Ueno's avatar
Daiki Ueno committed
87
88
89
90
91
#ifdef HAVE_IBUS
    IBusBus *ibus_bus;
    guint ibus_focus_message_filter;
#endif  /* HAVE_IBUS */

92
93
#ifdef HAVE_XTEST
    KeyCode modifier_keycodes[8]; 
94
    XkbDescRec *xkb;
95
#endif  /* HAVE_XTEST */
96
97

    GSettings *settings;
98
99
};

Daiki Ueno's avatar
Daiki Ueno committed
100
struct _ClientClass {
101
102
103
    GObjectClass parent_class;
};

Daiki Ueno's avatar
Daiki Ueno committed
104
G_DEFINE_TYPE (Client, client, G_TYPE_OBJECT);
105

106
107
108
109
110
111
#if ENABLE_FOCUS_LISTENER
#define IS_KEYBOARD_VISIBLE(client) (!client->follows_focus)
#else  /* ENABLE_FOCUS_LISTENER */
#define IS_KEYBOARD_VISIBLE(client) TRUE
#endif  /* !ENABLE_FOCUS_LISTENER */

Daiki Ueno's avatar
Daiki Ueno committed
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
static GdkFilterReturn filter_xkl_event     (GdkXEvent              *xev,
                                             GdkEvent               *event,
                                             gpointer                user_data);
static void            on_xkl_config_changed (XklEngine              *xklengine,
                                              gpointer                user_data);

static void            on_xkl_state_changed (XklEngine              *xklengine,
                                             XklEngineStateChange    type,
                                             gint                    value,
                                             gboolean                restore,
                                             gpointer                user_data);

#ifdef HAVE_ATSPI
static void            focus_listener_cb    (const AtspiEvent       *event,
                                             void                   *user_data);

static gboolean        keystroke_listener_cb (const AtspiDeviceEvent *stroke,
                                              void                   *user_data);
#endif  /* HAVE_ATSPI */
131
static gboolean        set_keyboards        (Client                 *client,
Daiki Ueno's avatar
Daiki Ueno committed
132
                                             const gchar * const    *keyboard);
133
134
static gboolean        set_keyboards_from_xkl
                                            (Client                 *client);
135
136
#ifdef HAVE_XTEST
static void            update_modifier_keycodes
137
                                            (Client                 *client);
138
#endif  /* HAVE_XTEST */
139
140

static void
Daiki Ueno's avatar
Daiki Ueno committed
141
142
143
144
client_set_property (GObject      *object,
                     guint         prop_id,
                     const GValue *value,
                     GParamSpec   *pspec)
145
{
Daiki Ueno's avatar
Daiki Ueno committed
146
    Client *client = CLIENT(object);
147
148
149
150
151
    GDBusConnection *connection;

    switch (prop_id) {
    case PROP_CONNECTION:
        connection = g_value_get_object (value);
152

Daiki Ueno's avatar
Daiki Ueno committed
153
        client->eekboard = eekboard_client_new (connection, NULL);
Daiki Ueno's avatar
Daiki Ueno committed
154
155
        if (client->eekboard != NULL) {
            client->context =
Daiki Ueno's avatar
Daiki Ueno committed
156
157
158
                eekboard_client_create_context (client->eekboard,
                                                "eekboard",
                                                NULL);
Daiki Ueno's avatar
Daiki Ueno committed
159
160
161
            if (client->context == NULL) {
                g_object_unref (client->eekboard);
                client->eekboard = NULL;
162
            } else {
Daiki Ueno's avatar
Daiki Ueno committed
163
164
165
                eekboard_client_push_context (client->eekboard,
                                              client->context,
                                              NULL);
166
167
168
169
                g_settings_bind (client->settings, "keyboards",
                                 client, "keyboards",
                                 G_SETTINGS_BIND_GET);
            }
Daiki Ueno's avatar
Daiki Ueno committed
170
        }
171
        break;
172
173
174
    case PROP_KEYBOARDS:
        client_set_keyboards (client, g_value_get_boxed (value));
        break;
175
    default:
176
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
177
178
179
180
        break;
    }
}

181
static void
Daiki Ueno's avatar
Daiki Ueno committed
182
183
184
185
client_get_property (GObject    *object,
                     guint       prop_id,
                     GValue     *value,
                     GParamSpec *pspec)
186
{
Daiki Ueno's avatar
Daiki Ueno committed
187
    Client *client = CLIENT(object);
188
189

    switch (prop_id) {
Daiki Ueno's avatar
Daiki Ueno committed
190
191
192
    case PROP_EEKBOARD:
        g_value_set_object (value, client->eekboard);
        break;
193
194
195
196
    case PROP_CONTEXT:
        g_value_set_object (value, client->context);
        break;
    default:
197
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
198
199
200
201
        break;
    }
}

202
static void
Daiki Ueno's avatar
Daiki Ueno committed
203
client_dispose (GObject *object)
204
{
Daiki Ueno's avatar
Daiki Ueno committed
205
    Client *client = CLIENT(object);
206

Daiki Ueno's avatar
Daiki Ueno committed
207
    client_disable_xkl (client);
Daiki Ueno's avatar
Daiki Ueno committed
208

Daiki Ueno's avatar
Daiki Ueno committed
209
#ifdef HAVE_ATSPI
Daiki Ueno's avatar
Daiki Ueno committed
210
211
    client_disable_atspi_focus (client);
    client_disable_atspi_keystroke (client);
Daiki Ueno's avatar
Daiki Ueno committed
212
#endif  /* HAVE_ATSPI */
Daiki Ueno's avatar
Daiki Ueno committed
213

Daiki Ueno's avatar
Daiki Ueno committed
214
#ifdef HAVE_IBUS
Daiki Ueno's avatar
Daiki Ueno committed
215
    client_disable_ibus_focus (client);
Daiki Ueno's avatar
Daiki Ueno committed
216
217
218
219
220
221
    if (client->ibus_bus) {
        g_object_unref (client->ibus_bus);
        client->ibus_bus = NULL;
    }
#endif  /* HAVE_IBUS */

222
#ifdef HAVE_XTEST
Daiki Ueno's avatar
Daiki Ueno committed
223
    client_disable_xtest (client);
224
#endif  /* HAVE_XTEST */
225

226
227
228
229
230
    if (client->context) {
        g_object_unref (client->context);
        client->context = NULL;
    }

231
232
233
    if (client->eekboard) {
        g_object_unref (client->eekboard);
        client->eekboard = NULL;
234
235
    }

236
237
238
239
240
    if (client->settings) {
        g_object_unref (client->settings);
        client->settings = NULL;
    }

Daiki Ueno's avatar
Daiki Ueno committed
241
    G_OBJECT_CLASS (client_parent_class)->dispose (object);
242
243
}

Daiki Ueno's avatar
Daiki Ueno committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
static void
client_finalize (GObject *object)
{
    Client *client = CLIENT(object);

    if (client->keyboards) {
        GSList *next = client->keyboards->next;
        /* client->keyboards is a ring; break it before free */
        client->keyboards->next = NULL;
        g_slist_free (next);
    }

    G_OBJECT_CLASS (client_parent_class)->finalize (object);
}

259
static void
Daiki Ueno's avatar
Daiki Ueno committed
260
client_class_init (ClientClass *klass)
261
262
263
264
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    GParamSpec *pspec;

Daiki Ueno's avatar
Daiki Ueno committed
265
266
267
    gobject_class->set_property = client_set_property;
    gobject_class->get_property = client_get_property;
    gobject_class->dispose = client_dispose;
Daiki Ueno's avatar
Daiki Ueno committed
268
    gobject_class->finalize = client_finalize;
269
270
271
272
273
274
275
276
277

    pspec = g_param_spec_object ("connection",
                                 "Connection",
                                 "Connection",
                                 G_TYPE_DBUS_CONNECTION,
                                 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE);
    g_object_class_install_property (gobject_class,
                                     PROP_CONNECTION,
                                     pspec);
278

Daiki Ueno's avatar
Daiki Ueno committed
279
280
281
    pspec = g_param_spec_object ("eekboard",
                                 "Eekboard",
                                 "Eekboard",
Daiki Ueno's avatar
Daiki Ueno committed
282
                                 EEKBOARD_TYPE_CLIENT,
Daiki Ueno's avatar
Daiki Ueno committed
283
284
285
286
287
                                 G_PARAM_READABLE);
    g_object_class_install_property (gobject_class,
                                     PROP_EEKBOARD,
                                     pspec);

288
289
290
291
292
293
294
295
    pspec = g_param_spec_object ("context",
                                 "Context",
                                 "Context",
                                 EEKBOARD_TYPE_CONTEXT,
                                 G_PARAM_READABLE);
    g_object_class_install_property (gobject_class,
                                     PROP_CONTEXT,
                                     pspec);
296
297
298
299
300
301
302
303
304

    pspec = g_param_spec_boxed ("keyboards",
                                "Keyboards",
                                "Keyboards",
                                G_TYPE_STRV,
                                G_PARAM_WRITABLE);
    g_object_class_install_property (gobject_class,
                                     PROP_KEYBOARDS,
                                     pspec);
305
306
307
}

static void
Daiki Ueno's avatar
Daiki Ueno committed
308
client_init (Client *client)
309
{
310
    client->settings = g_settings_new ("org.fedorahosted.eekboard");
311
312
}

313
gboolean
Daiki Ueno's avatar
Daiki Ueno committed
314
315
client_set_keyboards (Client              *client,
                      const gchar * const *keyboards)
316
317
{
    gboolean retval;
318
    retval = set_keyboards (client, keyboards);
319
    if (retval && IS_KEYBOARD_VISIBLE (client))
320
321
        eekboard_context_show_keyboard (client->context, NULL);
    return retval;
322
323
}

324
gboolean
Daiki Ueno's avatar
Daiki Ueno committed
325
client_enable_xkl (Client *client)
326
{
Daiki Ueno's avatar
Daiki Ueno committed
327
    GdkDisplay *display = gdk_display_get_default ();
328
329
    gboolean retval;

Daiki Ueno's avatar
Daiki Ueno committed
330
    g_assert (display);
331

332
333
    if (!client->xkl_engine) {
        client->xkl_engine =
Daiki Ueno's avatar
Daiki Ueno committed
334
            xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY (display));
335
    }
336
    g_assert (client->xkl_engine);
337
338
339
340
341
342
343

    if (!client->xkl_config_registry) {
        client->xkl_config_registry =
            xkl_config_registry_get_instance (client->xkl_engine);
        xkl_config_registry_load (client->xkl_config_registry, FALSE);
    }

344
    client->xkl_config_changed_handler =
345
346
        g_signal_connect (client->xkl_engine, "X-config-changed",
                          G_CALLBACK(on_xkl_config_changed), client);
347
    client->xkl_state_changed_handler =
348
349
350
351
352
353
354
355
356
357
358
359
        g_signal_connect (client->xkl_engine, "X-state-changed",
                          G_CALLBACK(on_xkl_state_changed), client);

    gdk_window_add_filter (NULL,
                           (GdkFilterFunc) filter_xkl_event,
                           client);
    gdk_window_add_filter (gdk_get_default_root_window (),
                           (GdkFilterFunc) filter_xkl_event,
                           client);

    xkl_engine_start_listen (client->xkl_engine, XKLL_TRACK_KEYBOARD_STATE);

360
    retval = set_keyboards_from_xkl (client);
361
362
363
364
    if (IS_KEYBOARD_VISIBLE (client))
        eekboard_context_show_keyboard (client->context, NULL);

    return retval;
365
366
367
}

void
Daiki Ueno's avatar
Daiki Ueno committed
368
client_disable_xkl (Client *client)
369
{
370
    if (client->xkl_engine) {
371
        xkl_engine_stop_listen (client->xkl_engine, XKLL_TRACK_KEYBOARD_STATE);
372
373
374
375
376
377
378
379
380
381
382

        if (g_signal_handler_is_connected (client->xkl_engine,
                                           client->xkl_config_changed_handler))
            g_signal_handler_disconnect (client->xkl_engine,
                                         client->xkl_config_changed_handler);
        if (g_signal_handler_is_connected (client->xkl_engine,
                                           client->xkl_state_changed_handler))
            g_signal_handler_disconnect (client->xkl_engine,
                                         client->xkl_state_changed_handler);
        client->xkl_engine = NULL;
    }
383
384
}

Daiki Ueno's avatar
Daiki Ueno committed
385
#ifdef HAVE_ATSPI
386
gboolean
Daiki Ueno's avatar
Daiki Ueno committed
387
client_enable_atspi_focus (Client *client)
388
{
Daiki Ueno's avatar
Daiki Ueno committed
389
    GError *error;
390

Daiki Ueno's avatar
Daiki Ueno committed
391
392
393
394
395
396
    error = NULL;
    if (!atspi_event_listener_register_from_callback
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         NULL,
         "object:state-changed:focused",
Daiki Ueno's avatar
Daiki Ueno committed
397
398
399
400
         &error)) {
        g_warning ("can't register object:state-changed:focused handler: %s",
                   error->message);
        g_error_free (error);
401
        return FALSE;
Daiki Ueno's avatar
Daiki Ueno committed
402
    }
403

Daiki Ueno's avatar
Daiki Ueno committed
404
405
406
407
408
409
    error = NULL;
    if (!atspi_event_listener_register_from_callback
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         NULL,
         "focus:",
Daiki Ueno's avatar
Daiki Ueno committed
410
411
412
413
         &error)) {
        g_warning ("can't register focus: handler: %s",
                   error->message);
        g_error_free (error);
414
        return FALSE;
Daiki Ueno's avatar
Daiki Ueno committed
415
    }
416

Daiki Ueno's avatar
Daiki Ueno committed
417
    client->follows_focus = TRUE;
418
419
420
421
    return TRUE;
}

void
Daiki Ueno's avatar
Daiki Ueno committed
422
client_disable_atspi_focus (Client *client)
423
{
Daiki Ueno's avatar
Daiki Ueno committed
424
425
426
427
428
    GError *error;

    client->follows_focus = FALSE;

    error = NULL;
Daiki Ueno's avatar
Daiki Ueno committed
429
    if (!atspi_event_listener_deregister_from_callback
Daiki Ueno's avatar
Daiki Ueno committed
430
431
432
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         "object:state-changed:focused",
Daiki Ueno's avatar
Daiki Ueno committed
433
434
435
436
437
         &error)) {
        g_warning ("can't deregister object:state-changed:focused handler: %s",
                   error->message);
        g_error_free (error);
    }
Daiki Ueno's avatar
Daiki Ueno committed
438
439

    error = NULL;
Daiki Ueno's avatar
Daiki Ueno committed
440
    if (!atspi_event_listener_deregister_from_callback
Daiki Ueno's avatar
Daiki Ueno committed
441
442
443
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         "focus:",
Daiki Ueno's avatar
Daiki Ueno committed
444
445
446
447
448
         &error)) {
        g_warning ("can't deregister focus: handler: %s",
                   error->message);
        g_error_free (error);
    }
449
450
451
}

gboolean
Daiki Ueno's avatar
Daiki Ueno committed
452
client_enable_atspi_keystroke (Client *client)
453
{
Daiki Ueno's avatar
Daiki Ueno committed
454
455
    GError *error;

456
    client->keystroke_listener =
Daiki Ueno's avatar
Daiki Ueno committed
457
        atspi_device_listener_new ((AtspiDeviceListenerCB)keystroke_listener_cb,
Daiki Ueno's avatar
Daiki Ueno committed
458
459
                                   client,
                                   NULL);
460

Daiki Ueno's avatar
Daiki Ueno committed
461
462
463
464
465
466
467
    error = NULL;
    if (!atspi_register_keystroke_listener
        (client->keystroke_listener,
         NULL,
         0,
         ATSPI_KEY_PRESSED,
         ATSPI_KEYLISTENER_NOSYNC,
Daiki Ueno's avatar
Daiki Ueno committed
468
469
470
471
         &error)) {
        g_warning ("can't register keystroke listener for key press: %s",
                   error->message);
        g_error_free (error);
Daiki Ueno's avatar
Daiki Ueno committed
472
        return FALSE;
Daiki Ueno's avatar
Daiki Ueno committed
473
    }
Daiki Ueno's avatar
Daiki Ueno committed
474
475
476

    error = NULL;
    if (!atspi_register_keystroke_listener
477
        (client->keystroke_listener,
Daiki Ueno's avatar
Daiki Ueno committed
478
         NULL,
479
         0,
Daiki Ueno's avatar
Daiki Ueno committed
480
481
         ATSPI_KEY_RELEASED,
         ATSPI_KEYLISTENER_NOSYNC,
Daiki Ueno's avatar
Daiki Ueno committed
482
483
484
485
         &error)) {
        g_warning ("can't register keystroke listener for key release: %s",
                   error->message);
        g_error_free (error);
486
        return FALSE;
Daiki Ueno's avatar
Daiki Ueno committed
487
    }
488
489
490
491
    return TRUE;
}

void
Daiki Ueno's avatar
Daiki Ueno committed
492
client_disable_atspi_keystroke (Client *client)
493
494
{
    if (client->keystroke_listener) {
Daiki Ueno's avatar
Daiki Ueno committed
495
496
497
        GError *error;

        error = NULL;
Daiki Ueno's avatar
Daiki Ueno committed
498
499
500
501
502
503
504
505
506
507
        if (!atspi_deregister_keystroke_listener
            (client->keystroke_listener,
             NULL,
             0,
             ATSPI_KEY_PRESSED,
             &error)) {
            g_warning ("can't deregister keystroke listener for key press: %s",
                       error->message);
            g_error_free (error);
        }
Daiki Ueno's avatar
Daiki Ueno committed
508
509

        error = NULL;
Daiki Ueno's avatar
Daiki Ueno committed
510
511
512
513
514
515
516
517
518
        if (!atspi_deregister_keystroke_listener (client->keystroke_listener,
                                                  NULL,
                                                  0,
                                                  ATSPI_KEY_RELEASED,
                                                  &error)) {
            g_warning ("can't deregister keystroke listener for key release: %s",
                       error->message);
            g_error_free (error);
        }
Daiki Ueno's avatar
Daiki Ueno committed
519
520

        g_object_unref (client->keystroke_listener);
521
522
523
524
        client->keystroke_listener = NULL;
    }
}

Daiki Ueno's avatar
Daiki Ueno committed
525
526
527
static void
focus_listener_cb (const AtspiEvent *event,
                   void             *user_data)
528
{
Daiki Ueno's avatar
Daiki Ueno committed
529
    Client *client = user_data;
Daiki Ueno's avatar
Daiki Ueno committed
530
531
532
533
534
535
536
    AtspiAccessible *accessible = event->source;
    AtspiStateSet *state_set = atspi_accessible_get_state_set (accessible);
    AtspiRole role;
    GError *error;

    error = NULL;
    role = atspi_accessible_get_role (accessible, &error);
Daiki Ueno's avatar
Daiki Ueno committed
537
    if (role == ATSPI_ROLE_INVALID) {
Daiki Ueno's avatar
Daiki Ueno committed
538
539
540
        g_warning ("can't get accessible role: %s",
                   error->message);
        g_error_free (error);
Daiki Ueno's avatar
Daiki Ueno committed
541
        return;
Daiki Ueno's avatar
Daiki Ueno committed
542
    }
543

Daiki Ueno's avatar
Daiki Ueno committed
544
545
    if (atspi_state_set_contains (state_set, ATSPI_STATE_EDITABLE) ||
        role == ATSPI_ROLE_TERMINAL) {
546
        switch (role) {
Daiki Ueno's avatar
Daiki Ueno committed
547
548
549
550
        case ATSPI_ROLE_TEXT:
        case ATSPI_ROLE_PARAGRAPH:
        case ATSPI_ROLE_PASSWORD_TEXT:
        case ATSPI_ROLE_TERMINAL:
Daiki Ueno's avatar
Daiki Ueno committed
551
552
553
            if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) {
                client->acc = accessible;
                eekboard_context_show_keyboard (client->context, NULL);
554
555
            } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
                       event->detail1 == 0 && accessible == client->acc) {
Daiki Ueno's avatar
Daiki Ueno committed
556
557
558
559
                client->acc = NULL;
                eekboard_context_hide_keyboard (client->context, NULL);
            }
            break;
Daiki Ueno's avatar
Daiki Ueno committed
560
        case ATSPI_ROLE_ENTRY:
Daiki Ueno's avatar
Daiki Ueno committed
561
562
            if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) {
                client->acc = accessible;
563
                eekboard_context_show_keyboard (client->context, NULL);
564
565
            } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
                       event->detail1 == 0) {
Daiki Ueno's avatar
Daiki Ueno committed
566
567
                client->acc = NULL;
                eekboard_context_hide_keyboard (client->context, NULL);
568
            }
Daiki Ueno's avatar
Daiki Ueno committed
569
570
            break;
            
571
572
573
        default:
            ;
        }
574
575
576
    } else {
        eekboard_context_hide_keyboard (client->context, NULL);
    }
577
578
}

Daiki Ueno's avatar
Daiki Ueno committed
579
580
581
static gboolean
keystroke_listener_cb (const AtspiDeviceEvent *stroke,
                       void                   *user_data)
582
{
Daiki Ueno's avatar
Daiki Ueno committed
583
    Client *client = user_data;
584

585
586
    switch (stroke->type) {
    case ATSPI_KEY_PRESSED:
Daiki Ueno's avatar
Daiki Ueno committed
587
        eekboard_context_press_keycode (client->context, stroke->hw_code, NULL);
588
589
        break;
    case ATSPI_KEY_RELEASED:
Daiki Ueno's avatar
Daiki Ueno committed
590
        eekboard_context_release_keycode (client->context, stroke->hw_code, NULL);
591
592
593
        break;
    default:
        g_return_val_if_reached (FALSE);
594
    }
595
596
    return TRUE;
}
Daiki Ueno's avatar
Daiki Ueno committed
597
#endif  /* HAVE_ATSPI */
Daiki Ueno's avatar
Daiki Ueno committed
598

Daiki Ueno's avatar
Daiki Ueno committed
599
600
601
602
603
#ifdef HAVE_IBUS
static void
add_match_rule (GDBusConnection *connection,
                const gchar     *match_rule)
{
Daiki Ueno's avatar
Daiki Ueno committed
604
605
    GError *error;
    GDBusMessage *message;
Daiki Ueno's avatar
Daiki Ueno committed
606

Daiki Ueno's avatar
Daiki Ueno committed
607
608
609
610
611
612
    message = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
                                              "/org/freedesktop/DBus", /* path */
                                              "org.freedesktop.DBus", /* interface */
                                              "AddMatch");
    g_dbus_message_set_body (message, g_variant_new ("(s)", match_rule));
    error = NULL;
Daiki Ueno's avatar
Daiki Ueno committed
613
614
615
616
617
618
619
620
621
    if (!g_dbus_connection_send_message (connection,
                                         message,
                                         G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                         NULL,
                                         &error)) {
        g_warning ("can't register match rule %s: %s",
                   match_rule, error->message);
        g_error_free (error);
    }
Daiki Ueno's avatar
Daiki Ueno committed
622
    g_object_unref (message);
Daiki Ueno's avatar
Daiki Ueno committed
623
624
}

Daiki Ueno's avatar
Daiki Ueno committed
625
static gboolean
Daiki Ueno's avatar
Daiki Ueno committed
626
on_hide_keyboard_timeout (Client *client)
Daiki Ueno's avatar
Daiki Ueno committed
627
628
629
630
631
632
{
    eekboard_context_hide_keyboard (client->context, NULL);
    client->hide_keyboard_timeout_id = 0;
    return FALSE;
}

Daiki Ueno's avatar
Daiki Ueno committed
633
634
635
636
637
638
static GDBusMessage *
focus_message_filter (GDBusConnection *connection,
                      GDBusMessage    *message,
                      gboolean         incoming,
                      gpointer         user_data)
{
Daiki Ueno's avatar
Daiki Ueno committed
639
    Client *client = user_data;
Daiki Ueno's avatar
Daiki Ueno committed
640
641
642

    if (incoming &&
        g_strcmp0 (g_dbus_message_get_interface (message),
643
                   IBUS_INTERFACE_PANEL) == 0) {
Daiki Ueno's avatar
Daiki Ueno committed
644
645
646
        const gchar *member = g_dbus_message_get_member (message);

        if (g_strcmp0 (member, "FocusIn") == 0) {
Daiki Ueno's avatar
Daiki Ueno committed
647
648
649
650
            if (client->hide_keyboard_timeout_id > 0) {
                g_source_remove (client->hide_keyboard_timeout_id);
                client->hide_keyboard_timeout_id = 0;
            }
Daiki Ueno's avatar
Daiki Ueno committed
651
            eekboard_context_show_keyboard (client->context, NULL);
652
653
        } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
                   g_strcmp0 (member, "FocusOut") == 0) {
654
655
            guint delay;
            g_settings_get (client->settings, "auto-hide-delay", "u", &delay);
Daiki Ueno's avatar
Daiki Ueno committed
656
            client->hide_keyboard_timeout_id =
657
                g_timeout_add (delay,
Daiki Ueno's avatar
Daiki Ueno committed
658
659
                               (GSourceFunc)on_hide_keyboard_timeout,
                               client);
Daiki Ueno's avatar
Daiki Ueno committed
660
661
662
663
664
665
        }
    }

    return message;
}

666
667
static void
_ibus_connect_focus_handlers (IBusBus *bus, gpointer user_data)
Daiki Ueno's avatar
Daiki Ueno committed
668
{
Daiki Ueno's avatar
Daiki Ueno committed
669
    Client *client = user_data;
Daiki Ueno's avatar
Daiki Ueno committed
670
671
    GDBusConnection *connection;

672
    connection = ibus_bus_get_connection (bus);
Daiki Ueno's avatar
Daiki Ueno committed
673
674
    add_match_rule (connection,
                    "type='method_call',"
675
                    "interface='" IBUS_INTERFACE_PANEL "',"
Daiki Ueno's avatar
Daiki Ueno committed
676
                    "member='FocusIn'");
677
678
    add_match_rule (connection,
                    "type='method_call',"
679
                    "interface='" IBUS_INTERFACE_PANEL "',"
680
                    "member='FocusOut'");
Daiki Ueno's avatar
Daiki Ueno committed
681
682
683
684
685
    client->ibus_focus_message_filter =
        g_dbus_connection_add_filter (connection,
                                      focus_message_filter,
                                      client,
                                      NULL);
686
687
688
}

gboolean
Daiki Ueno's avatar
Daiki Ueno committed
689
client_enable_ibus_focus (Client *client)
690
691
692
693
694
695
696
697
698
699
700
701
{
    if (!client->ibus_bus) {
        client->ibus_bus = ibus_bus_new ();
        g_object_ref (client->ibus_bus);
        g_signal_connect (client->ibus_bus, "connected",
                          G_CALLBACK(_ibus_connect_focus_handlers),
                          client);
    }

    if (ibus_bus_is_connected (client->ibus_bus))
        _ibus_connect_focus_handlers (client->ibus_bus, client);

Daiki Ueno's avatar
Daiki Ueno committed
702
703
704
705
706
    client->follows_focus = TRUE;
    return TRUE;
}

void
Daiki Ueno's avatar
Daiki Ueno committed
707
client_disable_ibus_focus (Client *client)
Daiki Ueno's avatar
Daiki Ueno committed
708
709
710
711
712
{
    GDBusConnection *connection;

    client->follows_focus = FALSE;

713
714
715
716
717
718
719
720
721
    if (client->ibus_bus) {
        if (client->ibus_focus_message_filter != 0) {
            connection = ibus_bus_get_connection (client->ibus_bus);
            g_dbus_connection_remove_filter (connection,
                                             client->ibus_focus_message_filter);
        }
        g_object_unref (client->ibus_bus);
        client->ibus_bus = NULL;
    }
Daiki Ueno's avatar
Daiki Ueno committed
722
723
724
}
#endif  /* HAVE_ATSPI */

Daiki Ueno's avatar
Daiki Ueno committed
725
726
Client *
client_new (GDBusConnection *connection)
Daiki Ueno's avatar
Daiki Ueno committed
727
{
Daiki Ueno's avatar
Daiki Ueno committed
728
729
730
    Client *client = g_object_new (TYPE_CLIENT,
                                   "connection", connection,
                                   NULL);
Daiki Ueno's avatar
Daiki Ueno committed
731
732
733
    if (client->context)
        return client;
    return NULL;
Daiki Ueno's avatar
Daiki Ueno committed
734
}
735
736
737
738
739
740

static GdkFilterReturn
filter_xkl_event (GdkXEvent *xev,
                  GdkEvent  *event,
                  gpointer   user_data)
{
Daiki Ueno's avatar
Daiki Ueno committed
741
    Client *client = user_data;
742
743
744
745
746
747
748
749
750
751
    XEvent *xevent = (XEvent *)xev;

    xkl_engine_filter_events (client->xkl_engine, xevent);
    return GDK_FILTER_CONTINUE;
}

static void
on_xkl_config_changed (XklEngine *xklengine,
                       gpointer   user_data)
{
Daiki Ueno's avatar
Daiki Ueno committed
752
    Client *client = user_data;
753
    gboolean retval;
754

755
    retval = set_keyboards_from_xkl (client);
756
    g_return_if_fail (retval);
757

758
#ifdef HAVE_XTEST
759
    update_modifier_keycodes (client);
760
#endif  /* HAVE_XTEST */
761
762
}

763
static gboolean
Daiki Ueno's avatar
Daiki Ueno committed
764
765
set_keyboards (Client              *client,
               const gchar * const *keyboards)
766
{
767
    guint keyboard_id;
Daiki Ueno's avatar
Daiki Ueno committed
768
    const gchar * const *p;
769
770
771
772
    GSList *head;

    g_return_val_if_fail (keyboards != NULL, FALSE);
    g_return_val_if_fail (client->context, FALSE);
Daiki Ueno's avatar
Daiki Ueno committed
773

774
775
776
777
778
779
    if (client->keyboards) {
        for (head = client->keyboards; head; head = head->next) {
            eekboard_context_remove_keyboard (client->context,
                                              GPOINTER_TO_UINT(head->data),
                                              NULL);
        }
Daiki Ueno's avatar
Daiki Ueno committed
780
        g_slist_free (client->keyboards);
781
782
        client->keyboards = NULL;
    }
783

784
785
786
787
    for (p = keyboards; *p != NULL; p++) {
        keyboard_id = eekboard_context_add_keyboard (client->context, *p, NULL);
        if (keyboard_id == 0) {
            g_slist_free (head);
Daiki Ueno's avatar
Daiki Ueno committed
788
            return FALSE;
789
        }
790
791
        client->keyboards = g_slist_prepend (client->keyboards,
                                             GUINT_TO_POINTER(keyboard_id));
Daiki Ueno's avatar
Daiki Ueno committed
792
793
    }

794
795
    client->keyboards = g_slist_reverse (client->keyboards);
    client->keyboards_head = client->keyboards;
Daiki Ueno's avatar
Daiki Ueno committed
796
797
798

    /* select the first keyboard */
    eekboard_context_set_keyboard (client->context,
799
                                   GPOINTER_TO_UINT(client->keyboards_head->data),
Daiki Ueno's avatar
Daiki Ueno committed
800
                                   NULL);
Daiki Ueno's avatar
Daiki Ueno committed
801
802
803
    return TRUE;
}

804
static gboolean
805
set_keyboards_from_xkl (Client *client)
806
807
808
{
    XklConfigRec *rec;
    gchar *layout, *keyboard;
Daiki Ueno's avatar
Daiki Ueno committed
809
    guint keyboard_id;
810
811
812
813
814
815
816
817

    rec = xkl_config_rec_new ();
    xkl_config_rec_get_from_server (rec, client->xkl_engine);
    layout = eekboard_xkl_config_rec_to_string (rec);
    g_object_unref (rec);

    keyboard = g_strdup_printf ("xkb:%s", layout);
    g_free (layout);
Daiki Ueno's avatar
Daiki Ueno committed
818
819
820
821

    keyboard_id = eekboard_context_add_keyboard (client->context,
                                                 keyboard,
                                                 NULL);
822
    g_free (keyboard);
Daiki Ueno's avatar
Daiki Ueno committed
823
824
825
    if (keyboard_id == 0)
        return FALSE;
    eekboard_context_set_keyboard (client->context, keyboard_id, NULL);
826

Daiki Ueno's avatar
Daiki Ueno committed
827
    return TRUE;
828
829
}

830
831
832
833
834
835
836
static void
on_xkl_state_changed (XklEngine           *xklengine,
                      XklEngineStateChange type,
                      gint                 value,
                      gboolean             restore,
                      gpointer             user_data)
{
Daiki Ueno's avatar
Daiki Ueno committed
837
    Client *client = user_data;
838

839
840
    if (type == GROUP_CHANGED)
        eekboard_context_set_group (client->context, value, NULL);
841
842
}

843
#ifdef HAVE_XTEST
844
845
846
847
848
849
850
851
/* The following functions for keyboard mapping change are direct
   translation of the code in Caribou (in libcaribou/xadapter.vala):

   - get_replaced_keycode (Caribou: get_reserved_keycode)
   - replace_keycode
   - get_keycode_from_gdk_keymap (Caribou: best_keycode_keyval_match)
*/
static guint
Daiki Ueno's avatar
Daiki Ueno committed
852
get_replaced_keycode (Client *client)
853
{
Daiki Ueno's avatar
Daiki Ueno committed
854
855
    GdkDisplay *display = gdk_display_get_default ();
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
856
857
858
859
    gint i;

    for (i = client->xkb->max_key_code; i >= client->xkb->min_key_code; --i)
        if (client->xkb->map->key_sym_map[i].kt_index[0] == XkbOneLevelIndex &&
Daiki Ueno's avatar
Daiki Ueno committed
860
            XKeycodeToKeysym (xdisplay, i, 0) != 0)
861
862
            return i;

Daiki Ueno's avatar
Daiki Ueno committed
863
    return XKeysymToKeycode (xdisplay, 0x0023); /* XK_numbersign */
864
865
866
867
868
869
870
871
872
873
}

/* Replace keysym assigned to KEYCODE to KEYSYM.  Both args are used
   as in-out.  If KEYCODE points to 0, this function picks a keycode
   from the current map and replace the associated keysym to KEYSYM.
   In that case, the replaced keycode is stored in KEYCODE and the old
   keysym is stored in KEYSYM.  If otherwise (KEYCODE points to
   non-zero keycode), it simply changes the current map with the
   specified KEYCODE and KEYSYM. */
static gboolean
Daiki Ueno's avatar
Daiki Ueno committed
874
replace_keycode (Client *client,
875
876
877
                 guint          *keycode,
                 guint          *keysym)
{
Daiki Ueno's avatar
Daiki Ueno committed
878
879
    GdkDisplay *display = gdk_display_get_default ();
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
880
881
882
883
884
885
886
887
888
889
    guint offset;
    XkbMapChangesRec changes;
    guint replaced_keycode, replaced_keysym;

    g_assert (keycode != NULL);
    g_assert (keysym != NULL && *keysym != 0);

    replaced_keycode = get_replaced_keycode (client);
    if (replaced_keycode == 0)
        return FALSE;
Daiki Ueno's avatar
Daiki Ueno committed
890
    replaced_keysym = XKeycodeToKeysym (xdisplay, replaced_keycode, 0);
Daiki Ueno's avatar
Daiki Ueno committed
891
    XSync (xdisplay, False);
892
893
894
895
896
897
898
899

    offset = client->xkb->map->key_sym_map[replaced_keycode].offset;
    client->xkb->map->syms[offset] = *keysym;

    changes.changed = XkbKeySymsMask;
    changes.first_key_sym = replaced_keycode;
    changes.num_key_syms = 1;

Daiki Ueno's avatar
Daiki Ueno committed
900
    XkbChangeMap (xdisplay, client->xkb, &changes);
Daiki Ueno's avatar
Daiki Ueno committed
901
    XSync (xdisplay, False);
902
903
904
905
906
907
908
909

    *keycode = replaced_keycode;
    *keysym = replaced_keysym;

    return TRUE;
}

static gboolean
Daiki Ueno's avatar
Daiki Ueno committed
910
get_keycode_from_gdk_keymap (Client *client,
911
912
913
914
915
                             guint           keysym,
                             guint          *keycode,
                             guint          *modifiers)
{
    GdkKeymap *keymap = gdk_keymap_get_default ();
Daiki Ueno's avatar
Daiki Ueno committed
916
    GdkKeymapKey *keys, *best_match = NULL;
917
918
919
920
921
922
    gint n_keys, i;

    if (!gdk_keymap_get_entries_for_keyval (keymap, keysym, &keys, &n_keys))
        return FALSE;

    for (i = 0; i < n_keys; i++)
923
        if (keys[i].group == eekboard_context_get_group (client->context, NULL))
924
925
            best_match = &keys[i];

Daiki Ueno's avatar
Daiki Ueno committed
926
927
928
929
930
    if (!best_match) {
        g_free (keys);
        return FALSE;
    }

931
932
933
934
935
936
937
    *keycode = best_match->keycode;
    *modifiers = best_match->level == 1 ? EEK_SHIFT_MASK : 0;

    g_free (keys);
    return TRUE;
}

938
static void
Daiki Ueno's avatar
Daiki Ueno committed
939
send_fake_modifier_key_event (Client *client,
940
941
                              EekModifierType modifiers,
                              gboolean        is_pressed)
Daiki Ueno's avatar
Daiki Ueno committed
942
{
Daiki Ueno's avatar
Daiki Ueno committed
943
    GdkDisplay *display = gdk_display_get_default ();
944
    gint i;
Daiki Ueno's avatar
Daiki Ueno committed
945

946
947
948
    for (i = 0; i < G_N_ELEMENTS(client->modifier_keycodes); i++) {
        if (modifiers & (1 << i)) {
            guint keycode = client->modifier_keycodes[i];
Daiki Ueno's avatar
Daiki Ueno committed
949

950
951
            g_return_if_fail (keycode > 0);

Daiki Ueno's avatar
Daiki Ueno committed
952
            XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY (display),
953
954
955
956
957
958
959
960
                               keycode,
                               is_pressed,
                               CurrentTime);
        }
    }
}

static void
961
962
963
964
send_fake_key_event (Client    *client,
                     EekSymbol *symbol,
                     guint      keyboard_modifiers,
                     gboolean   is_pressed)
965
{
Daiki Ueno's avatar
Daiki Ueno committed
966
    GdkDisplay *display = gdk_display_get_default ();
967
    EekModifierType modifiers;
968
    guint xkeysym;
969
    guint keycode, replaced_keysym = 0;
970
971
972
973

    xkeysym = eek_keysym_get_xkeysym (EEK_KEYSYM(symbol));
    g_return_if_fail (xkeysym > 0);

974
975
976
977
978
979
980
981
    modifiers = 0;
    if (!get_keycode_from_gdk_keymap (client, xkeysym, &keycode, &modifiers)) {
        keycode = 0;
        replaced_keysym = xkeysym;
        if (!replace_keycode (client, &keycode, &replaced_keysym)) {
            g_warning ("failed to lookup X keysym %X", xkeysym);
            return;
        }
982
    }
Daiki Ueno's avatar
Daiki Ueno committed
983

984
985
986
    /* Clear level shift modifiers */
    keyboard_modifiers &= ~EEK_SHIFT_MASK;
    keyboard_modifiers &= ~EEK_LOCK_MASK;
Daiki Ueno's avatar
Daiki Ueno committed
987
988
989
990
    /* FIXME: may need to remap ISO_Level3_Shift and NumLock */
    //keyboard_modifiers &= ~EEK_MOD5_MASK;
    //keyboard_modifiers &= ~client->alt_gr_mask;
    //keyboard_modifiers &= ~client->num_lock_mask;
991
992

    modifiers |= keyboard_modifiers;
993
994

    send_fake_modifier_key_event (client, modifiers, is_pressed);
Daiki Ueno's avatar
Daiki Ueno committed
995
    XSync (GDK_DISPLAY_XDISPLAY (display), False);
996

Daiki Ueno's avatar
Daiki Ueno committed
997
    keycode = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY (display), xkeysym);
998
999
    g_return_if_fail (keycode > 0);

Daiki Ueno's avatar
Daiki Ueno committed
1000
    XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY (display),
1001
1002
1003
                       keycode,
                       is_pressed,
                       CurrentTime);
Daiki Ueno's avatar
Daiki Ueno committed
1004
    XSync (GDK_DISPLAY_XDISPLAY (display), False);
1005
1006
1007

    if (replaced_keysym)
        replace_keycode (client, &keycode, &replaced_keysym);
Daiki Ueno's avatar
Daiki Ueno committed
1008
1009
}

Daiki Ueno's avatar
Daiki Ueno committed
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
static void
send_fake_key_events (Client    *client,
                      EekSymbol *symbol,
                      guint      keyboard_modifiers)
{
    /* Ignore modifier keys */
    if (eek_symbol_is_modifier (symbol))
        return;

    /* If symbol is a text, convert chars in it to keysym */
    if (EEK_IS_TEXT(symbol)) {
Daiki Ueno's avatar
Daiki Ueno committed
1021
        const gchar *utf8 = eek_text_get_text (EEK_TEXT(symbol));
Daiki Ueno's avatar
Daiki Ueno committed
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
        glong items_written;
        gunichar *ucs4 = g_utf8_to_ucs4_fast (utf8, -1, &items_written);
        gint i;

        for (i = 0; i < items_written; i++) {
            guint xkeysym;
            EekKeysym *keysym;
            gchar *name;

            name = g_strdup_printf ("U%04X", ucs4[i]);
            xkeysym = XStringToKeysym (name);
            g_free (name);

            keysym = eek_keysym_new (xkeysym);
            send_fake_key_events (client,
                                  EEK_SYMBOL(keysym),
                                  keyboard_modifiers);
        }
        g_free (ucs4);
        return;
    }

    /* Ignore special keys */
    if (!EEK_IS_KEYSYM(symbol))
        return;

    send_fake_key_event (client, symbol, keyboard_modifiers, TRUE);
    send_fake_key_event (client, symbol, keyboard_modifiers, FALSE);
}

1052
static void
1053
1054
1055
1056
1057
on_key_activated (EekboardContext *context,
                  guint            keycode,
                  EekSymbol       *symbol,
                  guint            modifiers,
                  gpointer         user_data)
1058
{
Daiki Ueno's avatar
Daiki Ueno committed
1059
    Client *client = user_data;
Daiki Ueno's avatar
Daiki Ueno committed
1060
1061

    if (g_strcmp0 (eek_symbol_get_name (symbol), "cycle-keyboard") == 0) {
1062
1063
1064
        client->keyboards_head = g_slist_next (client->keyboards_head);
        if (client->keyboards_head == NULL)
            client->keyboards_head = client->keyboards;
Daiki Ueno's avatar
Daiki Ueno committed
1065
        eekboard_context_set_keyboard (client->context,
1066
                                       GPOINTER_TO_UINT(client->keyboards_head->data),
Daiki Ueno's avatar
Daiki Ueno committed
1067
                                       NULL);
Daiki Ueno's avatar
Daiki Ueno committed
1068
        return;
Daiki Ueno's avatar
Daiki Ueno committed
1069
1070
    }

Daiki Ueno's avatar
Daiki Ueno committed
1071
    if (g_strcmp0 (eek_symbol_get_name (symbol), "preferences") == 0) {
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
        gchar *argv[2];
        GError *error;

        argv[0] = g_build_filename (LIBEXECDIR, "eekboard-setup", NULL);
        argv[1] = NULL;

        error = NULL;
        if (!g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, NULL, &error)) {
            g_warning ("can't spawn %s: %s", argv[0], error->message);
            g_error_free (error);
        }
        g_free (argv[0]);
Daiki Ueno's avatar
Daiki Ueno committed
1084
        return;
Daiki Ueno's avatar
Daiki Ueno committed
1085
1086
    }

Daiki Ueno's avatar
Daiki Ueno committed
1087
    send_fake_key_events (client, symbol, modifiers);
1088
}
1089

1090
static void
Daiki Ueno's avatar
Daiki Ueno committed
1091
update_modifier_keycodes (Client *client)
1092
{
Daiki Ueno's avatar
Daiki Ueno committed
1093
    GdkDisplay *display = gdk_display_get_default ();