client.c 29.6 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"
42
#include "eekboard/eekboard.h"
43
#include "client.h"
Daiki Ueno's avatar
Daiki Ueno committed
44
#include "xklutil.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
57
58
    PROP_LAST
};

59
typedef struct _EekboardClientClass EekboardClientClass;
60

61
struct _EekboardClient {
62
63
    GObject parent;

64
    EekboardEekboard *eekboard;
65
    EekboardContext *context;
66

67
    EekKeyboard *keyboard;
68
69
    XklEngine *xkl_engine;
    XklConfigRegistry *xkl_config_registry;
70
    gboolean use_xkl_layout;
71

72
73
    gulong xkl_config_changed_handler;
    gulong xkl_state_changed_handler;
74

75
76
    gulong key_pressed_handler;
    gulong key_released_handler;
77

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

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

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

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

    GSettings *settings;
97
98
};

99
struct _EekboardClientClass {
100
101
102
    GObjectClass parent_class;
};

103
G_DEFINE_TYPE (EekboardClient, eekboard_client, G_TYPE_OBJECT);
104

Daiki Ueno's avatar
Daiki Ueno committed
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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 */
static gboolean        set_keyboard         (EekboardClient         *client,
125
                                             const gchar *keyboard);
126
127
128
129
#ifdef HAVE_XTEST
static void            update_modifier_keycodes
                                            (EekboardClient         *client);
#endif  /* HAVE_XTEST */
130
131

static void
132
eekboard_client_set_property (GObject      *object,
133
134
135
136
                                      guint         prop_id,
                                      const GValue *value,
                                      GParamSpec   *pspec)
{
137
    EekboardClient *client = EEKBOARD_CLIENT(object);
138
139
140
141
142
    GDBusConnection *connection;

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

144
        client->eekboard = eekboard_eekboard_new (connection, NULL);
Daiki Ueno's avatar
Daiki Ueno committed
145
146
147
        if (client->eekboard != NULL) {
            client->context =
                eekboard_eekboard_create_context (client->eekboard,
148
                                                  "eekboard",
Daiki Ueno's avatar
Daiki Ueno committed
149
150
151
152
153
154
155
156
157
                                                  NULL);
            if (client->context == NULL) {
                g_object_unref (client->eekboard);
                client->eekboard = NULL;
            } else
                eekboard_eekboard_push_context (client->eekboard,
                                                client->context,
                                                NULL);
        }
158
159
160
161
162
163
164
165
166
        break;
    default:
        g_object_set_property (object,
                               g_param_spec_get_name (pspec),
                               value);
        break;
    }
}

167
static void
168
eekboard_client_get_property (GObject    *object,
169
170
171
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
172
{
173
    EekboardClient *client = EEKBOARD_CLIENT(object);
174
175

    switch (prop_id) {
Daiki Ueno's avatar
Daiki Ueno committed
176
177
178
    case PROP_EEKBOARD:
        g_value_set_object (value, client->eekboard);
        break;
179
180
181
182
183
184
185
186
187
188
189
    case PROP_CONTEXT:
        g_value_set_object (value, client->context);
        break;
    default:
        g_object_get_property (object,
                               g_param_spec_get_name (pspec),
                               value);
        break;
    }
}

190
static void
191
eekboard_client_dispose (GObject *object)
192
{
193
    EekboardClient *client = EEKBOARD_CLIENT(object);
194

195
    eekboard_client_disable_xkl (client);
Daiki Ueno's avatar
Daiki Ueno committed
196

Daiki Ueno's avatar
Daiki Ueno committed
197
198
199
200
#ifdef HAVE_ATSPI
    eekboard_client_disable_atspi_focus (client);
    eekboard_client_disable_atspi_keystroke (client);
#endif  /* HAVE_ATSPI */
Daiki Ueno's avatar
Daiki Ueno committed
201

Daiki Ueno's avatar
Daiki Ueno committed
202
203
204
205
206
207
208
209
#ifdef HAVE_IBUS
    eekboard_client_disable_ibus_focus (client);
    if (client->ibus_bus) {
        g_object_unref (client->ibus_bus);
        client->ibus_bus = NULL;
    }
#endif  /* HAVE_IBUS */

210
211
212
#ifdef HAVE_XTEST
    eekboard_client_disable_xtest (client);
#endif  /* HAVE_XTEST */
213

214
    if (client->context) {
215
216
        if (client->eekboard) {
            eekboard_eekboard_pop_context (client->eekboard, NULL);
217
218
219
220
221
222
        }

        g_object_unref (client->context);
        client->context = NULL;
    }

223
224
225
    if (client->eekboard) {
        g_object_unref (client->eekboard);
        client->eekboard = NULL;
226
227
    }

228
229
230
231
232
    if (client->settings) {
        g_object_unref (client->settings);
        client->settings = NULL;
    }

233
    G_OBJECT_CLASS (eekboard_client_parent_class)->dispose (object);
234
235
236
}

static void
237
eekboard_client_class_init (EekboardClientClass *klass)
238
239
240
241
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    GParamSpec *pspec;

242
243
244
    gobject_class->set_property = eekboard_client_set_property;
    gobject_class->get_property = eekboard_client_get_property;
    gobject_class->dispose = eekboard_client_dispose;
245
246
247
248
249
250
251
252
253

    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);
254

Daiki Ueno's avatar
Daiki Ueno committed
255
256
257
258
259
260
261
262
263
    pspec = g_param_spec_object ("eekboard",
                                 "Eekboard",
                                 "Eekboard",
                                 EEKBOARD_TYPE_EEKBOARD,
                                 G_PARAM_READABLE);
    g_object_class_install_property (gobject_class,
                                     PROP_EEKBOARD,
                                     pspec);

264
265
266
267
268
269
270
271
    pspec = g_param_spec_object ("context",
                                 "Context",
                                 "Context",
                                 EEKBOARD_TYPE_CONTEXT,
                                 G_PARAM_READABLE);
    g_object_class_install_property (gobject_class,
                                     PROP_CONTEXT,
                                     pspec);
272
273
274
}

static void
275
eekboard_client_init (EekboardClient *client)
276
{
277
    client->settings = g_settings_new ("org.fedorahosted.eekboard");
278
279
}

Daiki Ueno's avatar
Daiki Ueno committed
280
#if ENABLE_FOCUS_LISTENER
281
#define IS_KEYBOARD_VISIBLE(client) (!client->follows_focus)
Daiki Ueno's avatar
Daiki Ueno committed
282
#else  /* ENABLE_FOCUS_LISTENER */
283
#define IS_KEYBOARD_VISIBLE(client) TRUE
Daiki Ueno's avatar
Daiki Ueno committed
284
#endif  /* !ENABLE_FOCUS_LISTENER */
285
286
287
288
289
290
291
292
293
294
295

gboolean
eekboard_client_set_keyboard (EekboardClient *client,
                              const gchar    *keyboard)
{
    gboolean retval;

    retval = set_keyboard (client, keyboard);
    if (IS_KEYBOARD_VISIBLE (client))
        eekboard_context_show_keyboard (client->context, NULL);
    return retval;
296
297
}

298
gboolean
299
eekboard_client_enable_xkl (EekboardClient *client)
300
{
Daiki Ueno's avatar
Daiki Ueno committed
301
302
    GdkDisplay *display = gdk_display_get_default ();
    g_assert (display);
303

304
305
    if (!client->xkl_engine) {
        client->xkl_engine =
Daiki Ueno's avatar
Daiki Ueno committed
306
            xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY (display));
307
    }
308
    g_assert (client->xkl_engine);
309
310
311
312
313
314
315

    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);
    }

316
    client->xkl_config_changed_handler =
317
318
        g_signal_connect (client->xkl_engine, "X-config-changed",
                          G_CALLBACK(on_xkl_config_changed), client);
319
    client->xkl_state_changed_handler =
320
321
322
323
324
325
326
327
328
329
        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);

330
331
    client->use_xkl_layout = FALSE;

332
333
    xkl_engine_start_listen (client->xkl_engine, XKLL_TRACK_KEYBOARD_STATE);

334
    return TRUE;
335
336
337
}

void
338
eekboard_client_disable_xkl (EekboardClient *client)
339
{
340
341
    client->use_xkl_layout = FALSE;

342
343
344
    if (client->xkl_engine)
        xkl_engine_stop_listen (client->xkl_engine, XKLL_TRACK_KEYBOARD_STATE);
    if (g_signal_handler_is_connected (client->xkl_engine,
345
                                       client->xkl_config_changed_handler))
346
        g_signal_handler_disconnect (client->xkl_engine,
347
                                     client->xkl_config_changed_handler);
348
    if (g_signal_handler_is_connected (client->xkl_engine,
349
                                       client->xkl_state_changed_handler))
350
        g_signal_handler_disconnect (client->xkl_engine,
351
                                     client->xkl_state_changed_handler);
352
353
}

Daiki Ueno's avatar
Daiki Ueno committed
354
#ifdef HAVE_ATSPI
355
gboolean
Daiki Ueno's avatar
Daiki Ueno committed
356
eekboard_client_enable_atspi_focus (EekboardClient *client)
357
{
Daiki Ueno's avatar
Daiki Ueno committed
358
    GError *error;
359

Daiki Ueno's avatar
Daiki Ueno committed
360
361
362
363
364
365
366
    error = NULL;
    if (!atspi_event_listener_register_from_callback
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         NULL,
         "object:state-changed:focused",
         &error))
367
368
        return FALSE;

Daiki Ueno's avatar
Daiki Ueno committed
369
370
371
372
373
374
375
    error = NULL;
    if (!atspi_event_listener_register_from_callback
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         NULL,
         "focus:",
         &error))
376
377
        return FALSE;

Daiki Ueno's avatar
Daiki Ueno committed
378
    client->follows_focus = TRUE;
379
380
381
382
    return TRUE;
}

void
Daiki Ueno's avatar
Daiki Ueno committed
383
eekboard_client_disable_atspi_focus (EekboardClient *client)
384
{
Daiki Ueno's avatar
Daiki Ueno committed
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
    GError *error;

    client->follows_focus = FALSE;

    error = NULL;
    atspi_event_listener_deregister_from_callback
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         "object:state-changed:focused",
         &error);

    error = NULL;
    atspi_event_listener_deregister_from_callback
        ((AtspiEventListenerCB)focus_listener_cb,
         client,
         "focus:",
         &error);
402
403
404
}

gboolean
Daiki Ueno's avatar
Daiki Ueno committed
405
eekboard_client_enable_atspi_keystroke (EekboardClient *client)
406
{
Daiki Ueno's avatar
Daiki Ueno committed
407
408
    GError *error;

409
    client->keystroke_listener =
Daiki Ueno's avatar
Daiki Ueno committed
410
411
412
        atspi_device_listener_new ((AtspiDeviceListenerCB)keystroke_listener_cb,
                                   NULL,
                                   client);
413

Daiki Ueno's avatar
Daiki Ueno committed
414
415
416
417
418
419
420
421
422
423
424
425
    error = NULL;
    if (!atspi_register_keystroke_listener
        (client->keystroke_listener,
         NULL,
         0,
         ATSPI_KEY_PRESSED,
         ATSPI_KEYLISTENER_NOSYNC,
         &error))
        return FALSE;

    error = NULL;
    if (!atspi_register_keystroke_listener
426
        (client->keystroke_listener,
Daiki Ueno's avatar
Daiki Ueno committed
427
         NULL,
428
         0,
Daiki Ueno's avatar
Daiki Ueno committed
429
430
431
         ATSPI_KEY_RELEASED,
         ATSPI_KEYLISTENER_NOSYNC,
         &error))
432
433
434
435
436
        return FALSE;
    return TRUE;
}

void
Daiki Ueno's avatar
Daiki Ueno committed
437
eekboard_client_disable_atspi_keystroke (EekboardClient *client)
438
439
{
    if (client->keystroke_listener) {
Daiki Ueno's avatar
Daiki Ueno committed
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
        GError *error;

        error = NULL;
        atspi_deregister_keystroke_listener (client->keystroke_listener,
                                             NULL,
                                             0,
                                             ATSPI_KEY_PRESSED,
                                             &error);

        error = NULL;
        atspi_deregister_keystroke_listener (client->keystroke_listener,
                                             NULL,
                                             0,
                                             ATSPI_KEY_RELEASED,
                                             &error);

        g_object_unref (client->keystroke_listener);
457
458
459
460
        client->keystroke_listener = NULL;
    }
}

Daiki Ueno's avatar
Daiki Ueno committed
461
462
463
static void
focus_listener_cb (const AtspiEvent *event,
                   void             *user_data)
464
{
465
    EekboardClient *client = user_data;
Daiki Ueno's avatar
Daiki Ueno committed
466
467
468
469
470
471
472
473
474
    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);
    if (error)
        return;
475

Daiki Ueno's avatar
Daiki Ueno committed
476
477
    if (atspi_state_set_contains (state_set, ATSPI_STATE_EDITABLE) ||
        role == ATSPI_ROLE_TERMINAL) {
478
        switch (role) {
Daiki Ueno's avatar
Daiki Ueno committed
479
480
481
482
        case ATSPI_ROLE_TEXT:
        case ATSPI_ROLE_PARAGRAPH:
        case ATSPI_ROLE_PASSWORD_TEXT:
        case ATSPI_ROLE_TERMINAL:
Daiki Ueno's avatar
Daiki Ueno committed
483
484
485
            if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) {
                client->acc = accessible;
                eekboard_context_show_keyboard (client->context, NULL);
486
487
            } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
                       event->detail1 == 0 && accessible == client->acc) {
Daiki Ueno's avatar
Daiki Ueno committed
488
489
490
491
                client->acc = NULL;
                eekboard_context_hide_keyboard (client->context, NULL);
            }
            break;
Daiki Ueno's avatar
Daiki Ueno committed
492
        case ATSPI_ROLE_ENTRY:
Daiki Ueno's avatar
Daiki Ueno committed
493
494
            if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) {
                client->acc = accessible;
495
                eekboard_context_show_keyboard (client->context, NULL);
496
497
            } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
                       event->detail1 == 0) {
Daiki Ueno's avatar
Daiki Ueno committed
498
499
                client->acc = NULL;
                eekboard_context_hide_keyboard (client->context, NULL);
500
            }
Daiki Ueno's avatar
Daiki Ueno committed
501
502
            break;
            
503
504
505
        default:
            ;
        }
506
507
508
    } else {
        eekboard_context_hide_keyboard (client->context, NULL);
    }
509
510
}

Daiki Ueno's avatar
Daiki Ueno committed
511
512
513
static gboolean
keystroke_listener_cb (const AtspiDeviceEvent *stroke,
                       void                   *user_data)
514
{
515
    EekboardClient *client = user_data;
516

517
518
    switch (stroke->type) {
    case ATSPI_KEY_PRESSED:
Daiki Ueno's avatar
Daiki Ueno committed
519
        eekboard_context_press_key (client->context, stroke->hw_code, NULL);
520
521
        break;
    case ATSPI_KEY_RELEASED:
Daiki Ueno's avatar
Daiki Ueno committed
522
        eekboard_context_release_key (client->context, stroke->hw_code, NULL);
523
524
525
        break;
    default:
        g_return_val_if_reached (FALSE);
526
    }
527
528
    return TRUE;
}
Daiki Ueno's avatar
Daiki Ueno committed
529
#endif  /* HAVE_ATSPI */
Daiki Ueno's avatar
Daiki Ueno committed
530

Daiki Ueno's avatar
Daiki Ueno committed
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
#ifdef HAVE_IBUS
static void
add_match_rule (GDBusConnection *connection,
                const gchar     *match_rule)
{
  GError *error;
  GDBusMessage *message;

  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;
  g_dbus_connection_send_message (connection,
                                  message,
                                  G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                  NULL,
                                  &error);
  g_object_unref (message);
}

Daiki Ueno's avatar
Daiki Ueno committed
553
554
555
556
557
558
559
560
static gboolean
on_hide_keyboard_timeout (EekboardClient *client)
{
    eekboard_context_hide_keyboard (client->context, NULL);
    client->hide_keyboard_timeout_id = 0;
    return FALSE;
}

Daiki Ueno's avatar
Daiki Ueno committed
561
562
563
564
565
566
567
568
569
570
static GDBusMessage *
focus_message_filter (GDBusConnection *connection,
                      GDBusMessage    *message,
                      gboolean         incoming,
                      gpointer         user_data)
{
    EekboardClient *client = user_data;

    if (incoming &&
        g_strcmp0 (g_dbus_message_get_interface (message),
571
                   IBUS_INTERFACE_PANEL) == 0) {
Daiki Ueno's avatar
Daiki Ueno committed
572
573
574
        const gchar *member = g_dbus_message_get_member (message);

        if (g_strcmp0 (member, "FocusIn") == 0) {
Daiki Ueno's avatar
Daiki Ueno committed
575
576
577
578
            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
579
            eekboard_context_show_keyboard (client->context, NULL);
580
581
        } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
                   g_strcmp0 (member, "FocusOut") == 0) {
582
583
            gint delay = g_settings_get_int (client->settings,
                                             "auto-hide-delay");
Daiki Ueno's avatar
Daiki Ueno committed
584
            client->hide_keyboard_timeout_id =
585
                g_timeout_add (delay,
Daiki Ueno's avatar
Daiki Ueno committed
586
587
                               (GSourceFunc)on_hide_keyboard_timeout,
                               client);
Daiki Ueno's avatar
Daiki Ueno committed
588
589
590
591
592
593
        }
    }

    return message;
}

594
595
static void
_ibus_connect_focus_handlers (IBusBus *bus, gpointer user_data)
Daiki Ueno's avatar
Daiki Ueno committed
596
{
597
    EekboardClient *client = user_data;
Daiki Ueno's avatar
Daiki Ueno committed
598
599
    GDBusConnection *connection;

600
    connection = ibus_bus_get_connection (bus);
Daiki Ueno's avatar
Daiki Ueno committed
601
602
    add_match_rule (connection,
                    "type='method_call',"
603
                    "interface='" IBUS_INTERFACE_PANEL "',"
Daiki Ueno's avatar
Daiki Ueno committed
604
                    "member='FocusIn'");
605
606
    add_match_rule (connection,
                    "type='method_call',"
607
                    "interface='" IBUS_INTERFACE_PANEL "',"
608
                    "member='FocusOut'");
Daiki Ueno's avatar
Daiki Ueno committed
609
610
611
612
613
    client->ibus_focus_message_filter =
        g_dbus_connection_add_filter (connection,
                                      focus_message_filter,
                                      client,
                                      NULL);
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
}

gboolean
eekboard_client_enable_ibus_focus (EekboardClient *client)
{
    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
630
631
632
633
634
635
636
637
638
639
640
    client->follows_focus = TRUE;
    return TRUE;
}

void
eekboard_client_disable_ibus_focus (EekboardClient *client)
{
    GDBusConnection *connection;

    client->follows_focus = FALSE;

641
642
643
644
645
646
647
648
649
    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
650
651
652
}
#endif  /* HAVE_ATSPI */

653
654
EekboardClient *
eekboard_client_new (GDBusConnection *connection)
Daiki Ueno's avatar
Daiki Ueno committed
655
{
656
    EekboardClient *client = g_object_new (EEKBOARD_TYPE_CLIENT,
657
658
                                           "connection", connection,
                                           NULL);
Daiki Ueno's avatar
Daiki Ueno committed
659
660
661
    if (client->context)
        return client;
    return NULL;
Daiki Ueno's avatar
Daiki Ueno committed
662
}
663
664
665
666
667
668

static GdkFilterReturn
filter_xkl_event (GdkXEvent *xev,
                  GdkEvent  *event,
                  gpointer   user_data)
{
669
    EekboardClient *client = user_data;
670
671
672
673
674
675
676
677
678
679
    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)
{
680
    EekboardClient *client = user_data;
681
    gboolean retval;
682

683
    if (client->use_xkl_layout) {
684
685
686
687
688
689
690
691
692
693
694
695
696
        XklConfigRec *rec;
        gchar *layout, *keyboard;

        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 ("xkl:%s", layout);
        g_free (layout);
        retval = set_keyboard (client, keyboard);
        g_free (keyboard);

697
698
        g_return_if_fail (retval);
    }
699

700
#ifdef HAVE_XTEST
701
    update_modifier_keycodes (client);
702
#endif  /* HAVE_XTEST */
703
704
}

705
static gboolean
706
set_keyboard (EekboardClient *client,
707
              const gchar    *keyboard)
708
{
709
    guint keyboard_id;
710

Daiki Ueno's avatar
Daiki Ueno committed
711
    keyboard_id = eekboard_context_add_keyboard (client->context,
712
                                                 keyboard,
Daiki Ueno's avatar
Daiki Ueno committed
713
714
715
716
717
                                                 NULL);
    eekboard_context_set_keyboard (client->context, keyboard_id, NULL);
    return TRUE;
}

718
719
720
721
722
723
724
static void
on_xkl_state_changed (XklEngine           *xklengine,
                      XklEngineStateChange type,
                      gint                 value,
                      gboolean             restore,
                      gpointer             user_data)
{
725
    EekboardClient *client = user_data;
726

727
728
729
    if (type == GROUP_CHANGED) {
        if (client->use_xkl_layout)
            eekboard_context_set_group (client->context, value, NULL);
730
731
732
    }
}

733
#ifdef HAVE_XTEST
734
735
736
737
738
739
740
741
742
743
/* 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
get_replaced_keycode (EekboardClient *client)
{
Daiki Ueno's avatar
Daiki Ueno committed
744
745
    GdkDisplay *display = gdk_display_get_default ();
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
746
747
748
749
    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
750
            XKeycodeToKeysym (xdisplay, i, 0) != 0)
751
752
            return i;

Daiki Ueno's avatar
Daiki Ueno committed
753
    return XKeysymToKeycode (xdisplay, 0x0023); /* XK_numbersign */
754
755
756
757
758
759
760
761
762
763
764
765
766
767
}

/* 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
replace_keycode (EekboardClient *client,
                 guint          *keycode,
                 guint          *keysym)
{
Daiki Ueno's avatar
Daiki Ueno committed
768
769
    GdkDisplay *display = gdk_display_get_default ();
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
770
771
772
773
774
775
776
777
778
779
    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
780
781
    replaced_keysym = XKeycodeToKeysym (xdisplay, replaced_keycode, 0);
    XFlush (xdisplay);
782
783
784
785
786
787
788
789

    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
790
791
    XkbChangeMap (xdisplay, client->xkb, &changes);
    XFlush (xdisplay);
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812

    *keycode = replaced_keycode;
    *keysym = replaced_keysym;

    return TRUE;
}

static gboolean
get_keycode_from_gdk_keymap (EekboardClient *client,
                             guint           keysym,
                             guint          *keycode,
                             guint          *modifiers)
{
    GdkKeymap *keymap = gdk_keymap_get_default ();
    GdkKeymapKey *keys, *best_match;
    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++)
813
        if (keys[i].group == eekboard_context_get_group (client->context))
814
815
816
817
818
819
820
821
822
823
            best_match = &keys[i];

    *keycode = best_match->keycode;
    *modifiers = best_match->level == 1 ? EEK_SHIFT_MASK : 0;

    g_free (keys);

    return TRUE;
}

824
825
826
827
static void
send_fake_modifier_key_event (EekboardClient *client,
                              EekModifierType modifiers,
                              gboolean        is_pressed)
Daiki Ueno's avatar
Daiki Ueno committed
828
{
Daiki Ueno's avatar
Daiki Ueno committed
829
    GdkDisplay *display = gdk_display_get_default ();
830
    gint i;
Daiki Ueno's avatar
Daiki Ueno committed
831

832
833
834
    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
835

836
837
            g_return_if_fail (keycode > 0);

Daiki Ueno's avatar
Daiki Ueno committed
838
            XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY (display),
839
840
841
842
843
844
845
846
847
                               keycode,
                               is_pressed,
                               CurrentTime);
        }
    }
}

static void
send_fake_key_event (EekboardClient *client,
848
849
                     EekSymbol      *symbol,
                     guint           keyboard_modifiers,
850
851
                     gboolean        is_pressed)
{
Daiki Ueno's avatar
Daiki Ueno committed
852
    GdkDisplay *display = gdk_display_get_default ();
853
    EekModifierType modifiers;
854
    guint xkeysym;
855
    guint keycode, replaced_keysym = 0;
856
857
858
859
860
861
862
863

    /* Ignore special keys and modifiers */
    if (!EEK_IS_KEYSYM(symbol) || eek_symbol_is_modifier (symbol))
        return;

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

864
865
866
867
868
869
870
871
    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;
        }
872
    }
873
874
875
876
    
    /* Clear level shift modifiers */
    keyboard_modifiers &= ~EEK_SHIFT_MASK;
    keyboard_modifiers &= ~EEK_LOCK_MASK;
877
    //keyboard_modifiers &= ~eek_keyboard_get_alt_gr_mask (client->keyboard);
878
879

    modifiers |= keyboard_modifiers;
880
881

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

Daiki Ueno's avatar
Daiki Ueno committed
884
    keycode = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY (display),
885
886
887
                                xkeysym);
    g_return_if_fail (keycode > 0);

Daiki Ueno's avatar
Daiki Ueno committed
888
    XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY (display),
889
890
891
                       keycode,
                       is_pressed,
                       CurrentTime);
Daiki Ueno's avatar
Daiki Ueno committed
892
    XSync (GDK_DISPLAY_XDISPLAY (display), False);
893
894
895

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

898
static void
899
900
901
902
903
on_key_pressed (EekboardContext *context,
                guint            keycode,
                EekSymbol       *symbol,
                guint            modifiers,
                gpointer         user_data)
904
{
905
    EekboardClient *client = user_data;
906
907
    send_fake_key_event (client, symbol, modifiers, TRUE);
    send_fake_key_event (client, symbol, modifiers, FALSE);
908
}
909

910
911
912
static void
update_modifier_keycodes (EekboardClient *client)
{
Daiki Ueno's avatar
Daiki Ueno committed
913
    GdkDisplay *display = gdk_display_get_default ();
914
915
916
    XModifierKeymap *mods;
    gint i, j;

Daiki Ueno's avatar
Daiki Ueno committed
917
    mods = XGetModifierMapping (GDK_DISPLAY_XDISPLAY (display));
918
919
920
921
922
923
924
925
926
927
    for (i = 0; i < 8; i++) {
        client->modifier_keycodes[i] = 0;
        for (j = 0; j < mods->max_keypermod; j++) {
            KeyCode keycode = mods->modifiermap[mods->max_keypermod * i + j];
            if (keycode != 0) {
                client->modifier_keycodes[i] = keycode;
                break;
            }
        }
    }
928
929
930
}

gboolean
931
eekboard_client_enable_xtest (EekboardClient *client)
932
{
Daiki Ueno's avatar
Daiki Ueno committed
933
    GdkDisplay *display = gdk_display_get_default ();
934
    int opcode, event_base, error_base, major_version, minor_version;
935

Daiki Ueno's avatar
Daiki Ueno committed
936
    g_assert (display);
937

Daiki Ueno's avatar
Daiki Ueno committed
938
    if (!XTestQueryExtension (GDK_DISPLAY_XDISPLAY (display),
939
940
941
942
943
                              &event_base, &error_base,
                              &major_version, &minor_version)) {
        g_warning ("XTest extension is not available");
        return FALSE;
    }
944

Daiki Ueno's avatar
Daiki Ueno committed
945
    if (!XkbQueryExtension (GDK_DISPLAY_XDISPLAY (display),
946
947
948
949
950
951
952
                            &opcode, &event_base, &error_base,
                            &major_version, &minor_version)) {
        g_warning ("Xkb extension is not available");
        return FALSE;
    }

    if (!client->xkb)
Daiki Ueno's avatar
Daiki Ueno committed
953
        client->xkb = XkbGetMap (GDK_DISPLAY_XDISPLAY (display),
954
955
                                 XkbKeySymsMask,
                                 XkbUseCoreKbd);
956
957
    g_assert (client->xkb);

958
    update_modifier_keycodes (client);
959

960
    client->key_pressed_handler =
961
        g_signal_connect (client->context, "key-pressed",
962
963
964
965
966
967
                          G_CALLBACK(on_key_pressed), client);

    return TRUE;
}

void
968
eekboard_client_disable_xtest (EekboardClient *client)
969
{
970
971
972
973
    if (client->xkb) {
        XkbFreeKeyboard (client->xkb, 0, TRUE);	/* free_all = TRUE */
        client->xkb = NULL;
    }
Daiki Ueno's avatar
Daiki Ueno committed
974
}
975
#endif  /* HAVE_XTEST */