1038 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1038 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 
 | 
						|
 * 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/>.
 | 
						|
 */
 | 
						|
#ifdef HAVE_CONFIG_H
 | 
						|
#include "config.h"
 | 
						|
#endif  /* HAVE_CONFIG_H */
 | 
						|
 | 
						|
#include <libxklavier/xklavier.h>
 | 
						|
 | 
						|
#ifdef HAVE_ATSPI
 | 
						|
#include <dbus/dbus.h>
 | 
						|
#include <atspi/atspi.h>
 | 
						|
#endif  /* HAVE_ATSPI */
 | 
						|
 | 
						|
#include <gdk/gdkx.h>
 | 
						|
 | 
						|
#ifdef HAVE_XTEST
 | 
						|
#include <X11/extensions/XTest.h>
 | 
						|
#include <X11/XKBlib.h>
 | 
						|
#endif  /* HAVE_XTEST */
 | 
						|
 | 
						|
#ifdef HAVE_IBUS
 | 
						|
#include <ibus.h>
 | 
						|
#endif  /* HAVE_IBUS */
 | 
						|
 | 
						|
#include "eek/eek.h"
 | 
						|
#include "eek/eek-xkl.h"
 | 
						|
#include "eekboard/eekboard-client.h"
 | 
						|
#include "eekboard/eekboard-xklutil.h"
 | 
						|
#include "client.h"
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#define CSW 640
 | 
						|
#define CSH 480
 | 
						|
 | 
						|
enum {
 | 
						|
    PROP_0,
 | 
						|
    PROP_CONNECTION,
 | 
						|
    PROP_EEKBOARD,
 | 
						|
    PROP_CONTEXT,
 | 
						|
    PROP_LAST
 | 
						|
};
 | 
						|
 | 
						|
typedef struct _ClientClass ClientClass;
 | 
						|
 | 
						|
struct _Client {
 | 
						|
    GObject parent;
 | 
						|
 | 
						|
    EekboardClient *eekboard;
 | 
						|
    EekboardContext *context;
 | 
						|
 | 
						|
    GSList *keyboards;
 | 
						|
    XklEngine *xkl_engine;
 | 
						|
    XklConfigRegistry *xkl_config_registry;
 | 
						|
 | 
						|
    gulong xkl_config_changed_handler;
 | 
						|
    gulong xkl_state_changed_handler;
 | 
						|
 | 
						|
    gulong key_pressed_handler;
 | 
						|
    gulong key_released_handler;
 | 
						|
 | 
						|
    gboolean follows_focus;
 | 
						|
    guint hide_keyboard_timeout_id;
 | 
						|
 | 
						|
#ifdef HAVE_ATSPI
 | 
						|
    AtspiAccessible *acc;
 | 
						|
    AtspiDeviceListener *keystroke_listener;
 | 
						|
#endif  /* HAVE_ATSPI */
 | 
						|
 | 
						|
#ifdef HAVE_IBUS
 | 
						|
    IBusBus *ibus_bus;
 | 
						|
    guint ibus_focus_message_filter;
 | 
						|
#endif  /* HAVE_IBUS */
 | 
						|
 | 
						|
#ifdef HAVE_XTEST
 | 
						|
    KeyCode modifier_keycodes[8]; 
 | 
						|
    XkbDescRec *xkb;
 | 
						|
#endif  /* HAVE_XTEST */
 | 
						|
 | 
						|
    GSettings *settings;
 | 
						|
};
 | 
						|
 | 
						|
struct _ClientClass {
 | 
						|
    GObjectClass parent_class;
 | 
						|
};
 | 
						|
 | 
						|
G_DEFINE_TYPE (Client, client, G_TYPE_OBJECT);
 | 
						|
 | 
						|
#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 */
 | 
						|
 | 
						|
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         (Client         *client,
 | 
						|
                                             const gchar            *keyboard);
 | 
						|
static gboolean        set_keyboard_from_xkl (Client         *client);
 | 
						|
#ifdef HAVE_XTEST
 | 
						|
static void            update_modifier_keycodes
 | 
						|
(Client         *client);
 | 
						|
#endif  /* HAVE_XTEST */
 | 
						|
 | 
						|
static void
 | 
						|
client_set_property (GObject      *object,
 | 
						|
                     guint         prop_id,
 | 
						|
                     const GValue *value,
 | 
						|
                     GParamSpec   *pspec)
 | 
						|
{
 | 
						|
    Client *client = CLIENT(object);
 | 
						|
    GDBusConnection *connection;
 | 
						|
 | 
						|
    switch (prop_id) {
 | 
						|
    case PROP_CONNECTION:
 | 
						|
        connection = g_value_get_object (value);
 | 
						|
 | 
						|
        client->eekboard = eekboard_client_new (connection, NULL);
 | 
						|
        if (client->eekboard != NULL) {
 | 
						|
            client->context =
 | 
						|
                eekboard_client_create_context (client->eekboard,
 | 
						|
                                                "eekboard",
 | 
						|
                                                NULL);
 | 
						|
            if (client->context == NULL) {
 | 
						|
                g_object_unref (client->eekboard);
 | 
						|
                client->eekboard = NULL;
 | 
						|
            } else
 | 
						|
                eekboard_client_push_context (client->eekboard,
 | 
						|
                                              client->context,
 | 
						|
                                              NULL);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
    default:
 | 
						|
        g_object_set_property (object,
 | 
						|
                               g_param_spec_get_name (pspec),
 | 
						|
                               value);
 | 
						|
        break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
client_get_property (GObject    *object,
 | 
						|
                     guint       prop_id,
 | 
						|
                     GValue     *value,
 | 
						|
                     GParamSpec *pspec)
 | 
						|
{
 | 
						|
    Client *client = CLIENT(object);
 | 
						|
 | 
						|
    switch (prop_id) {
 | 
						|
    case PROP_EEKBOARD:
 | 
						|
        g_value_set_object (value, client->eekboard);
 | 
						|
        break;
 | 
						|
    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;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
client_dispose (GObject *object)
 | 
						|
{
 | 
						|
    Client *client = CLIENT(object);
 | 
						|
 | 
						|
    client_disable_xkl (client);
 | 
						|
 | 
						|
#ifdef HAVE_ATSPI
 | 
						|
    client_disable_atspi_focus (client);
 | 
						|
    client_disable_atspi_keystroke (client);
 | 
						|
#endif  /* HAVE_ATSPI */
 | 
						|
 | 
						|
#ifdef HAVE_IBUS
 | 
						|
    client_disable_ibus_focus (client);
 | 
						|
    if (client->ibus_bus) {
 | 
						|
        g_object_unref (client->ibus_bus);
 | 
						|
        client->ibus_bus = NULL;
 | 
						|
    }
 | 
						|
#endif  /* HAVE_IBUS */
 | 
						|
 | 
						|
#ifdef HAVE_XTEST
 | 
						|
    client_disable_xtest (client);
 | 
						|
#endif  /* HAVE_XTEST */
 | 
						|
 | 
						|
    if (client->context) {
 | 
						|
        if (client->eekboard) {
 | 
						|
            eekboard_client_pop_context (client->eekboard, NULL);
 | 
						|
        }
 | 
						|
 | 
						|
        g_object_unref (client->context);
 | 
						|
        client->context = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (client->eekboard) {
 | 
						|
        g_object_unref (client->eekboard);
 | 
						|
        client->eekboard = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (client->settings) {
 | 
						|
        g_object_unref (client->settings);
 | 
						|
        client->settings = NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    G_OBJECT_CLASS (client_parent_class)->dispose (object);
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
client_class_init (ClientClass *klass)
 | 
						|
{
 | 
						|
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 | 
						|
    GParamSpec *pspec;
 | 
						|
 | 
						|
    gobject_class->set_property = client_set_property;
 | 
						|
    gobject_class->get_property = client_get_property;
 | 
						|
    gobject_class->dispose = client_dispose;
 | 
						|
    gobject_class->finalize = client_finalize;
 | 
						|
 | 
						|
    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);
 | 
						|
 | 
						|
    pspec = g_param_spec_object ("eekboard",
 | 
						|
                                 "Eekboard",
 | 
						|
                                 "Eekboard",
 | 
						|
                                 EEKBOARD_TYPE_CLIENT,
 | 
						|
                                 G_PARAM_READABLE);
 | 
						|
    g_object_class_install_property (gobject_class,
 | 
						|
                                     PROP_EEKBOARD,
 | 
						|
                                     pspec);
 | 
						|
 | 
						|
    pspec = g_param_spec_object ("context",
 | 
						|
                                 "Context",
 | 
						|
                                 "Context",
 | 
						|
                                 EEKBOARD_TYPE_CONTEXT,
 | 
						|
                                 G_PARAM_READABLE);
 | 
						|
    g_object_class_install_property (gobject_class,
 | 
						|
                                     PROP_CONTEXT,
 | 
						|
                                     pspec);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
client_init (Client *client)
 | 
						|
{
 | 
						|
    client->settings = g_settings_new ("org.fedorahosted.eekboard");
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
client_set_keyboard (Client *client,
 | 
						|
                     const gchar    *keyboard)
 | 
						|
{
 | 
						|
    gboolean retval;
 | 
						|
    retval = set_keyboard (client, keyboard);
 | 
						|
    if (retval && IS_KEYBOARD_VISIBLE (client))
 | 
						|
        eekboard_context_show_keyboard (client->context, NULL);
 | 
						|
    return retval;
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
client_enable_xkl (Client *client)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    gboolean retval;
 | 
						|
 | 
						|
    g_assert (display);
 | 
						|
 | 
						|
    if (!client->xkl_engine) {
 | 
						|
        client->xkl_engine =
 | 
						|
            xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY (display));
 | 
						|
    }
 | 
						|
    g_assert (client->xkl_engine);
 | 
						|
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    client->xkl_config_changed_handler =
 | 
						|
        g_signal_connect (client->xkl_engine, "X-config-changed",
 | 
						|
                          G_CALLBACK(on_xkl_config_changed), client);
 | 
						|
    client->xkl_state_changed_handler =
 | 
						|
        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);
 | 
						|
 | 
						|
    retval = set_keyboard_from_xkl (client);
 | 
						|
    if (IS_KEYBOARD_VISIBLE (client))
 | 
						|
        eekboard_context_show_keyboard (client->context, NULL);
 | 
						|
 | 
						|
    return retval;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
client_disable_xkl (Client *client)
 | 
						|
{
 | 
						|
    if (client->xkl_engine) {
 | 
						|
        xkl_engine_stop_listen (client->xkl_engine, XKLL_TRACK_KEYBOARD_STATE);
 | 
						|
 | 
						|
        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;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#ifdef HAVE_ATSPI
 | 
						|
gboolean
 | 
						|
client_enable_atspi_focus (Client *client)
 | 
						|
{
 | 
						|
    GError *error;
 | 
						|
 | 
						|
    error = NULL;
 | 
						|
    if (!atspi_event_listener_register_from_callback
 | 
						|
        ((AtspiEventListenerCB)focus_listener_cb,
 | 
						|
         client,
 | 
						|
         NULL,
 | 
						|
         "object:state-changed:focused",
 | 
						|
         &error))
 | 
						|
        return FALSE;
 | 
						|
 | 
						|
    error = NULL;
 | 
						|
    if (!atspi_event_listener_register_from_callback
 | 
						|
        ((AtspiEventListenerCB)focus_listener_cb,
 | 
						|
         client,
 | 
						|
         NULL,
 | 
						|
         "focus:",
 | 
						|
         &error))
 | 
						|
        return FALSE;
 | 
						|
 | 
						|
    client->follows_focus = TRUE;
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
client_disable_atspi_focus (Client *client)
 | 
						|
{
 | 
						|
    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);
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
client_enable_atspi_keystroke (Client *client)
 | 
						|
{
 | 
						|
    GError *error;
 | 
						|
 | 
						|
    client->keystroke_listener =
 | 
						|
        atspi_device_listener_new ((AtspiDeviceListenerCB)keystroke_listener_cb,
 | 
						|
                                   NULL,
 | 
						|
                                   client);
 | 
						|
 | 
						|
    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
 | 
						|
        (client->keystroke_listener,
 | 
						|
         NULL,
 | 
						|
         0,
 | 
						|
         ATSPI_KEY_RELEASED,
 | 
						|
         ATSPI_KEYLISTENER_NOSYNC,
 | 
						|
         &error))
 | 
						|
        return FALSE;
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
client_disable_atspi_keystroke (Client *client)
 | 
						|
{
 | 
						|
    if (client->keystroke_listener) {
 | 
						|
        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);
 | 
						|
        client->keystroke_listener = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
focus_listener_cb (const AtspiEvent *event,
 | 
						|
                   void             *user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
    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;
 | 
						|
 | 
						|
    if (atspi_state_set_contains (state_set, ATSPI_STATE_EDITABLE) ||
 | 
						|
        role == ATSPI_ROLE_TERMINAL) {
 | 
						|
        switch (role) {
 | 
						|
        case ATSPI_ROLE_TEXT:
 | 
						|
        case ATSPI_ROLE_PARAGRAPH:
 | 
						|
        case ATSPI_ROLE_PASSWORD_TEXT:
 | 
						|
        case ATSPI_ROLE_TERMINAL:
 | 
						|
            if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) {
 | 
						|
                client->acc = accessible;
 | 
						|
                eekboard_context_show_keyboard (client->context, NULL);
 | 
						|
            } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
 | 
						|
                       event->detail1 == 0 && accessible == client->acc) {
 | 
						|
                client->acc = NULL;
 | 
						|
                eekboard_context_hide_keyboard (client->context, NULL);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        case ATSPI_ROLE_ENTRY:
 | 
						|
            if (strncmp (event->type, "focus", 5) == 0 || event->detail1 == 1) {
 | 
						|
                client->acc = accessible;
 | 
						|
                eekboard_context_show_keyboard (client->context, NULL);
 | 
						|
            } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
 | 
						|
                       event->detail1 == 0) {
 | 
						|
                client->acc = NULL;
 | 
						|
                eekboard_context_hide_keyboard (client->context, NULL);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
            
 | 
						|
        default:
 | 
						|
            ;
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        eekboard_context_hide_keyboard (client->context, NULL);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
keystroke_listener_cb (const AtspiDeviceEvent *stroke,
 | 
						|
                       void                   *user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
 | 
						|
    switch (stroke->type) {
 | 
						|
    case ATSPI_KEY_PRESSED:
 | 
						|
        eekboard_context_press_keycode (client->context, stroke->hw_code, NULL);
 | 
						|
        break;
 | 
						|
    case ATSPI_KEY_RELEASED:
 | 
						|
        eekboard_context_release_keycode (client->context, stroke->hw_code, NULL);
 | 
						|
        break;
 | 
						|
    default:
 | 
						|
        g_return_val_if_reached (FALSE);
 | 
						|
    }
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
#endif  /* HAVE_ATSPI */
 | 
						|
 | 
						|
#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);
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
on_hide_keyboard_timeout (Client *client)
 | 
						|
{
 | 
						|
    eekboard_context_hide_keyboard (client->context, NULL);
 | 
						|
    client->hide_keyboard_timeout_id = 0;
 | 
						|
    return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
static GDBusMessage *
 | 
						|
focus_message_filter (GDBusConnection *connection,
 | 
						|
                      GDBusMessage    *message,
 | 
						|
                      gboolean         incoming,
 | 
						|
                      gpointer         user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
 | 
						|
    if (incoming &&
 | 
						|
        g_strcmp0 (g_dbus_message_get_interface (message),
 | 
						|
                   IBUS_INTERFACE_PANEL) == 0) {
 | 
						|
        const gchar *member = g_dbus_message_get_member (message);
 | 
						|
 | 
						|
        if (g_strcmp0 (member, "FocusIn") == 0) {
 | 
						|
            if (client->hide_keyboard_timeout_id > 0) {
 | 
						|
                g_source_remove (client->hide_keyboard_timeout_id);
 | 
						|
                client->hide_keyboard_timeout_id = 0;
 | 
						|
            }
 | 
						|
            eekboard_context_show_keyboard (client->context, NULL);
 | 
						|
        } else if (g_settings_get_boolean (client->settings, "auto-hide") &&
 | 
						|
                   g_strcmp0 (member, "FocusOut") == 0) {
 | 
						|
            gint delay = g_settings_get_int (client->settings,
 | 
						|
                                             "auto-hide-delay");
 | 
						|
            client->hide_keyboard_timeout_id =
 | 
						|
                g_timeout_add (delay,
 | 
						|
                               (GSourceFunc)on_hide_keyboard_timeout,
 | 
						|
                               client);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return message;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
_ibus_connect_focus_handlers (IBusBus *bus, gpointer user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
    GDBusConnection *connection;
 | 
						|
 | 
						|
    connection = ibus_bus_get_connection (bus);
 | 
						|
    add_match_rule (connection,
 | 
						|
                    "type='method_call',"
 | 
						|
                    "interface='" IBUS_INTERFACE_PANEL "',"
 | 
						|
                    "member='FocusIn'");
 | 
						|
    add_match_rule (connection,
 | 
						|
                    "type='method_call',"
 | 
						|
                    "interface='" IBUS_INTERFACE_PANEL "',"
 | 
						|
                    "member='FocusOut'");
 | 
						|
    client->ibus_focus_message_filter =
 | 
						|
        g_dbus_connection_add_filter (connection,
 | 
						|
                                      focus_message_filter,
 | 
						|
                                      client,
 | 
						|
                                      NULL);
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
client_enable_ibus_focus (Client *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);
 | 
						|
 | 
						|
    client->follows_focus = TRUE;
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
client_disable_ibus_focus (Client *client)
 | 
						|
{
 | 
						|
    GDBusConnection *connection;
 | 
						|
 | 
						|
    client->follows_focus = FALSE;
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
}
 | 
						|
#endif  /* HAVE_ATSPI */
 | 
						|
 | 
						|
Client *
 | 
						|
client_new (GDBusConnection *connection)
 | 
						|
{
 | 
						|
    Client *client = g_object_new (TYPE_CLIENT,
 | 
						|
                                   "connection", connection,
 | 
						|
                                   NULL);
 | 
						|
    if (client->context)
 | 
						|
        return client;
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static GdkFilterReturn
 | 
						|
filter_xkl_event (GdkXEvent *xev,
 | 
						|
                  GdkEvent  *event,
 | 
						|
                  gpointer   user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
    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)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
    gboolean retval;
 | 
						|
 | 
						|
    retval = set_keyboard_from_xkl (client);
 | 
						|
    g_return_if_fail (retval);
 | 
						|
 | 
						|
#ifdef HAVE_XTEST
 | 
						|
    update_modifier_keycodes (client);
 | 
						|
#endif  /* HAVE_XTEST */
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
set_keyboard (Client *client,
 | 
						|
              const gchar *keyboard)
 | 
						|
{
 | 
						|
    GSList *keyboards = NULL;
 | 
						|
    gchar **strv, **p;
 | 
						|
 | 
						|
    if (client->keyboards)
 | 
						|
        g_slist_free (client->keyboards);
 | 
						|
 | 
						|
    strv = g_strsplit (keyboard, ",", -1);
 | 
						|
    for (p = strv; *p != NULL; p++) {
 | 
						|
        guint keyboard_id;
 | 
						|
 | 
						|
        keyboard_id = eekboard_context_add_keyboard (client->context,
 | 
						|
                                                     *p,
 | 
						|
                                                     NULL);
 | 
						|
        if (keyboard_id == 0)
 | 
						|
            return FALSE;
 | 
						|
        keyboards = g_slist_prepend (keyboards,
 | 
						|
                                     GUINT_TO_POINTER(keyboard_id));
 | 
						|
    }
 | 
						|
    g_strfreev (strv);
 | 
						|
 | 
						|
    /* make a cycle */
 | 
						|
    keyboards = g_slist_reverse (keyboards);
 | 
						|
    g_slist_last (keyboards)->next = keyboards;
 | 
						|
    client->keyboards = keyboards;
 | 
						|
 | 
						|
    /* select the first keyboard */
 | 
						|
    eekboard_context_set_keyboard (client->context,
 | 
						|
                                   GPOINTER_TO_UINT(keyboards->data),
 | 
						|
                                   NULL);
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
set_keyboard_from_xkl (Client *client)
 | 
						|
{
 | 
						|
    XklConfigRec *rec;
 | 
						|
    gchar *layout, *keyboard;
 | 
						|
    guint keyboard_id;
 | 
						|
 | 
						|
    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);
 | 
						|
 | 
						|
    keyboard_id = eekboard_context_add_keyboard (client->context,
 | 
						|
                                                 keyboard,
 | 
						|
                                                 NULL);
 | 
						|
    g_free (keyboard);
 | 
						|
    if (keyboard_id == 0)
 | 
						|
        return FALSE;
 | 
						|
    eekboard_context_set_keyboard (client->context, keyboard_id, NULL);
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
on_xkl_state_changed (XklEngine           *xklengine,
 | 
						|
                      XklEngineStateChange type,
 | 
						|
                      gint                 value,
 | 
						|
                      gboolean             restore,
 | 
						|
                      gpointer             user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
 | 
						|
    if (type == GROUP_CHANGED)
 | 
						|
        eekboard_context_set_group (client->context, value, NULL);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef HAVE_XTEST
 | 
						|
/* 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 (Client *client)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
 | 
						|
    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 &&
 | 
						|
            XKeycodeToKeysym (xdisplay, i, 0) != 0)
 | 
						|
            return i;
 | 
						|
 | 
						|
    return XKeysymToKeycode (xdisplay, 0x0023); /* XK_numbersign */
 | 
						|
}
 | 
						|
 | 
						|
/* 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 (Client *client,
 | 
						|
                 guint          *keycode,
 | 
						|
                 guint          *keysym)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
 | 
						|
    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;
 | 
						|
    replaced_keysym = XKeycodeToKeysym (xdisplay, replaced_keycode, 0);
 | 
						|
    XFlush (xdisplay);
 | 
						|
 | 
						|
    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;
 | 
						|
 | 
						|
    XkbChangeMap (xdisplay, client->xkb, &changes);
 | 
						|
    XFlush (xdisplay);
 | 
						|
 | 
						|
    *keycode = replaced_keycode;
 | 
						|
    *keysym = replaced_keysym;
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean
 | 
						|
get_keycode_from_gdk_keymap (Client *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++)
 | 
						|
        if (keys[i].group == eekboard_context_get_group (client->context, NULL))
 | 
						|
            best_match = &keys[i];
 | 
						|
 | 
						|
    *keycode = best_match->keycode;
 | 
						|
    *modifiers = best_match->level == 1 ? EEK_SHIFT_MASK : 0;
 | 
						|
 | 
						|
    g_free (keys);
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
send_fake_modifier_key_event (Client *client,
 | 
						|
                              EekModifierType modifiers,
 | 
						|
                              gboolean        is_pressed)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    gint i;
 | 
						|
 | 
						|
    for (i = 0; i < G_N_ELEMENTS(client->modifier_keycodes); i++) {
 | 
						|
        if (modifiers & (1 << i)) {
 | 
						|
            guint keycode = client->modifier_keycodes[i];
 | 
						|
 | 
						|
            g_return_if_fail (keycode > 0);
 | 
						|
 | 
						|
            XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY (display),
 | 
						|
                               keycode,
 | 
						|
                               is_pressed,
 | 
						|
                               CurrentTime);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
send_fake_key_event (Client *client,
 | 
						|
                     EekSymbol      *symbol,
 | 
						|
                     guint           keyboard_modifiers,
 | 
						|
                     gboolean        is_pressed)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    EekModifierType modifiers;
 | 
						|
    guint xkeysym;
 | 
						|
    guint keycode, replaced_keysym = 0;
 | 
						|
 | 
						|
    /* 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);
 | 
						|
 | 
						|
    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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    /* Clear level shift modifiers */
 | 
						|
    keyboard_modifiers &= ~EEK_SHIFT_MASK;
 | 
						|
    keyboard_modifiers &= ~EEK_LOCK_MASK;
 | 
						|
    //keyboard_modifiers &= ~eek_keyboard_get_alt_gr_mask (client->keyboard);
 | 
						|
 | 
						|
    modifiers |= keyboard_modifiers;
 | 
						|
 | 
						|
    send_fake_modifier_key_event (client, modifiers, is_pressed);
 | 
						|
    XSync (GDK_DISPLAY_XDISPLAY (display), False);
 | 
						|
 | 
						|
    keycode = XKeysymToKeycode (GDK_DISPLAY_XDISPLAY (display),
 | 
						|
                                xkeysym);
 | 
						|
    g_return_if_fail (keycode > 0);
 | 
						|
 | 
						|
    XTestFakeKeyEvent (GDK_DISPLAY_XDISPLAY (display),
 | 
						|
                       keycode,
 | 
						|
                       is_pressed,
 | 
						|
                       CurrentTime);
 | 
						|
    XSync (GDK_DISPLAY_XDISPLAY (display), False);
 | 
						|
 | 
						|
    if (replaced_keysym)
 | 
						|
        replace_keycode (client, &keycode, &replaced_keysym);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
on_key_pressed (EekboardContext *context,
 | 
						|
                const gchar     *keyname,
 | 
						|
                EekSymbol       *symbol,
 | 
						|
                guint            modifiers,
 | 
						|
                gpointer         user_data)
 | 
						|
{
 | 
						|
    Client *client = user_data;
 | 
						|
 | 
						|
    if (g_strcmp0 (eek_symbol_get_name (symbol), "cycle-keyboard") == 0) {
 | 
						|
        client->keyboards = g_slist_next (client->keyboards);
 | 
						|
        eekboard_context_set_keyboard (client->context,
 | 
						|
                                       GPOINTER_TO_UINT(client->keyboards->data),
 | 
						|
                                       NULL);
 | 
						|
    }
 | 
						|
 | 
						|
    send_fake_key_event (client, symbol, modifiers, TRUE);
 | 
						|
    send_fake_key_event (client, symbol, modifiers, FALSE);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
update_modifier_keycodes (Client *client)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    XModifierKeymap *mods;
 | 
						|
    gint i, j;
 | 
						|
 | 
						|
    mods = XGetModifierMapping (GDK_DISPLAY_XDISPLAY (display));
 | 
						|
    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;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    XFreeModifiermap (mods);
 | 
						|
}
 | 
						|
 | 
						|
gboolean
 | 
						|
client_enable_xtest (Client *client)
 | 
						|
{
 | 
						|
    GdkDisplay *display = gdk_display_get_default ();
 | 
						|
    int opcode, event_base, error_base, major_version, minor_version;
 | 
						|
 | 
						|
    g_assert (display);
 | 
						|
 | 
						|
    if (!XTestQueryExtension (GDK_DISPLAY_XDISPLAY (display),
 | 
						|
                              &event_base, &error_base,
 | 
						|
                              &major_version, &minor_version)) {
 | 
						|
        g_warning ("XTest extension is not available");
 | 
						|
        return FALSE;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!XkbQueryExtension (GDK_DISPLAY_XDISPLAY (display),
 | 
						|
                            &opcode, &event_base, &error_base,
 | 
						|
                            &major_version, &minor_version)) {
 | 
						|
        g_warning ("Xkb extension is not available");
 | 
						|
        return FALSE;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!client->xkb)
 | 
						|
        client->xkb = XkbGetMap (GDK_DISPLAY_XDISPLAY (display),
 | 
						|
                                 XkbKeySymsMask,
 | 
						|
                                 XkbUseCoreKbd);
 | 
						|
    g_assert (client->xkb);
 | 
						|
 | 
						|
    update_modifier_keycodes (client);
 | 
						|
 | 
						|
    client->key_pressed_handler =
 | 
						|
        g_signal_connect (client->context, "key-pressed",
 | 
						|
                          G_CALLBACK(on_key_pressed), client);
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
client_disable_xtest (Client *client)
 | 
						|
{
 | 
						|
    if (client->xkb) {
 | 
						|
        XkbFreeKeyboard (client->xkb, 0, TRUE);	/* free_all = TRUE */
 | 
						|
        client->xkb = NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
#endif  /* HAVE_XTEST */
 |