From d35bf5d331d885e94914fea6eab9c56f20666c8d Mon Sep 17 00:00:00 2001 From: Derek Dai <daiderek@gmail.com> Date: Sat, 20 Nov 2021 15:47:34 +0800 Subject: [PATCH 6/7] add GTK4 IM module --- configure.ac | 76 +- extras/immodules/Makefile.am | 2 +- extras/immodules/client-gtk/gtk4/Makefile.am | 52 + .../client-gtk/gtk4/im-scim-bridge-gtk.c | 61 ++ .../gtk4/scim-bridge-client-imcontext-gtk.c | 997 ++++++++++++++++++ .../gtk4/scim-bridge-client-imcontext-gtk.h | 111 ++ ...scim-bridge-client-key-event-utility-gtk.c | 132 +++ ...scim-bridge-client-key-event-utility-gtk.h | 54 + extras/immodules/configure.ac | 33 + 9 files changed, 1513 insertions(+), 5 deletions(-) create mode 100644 extras/immodules/client-gtk/gtk4/Makefile.am create mode 100644 extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c create mode 100644 extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h diff --git a/configure.ac b/configure.ac index 752c371..2841926 100644 --- a/configure.ac +++ b/configure.ac @@ -242,6 +242,10 @@ PKG_CHECK_MODULES(GTK3, [gtk+-3.0 >= 3.0.0 pango >= 1.1.0 gdk-pixbuf-2.0 >= 2.4. [SCIM_HAS_GTK3=yes], [SCIM_HAS_GTK3=no]) +PKG_CHECK_MODULES(GTK4, [gtk4 >= 4.0.0 pango >= 1.48.0 gdk-pixbuf-2.0 >= 2.42.0], + [SCIM_HAS_GTK4=yes], + [SCIM_HAS_GTK4=no]) + SCIM_HAS_GTK2_2=no if test "$SCIM_HAS_GTK2" = "yes"; then if $PKG_CONFIG --exists "gtk+-2.0 >= 2.2" ; then @@ -261,6 +265,14 @@ if test "$SCIM_HAS_GTK3" = "yes"; then AC_SUBST(GTK3_IM_MODULEDIR) fi +if test "$SCIM_HAS_GTK4" = "yes"; then + GTK4_VERSION=4.0.0 + GTK4_BINARY_VERSION=`$PKG_CONFIG --variable=gtk_binary_version gtk4` + GTK4_LIBDIR=`$PKG_CONFIG --variable=libdir gtk4` + GTK4_IM_MODULEDIR=$GTK4_LIBDIR/gtk-4.0/$GTK4_BINARY_VERSION/immodules + AC_SUBST(GTK4_IM_MODULEDIR) +fi + # Check if we have gthread PKG_CHECK_MODULES(GTHREAD2,[gthread-2.0 >= 2.0.0], [SCIM_HAS_GTHREAD2=yes], @@ -344,6 +356,11 @@ AC_ARG_WITH([gtk3-im-module-dir], [Select GTK3 immodule dir])], [GTK3_IM_MODULEDIR=$with_gtk3_im_module_dir]) +AC_ARG_WITH([gtk4-im-module-dir], + [AS_HELP_STRING([--with-gtk4-im-module-dir=dir], + [Select GTK4 immodule dir])], + [GTK4_IM_MODULEDIR=$with_gtk4_im_module_dir]) + AC_ARG_WITH([qt3-moc], [AS_HELP_STRING([--with-qt3-moc=file], [Select QT3 moc program (default to QT3_PREFIX/bin/moc)])], @@ -483,6 +500,12 @@ AC_ARG_ENABLE([gtk3-immodule], [], [enable_gtk3_immodule=yes]) +AC_ARG_ENABLE([gtk4-immodule], + [AS_HELP_STRING([--disable-gtk4-immodule], + [Do not build GTK4 IMModule])], + [], + [enable_gtk4_immodule=yes]) + AC_ARG_ENABLE([qt3-immodule], [AS_HELP_STRING([--disable-qt3-immodule], [Do not build QT3 IMModule])], @@ -625,6 +648,15 @@ else enable_gtk3_immodule=no fi +if test "$enable_gtk4_immodule" = "yes" -a "$SCIM_HAS_GTK4" = "yes"; then + SCIM_BUILD_GTK4_IMMODULE=1 + SCIM_BUILD_IMMODULE=1 + enable_immodule=yes +else + SCIM_BUILD_GTK4_IMMODULE=0 + enable_gtk4_immodule=no +fi + if test "$enable_im_agent" = "yes"; then SCIM_BUILD_IM_AGENT=1 else @@ -657,6 +689,17 @@ else enable_gtk3_immodule=no fi +if test "$enable_gtk4_immodule" = "yes" -a "$SCIM_HAS_GTK4" = "yes"; then + SCIM_BUILD_GTK4_IMMODULE=1 + SCIM_BUILD_IMMODULE=1 + enable_immodule=yes + SCIM_BUILD_IM_AGENT=1 + enable_im_agent=yes +else + SCIM_BUILD_GTK4_IMMODULE=0 + enable_gtk4_immodule=no +fi + if test "$enable_qt3_immodule" = "yes" -a "$SCIM_HAS_QT3" = "yes"; then SCIM_BUILD_QT3_IMMODULE=1 SCIM_BUILD_IMMODULE=1 @@ -692,19 +735,37 @@ else enable_clutter_immodule=no fi -if test "$SCIM_HAS_GTK3" = "yes" -a "$SCIM_HAS_GTK2" = "yes"; then +if test "$SCIM_HAS_GTK4" = "yes" -a "$SCIM_HAS_GTK3" = "yes" -a "$SCIM_HAS_GTK2" = "yes"; then if test "$WANT_GTK_VERSION" = "2"; then USE_GTK_VERSION="2" - else + elif test "$WANT_GTK_VERSION" = "3"; then USE_GTK_VERSION="3" + else + USE_GTK_VERSION="4" fi +elif test "$SCIM_HAS_GTK4" = "yes"; then + USE_GTK_VERSION="4" elif test "$SCIM_HAS_GTK3" = "yes"; then USE_GTK_VERSION="3" elif test "$SCIM_HAS_GTK2" = "yes"; then USE_GTK_VERSION="2" fi -if test "$USE_GTK_VERSION" = "3"; then +if test "$USE_GTK_VERSION" = "4"; then + SCIM_HAS_GTK=yes + GTK_LIBDIR=$GTK4_LIBDIR + GTK_VERSION=$GTK4_VERSION + GTK_MAJOR_VERSION=4 + GTK_BINARY_VERSION=$GTK4_BINARY_VERSION + GTK_CFLAGS=$GTK4_CFLAGS + GTK_LIBS=$GTK4_LIBS + AC_SUBST(GTK_LIBDIR) + AC_SUBST(GTK_VERSION) + AC_SUBST(GTK_MAJOR_VERSION) + AC_SUBST(GTK_BINARY_VERSION) + AC_SUBST(GTK_CFLAGS) + AC_SUBST(GTK_LIBS) +elif test "$USE_GTK_VERSION" = "3"; then SCIM_HAS_GTK=yes GTK_LIBDIR=$GTK3_LIBDIR GTK_VERSION=$GTK3_VERSION @@ -753,7 +814,7 @@ else fi enable_tray_icon=no -if test \( "$SCIM_HAS_GTK3"="yes" -o "$SCIM_HAS_GTK2_2" = "yes" \) -a "$no_x" != "yes"; then +if test \( "$SCIM_HAS_GTK4"="yes" -o "$SCIM_HAS_GTK3"="yes" -o "$SCIM_HAS_GTK2_2" = "yes" \) -a "$no_x" != "yes"; then enable_tray_icon=yes AC_DEFINE(ENABLE_TRAY_ICON,1,[Enable the TrayIcon code.]) fi @@ -818,6 +879,9 @@ AM_CONDITIONAL(SCIM_BUILD_GTK2_IMMODULE, AM_CONDITIONAL(SCIM_BUILD_GTK3_IMMODULE, [test "$enable_gtk3_immodule" = "yes"]) +AM_CONDITIONAL(SCIM_BUILD_GTK4_IMMODULE, + [test "$enable_gtk4_immodule" = "yes"]) + AM_CONDITIONAL(SCIM_BUILD_QT3_IMMODULE, [test "$enable_qt3_immodule" = "yes"]) @@ -868,6 +932,7 @@ AC_SUBST(SCIM_BUILD_X11_UTILS) AC_SUBST(SCIM_BUILD_IM_AGENT) AC_SUBST(SCIM_BUILD_GTK2_IMMODULE) AC_SUBST(SCIM_BUILD_GTK3_IMMODULE) +AC_SUBST(SCIM_BUILD_GTK4_IMMODULE) AC_SUBST(SCIM_BUILD_QT3_IMMODULE) AC_SUBST(SCIM_BUILD_QT4_IMMODULE) AC_SUBST(SCIM_BUILD_CLUTTER_IMMODULE) @@ -930,6 +995,7 @@ AC_CONFIG_FILES([Makefile extras/immodules/client-common/Makefile extras/immodules/client-gtk/gtk2/Makefile extras/immodules/client-gtk/gtk3/Makefile + extras/immodules/client-gtk/gtk4/Makefile extras/immodules/client-clutter/Makefile extras/immodules/doc/Makefile tests/Makefile @@ -976,6 +1042,7 @@ Module options: GTK2 IMModule dir $GTK2_IM_MODULEDIR GTK3 IMModule dir $GTK3_IM_MODULEDIR + GTK4 IMModule dir $GTK4_IM_MODULEDIR QT3 IMModule dir $QT3_IM_MODULEDIR QT4 IMModule dir $QT4_IM_MODULEDIR Clutter IMModule dir $CLUTTER_IM_MODULEDIR @@ -989,6 +1056,7 @@ Module options: Scim IM Agent $enable_im_agent GTK2 IMModule $enable_gtk2_immodule GTK3 IMModule $enable_gtk3_immodule + GTK4 IMModule $enable_gtk4_immodule QT3 IMModule $enable_qt3_immodule QT4 IMModule $enable_qt4_immodule CLUTTER IMModule $enable_clutter_immodule diff --git a/extras/immodules/Makefile.am b/extras/immodules/Makefile.am index 56b1fee..8fceaa8 100644 --- a/extras/immodules/Makefile.am +++ b/extras/immodules/Makefile.am @@ -37,4 +37,4 @@ MAINTAINERCLEANFILES = Makefile.in CLEANFILES = *.bak -SUBDIRS = common client-common client-gtk/gtk2 client-gtk/gtk3 client-clutter agent doc +SUBDIRS = common client-common client-gtk/gtk2 client-gtk/gtk3 client-gtk/gtk4 client-clutter agent doc diff --git a/extras/immodules/client-gtk/gtk4/Makefile.am b/extras/immodules/client-gtk/gtk4/Makefile.am new file mode 100644 index 0000000..ac20b37 --- /dev/null +++ b/extras/immodules/client-gtk/gtk4/Makefile.am @@ -0,0 +1,52 @@ +## Makefile.am -- Process this file with automake to produce Makefile.in +## +## Copyright (C) 2006 Ryo Dairiki +## +## +## This library is free software; you can redistribute it and/or +## modify it under the terms of the GNU Lesser General Public +## License as published by the Free Software Foundation and +## appearing in the file LICENSE.LGPL included in the package of this file. +## You can also redistribute it and/or modify it under the terms of +## the GNU General Public License as published by the Free Software Foundation and +## appearing in the file LICENSE.GPL included in the package of this file. +## +## This library 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. + +if SCIM_BUILD_GTK4_IMMODULE + +AM_CPPFLAGS = -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_srcdir)/extras/immodules/common \ + -I$(top_srcdir)/extras/immodules/client-common + +noinst_HEADERS = ../scim-bridge-client-gtk.h \ + ../scim-bridge-client-imcontext-gtk.h \ + ../scim-bridge-client-key-event-utility-gtk.h + +moduledir = @GTK4_IM_MODULEDIR@ + +module_LTLIBRARIES = libim-scim.la + +libim_scim_la_SOURCES = im-scim-bridge-gtk.c \ + ../scim-bridge-client-gtk.c \ + scim-bridge-client-imcontext-gtk.c \ + scim-bridge-client-key-event-utility-gtk.c + +libim_scim_la_CXXFLAGS=@GTK4_CFLAGS@ +libim_scim_la_CFLAGS =@GTK4_CFLAGS@ + +libim_scim_la_LDFLAGS = -rpath $(moduledir) \ + -avoid-version -no-undefined \ + -module + +libim_scim_la_LIBADD = $(top_builddir)/extras/immodules/common/libscimbridgecommon.la \ + $(top_builddir)/extras/immodules/client-common/libscimbridgeclientcommon.la \ + @GTK4_LIBS@ \ + @X_LIBS@ + +endif + +MAINTAINERCLEANFILES = Makefile.in diff --git a/extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c b/extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c new file mode 100644 index 0000000..93df5a4 --- /dev/null +++ b/extras/immodules/client-gtk/gtk4/im-scim-bridge-gtk.c @@ -0,0 +1,61 @@ +/* + * SCIM Bridge + * + * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation and + * appearing in the file LICENSE.LGPL included in the package of this file. + * You can also redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation and + * appearing in the file LICENSE.GPL included in the package of this file. + * + * This library 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <gtk/gtk.h> +#include <gtk/gtkimmodule.h> + +#include "scim-bridge.h" +#include "scim-bridge-client.h" +#include "../scim-bridge-client-gtk.h" +#include "scim-bridge-client-imcontext-gtk.h" + +/* Implementations */ +void g_io_im_scim_load (GIOModule *io_module) +{ + static boolean initialized = FALSE; + + if (initialized) { + return; + } + + scim_bridge_client_imcontext_register_type (io_module); + GIOExtension *ext = g_io_extension_point_implement ( + GTK_IM_MODULE_EXTENSION_POINT_NAME, + GTK_TYPE_SCIM_CLIENT_IMCONTEXT, + "scim", + 100); + scim_bridge_client_gtk_initialize (); + + atexit (scim_bridge_client_gtk_finalize); + + g_type_module_use(G_TYPE_MODULE(io_module)); + + initialized = TRUE; +} + + +void g_io_im_scim_unload (GIOModule *io_module) +{ + g_type_module_unuse(G_TYPE_MODULE(io_module)); + scim_bridge_client_gtk_finalize (); +} diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c new file mode 100644 index 0000000..79c670d --- /dev/null +++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.c @@ -0,0 +1,997 @@ +/* + * SCIM Bridge + * + * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation and + * appearing in the file LICENSE.LGPL included in the package of this file. + * You can also redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation and + * appearing in the file LICENSE.GPL included in the package of this file. + * + * This library 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. + */ + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <gtk/gtk.h> + +#include <gdk/gdk.h> + +#ifdef GDK_WINDOWING_X11 +#include <gdk/x11/gdkx.h> +#include <X11/Xlib.h> +#endif + +#include "scim-bridge-attribute.h" +#include "scim-bridge-client.h" +#include "scim-bridge-client-imcontext-gtk.h" +#include "scim-bridge-client-key-event-utility-gtk.h" +#include "scim-bridge-imcontext.h" +#include "scim-bridge-messenger.h" +#include "scim-bridge-output.h" +#include "scim-bridge-string.h" + +/* Typedef */ +struct _ScimBridgeClientIMContext +{ + GtkIMContext parent; + GtkIMContext *slave; + boolean slave_preedit; + + scim_bridge_imcontext_id_t id; + + char *preedit_string; + size_t preedit_string_capacity; + + PangoAttrList *preedit_attributes; + + unsigned int preedit_cursor_position; + boolean preedit_cursor_flicking; + + boolean preedit_shown; + boolean preedit_started; + + char *commit_string; + size_t commit_string_capacity; + + boolean enabled; + + GtkWidget *client_widget; + + int cursor_x; + int cursor_y; + int window_x; + int window_y; +}; + + +typedef struct { + guint16 red; + guint16 green; + guint16 blue; +} Color; + + +/* Private variables */ +static Color preedit_normal_background; +static Color preedit_normal_foreground; +static Color preedit_active_background; +static Color preedit_active_foreground; + +static GType class_type = 0; +static GObjectClass *root_klass = NULL; + +static ScimBridgeClientIMContext *focused_imcontext = NULL; +static GtkWidget *focused_widget = NULL; + +/* Class functions */ +static void scim_bridge_client_imcontext_class_initialize (ScimBridgeClientIMContextClass *klass, gpointer *klass_data); + +static void scim_bridge_client_imcontext_initialize (ScimBridgeClientIMContext *context, ScimBridgeClientIMContextClass *klass); +static void scim_bridge_client_imcontext_finalize (GObject *object); + +static gboolean scim_bridge_client_imcontext_filter_key_event (GtkIMContext *context, GdkEvent *event); +static void scim_bridge_client_imcontext_reset (GtkIMContext *context); +static void scim_bridge_client_imcontext_get_preedit_string (GtkIMContext *context, gchar **str, PangoAttrList **attrs, gint *cursor_pos); +static void scim_bridge_client_imcontext_set_preedit_enabled (GtkIMContext *context, gboolean enabled); + +static void scim_bridge_client_imcontext_set_client_widget (GtkIMContext *context, GtkWidget *window); +static void scim_bridge_client_imcontext_focus_in (GtkIMContext *context); +static void scim_bridge_client_imcontext_focus_out (GtkIMContext *context); +static void scim_bridge_client_imcontext_set_cursor_location (GtkIMContext *context, GdkRectangle *area); + +/* slave callbacks */ +static void gtk_im_slave_commit_cb (GtkIMContext *context, const char *str, ScimBridgeClientIMContext *imcontext); +static void gtk_im_slave_preedit_changed_cb (GtkIMContext *context, ScimBridgeClientIMContext *imcontext); +static void gtk_im_slave_preedit_start_cb (GtkIMContext *context, ScimBridgeClientIMContext *imcontext); +static void gtk_im_slave_preedit_end_cb (GtkIMContext *context, ScimBridgeClientIMContext *imcontext); + +static retval_t filter_key_event (ScimBridgeClientIMContext *imcontext, GdkEvent *event, boolean *consumed) +{ + scim_bridge_pdebugln (5, "filter_key_event ()"); + + if (focused_imcontext != imcontext) + scim_bridge_client_imcontext_focus_in (GTK_IM_CONTEXT (imcontext)); + //focused_widget = gtk_get_event_widget (event); + focused_widget = imcontext->client_widget; + + if (scim_bridge_client_is_messenger_opened ()) { + ScimBridgeKeyEvent *bridge_key_event = scim_bridge_alloc_key_event (); + scim_bridge_key_event_gdk_to_bridge (bridge_key_event, imcontext->client_widget, event); + + *consumed = FALSE; + const retval_t retval_error = scim_bridge_client_handle_key_event (imcontext, bridge_key_event, consumed); + scim_bridge_free_key_event (bridge_key_event); + + if (retval_error) { + scim_bridge_perrorln ("An IOException at filter_key_event ()"); + } else { + return RETVAL_SUCCEEDED; + } + } + + return RETVAL_FAILED; +} + + +static retval_t set_cursor_location (ScimBridgeClientIMContext *imcontext, int window_x, int window_y, int cursor_x, int cursor_y) +{ + scim_bridge_pdebugln (5, "set_cursor_location ()"); + + if (imcontext->window_x == window_x && imcontext->window_y == window_y && imcontext->cursor_x == cursor_x && imcontext->cursor_y == cursor_y) { + return RETVAL_SUCCEEDED; + } else { + imcontext->cursor_x = cursor_x; + imcontext->cursor_y = cursor_y; + imcontext->window_x = window_x; + imcontext->window_y = window_y; + + scim_bridge_pdebugln (3, "The cursor location is changed: x = %d + %d\ty = %d + %d", imcontext->window_x, imcontext->cursor_x, imcontext->window_y, imcontext->cursor_y); + + if (scim_bridge_client_is_messenger_opened ()) { + if (scim_bridge_client_set_cursor_location (imcontext, imcontext->window_x + imcontext->cursor_x, imcontext->window_y + imcontext->cursor_y)) { + scim_bridge_perrorln ("An IOException occurred at set_cursor_location ()"); + return RETVAL_FAILED; + } else { + return RETVAL_SUCCEEDED; + } + } + } + + return RETVAL_FAILED; +} + + +static void widget_get_origin(GtkWidget *widget, int *x, int *y) +{ + double widget_x, widget_y; + GtkRoot *root = gtk_widget_get_root(widget); + gtk_widget_translate_coordinates(widget, GTK_WIDGET(root), 0, 0, &widget_x, &widget_y); + + GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE(root)); + Window wiget_win = gdk_x11_surface_get_xid (surface); + XWindowAttributes attr; + Display *display = gdk_x11_display_get_xdisplay (gdk_surface_get_display (surface)); + XGetWindowAttributes(display, wiget_win, &attr); + + Window child_win = None; + XTranslateCoordinates(display, wiget_win, attr.root, widget_x, widget_y, x, y, &child_win); +} + + +static gboolean key_snooper (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + scim_bridge_pdebugln (7, "key_snooper ()"); + + GdkEventType type = gdk_event_get_event_type(event); + if (focused_imcontext && scim_bridge_client_is_messenger_opened () && + (type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE)) { //&& + //!(event->send_event & SEND_EVENT_MASK)) { + if (focused_imcontext->client_widget) { + // TODO get cliet_wiget's origin + int new_widget_x = 0; + int new_widget_y = 0; + widget_get_origin (focused_imcontext->client_widget, &new_widget_x, &new_widget_y); + + if (focused_imcontext->window_x != new_widget_x || focused_imcontext->window_y != new_widget_y) { + scim_bridge_pdebugln (1, + "The cursor location is changed: x = %d + %d\ty = %d + %d", + new_widget_x, focused_imcontext->cursor_x, new_widget_y, focused_imcontext->cursor_y); + + if (set_cursor_location (focused_imcontext, new_widget_x, new_widget_y, focused_imcontext->cursor_x, focused_imcontext->cursor_y)) { + scim_bridge_perrorln ("An IOException at key_snooper ()"); + return FALSE; + } + } + } + + boolean consumed = FALSE; + if (filter_key_event (focused_imcontext, event, &consumed)) { + scim_bridge_perrorln ("An IOException at key_snooper ()"); + return FALSE; + } else { + if (consumed) { + g_signal_emit_by_name (focused_imcontext, "preedit-changed"); + return TRUE; + } + } + } + + return FALSE; +} + + +static boolean is_precise_cursor_enabled () +{ + static boolean first_time = TRUE; + static boolean precise_cursor_enabled = FALSE; + + if (first_time) { + char *env_precise_cursor_enabled = getenv ("SCIM_BRIDGE_PRECISE_CURSOR_ENABLED"); + if (env_precise_cursor_enabled != NULL) scim_bridge_string_to_boolean (&precise_cursor_enabled, env_precise_cursor_enabled); + first_time = FALSE; + } + + return precise_cursor_enabled; +} + +/* Bindings */ +void scim_bridge_client_imcontext_set_id (ScimBridgeClientIMContext *imcontext, scim_bridge_imcontext_id_t new_id) +{ + imcontext->id = new_id; +} + + +scim_bridge_imcontext_id_t scim_bridge_client_imcontext_get_id (const ScimBridgeClientIMContext *imcontext) +{ + return imcontext->id; +} + + +void scim_bridge_client_imcontext_set_preedit_string (ScimBridgeClientIMContext *imcontext, const char *preedit_string) +{ + if (imcontext->preedit_string != NULL && preedit_string != NULL && !strcmp (imcontext->preedit_string, preedit_string)) + return; + + size_t preedit_string_length; + if (preedit_string != NULL) { + preedit_string_length = strlen (preedit_string); + } else { + preedit_string_length = 0; + } + if (imcontext->preedit_string_capacity <= preedit_string_length) { + imcontext->preedit_string_capacity = preedit_string_length; + free (imcontext->preedit_string); + imcontext->preedit_string = malloc (sizeof (char) * (imcontext->preedit_string_capacity + 1)); + } + if (preedit_string_length > 0) { + strcpy (imcontext->preedit_string, preedit_string); + } else { + imcontext->preedit_string[0] = '\0'; + } +} + + +void scim_bridge_client_imcontext_set_preedit_shown (ScimBridgeClientIMContext *imcontext, boolean preedit_shown) +{ + imcontext->preedit_shown = preedit_shown; + if (!preedit_shown) { + free (imcontext->preedit_string); + imcontext->preedit_string = malloc (sizeof (char)); + imcontext->preedit_string[0] = '\0'; + imcontext->preedit_string_capacity = 0; + imcontext->preedit_cursor_position = 0; + if (imcontext->preedit_attributes != NULL) { + pango_attr_list_unref (imcontext->preedit_attributes); + imcontext->preedit_attributes = NULL; + } + } +} + +void scim_bridge_client_imcontext_set_preedit_cursor_position (ScimBridgeClientIMContext *imcontext, int cursor_position) +{ + imcontext->preedit_cursor_position = cursor_position; +} + + +void scim_bridge_client_imcontext_set_preedit_attributes (ScimBridgeClientIMContext *imcontext, ScimBridgeAttribute** const preedit_attributes, int attribute_count) +{ + if (imcontext->preedit_attributes != NULL) + pango_attr_list_unref (imcontext->preedit_attributes); + + imcontext->preedit_attributes = pango_attr_list_new (); + + int preedit_string_length = 0; + int preedit_wstring_length = 0; + if (imcontext->preedit_string != NULL) { + preedit_string_length = strlen (imcontext->preedit_string); + preedit_wstring_length = g_utf8_strlen (imcontext->preedit_string, -1); + } + + boolean *has_attribute = alloca (sizeof (boolean) * preedit_string_length); + int i; + for (i = 0; i < preedit_string_length; ++i) { + has_attribute[i] = FALSE; + } + + for (i = 0; i < attribute_count; ++i) { + const ScimBridgeAttribute *attr = preedit_attributes[i]; + const int begin_pos = scim_bridge_attribute_get_begin (attr); + const int end_pos = scim_bridge_attribute_get_end (attr); + + if (begin_pos <= end_pos && 0 <= begin_pos && end_pos <= preedit_wstring_length) { + const int start_index = g_utf8_offset_to_pointer (imcontext->preedit_string, begin_pos) - imcontext->preedit_string; + const int end_index = g_utf8_offset_to_pointer (imcontext->preedit_string, end_pos) - imcontext->preedit_string; + + const scim_bridge_attribute_type_t attr_type = scim_bridge_attribute_get_type (attr); + const scim_bridge_attribute_value_t attr_value = scim_bridge_attribute_get_value (attr); + + boolean valid_attribute = FALSE; + if (attr_type == ATTRIBUTE_DECORATE) { + if (attr_value == SCIM_BRIDGE_ATTRIBUTE_DECORATE_UNDERLINE) { + valid_attribute = TRUE; + + PangoAttribute *pango_attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); + pango_attr->start_index = start_index; + pango_attr->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr); + } else if (attr_value == SCIM_BRIDGE_ATTRIBUTE_DECORATE_REVERSE) { + valid_attribute = TRUE; + + PangoAttribute *pango_attr0 = pango_attr_foreground_new (preedit_normal_background.red, preedit_normal_background.green, preedit_normal_background.blue); + pango_attr0->start_index = start_index; + pango_attr0->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr0); + + PangoAttribute *pango_attr1 = pango_attr_background_new (preedit_normal_foreground.red, preedit_normal_foreground.green, preedit_normal_foreground.blue); + pango_attr1->start_index = start_index; + pango_attr1->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr1); + } else if (attr_value == SCIM_BRIDGE_ATTRIBUTE_DECORATE_HIGHLIGHT) { + valid_attribute = TRUE; + + PangoAttribute *pango_attr0 = pango_attr_foreground_new (preedit_active_foreground.red, preedit_active_foreground.green, preedit_active_foreground.blue); + pango_attr0->start_index = start_index; + pango_attr0->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr0); + + PangoAttribute *pango_attr1 = pango_attr_background_new (preedit_active_background.red, preedit_active_background.green, preedit_active_background.blue); + pango_attr1->start_index = start_index; + pango_attr1->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr1); + } else { + scim_bridge_perrorln ("Unknown preedit decoration!"); + } + } else if (attr_type == ATTRIBUTE_FOREGROUND) { + valid_attribute = TRUE; + + const unsigned int red = scim_bridge_attribute_get_red (attr) * 256; + const unsigned int green = scim_bridge_attribute_get_green (attr) * 256; + const unsigned int blue = scim_bridge_attribute_get_blue (attr) * 256; + + PangoAttribute *pango_attr = pango_attr_foreground_new (red, green, blue); + pango_attr->start_index = start_index; + pango_attr->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr); + } else if (attr_type == ATTRIBUTE_BACKGROUND) { + valid_attribute = TRUE; + + const unsigned int red = scim_bridge_attribute_get_red (attr) * 256; + const unsigned int green = scim_bridge_attribute_get_green (attr) * 256; + const unsigned int blue = scim_bridge_attribute_get_blue (attr) * 256; + + PangoAttribute *pango_attr = pango_attr_background_new (red, green, blue); + pango_attr->start_index = start_index; + pango_attr->end_index = end_index; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr); + } + + if (valid_attribute) { + int j; + for (j = start_index; j < end_index; ++j) { + has_attribute[j] = TRUE; + } + } + } + + } + + // Add underlines for the all characters without attributes. + for (i = 0; i < preedit_string_length; ++i) { + if (has_attribute[i] == FALSE) { + PangoAttribute *pango_attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); + pango_attr->start_index = i; + for (; i < preedit_string_length && has_attribute[i] == FALSE; ++i); + pango_attr->end_index = i; + pango_attr_list_insert (imcontext->preedit_attributes, pango_attr); + } + } +} + + +void scim_bridge_client_imcontext_update_preedit (ScimBridgeClientIMContext *imcontext) +{ + if (imcontext->preedit_shown && !imcontext->preedit_started) { + g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-start"); + imcontext->preedit_started = TRUE; + } + + if (is_precise_cursor_enabled ()) { + const size_t old_cursor_position = imcontext->preedit_cursor_position; + imcontext->preedit_cursor_position = 0; + if (imcontext->preedit_string != NULL) + imcontext->preedit_cursor_position = g_utf8_strlen (imcontext->preedit_string, -1); + + imcontext->preedit_cursor_flicking = TRUE; + g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-changed"); + + imcontext->preedit_cursor_position = old_cursor_position; + imcontext->preedit_cursor_flicking = FALSE; + } + + g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-changed"); + + if (!imcontext->preedit_shown && imcontext->preedit_started) { + g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "preedit-end"); + imcontext->preedit_started = FALSE; + } +} + + +void scim_bridge_client_imcontext_set_commit_string (ScimBridgeClientIMContext *imcontext, const char *commit_string) +{ + size_t commit_string_length; + if (commit_string != NULL) { + commit_string_length = strlen (commit_string); + } else { + commit_string_length = 0; + } + + if (imcontext->commit_string_capacity <= commit_string_length) { + imcontext->commit_string_capacity = commit_string_length; + free (imcontext->commit_string); + imcontext->commit_string = malloc (sizeof (char) * (imcontext->commit_string_capacity + 1)); + } + if (commit_string_length > 0) { + strcpy (imcontext->commit_string, commit_string); + } else { + imcontext->commit_string[0] = '\0'; + } +} + + +void scim_bridge_client_imcontext_commit (ScimBridgeClientIMContext *imcontext) +{ + g_signal_emit_by_name ((ScimBridgeClientIMContext*) imcontext, "commit", imcontext->commit_string); +} + + +void scim_bridge_client_imcontext_beep (ScimBridgeClientIMContext *imcontext) +{ + gtk_widget_error_bell(imcontext->client_widget); +} + + +boolean scim_bridge_client_imcontext_get_surrounding_text (ScimBridgeClientIMContext *imcontext, int before_max, int after_max, char **string, int *cursor_position) +{ + char *str; + int cur_pos_in_utf8, an_pos_in_utf8; + + if (gtk_im_context_get_surrounding_with_selection (GTK_IM_CONTEXT (imcontext), + &str, + &cur_pos_in_utf8, + &an_pos_in_utf8)) { + const size_t fetch_wstr_length = g_utf8_strlen (str, -1); + const size_t after_wstr_length = g_utf8_strlen (str + cur_pos_in_utf8, an_pos_in_utf8); + const size_t before_wstr_length = fetch_wstr_length - after_wstr_length; + + size_t before_copy_wstr_length; + size_t after_copy_wstr_length; + if (after_wstr_length > after_max) { + after_copy_wstr_length = after_max; + } else { + after_copy_wstr_length = after_wstr_length; + } + if (before_wstr_length > before_max) { + before_copy_wstr_length = before_max; + } else { + before_copy_wstr_length = before_wstr_length; + } + + const size_t begin_wstr_index = before_wstr_length - before_copy_wstr_length; + const size_t end_wstr_index = fetch_wstr_length - (after_wstr_length - after_copy_wstr_length); + + char* begin_str_ptr = g_utf8_offset_to_pointer (str, begin_wstr_index); + char* end_str_ptr = g_utf8_offset_to_pointer (str, end_wstr_index); + size_t str_length = end_str_ptr - begin_str_ptr; + + *string = malloc (sizeof (char) * (str_length + 1)); + strncpy (*string, begin_str_ptr, str_length); + (*string)[str_length] = '\0'; + *cursor_position = before_copy_wstr_length; + + g_free (str); + return TRUE; + } else { + *string = NULL; + + return FALSE; + } +} + + +boolean scim_bridge_client_imcontext_delete_surrounding_text (ScimBridgeClientIMContext *imcontext, int offset, int length) +{ + boolean retval = gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (imcontext), offset, length); + return retval; +} + + +boolean scim_bridge_client_imcontext_replace_surrounding_text (ScimBridgeClientIMContext *imcontext, int cursor_position, const char *string) +{ + gtk_im_context_set_surrounding_with_selection (GTK_IM_CONTEXT (imcontext), + string, + -1, + cursor_position, + cursor_position); + return TRUE; +} + +void scim_bridge_client_imcontext_forward_key_event (ScimBridgeClientIMContext *imcontext, const ScimBridgeKeyEvent *key_event) +{ + scim_bridge_pdebugln (7, "scim_bridge_client_imcontext_forward_key_event: NOT IMPLEMENTED YET"); +} + + +void scim_bridge_client_imcontext_imengine_status_changed (ScimBridgeClientIMContext *imcontext, boolean enabled) +{ + if (imcontext->preedit_shown) { + if (imcontext->enabled) { + scim_bridge_client_imcontext_set_preedit_shown (imcontext, FALSE); + scim_bridge_client_imcontext_update_preedit (imcontext); + } + } + imcontext->enabled = enabled; +} + + +/* Class Implementations */ +void scim_bridge_client_imcontext_static_initialize () +{ + GdkRGBA color; + g_warn_if_fail(gdk_rgba_parse (&color, "gray92")); + preedit_normal_background = (Color){color.red * 65535, color.green * 65535, color.blue * 65535}; + g_warn_if_fail(gdk_rgba_parse (&color, "black")); + preedit_normal_foreground = (Color){color.red * 65535, color.green * 65535, color.blue * 65535}; + g_warn_if_fail(gdk_rgba_parse (&color, "light blue")); + preedit_active_background = (Color){color.red * 65535, color.green * 65535, color.blue * 65535}; + g_warn_if_fail(gdk_rgba_parse (&color, "black")); + preedit_active_foreground = (Color){color.red * 65535, color.green * 65535, color.blue * 65535}; + + focused_imcontext = NULL; +} + + +void scim_bridge_client_imcontext_static_finalize () +{ + focused_imcontext = NULL; +} + + +void scim_bridge_client_imcontext_connection_opened () +{ +} + + +void scim_bridge_client_imcontext_connection_closed () +{ + if (focused_imcontext != NULL) scim_bridge_client_imcontext_set_preedit_shown (focused_imcontext, FALSE); +} + + +void scim_bridge_client_imcontext_class_initialize (ScimBridgeClientIMContextClass *klass, gpointer *klass_data) +{ + root_klass = (GObjectClass *) g_type_class_peek_parent (klass); + + GtkIMContextClass *gtk_im_klass = GTK_IM_CONTEXT_CLASS (klass); + gtk_im_klass->set_client_widget = scim_bridge_client_imcontext_set_client_widget; + gtk_im_klass->filter_keypress = scim_bridge_client_imcontext_filter_key_event; + gtk_im_klass->reset = scim_bridge_client_imcontext_reset; + gtk_im_klass->get_preedit_string = scim_bridge_client_imcontext_get_preedit_string; + gtk_im_klass->focus_in = scim_bridge_client_imcontext_focus_in; + gtk_im_klass->focus_out = scim_bridge_client_imcontext_focus_out; + gtk_im_klass->set_cursor_location = scim_bridge_client_imcontext_set_cursor_location; + gtk_im_klass->set_use_preedit = scim_bridge_client_imcontext_set_preedit_enabled; + + GObjectClass *gobject_klass = G_OBJECT_CLASS (klass); + gobject_klass->finalize = scim_bridge_client_imcontext_finalize; +} + + +GType scim_bridge_client_imcontext_get_type () +{ + return class_type; +} + + +void scim_bridge_client_imcontext_register_type (GIOModule *io_module) +{ + scim_bridge_pdebugln (2, "scim_bridge_client_imcontext_register_type ()"); + + static const GTypeInfo klass_info = { + sizeof (ScimBridgeClientIMContextClass), + /* no base class initializer */ + NULL, + /* no base class finalizer */ + NULL, + /* class initializer */ + (GClassInitFunc) scim_bridge_client_imcontext_class_initialize, + /* no class finalizer */ + NULL, + /* no class data */ + NULL, + sizeof (ScimBridgeClientIMContext), + 0, + /* object initizlier */ + (GInstanceInitFunc) scim_bridge_client_imcontext_initialize, + 0 + }; + + class_type = g_type_module_register_type (G_TYPE_MODULE(io_module), + GTK_TYPE_IM_CONTEXT, + "ScimBridgeClientIMContext", + &klass_info, + 0); +} + + +GtkIMContext *scim_bridge_client_imcontext_new () +{ + scim_bridge_pdebugln (4, "scim_bridge_client_imcontext_new ()"); + + ScimBridgeClientIMContext *ic = SCIM_BRIDGE_CLIENT_IMCONTEXT (g_object_new (GTK_TYPE_SCIM_CLIENT_IMCONTEXT, NULL)); + return GTK_IM_CONTEXT (ic); +} + + +void scim_bridge_client_imcontext_initialize (ScimBridgeClientIMContext *imcontext, ScimBridgeClientIMContextClass *klass) +{ + scim_bridge_pdebugln (5, "scim_bridge_client_imcontext_initialize ()"); + + /* slave exists for using gtk+'s table based input method */ + imcontext->slave_preedit = FALSE; + imcontext->slave = gtk_im_context_simple_new (); + g_signal_connect(G_OBJECT(imcontext->slave), + "commit", + G_CALLBACK(gtk_im_slave_commit_cb), + imcontext); + + g_signal_connect(G_OBJECT(imcontext->slave), + "preedit-changed", + G_CALLBACK(gtk_im_slave_preedit_changed_cb), + imcontext); + + g_signal_connect(G_OBJECT(imcontext->slave), + "preedit-start", + G_CALLBACK(gtk_im_slave_preedit_start_cb), + imcontext); + + g_signal_connect(G_OBJECT(imcontext->slave), + "preedit-end", + G_CALLBACK(gtk_im_slave_preedit_end_cb), + imcontext); + + imcontext->preedit_shown = FALSE; + imcontext->preedit_started = FALSE; + + imcontext->preedit_cursor_position = 0; + imcontext->preedit_cursor_flicking = FALSE; + + imcontext->preedit_string = malloc (sizeof (char)); + imcontext->preedit_string[0] = '\0'; + imcontext->preedit_string_capacity = 0; + + imcontext->preedit_attributes = NULL; + + imcontext->commit_string = malloc (sizeof (char)); + imcontext->commit_string[0] = '\0'; + imcontext->commit_string_capacity = 0; + + imcontext->enabled = FALSE; + + imcontext->client_widget = NULL; + + imcontext->id = -1; + + if (!scim_bridge_client_is_messenger_opened ()) { + scim_bridge_perrorln ("The messenger is now down"); + } else if (scim_bridge_client_register_imcontext (imcontext)) { + scim_bridge_perrorln ("Failed to register the IMContext"); + } else { + scim_bridge_pdebugln (1, "IMContext registered: id = %d", imcontext->id); + } +} + + +void scim_bridge_client_imcontext_finalize (GObject *object) +{ + scim_bridge_pdebugln (5, "scim_bridge_client_imcontext_finalize ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (object); + + if (imcontext == focused_imcontext) scim_bridge_client_imcontext_focus_out (GTK_IM_CONTEXT (imcontext)); + + if (!scim_bridge_client_is_messenger_opened ()) { + scim_bridge_perrorln ("The messenger is now down"); + } else if (scim_bridge_client_deregister_imcontext (imcontext)) { + scim_bridge_perrorln ("Failed to deregister an IMContext"); + } else { + scim_bridge_pdebugln (3, "IMContext deregistered: id = %d", imcontext->id); + } + + if (imcontext->client_widget) g_object_unref (imcontext->client_widget); + + free (imcontext->preedit_string); + free (imcontext->commit_string); + + if (imcontext->preedit_attributes != NULL) + pango_attr_list_unref (imcontext->preedit_attributes); + + imcontext->preedit_attributes = NULL; + + g_signal_handlers_disconnect_by_func(imcontext->slave, + (void *)gtk_im_slave_commit_cb, + (void *)imcontext); + g_signal_handlers_disconnect_by_func(imcontext->slave, + (void *)gtk_im_slave_preedit_changed_cb, + (void *)imcontext); + g_signal_handlers_disconnect_by_func(imcontext->slave, + (void *)gtk_im_slave_preedit_start_cb, + (void *)imcontext); + g_signal_handlers_disconnect_by_func(imcontext->slave, + (void *)gtk_im_slave_preedit_end_cb, + (void *)imcontext); + g_object_unref(imcontext->slave); + + root_klass->finalize (object); +} + + +/* Class functions */ +gboolean scim_bridge_client_imcontext_filter_key_event (GtkIMContext *context, GdkEvent *event) +{ + scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_filter_key_event ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + + boolean ret = FALSE; + if (imcontext) { + ret = key_snooper(0, event, 0); + + if (imcontext->slave) { + if (!ret) { + ret = gtk_im_context_filter_keypress (imcontext->slave, event); + } else if (imcontext->slave_preedit) { + imcontext->slave_preedit = FALSE; + gtk_im_context_reset (imcontext->slave); + } + } + + } + + return ret; +} + + +void scim_bridge_client_imcontext_reset (GtkIMContext *context) +{ + scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_reset ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + + if (imcontext != focused_imcontext) return; + + if (scim_bridge_client_is_messenger_opened () && imcontext != NULL) { + if (scim_bridge_client_reset_imcontext (imcontext)) { + scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_reset ()"); + } + } +} + + +void scim_bridge_client_imcontext_get_preedit_string (GtkIMContext *context, gchar **str, PangoAttrList **pango_attrs, gint *cursor_pos) +{ + scim_bridge_pdebugln (4, "scim_bridge_client_imcontext_get_preedit_string ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + + if (imcontext->slave_preedit) { + gtk_im_context_get_preedit_string (imcontext->slave, str, pango_attrs, cursor_pos); + return; + } + + if (scim_bridge_client_is_messenger_opened () && imcontext != NULL && imcontext->preedit_shown) { + const size_t preedit_string_length = strlen (imcontext->preedit_string); + const size_t preedit_wstring_length = g_utf8_strlen (imcontext->preedit_string, -1); + + if (str) { + if (preedit_string_length > 0) { + *str = g_strdup (imcontext->preedit_string); + } else { + *str = g_strdup (""); + } + } + + if (cursor_pos) { + if (imcontext->preedit_cursor_position > preedit_wstring_length) { + *cursor_pos = preedit_wstring_length; + } else { + *cursor_pos = imcontext->preedit_cursor_position; + } + } + + if (pango_attrs) { + *pango_attrs = imcontext->preedit_attributes; + pango_attr_list_ref (imcontext->preedit_attributes); + } + } else { + if (str) *str = g_strdup (""); + if (cursor_pos) *cursor_pos = 0; + if (pango_attrs) *pango_attrs = pango_attr_list_new (); + } +} + + +void scim_bridge_client_imcontext_focus_in (GtkIMContext *context) +{ + scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_focus_in ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + + if (focused_imcontext != NULL && focused_imcontext != imcontext) { + scim_bridge_client_imcontext_focus_out (GTK_IM_CONTEXT (focused_imcontext)); + } + focused_imcontext = imcontext; + + if (!scim_bridge_client_is_messenger_opened () && scim_bridge_client_is_reconnection_enabled ()) { + scim_bridge_pdebugln (7, "Trying to open the connection again..."); + scim_bridge_client_open_messenger (); + } + + if (scim_bridge_client_is_messenger_opened () && imcontext != NULL) { + if (scim_bridge_client_change_focus (imcontext, TRUE)) { + scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_focus_in ()"); + } + } +} + + +void scim_bridge_client_imcontext_focus_out (GtkIMContext *context) +{ + scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_focus_out ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + focused_widget = NULL; + + focused_imcontext = imcontext; + if (imcontext->preedit_shown) { + if (imcontext->enabled) { + scim_bridge_client_imcontext_set_preedit_shown (imcontext, FALSE); + scim_bridge_client_imcontext_update_preedit (imcontext); + } + } + if (scim_bridge_client_is_messenger_opened () && imcontext != NULL) { + if (scim_bridge_client_change_focus (imcontext, FALSE)) { + scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_focus_out ()"); + } + } + + focused_imcontext = NULL; +} + + +void scim_bridge_client_imcontext_set_client_widget (GtkIMContext *context, GtkWidget *new_widget) +{ + scim_bridge_pdebugln (7, "scim_bridge_client_imcontext_set_client_widget ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + if (!imcontext) { + return; + } + + if (imcontext->client_widget) { + g_object_unref(imcontext->client_widget); + } + + imcontext->client_widget = new_widget; + if (!new_widget) { + return; + } + + g_object_ref (new_widget); + widget_get_origin (imcontext->client_widget, &imcontext->window_x, &imcontext->window_y); +} + +void scim_bridge_client_imcontext_set_cursor_location (GtkIMContext *context, GdkRectangle *area) +{ + scim_bridge_pdebugln (4, "scim_bridge_client_imcontext_set_cursor_location ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + if (imcontext->preedit_cursor_flicking) + return; + + if (imcontext != NULL && imcontext->client_widget != NULL) { + const int new_cursor_x = area->x + area->width; + const int new_cursor_y = area->y + area->height + 8; + + int widget_x, widget_y; + widget_get_origin(imcontext->client_widget, &widget_x, &widget_y); + + if (set_cursor_location (imcontext, widget_x, widget_y, new_cursor_x, new_cursor_y)) { + scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_set_cursor_location ()"); + } + } +} + + +void scim_bridge_client_imcontext_set_preedit_enabled (GtkIMContext *context, gboolean enabled) +{ + scim_bridge_pdebugln (8, "scim_bridge_client_imcontext_set_preedit_enabled ()"); + + ScimBridgeClientIMContext *imcontext = SCIM_BRIDGE_CLIENT_IMCONTEXT (context); + + if (imcontext != NULL) { + if (scim_bridge_client_is_messenger_opened ()) { + if (scim_bridge_client_set_preedit_mode (imcontext, enabled ? PREEDIT_EMBEDDED:PREEDIT_ANY)) { + scim_bridge_perrorln ("An IOException occurred at scim_bridge_client_imcontext_set_preedit_enabled ()"); + } + } + } +} + +static void +gtk_im_slave_commit_cb ( + GtkIMContext *context, + const char *str, + ScimBridgeClientIMContext *imcontext +) { + g_return_if_fail(str); + g_signal_emit_by_name(imcontext, "commit", str); +} + +static void +gtk_im_slave_preedit_changed_cb ( + GtkIMContext *context, + ScimBridgeClientIMContext *imcontext +) { + imcontext->slave_preedit = TRUE; + g_signal_emit_by_name(imcontext, "preedit-changed"); +} + +static void +gtk_im_slave_preedit_start_cb ( + GtkIMContext *context, + ScimBridgeClientIMContext *imcontext +) { + imcontext->slave_preedit = TRUE; + g_signal_emit_by_name(imcontext, "preedit-start"); +} + +static void +gtk_im_slave_preedit_end_cb ( + GtkIMContext *context, + ScimBridgeClientIMContext *imcontext +) { + imcontext->slave_preedit = FALSE; + g_signal_emit_by_name(imcontext, "preedit-end"); +} \ No newline at end of file diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h new file mode 100644 index 0000000..909871c --- /dev/null +++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-imcontext-gtk.h @@ -0,0 +1,111 @@ +/* + * SCIM Bridge + * + * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation and + * appearing in the file LICENSE.LGPL included in the package of this file. + * You can also redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation and + * appearing in the file LICENSE.GPL included in the package of this file. + * + * This library 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. + */ + +/** + * @file + * @author Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * @brief This is the header for gtk imcontext of scim-bridge. + */ + + +#ifndef SCIMBRIDGECLIENTIMCONTEXTGTK_H_ +#define SCIMBRIDGECLIENTIMCONTEXTGTK_H_ + +#include <gtk/gtk.h> +#if GTK_CHECK_VERSION(3, 0, 0) +#else +#include <gtk/gtkimcontext.h> +#endif + +#include <gdk/gdk.h> + +#include "scim-bridge.h" +#include "scim-bridge-client-imcontext.h" + +#define GTK_TYPE_SCIM_CLIENT_IMCONTEXT (scim_bridge_client_imcontext_get_type ()) +#if GTK_CHECK_VERSION(3, 0, 0) +#define SCIM_BRIDGE_CLIENT_IMCONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContext)) +#else +#define SCIM_BRIDGE_CLIENT_IMCONTEXT(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContext)) +#endif +#define SCIM_BRIDGE_CLIENT_IMCONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContextClass)) +#if GTK_CHECK_VERSION(3, 0, 0) +#define IS_SCIM_BRIDGE_CLIENT_IMCONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT)) +#else +#define IS_SCIM_BRIDGE_CLIENT_IMCONTEXT(class) (GTK_CHECK_TYPE ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT)) +#endif +#define IS_SCIM_BRIDGE_CLIENT_IMCONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SCIM_CLIENT_IMCONTEXT)) +#if GTK_CHECK_VERSION(3, 0, 0) +#define SCIM_BRIDGE_CLIENT_IMCONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContextClass)) +#else +#define SCIM_BRIDGE_CLIENT_IMCONTEXT_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_TYPE_SCIM_CLIENT_IMCONTEXT, ScimBridgeClientIMContextClass)) +#endif + +struct _ScimBridgeClientIMContextClass +{ + GtkIMContextClass parent_class; +}; + +/** + * IMContext class for gtk client. + */ +typedef struct _ScimBridgeClientIMContextClass ScimBridgeClientIMContextClass; + +/** + * Initialize IMContext class itself. + */ +void scim_bridge_client_imcontext_static_initialize (); + +/** + * Finalize IMContext class itself. + */ +void scim_bridge_client_imcontext_static_finalize (); + +/** + * The connection with the agent is opened. + */ +void scim_bridge_client_imcontext_connection_opened (); + +/** + * The connection with the agent is closed. + */ +void scim_bridge_client_imcontext_connection_closed (); + +/** + * Allocate an IMContext. + * + * @return new IMContext. + */ +GtkIMContext *scim_bridge_client_imcontext_new (); + +/** + * Get the type value of IMContexts. + * + * @return The type value of IMContexts. + */ +GType scim_bridge_client_imcontext_get_type (); + +/** + * Register the type value for IMContexts. + * + * @param type_module The type module. + */ +void scim_bridge_client_imcontext_register_type (GIOModule *io_module); + +#endif /*SCIMBRIDGECLIENTIMCONTEXTGTK_H_*/ diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c new file mode 100644 index 0000000..318f7e4 --- /dev/null +++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.c @@ -0,0 +1,132 @@ +/* + * SCIM Bridge + * + * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation and + * appearing in the file LICENSE.LGPL included in the package of this file. + * You can also redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation and + * appearing in the file LICENSE.GPL included in the package of this file. + * + * This library 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. + */ + +#include <sys/time.h> + +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> + +#ifdef GDK_WINDOWING_X11 +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <gdk/x11/gdkx.h> +#endif + +#include "scim-bridge-client-key-event-utility-gtk.h" +#include "scim-bridge-key-event.h" + +/* Implementations */ +void scim_bridge_key_event_bridge_to_gdk (GdkEvent *gdk_key_event, + GtkWidget *client_widget, + const ScimBridgeKeyEvent *key_event) +{ +// gdk_key_event->state = 0; +// if (scim_bridge_key_event_is_shift_down (key_event)) gdk_key_event->state |= GDK_SHIFT_MASK; +// if (scim_bridge_key_event_is_caps_lock_down (key_event)) gdk_key_event->state |= GDK_LOCK_MASK; +// if (scim_bridge_key_event_is_control_down (key_event)) gdk_key_event->state |= GDK_CONTROL_MASK; +// if (scim_bridge_key_event_is_alt_down (key_event)) gdk_key_event->state |= GDK_MOD1_MASK; +// if (scim_bridge_key_event_is_num_lock_down (key_event)) gdk_key_event->state |= GDK_MOD2_MASK; +// +// if (scim_bridge_key_event_is_pressed (key_event)) { +// gdk_key_event->type = GDK_KEY_PRESS; +// } else { +// gdk_key_event->type = GDK_KEY_RELEASE; +// gdk_key_event->state |= GDK_RELEASE_MASK; +// } +// +// gdk_key_event->widget = client_widget; +// +// struct timeval current_time; +// gettimeofday (¤t_time, NULL); +// +// gdk_key_event->time = current_time.tv_sec * 1000 + current_time.tv_usec / 1000; +// gdk_key_event->keyval = scim_bridge_key_event_get_code (key_event); +// gdk_key_event->length = 0; +// gdk_key_event->string = 0; +// +// GdkKeymap *key_map = get_gdk_keymap (gdk_key_event->widget); +// +// GdkKeymapKey *keys; +// gint n_keys; +// +// if (gdk_keymap_get_entries_for_keyval (key_map, gdk_key_event->keyval, &keys, &n_keys)) { +// gdk_key_event->hardware_keycode = keys[0].keycode; +// gdk_key_event->group = keys [0].group; +// } else { +// gdk_key_event->hardware_keycode = 0; +// gdk_key_event->group = 0; +// } +} + + +void scim_bridge_key_event_gdk_to_bridge (ScimBridgeKeyEvent *bridge_key_event, + GtkWidget *widget, + const GdkEvent *key_event) +{ + // Use Key Symbole provided by gtk. + guint keyval = gdk_key_event_get_keyval((GdkEvent *) key_event); + GdkModifierType state = gdk_event_get_modifier_state((GdkEvent *) key_event); + scim_bridge_key_event_set_code (bridge_key_event, (scim_bridge_key_code_t) keyval); + + scim_bridge_key_event_clear_modifiers (bridge_key_event); + if (state & GDK_SHIFT_MASK || keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R) + scim_bridge_key_event_set_shift_down (bridge_key_event, TRUE); + if (state & GDK_LOCK_MASK || keyval == GDK_KEY_Caps_Lock) + scim_bridge_key_event_set_caps_lock_down (bridge_key_event, TRUE); + if (state & GDK_CONTROL_MASK || keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R) + scim_bridge_key_event_set_control_down (bridge_key_event, TRUE); + if (state & GDK_ALT_MASK || keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R) + scim_bridge_key_event_set_alt_down (bridge_key_event, TRUE); + if (gdk_device_get_num_lock_state(gdk_event_get_device((GdkEvent *) key_event))) + scim_bridge_key_event_set_num_lock_down (bridge_key_event, TRUE); + + + if (gdk_event_get_event_type((GdkEvent *) key_event) != GDK_KEY_RELEASE) { + scim_bridge_key_event_set_pressed (bridge_key_event, TRUE); + } else { + scim_bridge_key_event_set_pressed (bridge_key_event, FALSE); + } + +#ifdef GDK_WINDOWING_X11 + GdkX11Display *display = NULL; + + if (widget != NULL) { + display = GDK_X11_DISPLAY (gtk_widget_get_display(widget)); + } else { + display = GDK_X11_DISPLAY (gdk_display_get_default ()); + } + + if (scim_bridge_key_event_get_code (bridge_key_event) == SCIM_BRIDGE_KEY_CODE_backslash) { + boolean kana_ro = FALSE; + int keysym_size = 0; + KeySym *keysyms = XGetKeyboardMapping ( + gdk_x11_display_get_xdisplay(display), + gdk_key_event_get_keycode((GdkEvent *) key_event), + 1, + &keysym_size); + if (keysyms != NULL) { + kana_ro = (keysyms[0] == XK_backslash && keysyms[1] == XK_underscore); + XFree (keysyms); + } + scim_bridge_key_event_set_quirk_enabled (bridge_key_event, SCIM_BRIDGE_KEY_QUIRK_KANA_RO, kana_ro); + } +#endif +} diff --git a/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h new file mode 100644 index 0000000..d5fd211 --- /dev/null +++ b/extras/immodules/client-gtk/gtk4/scim-bridge-client-key-event-utility-gtk.h @@ -0,0 +1,54 @@ +/* + * SCIM Bridge + * + * Copyright (c) 2006 Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation and + * appearing in the file LICENSE.LGPL included in the package of this file. + * You can also redistribute it and/or modify it under the terms of + * the GNU General Public License as published by the Free Software Foundation and + * appearing in the file LICENSE.GPL included in the package of this file. + * + * This library 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. + */ + +/** + * @file + * @author Ryo Dairiki <ryo-dairiki@users.sourceforge.net> + * @brief This is the header of the functions to translate key events between scim-bridge and gtk. + */ + + +#ifndef SCIMBRIDGECLIENTKEYEVENTUTILITYGTK_H_ +#define SCIMBRIDGECLIENTKEYEVENTUTILITYGTK_H_ + +#include <gdk/gdk.h> + +#include "scim-bridge.h" +#include "scim-bridge-key-event.h" + +/** + * Translate a key event from scim-bridge into gdk. + * + * @param gdk_key_event A key event of gdk. + * @param client_wiget The gdk window for the key event. + * @param key_event The key event from scim-bridge. + */ +void scim_bridge_key_event_bridge_to_gdk (GdkEvent *gdk_key_event, GtkWidget *client_wiget, const ScimBridgeKeyEvent *key_event); + + +/** + * Translate a key event from gdk into scim-bridge. + * + * @param bridge_key_event A key event of scim-bridge. + * @param client_wiget The gdk window for the key event. + * @param key_event The key event from gdk. + */ +void scim_bridge_key_event_gdk_to_bridge (ScimBridgeKeyEvent *bridge_key_event, GtkWidget *client_wiget, const GdkEvent *key_event); + +#endif /*SCIMBRIDGECLIENTKEYEVENTUTILITYGTK_H_*/ diff --git a/extras/immodules/configure.ac b/extras/immodules/configure.ac index 5481f3e..4959dac 100644 --- a/extras/immodules/configure.ac +++ b/extras/immodules/configure.ac @@ -65,6 +65,10 @@ PKG_CHECK_MODULES(GTK3, [gtk+-3.0 >= 3.0.0 pango >= 1.24.0 gdk-pixbuf-2.0 >= 2.2 [SCIM_BRIDGE_HAS_GTK3=yes], [SCIM_BRIDGE_HAS_GTK3=no]) +PKG_CHECK_MODULES(GTK4, [gtk4 >= 4.0.0 pango >= 1.48.0 gdk-pixbuf-2.0 >= 2.42.0], + [SCIM_BRIDGE_HAS_GTK4=yes], + [SCIM_BRIDGE_HAS_GTK4=no]) + SCIM_BRIDGE_HAS_GTK2_2=no if test "$SCIM_BRIDGE_HAS_GTK2" = "yes"; then if $PKG_CONFIG --exists "gtk+-2.0 >= 2.2" ; then @@ -98,6 +102,17 @@ if test "$SCIM_BRIDGE_HAS_GTK3" = "yes"; then AC_SUBST(GTK3_BINARY_VERSION) fi +if test "$SCIM_BRIDGE_HAS_GTK4" = "yes"; then + GTK4_BINARY_VERSION=`$PKG_CONFIG --variable=gtk_binary_version gtk4` + GTK4_LIBDIR=`$PKG_CONFIG --variable=libdir gtk4` + if test -z "$GTK4_LIBDIR)"; then + GTK4_LIBDIR="$libdir" + fi + AC_SUBST(GTK4_LIBDIR) + AC_SUBST(GTK4_VERSION) + AC_SUBST(GTK4_BINARY_VERSION) +fi + # Check if we should build scim-bridge-qt-immodule PKG_CHECK_MODULES(QT3, [qt-mt >= 3.3], [SCIM_BRIDGE_HAS_QT3=yes], @@ -162,6 +177,10 @@ AC_ARG_ENABLE(gtk3-immodule, [AS_HELP_STRING([--enable-gtk3-immodule], [build GTK3 IM module])], , enable_gtk3_immodule=yes) +AC_ARG_ENABLE(gtk4-immodule, + [AS_HELP_STRING([--enable-gtk4-immodule], [build GTK4 IM module])], , + enable_gtk4_immodule=yes) + AC_ARG_ENABLE(qt3-immodule, [AS_HELP_STRING([--enable-qt3-immodule], [build Qt3 IM module])], , enable_qt3_immodule=yes) @@ -224,6 +243,15 @@ else enable_gtk3_immodule=no fi +if test "$enable_gtk4_immodule" = "yes" -a "$SCIM_BRIDGE_HAS_GTK4" = "yes"; then + SCIM_BRIDGE_BUILD_GTK4_IMMODULE=1 + SCIM_BRIDGE_BUILD_IMMODULE=1 + enable_immodule=yes +else + SCIM_BRIDGE_BUILD_GTK4_IMMODULE=0 + enable_gtk4_immodule=no +fi + if test "$enable_qt3_immodule" = "yes" -a "$SCIM_BRIDGE_HAS_QT3" = "yes"; then SCIM_BRIDGE_BUILD_QT3_IMMODULE=1 SCIM_BRIDGE_BUILD_IMMODULE=1 @@ -269,6 +297,9 @@ AM_CONDITIONAL(SCIM_BRIDGE_BUILD_GTK2_IMMODULE, AM_CONDITIONAL(SCIM_BRIDGE_BUILD_GTK3_IMMODULE, [test "$enable_gtk3_immodule" = "yes"]) +AM_CONDITIONAL(SCIM_BRIDGE_BUILD_GTK4_IMMODULE, + [test "$enable_gtk4_immodule" = "yes"]) + AM_CONDITIONAL(SCIM_BRIDGE_BUILD_QT3_IMMODULE, [test "$enable_qt3_immodule" = "yes"]) @@ -285,6 +316,7 @@ AC_SUBST(SCIM_BRIDGE_BUILD_DOCUMENTS) AC_SUBST(SCIM_BRIDGE_BUILD_AGENT) AC_SUBST(SCIM_BRIDGE_BUILD_GTK2_IMMODULE) AC_SUBST(SCIM_BRIDGE_BUILD_GTK3_IMMODULE) +AC_SUBST(SCIM_BRIDGE_BUILD_GTK4_IMMODULE) AC_SUBST(SCIM_BRIDGE_BUILD_QT3_IMMODULE) AC_SUBST(SCIM_BRIDGE_BUILD_QT4_IMMODULE) AC_SUBST(SCIM_BRIDGE_BUILD_CLUTTER_IMMODULE) @@ -343,6 +375,7 @@ Module options: Build Agent $enable_agent Build GTK2 IMModule $enable_gtk2_immodule Build GTK3 IMModule $enable_gtk3_immodule + Build GTK4 IMModule $enable_gtk4_immodule Build QT3 IMModule $enable_qt3_immodule Build QT4 IMModule $enable_qt4_immodule Build CLUTTER IMModule $enable_clutter_immodule -- 2.37.3