From 134faacb5efd3e2d629d3fadd582d5683dcee297 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 1 Feb 2011 07:36:33 +0900 Subject: [PATCH] Reimplement eekboard as a D-Bus server. --- README | 25 +- src/Makefile.am | 51 ++++- src/proxy.c | 213 +++++++++++++++++ src/proxy.h | 48 ++++ src/server-main.c | 65 ++++++ src/server.c | 365 ++++++++++++++++++++++++++++++ src/server.h | 40 ++++ src/system-client-main.c | 86 +++++++ src/system-client.c | 477 +++++++++++++++++++++++++++++++++++++++ src/system-client.h | 53 +++++ 10 files changed, 1406 insertions(+), 17 deletions(-) create mode 100644 src/proxy.c create mode 100644 src/proxy.h create mode 100644 src/server-main.c create mode 100644 src/server.c create mode 100644 src/server.h create mode 100644 src/system-client-main.c create mode 100644 src/system-client.c create mode 100644 src/system-client.h diff --git a/README b/README index a303e593..92ae4e30 100644 --- a/README +++ b/README @@ -6,7 +6,7 @@ create keyboard-like UI ("libeek"). * Requirements - * GLib2, GTK, GConf2, PangoCairo, libxklavier, libfakekey, libnotify, CSPI + * GLib2, GTK, GConf2, PangoCairo, libxklavier, libfakekey, CSPI * Clutter (optional) * Clutter-Gtk (optional) * Vala (optional) @@ -19,15 +19,26 @@ Build from git repo: $ git clone git://github.com/ueno/eekboard.git $ cd eekboard $ ./autogen.sh --prefix=/usr --enable-gtk-doc - -Build from tarball: - $ ./configure $ make - $ sudo make install - $ eekboard + +How to test (dump/load the current layout): + + $ ./tests/eek-example-xml -d > keyboard.xml + $ ./tests/eek-example-xml -l keyboard.xml + +NOTE: eekboard is now being heavily rewritten and it may behave +wrongly. Make sure to close windows where you are doing any important +work, before running eekboard. + + $ ./src/eekboard-server & + $ ./src/eekboard-system-client --xklavier --accessibility --fakekey + +Where --xklavier is to monitor system keyboard layout change, +--accessibility is to track focus/key events via AT-SPI, and --fakekey +generates X key events when eekboard-server signals virtual +key-press/key-release events. * Documentation - diff --git a/src/Makefile.am b/src/Makefile.am index 17f08c98..dc8c711b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,8 +17,18 @@ # 02110-1301 USA if ENABLE_EEKBOARD -bin_PROGRAMS = eekboard -eekboard_CFLAGS = \ +bin_PROGRAMS = eekboard-system-client eekboard-server +noinst_LIBRARIES = libeekboard.a + +libeekboard_a_headers = proxy.h + +libeekboard_a_CFLAGS = \ + -I$(top_srcdir) \ + $(GIO2_CFLAGS) + +libeekboard_a_SOURCES = proxy.c + +eekboard_system_client_CFLAGS = \ -I$(top_srcdir) \ $(GIO2_CFLAGS) \ $(GTK_CFLAGS) \ @@ -26,24 +36,45 @@ eekboard_CFLAGS = \ $(XKB_CFLAGS) \ $(LIBXKLAVIER_CFLAGS) \ $(LIBFAKEKEY_CFLAGS) \ - $(CSPI_CFLAGS) \ - $(NOTIFY_CFLAGS) + $(CSPI_CFLAGS) -eekboard_LDFLAGS = \ +eekboard_system_client_LDADD = \ + libeekboard.a \ $(top_builddir)/eek/libeek.la \ $(top_builddir)/eek/libeek-xkl.la \ - $(top_builddir)/eek/libeek-gtk.la \ $(GIO2_LIBS) \ $(GTK_LIBS) \ $(GCONF2_LIBS) \ $(XKB_LIBS) \ $(LIBXKLAVIER_LIBS) \ $(LIBFAKEKEY_LIBS) \ - $(CSPI_LIBS) \ - $(NOTIFY_LIBS) + $(CSPI_LIBS) + +eekboard_system_client_headers = system-client.h +eekboard_system_client_SOURCES = system-client.c system-client-main.c + +eekboard_server_CFLAGS = \ + -I$(top_srcdir) \ + $(GIO2_CFLAGS) \ + $(GTK_CFLAGS) + +eekboard_server_LDADD = \ + libeekboard.a \ + $(top_builddir)/eek/libeek.la \ + $(top_builddir)/eek/libeek-gtk.la \ + $(GIO2_LIBS) \ + $(GTK_LIBS) + +eekboard_server_headers = server.h +eekboard_server_SOURCES = server.c server-main.c if HAVE_CLUTTER -eekboard_CFLAGS += $(CLUTTER_CFLAGS) $(CLUTTER_GTK_CFLAGS) -eekboard_LDFLAGS += $(CLUTTER_LIBS) $(top_builddir)/eek/libeek-clutter.la $(CLUTTER_GTK_LIBS) +eekboard_server_CFLAGS += $(CLUTTER_CFLAGS) $(CLUTTER_GTK_CFLAGS) +eekboard_server_LDADD += $(CLUTTER_LIBS) $(top_builddir)/eek/libeek-clutter.la $(CLUTTER_GTK_LIBS) endif + +noinst_HEADERS = \ + $(libeekboard_a_headers) \ + $(eekboard_system_client_headers) \ + $(eekboard_server_headers) endif diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 00000000..f47590ed --- /dev/null +++ b/src/proxy.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "proxy.h" + +#define BUFSIZE 8192 + +enum { + KEY_PRESSED, + KEY_RELEASED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +struct _EekboardProxy { + GDBusProxy parent; +}; + +struct _EekboardProxyClass { + GDBusProxyClass parent_class; +}; + +G_DEFINE_TYPE (EekboardProxy, eekboard_proxy, G_TYPE_DBUS_PROXY); + +static void +eekboard_proxy_real_g_signal (GDBusProxy *self, + const gchar *sender_name, + const gchar *signal_name, + GVariant *parameters) +{ + EekboardProxy *proxy = EEKBOARD_PROXY (self); + guint *keycode; + + g_debug ("%s %s", sender_name, signal_name); + if (g_strcmp0 (signal_name, "KeyPressed") == 0) { + + g_variant_get (parameters, "(u)", &keycode); + g_signal_emit_by_name (proxy, "key-pressed", keycode); + return; + } + + if (g_strcmp0 (signal_name, "KeyReleased") == 0) { + g_variant_get (parameters, "(u)", &keycode); + g_signal_emit_by_name (proxy, "key-released", keycode); + return; + } + + g_return_if_reached (); +} + +static void +eekboard_proxy_class_init (EekboardProxyClass *klass) +{ + GDBusProxyClass *proxy_class = G_DBUS_PROXY_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + proxy_class->g_signal = eekboard_proxy_real_g_signal; + + signals[KEY_PRESSED] = + g_signal_new ("key-pressed", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + signals[KEY_RELEASED] = + g_signal_new ("key-released", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); +} + +static void +eekboard_proxy_init (EekboardProxy *proxy) +{ +} + +EekboardProxy * +eekboard_proxy_new (const gchar *path, + GDBusConnection *connection, + GCancellable *cancellable, + GError **error) +{ + GInitable *initable; + + g_assert (path != NULL); + g_assert (G_IS_DBUS_CONNECTION(connection)); + + initable = + g_initable_new (EEKBOARD_TYPE_PROXY, + cancellable, + error, + "g-connection", connection, + "g-name", "com.redhat.eekboard.Keyboard", + "g-flags", G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START | + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, + "g-interface-name", "com.redhat.eekboard.Keyboard", + "g-object-path", path, + NULL); + if (initable != NULL) + return EEKBOARD_PROXY (initable); + return NULL; +} + +static void +proxy_call_async_ready_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error = NULL; + GVariant *result; + + result = g_dbus_proxy_call_finish (G_DBUS_PROXY(source_object), + res, + &error); + g_assert_no_error (error); + g_assert (result != NULL); + g_variant_unref (result); +} + +void +eekboard_proxy_set_keyboard (EekboardProxy *proxy, EekKeyboard *keyboard) +{ + GString *output; + GVariant *variant; + gchar *data; + + output = g_string_sized_new (BUFSIZE); + eek_keyboard_output (keyboard, output, 0); + + data = g_string_free (output, FALSE); + variant = g_variant_new ("(s)", data); + g_free (data); + + g_dbus_proxy_call (G_DBUS_PROXY(proxy), + "SetKeyboard", + g_variant_new ("(v)", variant), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + proxy_call_async_ready_cb, + NULL); + g_variant_unref (variant); +} + +void +eekboard_proxy_set_group (EekboardProxy *proxy, gint group) +{ + g_dbus_proxy_call (G_DBUS_PROXY(proxy), + "SetGroup", + g_variant_new ("(i)", group), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + proxy_call_async_ready_cb, + NULL); +} + +void +eekboard_proxy_show (EekboardProxy *proxy) +{ + g_dbus_proxy_call (G_DBUS_PROXY(proxy), + "Show", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + proxy_call_async_ready_cb, + NULL); +} + +void +eekboard_proxy_hide (EekboardProxy *proxy) +{ + g_dbus_proxy_call (G_DBUS_PROXY(proxy), + "Hide", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + proxy_call_async_ready_cb, + NULL); +} diff --git a/src/proxy.h b/src/proxy.h new file mode 100644 index 00000000..6c7aff33 --- /dev/null +++ b/src/proxy.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#ifndef EEKBOARD_PROXY_H +#define EEKBOARD_PROXY_H 1 + +#include +#include "eek/eek.h" + +G_BEGIN_DECLS + +#define EEKBOARD_TYPE_PROXY (eekboard_proxy_get_type()) +#define EEKBOARD_PROXY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_PROXY, EekboardProxy)) +#define EEKBOARD_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEKBOARD_TYPE_PROXY, EekboardProxyClass)) +#define EEKBOARD_IS_PROXY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEKBOARD_TYPE_PROXY)) +#define EEKBOARD_IS_PROXY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEKBOARD_TYPE_PROXY)) +#define EEKBOARD_PROXY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEKBOARD_TYPE_PROXY, EekboardProxyClass)) + +typedef struct _EekboardProxy EekboardProxy; +typedef struct _EekboardProxyClass EekboardProxyClass; + +EekboardProxy *eekboard_proxy_new (const gchar *path, + GDBusConnection *connection, + GCancellable *cancellable, + GError **error); +void eekboard_proxy_set_keyboard (EekboardProxy *proxy, + EekKeyboard *keyboard); +void eekboard_proxy_set_group (EekboardProxy *proxy, + gint group); +void eekboard_proxy_show (EekboardProxy *proxy); +void eekboard_proxy_hide (EekboardProxy *proxy); + +G_END_DECLS +#endif /* EEKBOARD_PROXY_H */ diff --git a/src/server-main.c b/src/server-main.c new file mode 100644 index 00000000..71c95879 --- /dev/null +++ b/src/server-main.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "server.h" +#include "eek/eek.h" + +int +main (int argc, char **argv) +{ + EekboardServer *server; + GDBusConnection *connection; + GError *error; + GMainLoop *loop; + + if (!gtk_init_check (&argc, &argv)) { + g_warning ("Can't init GTK"); + exit (1); + } + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error) { + g_printerr ("%s\n", error->message); + exit (1); + } + + server = eekboard_server_new (connection); + + loop = g_main_loop_new(NULL, FALSE); + + if (!eekboard_server_start (server)) { + g_printerr ("Can't start server\n"); + exit (1); + } + g_main_loop_run(loop); + eekboard_server_stop (server); + + g_object_unref(server); + g_object_unref(connection); + g_main_loop_unref(loop); + + return 0; +} diff --git a/src/server.c b/src/server.c new file mode 100644 index 00000000..580f1bfa --- /dev/null +++ b/src/server.c @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "eek/eek.h" +#include "eek/eek-gtk.h" +#include "server.h" + +#define CSW 640 +#define CSH 480 + +enum { + PROP_0, + PROP_CONNECTION, + PROP_LAST +}; + +static const gchar introspection_xml[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +typedef struct _EekboardServerClass EekboardServerClass; + +struct _EekboardServer { + GObject parent; + GDBusConnection *connection; + guint owner_id; + GDBusNodeInfo *introspection_data; + + GtkWidget *window; + GtkWidget *widget; + EekKeyboard *keyboard; +}; + +struct _EekboardServerClass { + GObjectClass parent_class; +}; + +G_DEFINE_TYPE (EekboardServer, eekboard_server, G_TYPE_OBJECT); + +static void +update_widget (EekboardServer *server) +{ + GdkScreen *screen; + GdkWindow *root; + gint monitor; + GdkRectangle rect; + EekBounds bounds; + + if (server->widget) + gtk_widget_destroy (server->widget); + + if (server->window) + gtk_widget_destroy (server->window); + + server->widget = eek_gtk_keyboard_new (server->keyboard); + + eek_element_get_bounds (EEK_ELEMENT(server->keyboard), &bounds); + gtk_widget_set_size_request (server->widget, bounds.width, bounds.height); + + server->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_add (GTK_CONTAINER(server->window), server->widget); + + gtk_widget_set_can_focus (server->window, FALSE); + g_object_set (G_OBJECT(server->window), "accept_focus", FALSE, NULL); + gtk_window_set_title (GTK_WINDOW(server->window), "Keyboard"); + + screen = gdk_screen_get_default (); + root = gtk_widget_get_root_window (server->window); + monitor = gdk_screen_get_monitor_at_window (screen, root); + gdk_screen_get_monitor_geometry (screen, monitor, &rect); + gtk_window_move (GTK_WINDOW(server->window), + MAX(rect.width - 20 - bounds.width, 0), + MAX(rect.height - 40 - bounds.height, 0)); +} + +static void +eekboard_server_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EekboardServer *server = EEKBOARD_SERVER(object); + GDBusConnection *connection; + + switch (prop_id) { + case PROP_CONNECTION: + connection = g_value_get_object (value); + if (server->connection) + g_object_unref (server->connection); + server->connection = g_object_ref (connection); + break; + default: + g_object_set_property (object, + g_param_spec_get_name (pspec), + value); + break; + } +} + +static void +eekboard_server_dispose (GObject *object) +{ + EekboardServer *server = EEKBOARD_SERVER(object); + if (server->connection) { + g_object_unref (server->connection); + server->connection = NULL; + } + G_OBJECT_CLASS (eekboard_server_parent_class)->dispose (object); +} + +static void +eekboard_server_class_init (EekboardServerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->set_property = eekboard_server_set_property; + gobject_class->dispose = eekboard_server_dispose; + + 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); +} + +static void +eekboard_server_init (EekboardServer *server) +{ + GError *error; + + error = NULL; + server->introspection_data = + g_dbus_node_info_new_for_xml (introspection_xml, &error); + g_assert (server->introspection_data != NULL); + server->owner_id = 0; + server->keyboard = NULL; + server->widget = NULL; + server->window = NULL; +} + +static void +on_key_pressed (EekKeyboard *keyboard, + EekKey *key, + gpointer user_data) +{ + EekboardServer *server = user_data; + GError *error; + + error = NULL; + g_dbus_connection_emit_signal (server->connection, + "com.redhat.eekboard.Keyboard", + "/com/redhat/eekboard/Keyboard", + "com.redhat.eekboard.Keyboard", + "KeyPressed", + g_variant_new ("(u)", + eek_key_get_keycode (key)), + &error); + g_assert_no_error (error); +} + +static void +on_key_released (EekKeyboard *keyboard, + EekKey *key, + gpointer user_data) +{ + EekboardServer *server = user_data; + GError *error; + + error = NULL; + g_dbus_connection_emit_signal (server->connection, + "com.redhat.eekboard.Keyboard", + "/com/redhat/eekboard/Keyboard", + "com.redhat.eekboard.Keyboard", + "KeyReleased", + g_variant_new ("(u)", + eek_key_get_keycode (key)), + &error); + g_assert_no_error (error); +} + +static void +handle_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + EekboardServer *server = user_data; + + g_debug ("%s", method_name); + if (g_strcmp0 (method_name, "SetKeyboard") == 0) { + GVariant *variant; + gchar *data; + GInputStream *input; + EekLayout *layout; + + g_variant_get (parameters, "(v)", &variant); + g_variant_get (variant, "(&s)", &data); + input = g_memory_input_stream_new_from_data (data, -1, NULL); + g_variant_unref (variant); + + layout = eek_xml_layout_new (input); + if (!layout) { + g_dbus_method_invocation_return_error (invocation, + G_IO_ERROR, + G_IO_ERROR_FAILED_HANDLED, + "can't create layout"); + return; + } + + server->keyboard = eek_keyboard_new (layout, CSW, CSH); + g_signal_connect (server->keyboard, "key-pressed", + G_CALLBACK(on_key_pressed), + server); + g_signal_connect (server->keyboard, "key-released", + G_CALLBACK(on_key_released), + server); + eek_keyboard_set_modifier_behavior (server->keyboard, + EEK_MODIFIER_BEHAVIOR_LATCH); + + update_widget (server); + g_dbus_method_invocation_return_value (invocation, NULL); + } else if (g_strcmp0 (method_name, "SetGroup") == 0) { + gint group; + + if (!server->keyboard) + g_dbus_method_invocation_return_error (invocation, + G_IO_ERROR, + G_IO_ERROR_FAILED_HANDLED, + "keyboard is not set"); + g_variant_get (parameters, "(i)", &group); + eek_keyboard_set_group (server->keyboard, group); + g_dbus_method_invocation_return_value (invocation, NULL); + } else if (g_strcmp0 (method_name, "Show") == 0) { + if (!server->keyboard) + g_dbus_method_invocation_return_error (invocation, + G_IO_ERROR, + G_IO_ERROR_FAILED_HANDLED, + "keyboard is not set"); + if (server->window) + gtk_widget_show_all (server->window); + g_dbus_method_invocation_return_value (invocation, NULL); + } else if (g_strcmp0 (method_name, "Hide") == 0) { + if (server->window) + gtk_widget_hide (server->window); + g_dbus_method_invocation_return_value (invocation, NULL); + } +} + +static const GDBusInterfaceVTable interface_vtable = +{ + handle_method_call, + NULL, + NULL +}; + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + //g_debug ("name acquired %s", name); +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + //g_debug ("name lost %s", name); +} + +EekboardServer * +eekboard_server_new (GDBusConnection *connection) +{ + return g_object_new (EEKBOARD_TYPE_SERVER, "connection", connection, NULL); +} + +gboolean +eekboard_server_start (EekboardServer *server) +{ + guint registration_id; + GError *error; + + error = NULL; + registration_id = g_dbus_connection_register_object + (server->connection, + "/com/redhat/eekboard/Keyboard", + server->introspection_data->interfaces[0], + &interface_vtable, + server, + NULL, + &error); + if (error) + g_printerr ("%s\n", error->message); + g_assert (registration_id > 0); + + server->owner_id = + g_bus_own_name_on_connection (server->connection, + "com.redhat.eekboard.Keyboard", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_name_acquired, + on_name_lost, + NULL, + NULL); + return server->owner_id > 0; +} + +void +eekboard_server_stop (EekboardServer *server) +{ + if (server->owner_id > 0) + g_bus_unown_name (server->owner_id); + if (server->introspection_data) + g_dbus_node_info_unref (server->introspection_data); +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 00000000..45ba6b6f --- /dev/null +++ b/src/server.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#ifndef EEKBOARD_SERVER_H +#define EEKBOARD_SERVER_H 1 + +#include + +G_BEGIN_DECLS + +#define EEKBOARD_TYPE_SERVER (eekboard_server_get_type()) +#define EEKBOARD_SERVER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_SERVER, EekboardServer)) +#define EEKBOARD_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEKBOARD_TYPE_SERVER, EekboardServerClass)) +#define EEKBOARD_IS_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEKBOARD_TYPE_SERVER)) +#define EEKBOARD_IS_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEKBOARD_TYPE_SERVER)) +#define EEKBOARD_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEKBOARD_TYPE_SERVER, EekboardServerClass)) + +typedef struct _EekboardServer EekboardServer; + +EekboardServer *eekboard_server_new (GDBusConnection *connection); + +gboolean eekboard_server_start (EekboardServer *server); +void eekboard_server_stop (EekboardServer *server); + +G_END_DECLS +#endif /* EEKBOARD_SERVER_H */ diff --git a/src/system-client-main.c b/src/system-client-main.c new file mode 100644 index 00000000..ee79c33d --- /dev/null +++ b/src/system-client-main.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include "system-client.h" + +gboolean opt_xkl = FALSE; +gboolean opt_cspi = FALSE; +gboolean opt_fakekey = FALSE; + +static const GOptionEntry options[] = { + {"xklavier", 'x', 0, G_OPTION_ARG_NONE, &opt_xkl, + "Listen xklavier events"}, + {"accessibility", 'a', 0, G_OPTION_ARG_NONE, &opt_cspi, + "Listen accessibility events"}, + {"fakekey", 'k', 0, G_OPTION_ARG_NONE, &opt_fakekey, + "Generate X key events via libfakekey"}, + {NULL} +}; + +int +main (int argc, char **argv) +{ + EekboardSystemClient *client; + GDBusConnection *connection; + GError *error; + GConfClient *gconfc; + GOptionContext *context; + + if (!gtk_init_check (&argc, &argv)) { + g_printerr ("Can't init GTK\n"); + exit (1); + } + + context = g_option_context_new ("eekboard-system-client"); + g_option_context_add_main_entries (context, options, NULL); + g_option_context_parse (context, &argc, &argv, NULL); + g_option_context_free (context); + + error = NULL; + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + if (error) { + g_printerr ("%s\n", error->message); + exit (1); + } + client = eekboard_system_client_new (connection); + + gconfc = gconf_client_get_default (); + error = NULL; + if (opt_cspi) { + if (gconf_client_get_bool (gconfc, + "/desktop/gnome/interface/accessibility", + &error) || + gconf_client_get_bool (gconfc, + "/desktop/gnome/interface/accessibility2", + &error)) { + if (SPI_init () != 0) { + g_printerr ("Can't init CSPI\n"); + exit (1); + } + + if (!eekboard_system_client_enable_cspi (client)) { + g_printerr ("Can't register accessibility event listeners\n"); + exit (1); + } + } else { + g_printerr ("System accessibility support is disabled"); + exit (1); + } + } + if (opt_xkl && + !eekboard_system_client_enable_xkl (client)) { + g_printerr ("Can't register xklavier event listeners\n"); + exit (1); + } + + if (opt_fakekey && + !eekboard_system_client_enable_fakekey (client)) { + g_printerr ("Can't init fakekey\n"); + exit (1); + } + + gtk_main (); + + return 0; +} diff --git a/src/system-client.c b/src/system-client.c new file mode 100644 index 00000000..5629cdb4 --- /dev/null +++ b/src/system-client.c @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "eek/eek.h" +#include "eek/eek-xkl.h" +#include "system-client.h" +#include "proxy.h" + +#define CSW 640 +#define CSH 480 + +enum { + PROP_0, + PROP_CONNECTION, + PROP_LAST +}; + +typedef struct _EekboardSystemClientClass EekboardSystemClientClass; + +struct _EekboardSystemClient { + GObject parent; + + EekboardProxy *proxy; + + EekKeyboard *keyboard; + Accessible *accessible; + XklEngine *xkl_engine; + XklConfigRegistry *xkl_config_registry; + FakeKey *fakekey; + + gulong xkl_config_changed_handler_id; + gulong xkl_state_changed_handler_id; + + gulong key_pressed_handler_id; + gulong key_released_handler_id; + + AccessibleEventListener *focus_listener; + AccessibleEventListener *keystroke_listener; +}; + +struct _EekboardSystemClientClass { + GObjectClass parent_class; +}; + +G_DEFINE_TYPE (EekboardSystemClient, eekboard_system_client, G_TYPE_OBJECT); + +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); + +static SPIBoolean focus_listener_cb (const AccessibleEvent *event, + void *user_data); + +static SPIBoolean keystroke_listener_cb + (const AccessibleKeystroke *stroke, + void *user_data); +static void set_keyboard (EekboardSystemClient *client); + +static void +eekboard_system_client_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EekboardSystemClient *client = EEKBOARD_SYSTEM_CLIENT(object); + GDBusConnection *connection; + GError *error; + + switch (prop_id) { + case PROP_CONNECTION: + connection = g_value_get_object (value); + error = NULL; + client->proxy = eekboard_proxy_new ("/com/redhat/eekboard/Keyboard", + connection, + NULL, + &error); + break; + default: + g_object_set_property (object, + g_param_spec_get_name (pspec), + value); + break; + } +} + +static void +eekboard_system_client_dispose (GObject *object) +{ + EekboardSystemClient *client = EEKBOARD_SYSTEM_CLIENT(object); + + eekboard_system_client_disable_xkl (client); + eekboard_system_client_disable_cspi (client); + eekboard_system_client_disable_fakekey (client); + + if (client->proxy) { + g_object_unref (client->proxy); + client->proxy = NULL; + } + + if (client->fakekey) { + client->fakekey = NULL; + } + + G_OBJECT_CLASS (eekboard_system_client_parent_class)->dispose (object); +} + +static void +eekboard_system_client_class_init (EekboardSystemClientClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GParamSpec *pspec; + + gobject_class->set_property = eekboard_system_client_set_property; + gobject_class->dispose = eekboard_system_client_dispose; + + 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); +} + +static void +eekboard_system_client_init (EekboardSystemClient *client) +{ + client->keyboard = NULL; + client->accessible = NULL; + client->xkl_engine = NULL; + client->xkl_config_registry = NULL; + client->focus_listener = NULL; + client->keystroke_listener = NULL; + client->proxy = NULL; + client->fakekey = NULL; + client->key_pressed_handler_id = 0; + client->key_released_handler_id = 0; + client->xkl_config_changed_handler_id = 0; + client->xkl_state_changed_handler_id = 0; +} + +gboolean +eekboard_system_client_enable_xkl (EekboardSystemClient *client) +{ + if (!client->xkl_engine) { + Display *display; + + display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + client->xkl_engine = xkl_engine_get_instance (display); + } + + 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_id = + g_signal_connect (client->xkl_engine, "X-config-changed", + G_CALLBACK(on_xkl_config_changed), client); + client->xkl_state_changed_handler_id = + 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); + + set_keyboard (client); + + return TRUE; +} + +void +eekboard_system_client_disable_xkl (EekboardSystemClient *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_id)) + g_signal_handler_disconnect (client->xkl_engine, + client->xkl_config_changed_handler_id); + if (g_signal_handler_is_connected (client->xkl_engine, + client->xkl_state_changed_handler_id)) + g_signal_handler_disconnect (client->xkl_engine, + client->xkl_state_changed_handler_id); +} + +gboolean +eekboard_system_client_enable_cspi (EekboardSystemClient *client) +{ + client->focus_listener = SPI_createAccessibleEventListener + ((AccessibleEventListenerCB)focus_listener_cb, + client); + + if (!SPI_registerGlobalEventListener (client->focus_listener, + "object:state-changed:focused")) + return FALSE; + + if (!SPI_registerGlobalEventListener (client->focus_listener, + "focus:")) + return FALSE; + + client->keystroke_listener = + SPI_createAccessibleKeystrokeListener (keystroke_listener_cb, + client); + + if (!SPI_registerAccessibleKeystrokeListener + (client->keystroke_listener, + SPI_KEYSET_ALL_KEYS, + 0, + SPI_KEY_PRESSED | + SPI_KEY_RELEASED, + SPI_KEYLISTENER_NOSYNC)) + return FALSE; + return TRUE; +} + +void +eekboard_system_client_disable_cspi (EekboardSystemClient *client) +{ + if (client->focus_listener) { + SPI_deregisterGlobalEventListenerAll (client->focus_listener); + AccessibleEventListener_unref (client->focus_listener); + client->focus_listener = NULL; + } + if (client->keystroke_listener) { + SPI_deregisterAccessibleKeystrokeListener (client->keystroke_listener, + 0); + AccessibleKeystrokeListener_unref (client->keystroke_listener); + client->keystroke_listener = NULL; + } +} + +EekboardSystemClient * +eekboard_system_client_new (GDBusConnection *connection) +{ + return g_object_new (EEKBOARD_TYPE_SYSTEM_CLIENT, + "connection", connection, + NULL); +} + +static SPIBoolean +focus_listener_cb (const AccessibleEvent *event, + void *user_data) +{ + EekboardSystemClient *client = user_data; + Accessible *accessible = event->source; + AccessibleStateSet *state_set = Accessible_getStateSet (accessible); + AccessibleRole role = Accessible_getRole (accessible); + + if (AccessibleStateSet_contains (state_set, SPI_STATE_EDITABLE) || + role == SPI_ROLE_TERMINAL) { + switch (role) { + case SPI_ROLE_TEXT: + case SPI_ROLE_PARAGRAPH: + case SPI_ROLE_PASSWORD_TEXT: + case SPI_ROLE_TERMINAL: + if (g_strcmp0 (event->type, "focus") == 0 || + event->detail1 == 1) { + client->accessible = accessible; + eekboard_proxy_show (client->proxy); + } else if (event->detail1 == 0 && + accessible == client->accessible) { + client->accessible = NULL; + eekboard_proxy_hide (client->proxy); + } + case SPI_ROLE_ENTRY: + if (g_strcmp0 (event->type, "focus") == 0 || + event->detail1 == 1) { + client->accessible = accessible; + eekboard_proxy_show (client->proxy); + } else if (event->detail1 == 0 && + accessible == client->accessible) { + client->accessible = NULL; + eekboard_proxy_hide (client->proxy); + } + default: + ; + } + } + + return FALSE; +} + +static SPIBoolean +keystroke_listener_cb (const AccessibleKeystroke *stroke, + void *user_data) +{ + EekboardSystemClient *client = user_data; + EekKey *key; + EekSymbol *symbol; + + return FALSE; + + key = eek_keyboard_find_key_by_keycode (client->keyboard, + stroke->keycode); + if (!key) + return FALSE; + + symbol = eek_key_get_symbol_with_fallback (key, 0, 0); + if (!symbol) + return FALSE; + + /* XXX: Ignore modifier keys since there is no way to receive + SPI_KEY_RELEASED event for them. */ + if (eek_symbol_is_modifier (symbol)) + return FALSE; + + if (stroke->type == SPI_KEY_PRESSED) + g_signal_emit_by_name (key, "pressed"); + else + g_signal_emit_by_name (key, "released"); + return TRUE; +} + +static GdkFilterReturn +filter_xkl_event (GdkXEvent *xev, + GdkEvent *event, + gpointer user_data) +{ + EekboardSystemClient *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) +{ + EekboardSystemClient *client = user_data; + + set_keyboard (client); +} + +static void +set_keyboard (EekboardSystemClient *client) +{ + EekLayout *layout; + gchar *keyboard_name; + static gint keyboard_serial = 0; + + if (client->keyboard) + g_object_unref (client->keyboard); + layout = eek_xkl_layout_new (); + client->keyboard = eek_keyboard_new (layout, CSW, CSH); + + keyboard_name = g_strdup_printf ("keyboard%d", keyboard_serial++); + eek_element_set_name (EEK_ELEMENT(client->keyboard), keyboard_name); + + eekboard_proxy_set_keyboard (client->proxy, client->keyboard); +} + +static void +on_xkl_state_changed (XklEngine *xklengine, + XklEngineStateChange type, + gint value, + gboolean restore, + gpointer user_data) +{ + EekboardSystemClient *client = user_data; + + if (type == GROUP_CHANGED && client->keyboard) { + gint group = eek_keyboard_get_group (client->keyboard); + if (group != value) { + eek_keyboard_set_group (client->keyboard, value); + eekboard_proxy_set_group (client->proxy, value); + } + } +} + +static void +on_key_pressed (EekboardProxy *proxy, + guint keycode, + gpointer user_data) +{ + EekboardSystemClient *client = user_data; + EekKey *key; + EekSymbol *symbol; + + g_assert (client->fakekey); + key = eek_keyboard_find_key_by_keycode (client->keyboard, keycode); + symbol = eek_key_get_symbol_with_fallback (key, 0, 0); + if (EEK_IS_KEYSYM(symbol) && !eek_symbol_is_modifier (symbol)) + fakekey_press_keysym (client->fakekey, + eek_keysym_get_xkeysym (EEK_KEYSYM(symbol)), + eek_keyboard_get_modifiers (client->keyboard)); +} + +static void +on_key_released (EekboardProxy *proxy, + guint keycode, + gpointer user_data) +{ + EekboardSystemClient *client = user_data; + + g_assert (client->fakekey); + fakekey_release (client->fakekey); +} + +gboolean +eekboard_system_client_enable_fakekey (EekboardSystemClient *client) +{ + if (!client->fakekey) { + Display *display; + + display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + client->fakekey = fakekey_init (display); + } + g_assert (client->fakekey); + + client->key_pressed_handler_id = + g_signal_connect (client->proxy, "key-pressed", + G_CALLBACK(on_key_pressed), client); + client->key_released_handler_id = + g_signal_connect (client->proxy, "key-pressed", + G_CALLBACK(on_key_released), client); + + return TRUE; +} + +void +eekboard_system_client_disable_fakekey (EekboardSystemClient *client) +{ + if (client->fakekey) + fakekey_release (client->fakekey); + + if (g_signal_handler_is_connected (client->proxy, + client->key_pressed_handler_id)) + g_signal_handler_disconnect (client->proxy, + client->key_pressed_handler_id); + if (g_signal_handler_is_connected (client->proxy, + client->key_released_handler_id)) + g_signal_handler_disconnect (client->proxy, + client->key_released_handler_id); +} diff --git a/src/system-client.h b/src/system-client.h new file mode 100644 index 00000000..5c032f8e --- /dev/null +++ b/src/system-client.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010-2011 Daiki Ueno + * 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 . + */ +#ifndef EEKBOARD_SYSTEM_CLIENT_H +#define EEKBOARD_SYSTEM_CLIENT_H 1 + +#include + +G_BEGIN_DECLS + +#define EEKBOARD_TYPE_SYSTEM_CLIENT (eekboard_system_client_get_type()) +#define EEKBOARD_SYSTEM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_SYSTEM_CLIENT, EekboardSystemClient)) +#define EEKBOARD_SYSTEM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEKBOARD_TYPE_SYSTEM_CLIENT, EekboardSystemClientClass)) +#define EEKBOARD_IS_SYSTEM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEKBOARD_TYPE_SYSTEM_CLIENT)) +#define EEKBOARD_IS_SYSTEM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEKBOARD_TYPE_SYSTEM_CLIENT)) +#define EEKBOARD_SYSTEM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEKBOARD_TYPE_SYSTEM_CLIENT, EekboardSystemClientClass)) + +typedef struct _EekboardSystemClient EekboardSystemClient; + +EekboardSystemClient * eekboard_system_client_new + (GDBusConnection *connection); + +gboolean eekboard_system_client_enable_xkl + (EekboardSystemClient *client); +void eekboard_system_client_disable_xkl + (EekboardSystemClient *client); + +gboolean eekboard_system_client_enable_cspi + (EekboardSystemClient *client); +void eekboard_system_client_disable_cspi + (EekboardSystemClient *client); + +gboolean eekboard_system_client_enable_fakekey + (EekboardSystemClient *client); +void eekboard_system_client_disable_fakekey + (EekboardSystemClient *client); + +G_END_DECLS +#endif /* EEKBOARD_SYSTEM_CLIENT_H */