From c6439d74d5472c95de4d5c2cdc6487bfd508e3d8 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Thu, 15 Mar 2018 16:57:02 +0900 Subject: [PATCH] ui/gtk3: Add num pad Enter, Down, Up, Left, Right on Emojier BUG=rhbz#1554813 R=Shawn.P.Huang@gmail.com Review URL: https://codereview.appspot.com/337690043 --- ui/gtk3/emojier.vala | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala index 8707e432..24029703 100644 --- a/ui/gtk3/emojier.vala +++ b/ui/gtk3/emojier.vala @@ -1918,6 +1918,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { return true; break; case Gdk.Key.Return: + case Gdk.Key.KP_Enter: key_press_enter(); return true; case Gdk.Key.BackSpace: @@ -1959,29 +1960,37 @@ public class IBusEmojier : Gtk.ApplicationWindow { } return true; case Gdk.Key.Right: - key_press_cursor_horizontal(keyval, modifiers); + case Gdk.Key.KP_Right: + key_press_cursor_horizontal(Gdk.Key.Right, modifiers); return true; case Gdk.Key.Left: - key_press_cursor_horizontal(keyval, modifiers); + case Gdk.Key.KP_Left: + key_press_cursor_horizontal(Gdk.Key.Left, modifiers); return true; case Gdk.Key.Down: - key_press_cursor_vertical(keyval, modifiers); + case Gdk.Key.KP_Down: + key_press_cursor_vertical(Gdk.Key.Down, modifiers); return true; case Gdk.Key.Up: - key_press_cursor_vertical(keyval, modifiers); + case Gdk.Key.KP_Up: + key_press_cursor_vertical(Gdk.Key.Up, modifiers); return true; case Gdk.Key.Page_Down: - key_press_cursor_vertical(keyval, modifiers); + case Gdk.Key.KP_Page_Down: + key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers); return true; case Gdk.Key.Page_Up: - key_press_cursor_vertical(keyval, modifiers); + case Gdk.Key.KP_Page_Up: + key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers); return true; case Gdk.Key.Home: - if (key_press_cursor_home_end(keyval, modifiers)) + case Gdk.Key.KP_Home: + if (key_press_cursor_home_end(Gdk.Key.Home, modifiers)) return true; break; case Gdk.Key.End: - if (key_press_cursor_home_end(keyval, modifiers)) + case Gdk.Key.KP_End: + if (key_press_cursor_home_end(Gdk.Key.End, modifiers)) return true; break; case Gdk.Key.Insert: -- 2.14.3 From b184861396279d903e62bf6aad271a2205a79832 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 30 Mar 2018 12:33:59 +0900 Subject: [PATCH] ui/gtk3: Sort Unicode candidates BUG=rhbz#1554714 R=Shawn.P.Huang@gmail.com Review URL: https://codereview.appspot.com/339430043 --- ui/gtk3/emojier.vala | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala index 24029703..0c0865f1 100644 --- a/ui/gtk3/emojier.vala +++ b/ui/gtk3/emojier.vala @@ -1144,9 +1144,11 @@ public class IBusEmojier : Gtk.ApplicationWindow { lookup_emojis_from_annotation(string annotation) { GLib.SList? total_emojis = null; unowned GLib.SList? sub_emojis = null; + unowned GLib.SList? sub_exact_unicodes = null; unowned GLib.SList? sub_unicodes = null; int length = annotation.length; if (m_has_partial_match && length >= m_partial_match_length) { + GLib.SList? sorted_emojis = null; foreach (unowned string key in m_annotation_to_emojis_dict.get_keys()) { if (key.length < length) @@ -1173,16 +1175,29 @@ public class IBusEmojier : Gtk.ApplicationWindow { sub_emojis = m_annotation_to_emojis_dict.lookup(key); foreach (unowned string emoji in sub_emojis) { if (total_emojis.find_custom(emoji, GLib.strcmp) == null) { - total_emojis.append(emoji); + sorted_emojis.insert_sorted(emoji, GLib.strcmp); } } } + foreach (string emoji in sorted_emojis) { + if (total_emojis.find_custom(emoji, GLib.strcmp) == null) { + total_emojis.append(emoji); + } + } } else { sub_emojis = m_annotation_to_emojis_dict.lookup(annotation); foreach (unowned string emoji in sub_emojis) total_emojis.append(emoji); } + sub_exact_unicodes = m_name_to_unicodes_dict.lookup(annotation); + foreach (unichar code in sub_exact_unicodes) { + string ch = code.to_string(); + if (total_emojis.find_custom(ch, GLib.strcmp) == null) { + total_emojis.append(ch); + } + } if (length >= m_partial_match_length) { + GLib.SList? sorted_unicodes = null; foreach (unowned string key in m_name_to_unicodes_dict.get_keys()) { bool matched = false; if (key.index_of(annotation) >= 0) @@ -1192,11 +1207,16 @@ public class IBusEmojier : Gtk.ApplicationWindow { sub_unicodes = m_name_to_unicodes_dict.lookup(key); foreach (unichar code in sub_unicodes) { string ch = code.to_string(); - if (total_emojis.find_custom(ch, GLib.strcmp) == null) { - total_emojis.append(ch); + if (sorted_unicodes.find_custom(ch, GLib.strcmp) == null) { + sorted_unicodes.insert_sorted(ch, GLib.strcmp); } } } + foreach (string ch in sorted_unicodes) { + if (total_emojis.find_custom(ch, GLib.strcmp) == null) { + total_emojis.append(ch); + } + } } return total_emojis; } -- 2.14.3 From 5788be80685f397c3db3bdf4e672d67cfb9b3433 Mon Sep 17 00:00:00 2001 From: Jeremy Bicha Date: Fri, 30 Mar 2018 12:37:27 +0900 Subject: [PATCH] Fix ucd directory override BUG=https://github.com/ibus/ibus/pull/1995 R=Shawn.P.Huang@gmail.com Review URL: https://codereview.appspot.com/339450043 Patch from Jeremy Bicha . --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6c00803f..d19aa874 100644 --- a/configure.ac +++ b/configure.ac @@ -666,7 +666,7 @@ AC_ARG_WITH(ucd-dir, AS_HELP_STRING([--with-ucd-dir[=DIR]], [Set the directory of UCD (Unicode Character Database) files. (default: "/usr/share/unicode/ucd")]), - UCD_DIR=$with_emoji_annotation_dir, + UCD_DIR=$with_ucd_dir, UCD_DIR="/usr/share/unicode/ucd" ) AC_SUBST(UCD_DIR) -- 2.14.3 From 75a6667b6ad8c8cb801cb160b7b04625334f9094 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Thu, 5 Apr 2018 16:54:41 +0900 Subject: [PATCH] src/tests: Fix ibus-compose for the latest GTK --- src/tests/ibus-compose.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/tests/ibus-compose.c b/src/tests/ibus-compose.c index eb7b9f19..aabb36ac 100644 --- a/src/tests/ibus-compose.c +++ b/src/tests/ibus-compose.c @@ -2,6 +2,10 @@ #include "ibus.h" #include "ibuscomposetable.h" +#define GREEN "\033[0;32m" +#define RED "\033[0;31m" +#define NC "\033[0m" + IBusBus *m_bus; IBusComposeTable *m_compose_table; IBusEngine *m_engine; @@ -172,7 +176,12 @@ window_inserted_text_cb (GtkEntryBuffer *buffer, guint nchars, gpointer data) { +/* https://gitlab.gnome.org/GNOME/gtk/commit/9981f46e0b + * The latest GTK does not emit "inserted-text" when the text is "". + */ +#if !GTK_CHECK_VERSION (3, 22, 16) static int n_loop = 0; +#endif static guint stride = 0; guint i; int seq; @@ -182,16 +191,18 @@ window_inserted_text_cb (GtkEntryBuffer *buffer, g_assert (m_compose_table != NULL); +#if !GTK_CHECK_VERSION (3, 22, 16) if (n_loop % 2 == 1) { n_loop = 0; return; } +#endif i = stride + (m_compose_table->max_seq_len + 2) - 1; seq = (i + 1) / (m_compose_table->max_seq_len + 2); if (m_compose_table->data[i] == code) { - test = "OK"; + test = GREEN "PASS" NC; } else { - test = "NG"; + test = RED "FAIL" NC; m_retval = -1; } g_print ("%05d/%05d %s expected: %04X typed: %04X\n", @@ -207,7 +218,9 @@ window_inserted_text_cb (GtkEntryBuffer *buffer, } stride += m_compose_table->max_seq_len + 2; +#if !GTK_CHECK_VERSION (3, 22, 16) n_loop++; +#endif gtk_entry_set_text (entry, ""); } -- 2.14.3 From 28d0c1d4bc47beb38995d84cc4bb1d539c08a070 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Fri, 6 Apr 2018 16:02:11 +0900 Subject: [PATCH] src: Make the call to chmod in ibus_bus_init conditional BUG=https://github.com/ibus/ibus/issues/1996 --- src/ibusbus.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ibusbus.c b/src/ibusbus.c index 11659c41..98820e8a 100644 --- a/src/ibusbus.c +++ b/src/ibusbus.c @@ -557,7 +557,6 @@ ibus_bus_init (IBusBus *bus) path = g_path_get_dirname (ibus_get_socket_path ()); g_mkdir_with_parents (path, 0700); - g_chmod (path, 0700); if (stat (path, &buf) == 0) { if (buf.st_uid != getuid ()) { @@ -565,6 +564,9 @@ ibus_bus_init (IBusBus *bus) path, ibus_get_user_name ()); return; } + if (buf.st_mode != (S_IFDIR | S_IRWXU)) { + g_chmod (path, 0700); + } } g_free (path); -- 2.14.3 From 32f2f2bab149ad766674e7421f7044ebe98bb0b6 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 6 Apr 2018 20:24:08 +0900 Subject: [PATCH] tests: Added an automation testing on console test-console.sh runs /usr/bin/ibus-daemon on console after install ibus. Login as root --builddir /root/ibus/src/tests --srcdir /root/ibus/src/tests Also added DISABLE_GUI_TESTS parameters for make check. --- bus/Makefile.am | 1 + src/tests/Makefile.am | 5 +- src/tests/runtest | 151 +++++++++++++++++++------------ test/test-console.sh | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+), 56 deletions(-) create mode 100755 test/test-console.sh diff --git a/bus/Makefile.am b/bus/Makefile.am index 8bcc8e16..76166a0f 100644 --- a/bus/Makefile.am +++ b/bus/Makefile.am @@ -122,6 +122,7 @@ TESTS_ENVIRONMENT = \ top_builddir=$(top_builddir) \ top_srcdir=$(top_srcdir) \ builddir=$(builddir) \ + srcdir=$(srcdir) \ $(NULL) LOG_COMPILER = $(top_srcdir)/src/tests/runtest diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index 125be3fc..8bcac8f2 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -3,7 +3,8 @@ # ibus - The Input Bus # # Copyright (c) 2007-2015 Peng Huang -# Copyright (c) 2007-2015 Red Hat, Inc. +# Copyright (c) 2015-2018 Takao Fujiwara +# Copyright (c) 2007-2018 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -66,6 +67,8 @@ TESTS_ENVIRONMENT = \ top_builddir=$(top_builddir) \ top_srcdir=$(top_srcdir) \ builddir=$(builddir) \ + srcdir=$(srcdir) \ + DISABLE_GUI_TESTS=$(DISABLE_GUI_TESTS) \ $(NULL) LOG_COMPILER = $(srcdir)/runtest diff --git a/src/tests/runtest b/src/tests/runtest index 0e43fee5..b3b2a1ce 100755 --- a/src/tests/runtest +++ b/src/tests/runtest @@ -1,5 +1,8 @@ #!/bin/sh +# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- +# vim:set et sts=4: + # Run a test case given by the first argument in a separate directory. # This script may also launch $top_builddir/bus/ibus-daemon for testing. @@ -17,6 +20,8 @@ : ${top_builddir:=../..} : ${top_srcdir:=../..} : ${builddir:=.} +: ${srcdir:=.} +: ${DISABLE_GUI_TESTS:=''} BUS_REQUIRED_TESTS=" ibus-bus @@ -29,50 +34,51 @@ ibus-engine-switch ibus-compose test-stress " +retval=0 # Portable replacement of basename. func_basename () { - case "$1" in + case "$1" in */*) - expr "$1" : '.*/\(.*\)' - ;; + expr "$1" : '.*/\(.*\)' + ;; *) - echo "$1" - esac + echo "$1" + esac } # Portable replacement of dirname. func_dirname () { - case "$1" in + case "$1" in */*) - expr "$1" : '\(.*\)/.*' - ;; + expr "$1" : '\(.*\)/.*' + ;; *) - echo . - esac + echo . + esac } # Kill ibus-daemon process and remove temporary files. func_cleanup () { - tstdir=$1 - if test -f $tstdir/ibus-daemon.pid; then - . $tstdir/ibus-daemon.pid - kill $IBUS_DAEMON_PID &> /dev/null - fi - rm -fr $tstdir + tstdir=$1 + if test -f $tstdir/ibus-daemon.pid; then + . $tstdir/ibus-daemon.pid + kill $IBUS_DAEMON_PID &> /dev/null + fi + rm -fr $tstdir } # Prepare component files necessary for testing, under components/. func_copy_component () { - file=$1 - base=`func_basename $file` - libexecdir=`func_dirname $file` - # top_srcdir != top_builddir in make dist - libexecdir=`echo "$libexecdir" | sed -e "s|$top_srcdir|$top_builddir|"` - if test -f $file.in; then - mkdir -p components - sed "s|@libexecdir@|$libexecdir|g" < $file.in > components/$base - fi + file=$1 + base=`func_basename $file` + libexecdir=`func_dirname $file` + # top_srcdir != top_builddir in make dist + libexecdir=`echo "$libexecdir" | sed -e "s|$top_srcdir|$top_builddir|"` + if test -f $file.in; then + mkdir -p components + sed "s|@libexecdir@|$libexecdir|g" < $file.in > components/$base + fi } trap 'func_cleanup $tstdir' 1 2 3 15 @@ -80,44 +86,79 @@ trap 'func_cleanup $tstdir' 1 2 3 15 tst=$1; shift tstdir=tmp-`func_basename $tst` -test -d $tstdir || mkdir $tstdir - -( cd $tstdir - - need_bus=no - for t in $BUS_REQUIRED_TESTS; do +for t in $DISABLE_GUI_TESTS; do if test $t = `func_basename $tst`; then - need_bus=yes + exit 77 fi - done - - if test $need_bus = yes; then - func_copy_component "../$top_srcdir/engine/simple.xml" - func_copy_component "../$top_srcdir/conf/memconf/memconf.xml" +done - IBUS_COMPONENT_PATH=$PWD/components - export IBUS_COMPONENT_PATH +test -d $tstdir || mkdir $tstdir - IBUS_ADDRESS_FILE=$PWD/ibus-daemon.pid - export IBUS_ADDRESS_FILE +run_test_case() +{ + pushd $tstdir + + need_bus=no + for t in $BUS_REQUIRED_TESTS; do + if test $t = `func_basename $tst`; then + need_bus=yes + fi + done + + if test $need_bus = yes; then + func_copy_component "../$top_srcdir/engine/simple.xml" + func_copy_component "../$top_srcdir/conf/memconf/memconf.xml" + + IBUS_COMPONENT_PATH=$PWD/components + export IBUS_COMPONENT_PATH + + IBUS_ADDRESS_FILE=$PWD/ibus-daemon.pid + export IBUS_ADDRESS_FILE + + # Start ibus-daemon. + ../$top_builddir/bus/ibus-daemon \ + --daemonize \ + --cache=none \ + --panel=disable \ + --panel-extension=disable \ + --config=default \ + --verbose; + + # Wait until all necessary components are up. + sleep 1 + fi - # Start ibus-daemon. - ../$top_builddir/bus/ibus-daemon \ - --daemonize \ - --cache=none \ - --panel=disable \ - --panel-extension=disable \ - --config=default \ - --verbose; + "../$tst" ${1+"$@"} - # Wait until all necessary components are up. - sleep 1 - fi + retval=`expr $retval \| $?` - exec "../$tst" ${1+"$@"} ) + $popd -retval=$? + func_cleanup $tstdir +} -func_cleanup $tstdir +envfile=$srcdir/`func_basename $tst`.env +if test -f $envfile ; then + ENVS="`cat $envfile`" +fi; +if test x"$ENVS" = x ; then + run_test_case +else + LANG_backup=$LANG + i=1 + for e in $ENVS; do + first=`echo "$e" | cut -c1-1` + if test x"$first" = x"#" ; then + continue + fi + export $e + echo "Run `func_basename $tst` on $e" + echo "=======================" + run_test_case + echo "" + i=`expr $i + 1` + done + export LANG=$LANG_backup +fi exit $retval diff --git a/test/test-console.sh b/test/test-console.sh new file mode 100755 index 00000000..7199f7a7 --- /dev/null +++ b/test/test-console.sh @@ -0,0 +1,242 @@ +#!/bin/sh +# -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- +# vim:set noet ts=4: +# +# ibus-anthy - The Anthy engine for IBus +# +# Copyright (c) 2018 Takao Fujiwara +# Copyright (c) 2018 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 2 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# This test runs /usr/bin/ibus-daemon after install ibus +# +# # init 3 +# Login as root +# # /root/ibus/tests/test-console.sh --tests ibus-compose \ +# --builddir /root/ibus/src/tests --srcdir /root/ibus/src/tests + +PROGNAME=`basename $0` +VERSION=0.1 +DISPLAY=:99.0 +BUILDDIR="." +SRCDIR="." +TEST_LOG=test-suite.log +HAVE_GRAPHICS=1 +DESKTOP_COMMAND="gnome-session" +PID_XORG=0 +PID_GNOME_SESSION=0 +TESTS="" +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +usage() +{ + echo -e \ +"This test runs /usr/bin/ibus-daemon after install ibus\n" \ +"$PROGNAME [OPTIONS…]\n" \ +"\n" \ +"OPTIONS:\n" \ +"-h, --help This help\n" \ +"-v, --version Show version\n" \ +"-b, --builddir=BUILDDIR Set the BUILDDIR\n" \ +"-s, --srcdir=SOURCEDIR Set the SOURCEDIR\n" \ +"-c, --no-graphics Use Xvfb instead of Xorg\n" \ +"-d, --desktop=DESKTOP Run DESTKTOP. The default is gnome-session\n" \ +"-t, --tests=\"TESTS...\" Run TESTS programs which is separated by space\n" \ +"" +} + +parse_args() +{ + # This is GNU getopt. "sudo port getopt" in BSD? + ARGS=`getopt -o hvb:s:cd:t: --long help,version,builddir:,srcdir:,no-graphics,desktop:,tests:\ + -- "$@"`; + eval set -- "$ARGS" + while [ 1 ] ; do + case "$1" in + -h | --help ) usage; exit 0;; + -v | --version ) echo -e "$VERSION"; exit 0;; + -b | --builddir ) BUILDDIR="$2"; shift 2;; + -s | --srcdir ) SRCDIR="$2"; shift 2;; + -c | --no-graphics ) HAVE_GRAPHICS=0; shift;; + -d | --desktop ) DESKTOP_COMMAND="$2"; shift 2;; + -t | --tests ) TESTS="$2"; shift 2;; + -- ) shift; break;; + * ) usage; exit 1;; + esac + done +} + +init_desktop() +{ + if test x$FORCE_TEST != x ; then + RUN_ARGS="$RUN_ARGS --force" + fi + + if test ! -f $HOME/.config/gnome-initial-setup-done ; then + if test ! -f /var/lib/AccountsService/users/$USER ; then + mkdir -p /var/lib/AccountsService/users + cat >> /var/lib/AccountsService/users/$USER << _EOF +[User] +Language=ja_JP.UTF-8 +XSession=gnome +SystemAccount=false +_EOF + fi + mkdir -p $HOME/.config + touch $HOME/.config/gnome-initial-setup-done + fi +} + +run_dbus_daemon() +{ + a=`ps -ef | grep dbus-daemon | grep "\-\-system" | grep -v session | grep -v grep` + if test x"$a" = x ; then + eval `dbus-launch --sh-syntax` + fi + SUSER=`echo "$USER" | cut -c 1-7` + a=`ps -ef | grep dbus-daemon | grep "$SUSER" | grep -v gdm | grep session | grep -v grep` + if test x"$a" = x ; then + systemctl --user start dbus + export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$UID/bus + fi + systemctl --user status dbus | col -b + ps -ef | grep dbus-daemon | grep "$SUSER" | grep -v gdm | egrep 'session|system' | grep -v grep + systemctl --user show-environment | col -b +} + +run_desktop() +{ + if test $HAVE_GRAPHICS -eq 1 ; then + /usr/libexec/Xorg.wrap -noreset +extension GLX +extension RANDR +extension RENDER -logfile ./xorg.log -config ./xorg.conf -configdir . $DISPLAY & + else + /usr/bin/Xvfb $DISPLAY -noreset +extension GLX +extension RANDR +extension RENDER -screen 0 1280x1024x24 & + fi + PID_XORG=$! + sleep 1 + export DISPLAY=$DISPLAY + $DESKTOP_COMMAND & + PID_GNOME_SESSION=$! + sleep 30 + if test "$DESKTOP_COMMAND" != "gnome-session" ; then + ibus-daemon --daemonize --verbose + sleep 1 + fi +} + +count_case_result() +{ + retval=$1 + pass=$2 + fail=$3 + + if test $retval -eq 0 ; then + pass=`expr $pass + 1` + else + fail=`expr $fail + 1` + fi + echo $pass $fail +} + +echo_case_result() +{ + retval=$1 + tst=$2 + log=$3 + subtst=${4:-''} + + if test $retval -eq 0 ; then + echo -e "${GREEN}PASS${NC}: $tst $subtst" + else + echo -e "${RED}FAIL${NC}: $tst $subtst" + echo "FAIL: $tst $subtst" >> $TEST_LOG + echo "======================" >> $TEST_LOG + echo "" >> $TEST_LOG + cat "$log" >> $TEST_LOG + echo "" >> $TEST_LOG + fi +} + +run_test_suite() +{ + cd `dirname $0` + pass=0 + fail=0 + + if test -f $TEST_LOG ; then + rm $TEST_LOG + fi + for tst in $TESTS; do + ENVS= + if test -f $SRCDIR/${tst}.env ; then + ENVS="`cat $SRCDIR/${tst}.env`" + fi + if test x"$ENVS" = x ; then + $BUILDDIR/$tst >&${tst}.log + retval=$? + read pass fail << EOF + `count_case_result $retval $pass $fail` +EOF + echo_case_result $retval $tst ${tst}.log + else + LANG_backup=$LANG + i=1 + for e in $ENVS; do + first=`echo "$e" | cut -c1-1` + if test x"$first" = x"#" ; then + continue + fi + export $e + $BUILDDIR/$tst >&${tst}.${i}.log + retval=$? + read pass fail << EOF + `count_case_result $retval $pass $fail` +EOF + echo_case_result $retval $tst ${tst}.${i}.log $e + i=`expr $i + 1` + done + export LANG=$LANG_backup + fi + done + echo "" + echo -e "# ${GREEN}PASS${NC}: $pass" + echo -e "# ${RED}FAIL${NC}: $fail" + if test -f ${TEST_LOG} ; then + echo "" + echo -e "${RED}See ${TEST_LOG}$NC" + fi +} + +finit() +{ + if test "$DESKTOP_COMMAND" != "gnome-session" ; then + ibus exit + fi + kill $PID_GNOME_SESSION $PID_XORG +} + +main() +{ + parse_args $@ + init_desktop + run_dbus_daemon + run_desktop + run_test_suite + finit +} + +main $@ -- 2.14.3 From 68e162a59c7943ee6207ff7d21f9a75d1e6f2f79 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 6 Apr 2018 20:35:50 +0900 Subject: [PATCH] src/tests: Fix a typo in runtest --- src/tests/runtest | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tests/runtest b/src/tests/runtest index b3b2a1ce..09026be0 100755 --- a/src/tests/runtest +++ b/src/tests/runtest @@ -92,10 +92,9 @@ for t in $DISABLE_GUI_TESTS; do fi done -test -d $tstdir || mkdir $tstdir - run_test_case() { + test -d $tstdir || mkdir $tstdir pushd $tstdir need_bus=no @@ -132,7 +131,7 @@ run_test_case() retval=`expr $retval \| $?` - $popd + popd func_cleanup $tstdir } -- 2.14.3 From c360cbd830943a4bfb0ece9cc07b99a426dc2121 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Mon, 9 Apr 2018 11:57:09 +0900 Subject: [PATCH] src/tests: Add ibus-compose.env --- src/tests/ibus-compose.env | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/tests/ibus-compose.env diff --git a/src/tests/ibus-compose.env b/src/tests/ibus-compose.env new file mode 100644 index 00000000..734ab8fa --- /dev/null +++ b/src/tests/ibus-compose.env @@ -0,0 +1,3 @@ +LANG=el_GR.UTF-8 +LANG=fi_FI.UTF-8 +LANG=pt_BR.UTF-8 -- 2.14.3 From 68bd2695c4cc6a06cb8a55a55fed2054d29f0995 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 13 Apr 2018 16:31:29 +0900 Subject: [PATCH] src/tests: Fix a typo --- src/tests/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index 8bcac8f2..11ebb531 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -68,7 +68,7 @@ TESTS_ENVIRONMENT = \ top_srcdir=$(top_srcdir) \ builddir=$(builddir) \ srcdir=$(srcdir) \ - DISABLE_GUI_TESTS=$(DISABLE_GUI_TESTS) \ + DISABLE_GUI_TESTS="$(DISABLE_GUI_TESTS)" \ $(NULL) LOG_COMPILER = $(srcdir)/runtest -- 2.14.3 From 8d4c4738d07b6850e56ae74d46b7b13b7382f865 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 13 Apr 2018 17:33:50 +0900 Subject: [PATCH] configure: Add --disable-python2 option --- bindings/pygobject/Makefile.am | 6 ++++++ configure.ac | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/bindings/pygobject/Makefile.am b/bindings/pygobject/Makefile.am index 238a537a..fb2e2a7a 100644 --- a/bindings/pygobject/Makefile.am +++ b/bindings/pygobject/Makefile.am @@ -4,6 +4,8 @@ # # Copyright (c) 2012 Daiki Ueno # Copyright (c) 2014-2016 Peng Huang +# Copyright (c) 2018 Takao Fujiwara +# Copyright (c) 2012-2018 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -22,11 +24,13 @@ NULL = +if ENABLE_PYTHON2 py2_compile = PYTHON=$(PYTHON2) $(SHELL) $(py_compile) overrides2dir = $(py2overridesdir) overrides2_DATA = \ gi/overrides/IBus.py \ $(NULL) +endif overridesdir = $(pyoverridesdir) overrides_PYTHON = \ @@ -56,6 +60,7 @@ EXTRA_DIST = \ $(NULL) install-data-hook: +if ENABLE_PYTHON2 @for data in $(overrides2_DATA); do \ file=`echo $$data | sed -e 's|^.*/||'`; \ dlist="$$dlist $$file"; \ @@ -63,6 +68,7 @@ install-data-hook: $(py2_compile) --destdir "$(DESTDIR)" \ --basedir "$(overrides2dir)" \ $$dlist +endif $(NULL) -include $(top_srcdir)/git.mk diff --git a/configure.ac b/configure.ac index d19aa874..085cecb8 100644 --- a/configure.ac +++ b/configure.ac @@ -391,6 +391,14 @@ fi AC_PATH_PROG(ENV_IBUS_TEST, env) AC_SUBST(ENV_IBUS_TEST) +AC_ARG_ENABLE(python2, + AS_HELP_STRING([--disable-python2], + [Do not install bindings/pygobject/gi and ibus for python2. + '--disable-python2' bring '--disable-python-library'.]), + [enable_python2=$enableval], + [enable_python2=yes] +) + AC_ARG_ENABLE(python-library, AS_HELP_STRING([--enable-python-library], [Use ibus python library]), @@ -405,10 +413,6 @@ AC_ARG_ENABLE(setup, [enable_setup=yes] ) -AM_CONDITIONAL([ENABLE_PYTHON_LIBRARY], [test x"$enable_python_library" = x"yes"]) -AM_CONDITIONAL([ENABLE_SETUP], [test x"$enable_setup" = x"yes"]) -AM_CONDITIONAL([ENABLE_DAEMON], [true]) - # Define python version AC_ARG_WITH(python, AS_HELP_STRING([--with-python[=PATH]], @@ -417,12 +421,24 @@ AC_ARG_WITH(python, ) AM_PATH_PYTHON([2.5]) -AC_PATH_PROG(PYTHON2, python2) -if test x"$PYTHON2" = x""; then - PYTHON2=$PYTHON +if test x"$enable_python2" != x"yes"; then + enable_python_library=no + PYTHON2= + enable_python2="no (disabled, use --enable-python2 to enable)" +else + AC_PATH_PROG(PYTHON2, python2) + + if test x"$PYTHON2" = x""; then + PYTHON2=$PYTHON + fi fi +AM_CONDITIONAL([ENABLE_PYTHON2], [test x"$enable_python2" = x"yes"]) +AM_CONDITIONAL([ENABLE_PYTHON_LIBRARY], [test x"$enable_python_library" = x"yes"]) +AM_CONDITIONAL([ENABLE_SETUP], [test x"$enable_setup" = x"yes"]) +AM_CONDITIONAL([ENABLE_DAEMON], [true]) + PYGOBJECT_REQUIRED=3.0.0 PKG_CHECK_EXISTS([pygobject-3.0 >= $PYGOBJECT_REQUIRED], @@ -434,8 +450,10 @@ if test "x$enable_pygobject" = "xyes"; then pyoverridesdir=`$PYTHON -c "import gi; print(gi._overridesdir)"` AC_SUBST(pyoverridesdir) - py2overridesdir=`$PYTHON2 -c "import gi; print(gi._overridesdir)"` - AC_SUBST(py2overridesdir) + if test x"$enable_python2" = x"yes"; then + py2overridesdir=`$PYTHON2 -c "import gi; print(gi._overridesdir)"` + AC_SUBST(py2overridesdir) + fi fi AM_CONDITIONAL(ENABLE_PYGOBJECT, test x"$enable_pygobject" = "xyes") @@ -752,6 +770,7 @@ Build options: CFLAGS $CFLAGS PYTHON $PYTHON PYTHON2 $PYTHON2 + Enable python2 $enable_python2 Gtk2 immodule dir $GTK2_IM_MODULEDIR Gtk3 immodule dir $GTK3_IM_MODULEDIR Build gtk2 immodule $enable_gtk2 -- 2.14.3 From 7bc160f2139799b853678264c6b01277f0721336 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 13 Apr 2018 19:39:09 +0900 Subject: [PATCH] bus: Add DISABLE_GUI_TESTS for test-stress --- bus/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bus/Makefile.am b/bus/Makefile.am index 76166a0f..dda79eac 100644 --- a/bus/Makefile.am +++ b/bus/Makefile.am @@ -110,7 +110,6 @@ if ENABLE_EMOJI_DICT AM_CFLAGS += -DEMOJI_DICT endif - if ENABLE_TESTS TESTS = \ test-matchrule \ @@ -123,6 +122,7 @@ TESTS_ENVIRONMENT = \ top_srcdir=$(top_srcdir) \ builddir=$(builddir) \ srcdir=$(srcdir) \ + DISABLE_GUI_TESTS="$(DISABLE_GUI_TESTS)" \ $(NULL) LOG_COMPILER = $(top_srcdir)/src/tests/runtest -- 2.14.3 From 10cc30eac200d10b581d9d2122d5a732f4880943 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Tue, 17 Apr 2018 14:00:20 +0900 Subject: [PATCH] src/tests: Enable GSettings in runtest --- src/tests/runtest | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/tests/runtest b/src/tests/runtest index 09026be0..35825b1b 100755 --- a/src/tests/runtest +++ b/src/tests/runtest @@ -34,6 +34,8 @@ ibus-engine-switch ibus-compose test-stress " +IBUS_SCHEMA_FILE='org.freedesktop.ibus.gschema.xml' + retval=0 # Portable replacement of basename. @@ -92,6 +94,12 @@ for t in $DISABLE_GUI_TESTS; do fi done +# IBusEngine has GSettings +if test ! -f "$top_builddir/data/dconf/$IBUS_SCHEMA_FILE" ; then + echo "NOT FOUND $top_builddir/data/dconf/$IBUS_SCHEMA_FILE" + exit -1 +fi + run_test_case() { test -d $tstdir || mkdir $tstdir @@ -114,6 +122,20 @@ run_test_case() IBUS_ADDRESS_FILE=$PWD/ibus-daemon.pid export IBUS_ADDRESS_FILE + cp "../$top_builddir/data/dconf/$IBUS_SCHEMA_FILE" $PWD + glib-compile-schemas $PWD + if test $? -ne 0 ; then + echo "FAILED glib-compile-schemas" + retval=1 + return + fi + if test ! -f $PWD/gschemas.compiled ; then + echo "NOT FOUND $PWD/gschemas.compiled" + retval=1 + return + fi + export GSETTINGS_SCHEMA_DIR=$PWD + # Start ibus-daemon. ../$top_builddir/bus/ibus-daemon \ --daemonize \ -- 2.14.3 From 3280848b42b07afbac3d59066474c5f429de9182 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Tue, 17 Apr 2018 14:43:02 +0900 Subject: [PATCH] bus: Enable sub package of gtkextension.xml and ibus-extension-gtk3 GNOME destkop asked not to install ibus-extension-gtk3 by default since the UI is not called by gnome-shell. BUG=rhbz#1567689 --- bus/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bus/main.c b/bus/main.c index 7aa89fc4..e1cc423b 100644 --- a/bus/main.c +++ b/bus/main.c @@ -293,7 +293,7 @@ main (gint argc, gchar **argv) if (component) { bus_component_set_restart (component, restart); } - if (component == NULL || + if (component != NULL && !bus_component_start (component, g_verbose)) { g_printerr ("Can not execute default panel program\n"); exit (-1); -- 2.14.3 From d8f901f856ddd75baba5826038d1346c5a43d048 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 20 Apr 2018 15:58:06 +0900 Subject: [PATCH] Replace OnlyShowIn= with NoDisplay=true BUG=rhbz#1567689 --- ui/gtk3/ibus-extension-gtk3.desktop.in.in | 2 +- ui/gtk3/ibus-ui-emojier.desktop.in.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/gtk3/ibus-extension-gtk3.desktop.in.in b/ui/gtk3/ibus-extension-gtk3.desktop.in.in index 6ec5585f..a119ec8e 100644 --- a/ui/gtk3/ibus-extension-gtk3.desktop.in.in +++ b/ui/gtk3/ibus-extension-gtk3.desktop.in.in @@ -3,4 +3,4 @@ _Name=Emoji Choice Icon=ibus Exec=@libexecdir@/ibus-extension-gtk3 Type=Application -OnlyShowIn= +NoDisplay=true diff --git a/ui/gtk3/ibus-ui-emojier.desktop.in.in b/ui/gtk3/ibus-ui-emojier.desktop.in.in index f4b750a8..6d9422d5 100644 --- a/ui/gtk3/ibus-ui-emojier.desktop.in.in +++ b/ui/gtk3/ibus-ui-emojier.desktop.in.in @@ -3,4 +3,4 @@ _Name=Emoji Choice Icon=ibus Exec=ibus emoji Type=Application -OnlyShowIn= +NoDisplay=true -- 2.14.3 From 886ad3651d16dd821e2526e8601c69738533a7e8 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Mon, 7 May 2018 12:35:03 +0900 Subject: [PATCH] src: Fix SEGV in IBusEngine if no emoji shortcut keys BUG=https://github.com/ibus/ibus/issues/2005 --- src/ibusengine.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ibusengine.c b/src/ibusengine.c index 9a0b1a8a..fd61102a 100644 --- a/src/ibusengine.c +++ b/src/ibusengine.c @@ -925,6 +925,9 @@ ibus_engine_filter_key_event (IBusEngine *engine, g_return_val_if_fail (IBUS_IS_ENGINE (engine), FALSE); priv = engine->priv; + if (!priv->emoji_keybindings) + return FALSE; + modifiers = state & IBUS_MODIFIER_FILTER; if (keyval >= IBUS_KEY_A && keyval <= IBUS_KEY_Z && (modifiers & IBUS_SHIFT_MASK) != 0) { -- 2.14.3 From 196216a89a9167425dd9b41f4f1d8a494d370249 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 11 May 2018 19:13:03 +0900 Subject: [PATCH] src: Add ibus-keypress --- configure.ac | 8 ++ src/tests/Makefile.am | 7 ++ src/tests/ibus-keypress.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++ src/tests/runtest | 1 + 4 files changed, 314 insertions(+) create mode 100644 src/tests/ibus-keypress.c diff --git a/configure.ac b/configure.ac index 085cecb8..f332a775 100644 --- a/configure.ac +++ b/configure.ac @@ -621,6 +621,14 @@ if test x"$enable_libnotify" = x"yes"; then enable_libnotify="yes (enabled, use --disable-libnotify to disable)" fi +PKG_CHECK_MODULES(XTEST, + [x11 xtst], + [enable_xtest=yes], + [enable_xtest=no] +) +AM_CONDITIONAL([ENABLE_XTEST], [test x"$enable_xtest" = x"yes"]) + + # --disable-emoji-dict option. AC_ARG_ENABLE(emoji-dict, AS_HELP_STRING([--disable-emoji-dict], diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index 11ebb531..5f21ebcd 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -61,6 +61,9 @@ endif if ENABLE_GTK3 TESTS += ibus-compose +if ENABLE_XTEST +TESTS += ibus-keypress +endif endif TESTS_ENVIRONMENT = \ @@ -103,6 +106,10 @@ ibus_inputcontext_create_LDADD = $(prog_ldadd) ibus_keynames_SOURCES = ibus-keynames.c ibus_keynames_LDADD = $(prog_ldadd) +ibus_keypress_SOURCES = ibus-keypress.c +ibus_keypress_CFLAGS = @GTK3_CFLAGS@ @XTEST_CFLAGS@ +ibus_keypress_LDADD = $(prog_ldadd) @GTK3_LIBS@ @XTEST_LIBS@ + ibus_registry_SOURCES = ibus-registry.c ibus_registry_LDADD = $(prog_ldadd) diff --git a/src/tests/ibus-keypress.c b/src/tests/ibus-keypress.c new file mode 100644 index 00000000..3486523b --- /dev/null +++ b/src/tests/ibus-keypress.c @@ -0,0 +1,298 @@ +#include +#include +#include "ibus.h" +#include +#include +#include + +#define GREEN "\033[0;32m" +#define RED "\033[0;31m" +#define NC "\033[0m" + +typedef struct _KeyData { + guint keyval; + guint modifiers; +} KeyData; + +static const KeyData test_cases[][30] = { + { { IBUS_KEY_a, 0 }, { IBUS_KEY_comma, IBUS_SHIFT_MASK }, + { IBUS_KEY_b, 0 }, { IBUS_KEY_period, IBUS_SHIFT_MASK }, + { IBUS_KEY_c, 0 }, { IBUS_KEY_slash, IBUS_SHIFT_MASK }, + { IBUS_KEY_d, 0 }, { IBUS_KEY_semicolon, IBUS_SHIFT_MASK }, + { IBUS_KEY_e, 0 }, { IBUS_KEY_apostrophe, IBUS_SHIFT_MASK }, + { IBUS_KEY_f, 0 }, { IBUS_KEY_bracketleft, IBUS_SHIFT_MASK }, + { IBUS_KEY_g, 0 }, { IBUS_KEY_backslash, IBUS_SHIFT_MASK }, + { 0, 0 } }, + { { IBUS_KEY_grave, IBUS_SHIFT_MASK }, { IBUS_KEY_a, IBUS_SHIFT_MASK }, + { IBUS_KEY_1, IBUS_SHIFT_MASK }, { IBUS_KEY_b, IBUS_SHIFT_MASK }, + { IBUS_KEY_2, IBUS_SHIFT_MASK }, { IBUS_KEY_c, IBUS_SHIFT_MASK }, + { IBUS_KEY_3, IBUS_SHIFT_MASK }, { IBUS_KEY_d, IBUS_SHIFT_MASK }, + { IBUS_KEY_9, IBUS_SHIFT_MASK }, { IBUS_KEY_e, IBUS_SHIFT_MASK }, + { IBUS_KEY_0, IBUS_SHIFT_MASK }, { IBUS_KEY_f, IBUS_SHIFT_MASK }, + { IBUS_KEY_equal, IBUS_SHIFT_MASK }, { IBUS_KEY_g, IBUS_SHIFT_MASK }, + { 0, 0 } }, + { { 0, 0 } } +}; + +KeyData test_end_key = { IBUS_KEY_z, IBUS_SHIFT_MASK }; + +static const gunichar test_results[][60] = { + { 'a', '<', 'b', '>', 'c', '?', 'd', ':', 'e', '"', 'f', '{', 'g', '|', 0 }, + { '~', 'A', '!', 'B', '@', 'C', '#', 'D', '(', 'E', ')', 'F', '+', 'G', 0 }, + { 0 } +}; + + +IBusBus *m_bus; +IBusEngine *m_engine; + +static gboolean window_focus_in_event_cb (GtkWidget *entry, + GdkEventFocus *event, + gpointer data); + +static IBusEngine * +create_engine_cb (IBusFactory *factory, const gchar *name, gpointer data) +{ + static int i = 1; + gchar *engine_path = + g_strdup_printf ("/org/freedesktop/IBus/engine/simpletest/%d", + i++); + + m_engine = ibus_engine_new_with_type (IBUS_TYPE_ENGINE_SIMPLE, + name, + engine_path, + ibus_bus_get_connection (m_bus)); + g_free (engine_path); + return m_engine; +} + +static gboolean +register_ibus_engine () +{ + IBusFactory *factory; + IBusComponent *component; + IBusEngineDesc *desc; + + m_bus = ibus_bus_new (); + if (!ibus_bus_is_connected (m_bus)) { + g_critical ("ibus-daemon is not running."); + return FALSE; + } + factory = ibus_factory_new (ibus_bus_get_connection (m_bus)); + g_signal_connect (factory, "create-engine", + G_CALLBACK (create_engine_cb), NULL); + + component = ibus_component_new ( + "org.freedesktop.IBus.SimpleTest", + "Simple Engine Test", + "0.0.1", + "GPL", + "Takao Fujiwara ", + "https://github.com/ibus/ibus/wiki", + "", + "ibus"); + desc = ibus_engine_desc_new ( + "xkbtest:us::eng", + "XKB Test", + "XKB Test", + "en", + "GPL", + "Takao Fujiwara ", + "ibus-engine", + "us"); + ibus_component_add_engine (component, desc); + ibus_bus_register_component (m_bus, component); + + return TRUE; +} + +static gboolean +finit (gpointer data) +{ + g_critical ("time out"); + gtk_main_quit (); + return FALSE; +} + +static void +send_key_event (Display *xdisplay, + guint keyval, + guint modifiers) +{ + static struct { + guint state; + KeySym keysym; + } state2keysym[] = { + { IBUS_CONTROL_MASK, XK_Control_L } , + { IBUS_MOD1_MASK, XK_Alt_L }, + { IBUS_MOD4_MASK, XK_Super_L }, + { IBUS_SHIFT_MASK, XK_Shift_L }, + { IBUS_LOCK_MASK, XK_Caps_Lock }, + { 0, 0L } + }; + int i; + guint keycode; + guint state = modifiers; + + while (state) { + for (i = 0; state2keysym[i].state; i++) { + if ((state2keysym[i].state & state) != 0) { + keycode = XKeysymToKeycode (xdisplay, state2keysym[i].keysym); + XTestFakeKeyEvent (xdisplay, keycode, True, CurrentTime); + XSync (xdisplay, False); + state ^= state2keysym[i].state; + break; + } + } + } + keycode = XKeysymToKeycode (xdisplay, keyval); + XTestFakeKeyEvent (xdisplay, keycode, True, CurrentTime); + XSync (xdisplay, False); + XTestFakeKeyEvent (xdisplay, keycode, False, CurrentTime); + XSync (xdisplay, False); + + state = modifiers; + while (state) { + for (i = G_N_ELEMENTS (state2keysym) - 1; i >= 0; i--) { + if ((state2keysym[i].state & state) != 0) { + keycode = XKeysymToKeycode (xdisplay, state2keysym[i].keysym); + XTestFakeKeyEvent (xdisplay, keycode, False, CurrentTime); + XSync (xdisplay, False); + state ^= state2keysym[i].state; + break; + } + } + } +} + +static void +set_engine_cb (GObject *object, + GAsyncResult *res, + gpointer data) +{ + IBusBus *bus = IBUS_BUS (object); + GtkWidget *entry = GTK_WIDGET (data); + GdkDisplay *display; + Display *xdisplay; + GError *error = NULL; + int i, j; + + g_assert (GTK_IS_ENTRY (entry)); + + if (!ibus_bus_set_global_engine_async_finish (bus, res, &error)) { + g_critical ("set engine failed: %s", error->message); + g_error_free (error); + return; + } + + display = gtk_widget_get_display (entry); + if (GDK_IS_X11_DISPLAY (display)) { + xdisplay = gdk_x11_display_get_xdisplay (display); + } else { +#if 0 + xdisplay = XOpenDisplay (NULL); +#else + g_critical ("No idea to simulate key events in Wayland\n"); +#endif + } + g_return_if_fail (xdisplay); + + for (i = 0; test_cases[i][0].keyval; i++) { + for (j = 0; test_cases[i][j].keyval; j++) { + send_key_event (xdisplay, + test_cases[i][j].keyval, + test_cases[i][j].modifiers); + } + send_key_event (xdisplay, test_end_key.keyval, test_end_key.modifiers); + } + + g_timeout_add_seconds (10, finit, NULL); +} + +static gboolean +window_focus_in_event_cb (GtkWidget *entry, GdkEventFocus *event, gpointer data) +{ + g_assert (m_bus != NULL); + ibus_bus_set_global_engine_async (m_bus, + "xkbtest:us::eng", + -1, + NULL, + set_engine_cb, + entry); + return FALSE; +} + +static void +window_inserted_text_cb (GtkEntryBuffer *buffer, + guint position, + const gchar *chars, + guint nchars, + gpointer data) +{ + GtkWidget *entry = data; + static int i = 0; + static int j = 0; + + if (g_utf8_get_char (chars) == 'Z') { + int k; + g_print ("\n" GREEN "PASS" NC ": "); + for (k = 0; k < j; k++) + g_print ("%lc(%X) ", test_results[i][k], test_results[i][k]); + g_print ("\n"); + i++; + j = 0; + if (test_results[i][0] == 0) + gtk_main_quit (); + else + gtk_entry_set_text (GTK_ENTRY (entry), ""); + return; + } + g_assert (g_utf8_get_char (chars) == test_results[i][j]); + j++; +} + +static void +create_window () +{ + GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + GtkWidget *entry = gtk_entry_new (); + GtkEntryBuffer *buffer; + + g_signal_connect (window, "destroy", + G_CALLBACK (gtk_main_quit), NULL); + g_signal_connect (entry, "focus-in-event", + G_CALLBACK (window_focus_in_event_cb), NULL); + buffer = gtk_entry_get_buffer (GTK_ENTRY (entry)); + g_signal_connect (buffer, "inserted-text", + G_CALLBACK (window_inserted_text_cb), entry); + gtk_container_add (GTK_CONTAINER (window), entry); + gtk_widget_show_all (window); +} + +static void +test_keypress (void) +{ + int status = 0; + GError *error = NULL; + + g_spawn_command_line_sync ("setxkbmap -layout us", + NULL, NULL, + &status, &error); + g_assert (register_ibus_engine ()); + + create_window (); + gtk_main (); +} + +int +main (int argc, char *argv[]) +{ + ibus_init (); + g_test_init (&argc, &argv, NULL); + gtk_init (&argc, &argv); + + g_test_add_func ("/ibus/keyrepss", test_keypress); + + + return g_test_run (); +} diff --git a/src/tests/runtest b/src/tests/runtest index 35825b1b..b6b845d6 100755 --- a/src/tests/runtest +++ b/src/tests/runtest @@ -32,6 +32,7 @@ ibus-inputcontext ibus-inputcontext-create ibus-engine-switch ibus-compose +ibus-keypress test-stress " IBUS_SCHEMA_FILE='org.freedesktop.ibus.gschema.xml' -- 2.14.3 From 8ab0b603ba1cd8701583aee46c712898d52005f1 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Wed, 23 May 2018 19:20:10 +0900 Subject: [PATCH] bus: Fix a SEGV in bus_input_context_emit_signal IBus engines can call 'RequireSurroundingText' for a fake input context if there is no input focus. --- bus/inputcontext.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bus/inputcontext.c b/bus/inputcontext.c index a957d107..dfb98c36 100644 --- a/bus/inputcontext.c +++ b/bus/inputcontext.c @@ -716,7 +716,9 @@ bus_input_context_emit_signal (BusInputContext *context, GError **error) { if (context->connection == NULL) { - g_variant_unref (parameters); + /* fake context has no connections. */ + if (parameters) + g_variant_unref (parameters); return TRUE; } -- 2.14.3 From a1f91b27145b046a112bb5eba2561880dae5d6a2 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Mon, 4 Jun 2018 17:44:17 +0900 Subject: [PATCH] ui/gtk3: Get PangoAttrList of auxiliary text from IBusText Since IBus auxiliary text would be one line, it's better to show the character attributes likes color, italic, bold, on the auxiliary text. Also deleted the cursor width from the X position of CandidatePanel because IBus preedit overrides the original cursor of the applications. --- ui/gtk3/candidatepanel.vala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/gtk3/candidatepanel.vala b/ui/gtk3/candidatepanel.vala index ec2d3db4..d404c659 100644 --- a/ui/gtk3/candidatepanel.vala +++ b/ui/gtk3/candidatepanel.vala @@ -3,7 +3,7 @@ * ibus - The Input Bus * * Copyright(c) 2011-2015 Peng Huang - * Copyright(c) 2015-2017 Takao Fujiwara + * Copyright(c) 2015-2018 Takao Fujiwara * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -153,6 +153,8 @@ public class CandidatePanel : Gtk.Box{ public void set_auxiliary_text(IBus.Text? text) { if (text != null) { m_aux_label.set_text(text.get_text()); + Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text); + m_aux_label.set_attributes(attrs); m_aux_label.show(); } else { m_aux_label.set_text(""); @@ -314,7 +316,7 @@ public class CandidatePanel : Gtk.Box{ private void adjust_window_position_horizontal() { Gdk.Point cursor_right_bottom = { - m_cursor_location.x + m_cursor_location.width, + m_cursor_location.x, m_cursor_location.y + m_cursor_location.height }; -- 2.14.3 From cf4e2f1d815b700b0470380e0ff428ff266cc18a Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Thu, 14 Jun 2018 17:29:06 +0900 Subject: [PATCH] bus: Rename panel-extension to emoji-extension for CLI --- bus/main.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bus/main.c b/bus/main.c index e1cc423b..2fb37b69 100644 --- a/bus/main.c +++ b/bus/main.c @@ -43,7 +43,7 @@ static gboolean xim = FALSE; static gboolean replace = FALSE; static gboolean restart = FALSE; static gchar *panel = "default"; -static gchar *panel_extension = "default"; +static gchar *emoji_extension = "default"; static gchar *config = "default"; static gchar *desktop = "gnome"; @@ -67,7 +67,7 @@ static const GOptionEntry entries[] = { "xim", 'x', 0, G_OPTION_ARG_NONE, &xim, "execute ibus XIM server.", NULL }, { "desktop", 'n', 0, G_OPTION_ARG_STRING, &desktop, "specify the name of desktop session. [default=gnome]", "name" }, { "panel", 'p', 0, G_OPTION_ARG_STRING, &panel, "specify the cmdline of panel program. pass 'disable' not to start a panel program.", "cmdline" }, - { "panel-extension", 'E', 0, G_OPTION_ARG_STRING, &panel_extension, "specify the cmdline of panel extension program. pass 'disable' not to start an extension program.", "cmdline" }, + { "emoji-extension", 'E', 0, G_OPTION_ARG_STRING, &emoji_extension, "specify the cmdline of emoji extension program. pass 'disable' not to start an extension program.", "cmdline" }, { "config", 'c', 0, G_OPTION_ARG_STRING, &config, "specify the cmdline of config program. pass 'disable' not to start a config program.", "cmdline" }, { "address", 'a', 0, G_OPTION_ARG_STRING, &g_address, "specify the address of ibus daemon.", "address" }, { "replace", 'r', 0, G_OPTION_ARG_NONE, &replace, "if there is an old ibus-daemon is running, it will be replaced.", NULL }, @@ -245,7 +245,7 @@ main (gint argc, gchar **argv) bus_server_init (); for (i = 0; i < G_N_ELEMENTS(panel_extension_disable_users); i++) { if (!g_strcmp0 (username, panel_extension_disable_users[i]) != 0) { - panel_extension = "disable"; + emoji_extension = "disable"; break; } } @@ -286,7 +286,7 @@ main (gint argc, gchar **argv) } #ifdef EMOJI_DICT - if (g_strcmp0 (panel_extension, "default") == 0) { + if (g_strcmp0 (emoji_extension, "default") == 0) { BusComponent *component; component = bus_ibus_impl_lookup_component_by_name ( BUS_DEFAULT_IBUS, IBUS_SERVICE_PANEL_EXTENSION); @@ -298,9 +298,9 @@ main (gint argc, gchar **argv) g_printerr ("Can not execute default panel program\n"); exit (-1); } - } else if (g_strcmp0 (panel_extension, "disable") != 0 && - g_strcmp0 (panel_extension, "") != 0) { - if (!execute_cmdline (panel_extension)) + } else if (g_strcmp0 (emoji_extension, "disable") != 0 && + g_strcmp0 (emoji_extension, "") != 0) { + if (!execute_cmdline (emoji_extension)) exit (-1); } #endif -- 2.14.3 From ddc2284200971141947a37057356b4bbd84be7ce Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Thu, 14 Jun 2018 18:30:46 +0900 Subject: [PATCH] tools: Add ibus read-config --engine-id option for engine schemas Fixed ibus read-config and reset-config options and also added --engine-id sub option for engine schemas. E.g. % ibus read-config --engine-id anthy % ibus read-config --engine-id com.github.libpinyin.ibus-libpinyin.libpinyin --- tools/main.vala | 99 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 14 deletions(-) diff --git a/tools/main.vala b/tools/main.vala index 8c0b64d3..6e201f30 100644 --- a/tools/main.vala +++ b/tools/main.vala @@ -3,7 +3,7 @@ * ibus - The Input Bus * * Copyright(c) 2013 Peng Huang - * Copyright(c) 2015-2017 Takao Fujiwara + * Copyright(c) 2015-2018 Takao Fujiwara * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,20 +22,17 @@ */ private const string IBUS_SCHEMAS_GENERAL = "org.freedesktop.ibus.general"; -private const string IBUS_SCHEMAS_GENERAL_PANEL = - "org.freedesktop.ibus.general.panel"; +private const string IBUS_SCHEMAS_GENERAL_HOTKEY = + "org.freedesktop.ibus.general.hotkey"; private const string IBUS_SCHEMAS_PANEL = "org.freedesktop.ibus.panel"; - -private const string[] IBUS_SCHEMAS = { - IBUS_SCHEMAS_GENERAL, - IBUS_SCHEMAS_GENERAL_PANEL, - IBUS_SCHEMAS_PANEL, -}; +private const string IBUS_SCHEMAS_PANEL_EMOJI = + "org.freedesktop.ibus.panel.emoji"; bool name_only = false; /* system() exists as a public API. */ bool is_system = false; string cache_file = null; +string engine_id = null; class EngineList { public IBus.EngineDesc[] data = {}; @@ -292,15 +289,78 @@ int print_address(string[] argv) { return Posix.EXIT_SUCCESS; } +private int read_config_options(string[] argv) { + const OptionEntry[] options = { + { "engine-id", 0, 0, OptionArg.STRING, out engine_id, + N_("Use engine schema paths instead of ibus core, " + + "which can be comma-separated values."), "ENGINE_ID" }, + { null } + }; + + var option = new OptionContext(); + option.add_main_entries(options, Config.GETTEXT_PACKAGE); + + try { + option.parse(ref argv); + } catch (OptionError e) { + stderr.printf("%s\n", e.message); + return Posix.EXIT_FAILURE; + } + return Posix.EXIT_SUCCESS; +} + +private GLib.SList get_ibus_schemas() { + string[] ids = {}; + if (engine_id != null) { + ids = engine_id.split(","); + } + GLib.SList ibus_schemas = new GLib.SList(); + GLib.SettingsSchemaSource schema_source = + GLib.SettingsSchemaSource.get_default(); + string[] list_schemas = {}; + schema_source.list_schemas(true, out list_schemas, null); + foreach (string schema in list_schemas) { + if (ids.length != 0) { + foreach (unowned string id in ids) { + if (id == schema || + schema.has_prefix("org.freedesktop.ibus.engine." + id)) { + ibus_schemas.prepend(schema); + break; + } + } + } else if (schema.has_prefix("org.freedesktop.ibus") && + !schema.has_prefix("org.freedesktop.ibus.engine")) { + ibus_schemas.prepend(schema); + } + } + if (ibus_schemas.length() == 0) { + printerr("Not found schemas of \"org.freedesktop.ibus\"\n"); + return ibus_schemas; + } + ibus_schemas.sort(GLib.strcmp); + + return ibus_schemas; +} + int read_config(string[] argv) { - var output = new GLib.StringBuilder(); + if (read_config_options(argv) == Posix.EXIT_FAILURE) + return Posix.EXIT_FAILURE; + + GLib.SList ibus_schemas = get_ibus_schemas(); + if (ibus_schemas.length() == 0) + return Posix.EXIT_FAILURE; - foreach (string schema in IBUS_SCHEMAS) { + GLib.SettingsSchemaSource schema_source = + GLib.SettingsSchemaSource.get_default(); + var output = new GLib.StringBuilder(); + foreach (string schema in ibus_schemas) { + GLib.SettingsSchema settings_schema = schema_source.lookup(schema, + false); GLib.Settings settings = new GLib.Settings(schema); output.append_printf("SCHEMA: %s\n", schema); - foreach (string key in settings.list_keys()) { + foreach (string key in settings_schema.list_keys()) { GLib.Variant variant = settings.get_value(key); output.append_printf(" %s: %s\n", key, variant.print(true)); } @@ -311,14 +371,25 @@ int read_config(string[] argv) { } int reset_config(string[] argv) { + if (read_config_options(argv) == Posix.EXIT_FAILURE) + return Posix.EXIT_FAILURE; + + GLib.SList ibus_schemas = get_ibus_schemas(); + if (ibus_schemas.length() == 0) + return Posix.EXIT_FAILURE; + print("%s\n", _("Resetting…")); - foreach (string schema in IBUS_SCHEMAS) { + GLib.SettingsSchemaSource schema_source = + GLib.SettingsSchemaSource.get_default(); + foreach (string schema in ibus_schemas) { + GLib.SettingsSchema settings_schema = schema_source.lookup(schema, + false); GLib.Settings settings = new GLib.Settings(schema); print("SCHEMA: %s\n", schema); - foreach (string key in settings.list_keys()) { + foreach (string key in settings_schema.list_keys()) { print(" %s\n", key); settings.reset(key); } -- 2.14.3 From 37aa95f1adcdde82ef473936cadc0fa3fe8a4e44 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Fri, 15 Jun 2018 19:23:27 +0900 Subject: [PATCH] setup: Replace GtkTable /w GtkGrid --- setup/setup.ui | 113 +++++++++++++++++++++------------------------------------ 1 file changed, 41 insertions(+), 72 deletions(-) diff --git a/setup/setup.ui b/setup/setup.ui index 322f5146..e64b1046 100644 --- a/setup/setup.ui +++ b/setup/setup.ui @@ -99,11 +99,9 @@ 0 none - + True False - 5 - 2 12 6 6 @@ -117,8 +115,8 @@ Next input method: - GTK_FILL - GTK_FILL + 0 + 0 @@ -131,10 +129,8 @@ Previous input method: + 0 1 - 2 - GTK_FILL - GTK_FILL @@ -143,6 +139,7 @@ True False 6 + True True @@ -174,8 +171,7 @@ 1 - 2 - GTK_FILL + 0 @@ -184,6 +180,7 @@ True False 6 + True > True @@ -217,10 +214,7 @@ 1 - 2 1 - 2 - GTK_FILL @@ -232,10 +226,8 @@ Enable or disable: + 0 2 - 3 - GTK_FILL - GTK_FILL @@ -246,10 +238,8 @@ Enable: + 0 3 - 4 - GTK_FILL - GTK_FILL @@ -258,6 +248,7 @@ True False 6 + True True @@ -289,10 +280,7 @@ 1 - 2 3 - 4 - GTK_FILL @@ -303,10 +291,8 @@ Disable: + 0 4 - 5 - GTK_FILL - GTK_FILL @@ -315,6 +301,7 @@ True False 6 + True True @@ -346,10 +333,7 @@ 1 - 2 4 - 5 - GTK_FILL @@ -376,11 +360,9 @@ 0 none - + True False - 7 - 2 12 6 6 @@ -393,10 +375,11 @@ start Candidates orientation: right + True - GTK_FILL - GTK_FILL + 0 + 0 @@ -404,6 +387,7 @@ True False model_candidates_orientation + True @@ -413,8 +397,7 @@ 1 - 2 - GTK_FILL + 0 @@ -425,12 +408,11 @@ start Show property panel: right + True + 0 1 - 2 - GTK_FILL - GTK_FILL @@ -440,12 +422,11 @@ start Language panel position: right + True + 0 2 - 3 - GTK_FILL - GTK_FILL @@ -453,6 +434,7 @@ True False model_panel_show_mode + True @@ -462,10 +444,7 @@ 1 - 2 1 - 2 - GTK_FILL @@ -473,6 +452,7 @@ False True model_panel_position + True @@ -482,10 +462,7 @@ 1 - 2 2 - 3 - GTK_FILL @@ -499,13 +476,12 @@ False start True + True - 2 + 0 3 - 4 - GTK_FILL - GTK_FILL + 2 @@ -519,13 +495,12 @@ False start True + True - 2 + 0 4 - 5 - GTK_FILL - GTK_FILL + 2 @@ -539,13 +514,12 @@ False start True + True - 2 + 0 5 - 6 - GTK_FILL - GTK_FILL + 2 @@ -559,12 +533,11 @@ True start True + True + 0 6 - 7 - GTK_FILL - GTK_FILL @@ -573,13 +546,11 @@ True True False + True 1 - 2 6 - 7 - GTK_FILL @@ -888,11 +859,9 @@ 0 none - + True False - 5 - 2 12 6 6 @@ -906,8 +875,8 @@ Emoji choice: - GTK_FILL - GTK_FILL + 0 + 0 @@ -916,6 +885,7 @@ True False 6 + true True @@ -947,8 +917,7 @@ 1 - 2 - GTK_FILL + 0 -- 2.14.3 From 5ee3f48049ecf128391da6448ae7e74786bd171b Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Mon, 18 Jun 2018 12:46:11 +0900 Subject: [PATCH] Move input focus on Emojier to engines' preedit --- bindings/vala/IBus-1.0-custom.vala | 7 + bindings/vala/Makefile.am | 3 +- bindings/vala/gdk-wayland.vapi | 7 + bus/engineproxy.c | 53 +- bus/engineproxy.h | 25 +- bus/ibusimpl.c | 247 +++++++-- bus/inputcontext.c | 399 +++++++++++---- bus/inputcontext.h | 110 +++- bus/panelproxy.c | 210 +++++++- bus/panelproxy.h | 23 +- data/ibus.schemas.in | 12 + setup/main.py | 10 +- setup/setup.ui | 58 ++- src/ibusengine.c | 305 ++++++++---- src/ibuspanelservice.c | 318 +++++++++++- src/ibuspanelservice.h | 117 ++++- src/ibusshare.h | 17 +- src/ibusxevent.c | 375 +++++++++++++- src/ibusxevent.h | 143 +++++- ui/gtk3/Makefile.am | 3 + ui/gtk3/emojier.vala | 991 +++++++++++++++++++++++++++---------- ui/gtk3/emojierapp.vala | 74 ++- ui/gtk3/extension.vala | 6 +- ui/gtk3/panel.vala | 23 +- ui/gtk3/panelbinding.vala | 859 +++++++++++++++++++++++++++++--- 25 files changed, 3695 insertions(+), 700 deletions(-) create mode 100644 bindings/vala/gdk-wayland.vapi diff --git a/bindings/vala/IBus-1.0-custom.vala b/bindings/vala/IBus-1.0-custom.vala index cf1fc3fa..7d34a8bd 100644 --- a/bindings/vala/IBus-1.0-custom.vala +++ b/bindings/vala/IBus-1.0-custom.vala @@ -6,8 +6,15 @@ namespace IBus { [CCode (cname = "ibus_text_new_from_static_string", has_construct_function = false)] public Text.from_static_string (string str); } + public class ExtensionEvent : IBus.Serializable { + [CCode (cname = "ibus_extension_event_new", has_construct_function = true)] + public ExtensionEvent (string first_property_name, ...); + } public class XEvent : IBus.Serializable { [CCode (cname = "ibus_x_event_new", has_construct_function = true)] public XEvent (string first_property_name, ...); } + public class PanelService : IBus.Service { + public void panel_extension_register_keys(string first_property_name, ...); + } } diff --git a/bindings/vala/Makefile.am b/bindings/vala/Makefile.am index fc8e2f01..e4ecab97 100644 --- a/bindings/vala/Makefile.am +++ b/bindings/vala/Makefile.am @@ -3,7 +3,7 @@ # ibus - The Input Bus # # Copyright (c) 2007-2016 Peng Huang -# Copyright (c) 2017 Takao Fujiwara +# Copyright (c) 2017-2018 Takao Fujiwara # Copyright (c) 2007-2017 Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -86,6 +86,7 @@ EXTRA_DIST = \ ibus-1.0.deps \ ibus-emoji-dialog-1.0.deps \ config.vapi \ + gdk-wayland.vapi \ xi.vapi \ $(NULL) diff --git a/bindings/vala/gdk-wayland.vapi b/bindings/vala/gdk-wayland.vapi new file mode 100644 index 00000000..c65f2be4 --- /dev/null +++ b/bindings/vala/gdk-wayland.vapi @@ -0,0 +1,7 @@ +[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "gdk/gdkwayland.h")] +namespace GdkWayland +{ + [CCode (type_id = "gdk_wayland_display_get_type ()")] + public class Display : Gdk.Display { + } +} diff --git a/bus/engineproxy.c b/bus/engineproxy.c index 175aec56..2d98995c 100644 --- a/bus/engineproxy.c +++ b/bus/engineproxy.c @@ -377,10 +377,10 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - bus_marshal_VOID__VARIANT, + bus_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - G_TYPE_VARIANT); + IBUS_TYPE_EXTENSION_EVENT); text_empty = ibus_text_new_from_static_string (""); g_object_ref_sink (text_empty); @@ -644,7 +644,16 @@ bus_engine_proxy_g_signal (GDBusProxy *proxy, } if (g_strcmp0 (signal_name, "PanelExtension") == 0) { - g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, parameters); + GVariant *arg0 = NULL; + g_variant_get (parameters, "(v)", &arg0); + g_return_if_fail (arg0 != NULL); + + IBusExtensionEvent *event = IBUS_EXTENSION_EVENT ( + ibus_serializable_deserialize (arg0)); + g_variant_unref (arg0); + g_return_if_fail (event != NULL); + g_signal_emit (engine, engine_signals[PANEL_EXTENSION], 0, event); + _g_object_unref_if_floating (event); return; } @@ -1323,6 +1332,44 @@ bus_engine_proxy_is_enabled (BusEngineProxy *engine) return engine->enabled; } +void +bus_engine_proxy_panel_extension_received (BusEngineProxy *engine, + IBusExtensionEvent *event) +{ + GVariant *variant; + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (IBUS_IS_EXTENSION_EVENT (event)); + + variant = ibus_serializable_serialize_object ( + IBUS_SERIALIZABLE (event)); + g_return_if_fail (variant != NULL); + g_dbus_proxy_call ((GDBusProxy *)engine, + "PanelExtensionReceived", + g_variant_new ("(v)", variant), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + +void +bus_engine_proxy_panel_extension_register_keys (BusEngineProxy *engine, + GVariant *parameters) +{ + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (parameters); + + g_dbus_proxy_call ((GDBusProxy *)engine, + "PanelExtensionRegisterKeys", + g_variant_new ("(v)", g_variant_ref (parameters)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + static gboolean initable_init (GInitable *initable, GCancellable *cancellable, diff --git a/bus/engineproxy.h b/bus/engineproxy.h index 528e61b7..a3006b47 100644 --- a/bus/engineproxy.h +++ b/bus/engineproxy.h @@ -2,7 +2,8 @@ /* vim:set et sts=4: */ /* ibus - The Input Bus * Copyright (C) 2008-2013 Peng Huang - * Copyright (C) 2008-2013 Red Hat, Inc. + * Copyright (C) 2018 Takao Fujiwara + * Copyright (C) 2008-2018 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -325,5 +326,27 @@ void bus_engine_proxy_set_content_type IBusPropList *bus_engine_proxy_get_properties (BusEngineProxy *engine); +/** + * bus_engine_proxy_panel_extension_received: + * @engine: A #BusEngineProxy. + * @event: An #IBusExtensionEvent. + * + * Send an #IBusExtensionEvent to the engine. + */ +void bus_engine_proxy_panel_extension_received + (BusEngineProxy *engine, + IBusExtensionEvent *event); + +/** + * bus_engine_proxy_panel_extension_register_keys: + * @engine: A #BusEngineProxy. + * @parameters: A #GVariant array which includes the name and shortcut keys. + * + * Send shortcut keys to the engine to enable the extension. + */ +void bus_engine_proxy_panel_extension_register_keys + (BusEngineProxy *engine, + GVariant *parameters); + G_END_DECLS #endif diff --git a/bus/ibusimpl.c b/bus/ibusimpl.c index a4ce3d9d..ec1caea8 100644 --- a/bus/ibusimpl.c +++ b/bus/ibusimpl.c @@ -74,7 +74,8 @@ struct _BusIBusImpl { BusInputContext *focused_context; BusPanelProxy *panel; - BusPanelProxy *extension; + BusPanelProxy *emoji_extension; + gboolean enable_emoji_extension; /* a default keymap of ibus-daemon (usually "us") which is used only * when use_sys_layout is FALSE. */ @@ -83,6 +84,7 @@ struct _BusIBusImpl { gboolean use_global_engine; gchar *global_engine_name; gchar *global_previous_engine_name; + GVariant *extension_register_keys; }; struct _BusIBusImplClass { @@ -294,40 +296,158 @@ _panel_destroy_cb (BusPanelProxy *panel, if (ibus->panel == panel) ibus->panel = NULL; - else if (ibus->extension == panel) - ibus->extension = NULL; + else if (ibus->emoji_extension == panel) + ibus->emoji_extension = NULL; else g_return_if_reached (); g_object_unref (panel); } static void -bus_ibus_impl_panel_extension_received (BusIBusImpl *ibus, - GVariant *parameters) +bus_ibus_impl_set_panel_extension_mode (BusIBusImpl *ibus, + IBusExtensionEvent *event) { - if (!ibus->extension) { + gboolean is_extension = FALSE; + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event)); + + if (!ibus->emoji_extension) { g_warning ("Panel extension is not running."); return; } - g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); - g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->extension)); + g_return_if_fail (BUS_IS_PANEL_PROXY (ibus->emoji_extension)); + + ibus->enable_emoji_extension = ibus_extension_event_is_enabled (event); + is_extension = ibus_extension_event_is_extension (event); + if (ibus->focused_context != NULL) { + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension (ibus->focused_context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension (ibus->focused_context, NULL); + } + if (is_extension) + bus_input_context_panel_extension_received (ibus->focused_context, + event); + } + if (is_extension) + return; /* Use the DBus method because it seems any DBus signal, * g_dbus_message_new_signal(), cannot be reached to the server. */ - g_dbus_proxy_call (G_DBUS_PROXY (ibus->extension), - "PanelExtensionReceived", - parameters, - G_DBUS_CALL_FLAGS_NONE, - -1, NULL, NULL, NULL); + bus_panel_proxy_panel_extension_received (ibus->emoji_extension, + event); +} + +static void +bus_ibus_impl_set_panel_extension_keys (BusIBusImpl *ibus, + GVariant *parameters) +{ + BusEngineProxy *engine = NULL; + + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (parameters); + + if (!ibus->emoji_extension) { + g_warning ("Panel extension is not running."); + return; + } + + if (ibus->extension_register_keys) + g_variant_unref (ibus->extension_register_keys); + ibus->extension_register_keys = g_variant_ref_sink (parameters); + if (ibus->focused_context != NULL) { + engine = bus_input_context_get_engine (ibus->focused_context); + } + if (!engine) + return; + bus_engine_proxy_panel_extension_register_keys (engine, parameters); } static void -_panel_panel_extension_cb (BusPanelProxy *panel, - GVariant *parameters, - BusIBusImpl *ibus) +_panel_panel_extension_cb (BusPanelProxy *panel, + IBusExtensionEvent *event, + BusIBusImpl *ibus) { - bus_ibus_impl_panel_extension_received (ibus, parameters); + bus_ibus_impl_set_panel_extension_mode (ibus, event); +} + +static void +_panel_panel_extension_register_keys_cb (BusInputContext *context, + GVariant *parameters, + BusIBusImpl *ibus) +{ + bus_ibus_impl_set_panel_extension_keys (ibus, parameters); +} + +static void +_panel_update_preedit_text_received_cb (BusPanelProxy *panel, + IBusText *text, + guint cursor_pos, + gboolean visible, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + + if (!ibus->focused_context) + return; + bus_input_context_update_preedit_text (ibus->focused_context, + text, cursor_pos, visible, IBUS_ENGINE_PREEDIT_CLEAR, FALSE); +} + +static void +_panel_update_lookup_table_received_cb (BusPanelProxy *panel, + IBusLookupTable *table, + gboolean visible, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (IBUS_IS_LOOKUP_TABLE (table)); + + if (!ibus->focused_context) + return; + /* Call bus_input_context_update_lookup_table() instead of + * bus_panel_proxy_update_lookup_table() for panel extensions because + * bus_input_context_page_up() can call bus_panel_proxy_page_up_received(). + */ + bus_input_context_update_lookup_table ( + ibus->focused_context, table, visible, TRUE); +} + +static void +_panel_update_auxiliary_text_received_cb (BusPanelProxy *panel, + IBusText *text, + gboolean visible, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + g_return_if_fail (IBUS_IS_TEXT (text)); + + if (!ibus->panel) + return; + bus_panel_proxy_update_auxiliary_text ( + ibus->panel, text, visible); +} + +static void +_panel_show_lookup_table_received_cb (BusPanelProxy *panel, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + + if (ibus->panel) + bus_panel_proxy_show_lookup_table (ibus->panel); +} + +static void +_panel_hide_lookup_table_received_cb (BusPanelProxy *panel, + BusIBusImpl *ibus) +{ + g_return_if_fail (BUS_IS_IBUS_IMPL (ibus)); + + if (ibus->panel) + bus_panel_proxy_hide_lookup_table (ibus->panel); } static void @@ -361,8 +481,8 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, if (!g_strcmp0 (name, IBUS_SERVICE_PANEL)) panel_type = PANEL_TYPE_PANEL; - else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION)) - panel_type = PANEL_TYPE_EXTENSION; + else if (!g_strcmp0 (name, IBUS_SERVICE_PANEL_EXTENSION_EMOJI)) + panel_type = PANEL_TYPE_EXTENSION_EMOJI; if (panel_type != PANEL_TYPE_NONE) { if (g_strcmp0 (new_name, "") != 0) { @@ -370,7 +490,7 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, BusConnection *connection; BusInputContext *context = NULL; BusPanelProxy **panel = (panel_type == PANEL_TYPE_PANEL) ? - &ibus->panel : &ibus->extension; + &ibus->panel : &ibus->emoji_extension; if (*panel != NULL) { ibus_proxy_destroy ((IBusProxy *)(*panel)); @@ -383,6 +503,8 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, g_return_if_fail (connection != NULL); *panel = bus_panel_proxy_new (connection, panel_type); + if (panel_type == PANEL_TYPE_EXTENSION_EMOJI) + ibus->enable_emoji_extension = FALSE; g_signal_connect (*panel, "destroy", @@ -392,6 +514,26 @@ _dbus_name_owner_changed_cb (BusDBusImpl *dbus, "panel-extension", G_CALLBACK (_panel_panel_extension_cb), ibus); + g_signal_connect (*panel, + "panel-extension-register-keys", + G_CALLBACK ( + _panel_panel_extension_register_keys_cb), + ibus); + g_signal_connect ( + *panel, + "update-preedit-text-received", + G_CALLBACK (_panel_update_preedit_text_received_cb), + ibus); + g_signal_connect ( + *panel, + "update-lookup-table-received", + G_CALLBACK (_panel_update_lookup_table_received_cb), + ibus); + g_signal_connect ( + *panel, + "update-auxiliary-text-received", + G_CALLBACK (_panel_update_auxiliary_text_received_cb), + ibus); if (ibus->focused_context != NULL) { context = ibus->focused_context; @@ -450,7 +592,7 @@ bus_ibus_impl_init (BusIBusImpl *ibus) ibus->contexts = NULL; ibus->focused_context = NULL; ibus->panel = NULL; - ibus->extension = NULL; + ibus->emoji_extension = NULL; ibus->keymap = ibus_keymap_get ("us"); @@ -650,11 +792,11 @@ bus_ibus_impl_set_context_engine_from_desc (BusIBusImpl *ibus, } static void -_context_panel_extension_cb (BusInputContext *context, - GVariant *parameters, - BusIBusImpl *ibus) +_context_panel_extension_cb (BusInputContext *context, + IBusExtensionEvent *event, + BusIBusImpl *ibus) { - bus_ibus_impl_panel_extension_received (ibus, parameters); + bus_ibus_impl_set_panel_extension_mode (ibus, event); } const static struct { @@ -694,13 +836,18 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus, if (engine) { g_object_ref (engine); bus_input_context_set_engine (ibus->focused_context, NULL); + bus_input_context_set_emoji_extension (ibus->focused_context, + NULL); } } if (ibus->panel != NULL) bus_panel_proxy_focus_out (ibus->panel, ibus->focused_context); - if (ibus->extension != NULL) - bus_panel_proxy_focus_out (ibus->extension, ibus->focused_context); + if (ibus->emoji_extension != NULL) { + bus_panel_proxy_focus_out (ibus->emoji_extension, + ibus->focused_context); + } + bus_input_context_set_emoji_extension (ibus->focused_context, NULL); bus_input_context_get_content_type (ibus->focused_context, &purpose, &hints); @@ -724,6 +871,12 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus, if (engine != NULL) { bus_input_context_set_engine (context, engine); bus_input_context_enable (context); + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension (context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension (context, NULL); + } } for (i = 0; i < G_N_ELEMENTS(context_signals); i++) { g_signal_connect (ibus->focused_context, @@ -734,8 +887,8 @@ bus_ibus_impl_set_focused_context (BusIBusImpl *ibus, if (ibus->panel != NULL) bus_panel_proxy_focus_in (ibus->panel, context); - if (ibus->extension != NULL) - bus_panel_proxy_focus_in (ibus->extension, context); + if (ibus->emoji_extension != NULL) + bus_panel_proxy_focus_in (ibus->emoji_extension, context); } if (engine != NULL) @@ -751,6 +904,12 @@ bus_ibus_impl_set_global_engine (BusIBusImpl *ibus, if (ibus->focused_context) { bus_input_context_set_engine (ibus->focused_context, engine); + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension (ibus->focused_context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension (ibus->focused_context, NULL); + } } else if (ibus->fake_context) { bus_input_context_set_engine (ibus->fake_context, engine); } @@ -927,9 +1086,9 @@ _context_destroy_cb (BusInputContext *context, bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) { bus_panel_proxy_destroy_context (ibus->panel, context); } - if (ibus->extension && + if (ibus->emoji_extension && bus_input_context_get_capabilities (context) & IBUS_CAP_FOCUS) { - bus_panel_proxy_destroy_context (ibus->extension, context); + bus_panel_proxy_destroy_context (ibus->emoji_extension, context); } ibus->contexts = g_list_remove (ibus->contexts, context); @@ -1489,6 +1648,7 @@ _ibus_set_global_engine_ready_cb (BusInputContext *context, else { g_dbus_method_invocation_return_value (data->invocation, NULL); + BusEngineProxy *engine = bus_input_context_get_engine (context); if (ibus->use_global_engine && (context != ibus->focused_context)) { /* context and ibus->focused_context don't match. This means that * the focus is moved before _ibus_set_global_engine() asynchronous @@ -1496,14 +1656,28 @@ _ibus_set_global_engine_ready_cb (BusInputContext *context, * being focused hasn't been updated. Update the engine here so that * subsequent _ibus_get_global_engine() call could return a * consistent engine name. */ - BusEngineProxy *engine = bus_input_context_get_engine (context); if (engine && ibus->focused_context != NULL) { g_object_ref (engine); bus_input_context_set_engine (context, NULL); + bus_input_context_set_emoji_extension (context, NULL); bus_input_context_set_engine (ibus->focused_context, engine); + if (ibus->enable_emoji_extension) { + bus_input_context_set_emoji_extension ( + ibus->focused_context, + ibus->emoji_extension); + } else { + bus_input_context_set_emoji_extension ( + ibus->focused_context, + NULL); + } g_object_unref (engine); } } + if (engine && ibus->extension_register_keys) { + bus_engine_proxy_panel_extension_register_keys ( + engine, + ibus->extension_register_keys); + } } g_object_unref (ibus); @@ -2013,11 +2187,12 @@ bus_ibus_impl_registry_destroy (BusIBusImpl *ibus) g_list_free_full (ibus->components, g_object_unref); ibus->components = NULL; - g_hash_table_destroy (ibus->engine_table); - ibus->engine_table = NULL; + g_clear_pointer (&ibus->engine_table, g_hash_table_destroy); - ibus_object_destroy (IBUS_OBJECT (ibus->registry)); - ibus->registry = NULL; + g_clear_pointer (&ibus->registry, ibus_object_destroy); + + if (ibus->extension_register_keys) + g_clear_pointer (&ibus->extension_register_keys, g_variant_unref); } static gint diff --git a/bus/inputcontext.c b/bus/inputcontext.c index dfb98c36..bf9eafcf 100644 --- a/bus/inputcontext.c +++ b/bus/inputcontext.c @@ -94,6 +94,9 @@ struct _BusInputContext { /* content-type (primary purpose and hints) */ guint purpose; guint hints; + + BusPanelProxy *emoji_extension; + gboolean is_extension_lookup_table; }; struct _BusInputContextClass { @@ -162,16 +165,12 @@ static gboolean bus_input_context_service_set_property GError **error); static void bus_input_context_unset_engine (BusInputContext *context); -static void bus_input_context_update_preedit_text - (BusInputContext *context, - IBusText *text, - guint cursor_pos, - gboolean visible, - guint mode); static void bus_input_context_show_preedit_text - (BusInputContext *context); + (BusInputContext *context, + gboolean is_extension); static void bus_input_context_hide_preedit_text - (BusInputContext *context); + (BusInputContext *context, + gboolean is_extension); static void bus_input_context_update_auxiliary_text (BusInputContext *context, IBusText *text, @@ -180,10 +179,6 @@ static void bus_input_context_show_auxiliary_text (BusInputContext *context); static void bus_input_context_hide_auxiliary_text (BusInputContext *context); -static void bus_input_context_update_lookup_table - (BusInputContext *context, - IBusLookupTable *table, - gboolean visible); static void bus_input_context_show_lookup_table (BusInputContext *context); static void bus_input_context_hide_lookup_table @@ -605,10 +600,10 @@ bus_input_context_class_init (BusInputContextClass *class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - bus_marshal_VOID__VARIANT, + bus_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - G_TYPE_VARIANT); + IBUS_TYPE_EXTENSION_EVENT); text_empty = ibus_text_new_from_string (""); g_object_ref_sink (text_empty); @@ -760,28 +755,85 @@ bus_input_context_property_changed (BusInputContext *context, error); } + +/** + * _panel_process_key_event_cb: + * + * A GAsyncReadyCallback function to be called when + * bus_panel_proxy_process_key_event() is finished. + */ +static void +_panel_process_key_event_cb (GObject *source, + GAsyncResult *res, + GDBusMethodInvocation *invocation) +{ + GError *error = NULL; + GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source, + res, + &error); + if (value != NULL) { + g_dbus_method_invocation_return_value (invocation, value); + g_variant_unref (value); + } + else { + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } +} + +typedef struct _ProcessKeyEventData ProcessKeyEventData; +struct _ProcessKeyEventData { + GDBusMethodInvocation *invocation; + BusInputContext *context; + guint keyval; + guint keycode; + guint modifiers; +}; + /** * _ic_process_key_event_reply_cb: * - * A GAsyncReadyCallback function to be called when bus_engine_proxy_process_key_event() is finished. + * A GAsyncReadyCallback function to be called when + * bus_engine_proxy_process_key_event() is finished. */ static void _ic_process_key_event_reply_cb (GObject *source, GAsyncResult *res, - GDBusMethodInvocation *invocation) + ProcessKeyEventData *data) { + GDBusMethodInvocation *invocation = data->invocation; + BusInputContext *context = data->context; + guint keyval = data->keyval; + guint keycode = data->keycode; + guint modifiers = data->modifiers; GError *error = NULL; GVariant *value = g_dbus_proxy_call_finish ((GDBusProxy *)source, res, &error); + if (value != NULL) { - g_dbus_method_invocation_return_value (invocation, value); + gboolean retval = FALSE; + g_variant_get (value, "(b)", &retval); + if (context->emoji_extension && !retval) { + bus_panel_proxy_process_key_event (context->emoji_extension, + keyval, + keycode, + modifiers, + (GAsyncReadyCallback) + _panel_process_key_event_cb, + invocation); + } else { + g_dbus_method_invocation_return_value (invocation, value); + } g_variant_unref (value); } else { g_dbus_method_invocation_return_gerror (invocation, error); g_error_free (error); } + + g_object_unref (context); + g_slice_free (ProcessKeyEventData, data); } /** @@ -840,12 +892,19 @@ _ic_process_key_event (BusInputContext *context, /* ignore key events, if it is a fake input context */ if (context->has_focus && context->engine && context->fake == FALSE) { + ProcessKeyEventData *data = g_slice_new0 (ProcessKeyEventData); + data->invocation = invocation; + data->context = g_object_ref (context); + data->keyval = keyval; + data->keycode = keycode; + data->modifiers = modifiers; bus_engine_proxy_process_key_event (context->engine, keyval, keycode, modifiers, - (GAsyncReadyCallback) _ic_process_key_event_reply_cb, - invocation); + (GAsyncReadyCallback) + _ic_process_key_event_reply_cb, + data); } else { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", FALSE)); @@ -880,6 +939,13 @@ _ic_set_cursor_location (BusInputContext *context, context->y, context->w, context->h); + if (context->emoji_extension) { + bus_panel_proxy_set_cursor_location (context->emoji_extension, + context->x, + context->y, + context->w, + context->h); + } } } @@ -912,6 +978,14 @@ _ic_set_cursor_location_relative (BusInputContext *context, y, w, h); + if (context->emoji_extension) { + bus_panel_proxy_set_cursor_location_relative ( + context->emoji_extension, + x, + y, + w, + h); + } } } @@ -1394,7 +1468,7 @@ bus_input_context_clear_preedit_text (BusInputContext *context) /* always clear preedit text */ bus_input_context_update_preedit_text (context, - text_empty, 0, FALSE, IBUS_ENGINE_PREEDIT_CLEAR); + text_empty, 0, FALSE, IBUS_ENGINE_PREEDIT_CLEAR, TRUE); } void @@ -1407,7 +1481,10 @@ bus_input_context_focus_out (BusInputContext *context) bus_input_context_clear_preedit_text (context); bus_input_context_update_auxiliary_text (context, text_empty, FALSE); - bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE); + bus_input_context_update_lookup_table (context, + lookup_table_empty, + FALSE, + FALSE); bus_input_context_register_properties (context, props_empty); if (context->engine) { @@ -1427,7 +1504,12 @@ bus_input_context_focus_out (BusInputContext *context) { \ g_assert (BUS_IS_INPUT_CONTEXT (context)); \ \ - if (context->has_focus && context->engine) { \ + if (context->is_extension_lookup_table && \ + context->emoji_extension) { \ + bus_panel_proxy_##name##_lookup_table (context->emoji_extension); \ + return; \ + } \ + if (context->has_focus && context->engine) { \ bus_engine_proxy_##name (context->engine); \ } \ } @@ -1447,6 +1529,14 @@ bus_input_context_candidate_clicked (BusInputContext *context, { g_assert (BUS_IS_INPUT_CONTEXT (context)); + if (context->is_extension_lookup_table && context->emoji_extension) { + bus_panel_proxy_candidate_clicked_lookup_table ( + context->emoji_extension, + index, + button, + state); + return; + } if (context->engine) { bus_engine_proxy_candidate_clicked (context->engine, index, @@ -1467,61 +1557,33 @@ bus_input_context_property_activate (BusInputContext *context, } } -/** - * bus_input_context_update_preedit_text: - * - * Update a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. - */ -static void -bus_input_context_update_preedit_text (BusInputContext *context, - IBusText *text, - guint cursor_pos, - gboolean visible, - guint mode) -{ - g_assert (BUS_IS_INPUT_CONTEXT (context)); - - if (context->preedit_text) { - g_object_unref (context->preedit_text); - } - - context->preedit_text = (IBusText *) g_object_ref_sink (text ? text : text_empty); - context->preedit_cursor_pos = cursor_pos; - context->preedit_visible = visible; - context->preedit_mode = mode; - - if (PREEDIT_CONDITION) { - GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)context->preedit_text); - bus_input_context_emit_signal (context, - "UpdatePreeditText", - g_variant_new ("(vub)", variant, context->preedit_cursor_pos, context->preedit_visible), - NULL); - } - else { - g_signal_emit (context, - context_signals[UPDATE_PREEDIT_TEXT], - 0, - context->preedit_text, - context->preedit_cursor_pos, - context->preedit_visible); - } -} - /** * bus_input_context_show_preedit_text: * * Show a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. */ static void -bus_input_context_show_preedit_text (BusInputContext *context) +bus_input_context_show_preedit_text (BusInputContext *context, + gboolean is_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); - if (context->preedit_visible) { + if (context->preedit_visible) return; - } + if (!is_extension && context->emoji_extension) + return; + + if (!is_extension) + context->preedit_visible = TRUE; - context->preedit_visible = TRUE; + if (context->emoji_extension && !is_extension) { + /* Do not use HIDE_PREEDIT_TEXT signal below but call + * bus_panel_proxy_hide_preedit_text() directly for the extension only + * but not for the normal panel. + */ + bus_panel_proxy_show_preedit_text (context->emoji_extension); + return; + } if (PREEDIT_CONDITION) { bus_input_context_emit_signal (context, @@ -1542,15 +1604,25 @@ bus_input_context_show_preedit_text (BusInputContext *context) * Hide a preedit text. Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. */ static void -bus_input_context_hide_preedit_text (BusInputContext *context) +bus_input_context_hide_preedit_text (BusInputContext *context, + gboolean is_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); - if (!context->preedit_visible) { + if (!is_extension && !context->preedit_visible) return; - } - context->preedit_visible = FALSE; + if (!is_extension) + context->preedit_visible = FALSE; + + if (context->emoji_extension && !is_extension) { + /* Do not use HIDE_PREEDIT_TEXT signal below but call + * bus_panel_proxy_hide_preedit_text() directly for the extension only + * but not for the normal panel. + */ + bus_panel_proxy_hide_preedit_text (context->emoji_extension); + return; + } if (PREEDIT_CONDITION) { bus_input_context_emit_signal (context, @@ -1658,19 +1730,15 @@ bus_input_context_hide_auxiliary_text (BusInputContext *context) } } -/** - * bus_input_context_update_lookup_table: - * - * Update contents in the lookup table. - * Send D-Bus signal to update status of client or send glib signal to the panel, depending on capabilities of the client. - */ -static void +void bus_input_context_update_lookup_table (BusInputContext *context, IBusLookupTable *table, - gboolean visible) + gboolean visible, + gboolean is_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); + context->is_extension_lookup_table = is_extension; if (context->lookup_table) { g_object_unref (context->lookup_table); } @@ -2035,7 +2103,9 @@ _engine_update_preedit_text_cb (BusEngineProxy *engine, g_assert (context->engine == engine); - bus_input_context_update_preedit_text (context, text, cursor_pos, visible, mode); + bus_input_context_update_preedit_text (context, text, + cursor_pos, visible, mode, + TRUE); } /** @@ -2075,7 +2145,7 @@ _engine_update_lookup_table_cb (BusEngineProxy *engine, g_assert (context->engine == engine); - bus_input_context_update_lookup_table (context, table, visible); + bus_input_context_update_lookup_table (context, table, visible, FALSE); } /** @@ -2123,11 +2193,35 @@ _engine_update_property_cb (BusEngineProxy *engine, * from the engine object. */ static void -_engine_panel_extension_cb (BusEngineProxy *engine, - GVariant *parameters, - BusInputContext *context) +_engine_panel_extension_cb (BusEngineProxy *engine, + IBusExtensionEvent *event, + BusInputContext *context) { - g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, parameters); + g_signal_emit (context, context_signals[PANEL_EXTENSION], 0, event); +} + +static void +_engine_show_preedit_text_cb (BusEngineProxy *engine, + BusInputContext *context) +{ + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + g_assert (context->engine == engine); + + bus_input_context_show_preedit_text (context, FALSE); +} + +static void +_engine_hide_preedit_text_cb (BusEngineProxy *engine, + BusInputContext *context) +{ + g_assert (BUS_IS_ENGINE_PROXY (engine)); + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + g_assert (context->engine == engine); + + bus_input_context_hide_preedit_text (context, FALSE); } #define DEFINE_FUNCTION(name) \ @@ -2143,8 +2237,6 @@ _engine_panel_extension_cb (BusEngineProxy *engine, bus_input_context_##name (context); \ } -DEFINE_FUNCTION (show_preedit_text) -DEFINE_FUNCTION (hide_preedit_text) DEFINE_FUNCTION (show_auxiliary_text) DEFINE_FUNCTION (hide_auxiliary_text) DEFINE_FUNCTION (show_lookup_table) @@ -2239,7 +2331,10 @@ bus_input_context_disable (BusInputContext *context) bus_input_context_clear_preedit_text (context); bus_input_context_update_auxiliary_text (context, text_empty, FALSE); - bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE); + bus_input_context_update_lookup_table (context, + lookup_table_empty, + FALSE, + FALSE); bus_input_context_register_properties (context, props_empty); if (context->engine) { @@ -2283,7 +2378,10 @@ bus_input_context_unset_engine (BusInputContext *context) bus_input_context_clear_preedit_text (context); bus_input_context_update_auxiliary_text (context, text_empty, FALSE); - bus_input_context_update_lookup_table (context, lookup_table_empty, FALSE); + bus_input_context_update_lookup_table (context, + lookup_table_empty, + FALSE, + FALSE); bus_input_context_register_properties (context, props_empty); if (context->engine) { @@ -2639,17 +2737,128 @@ bus_input_context_set_content_type (BusInputContext *context, } void -bus_input_context_commit_text (BusInputContext *context, - IBusText *text) +bus_input_context_commit_text_use_extension (BusInputContext *context, + IBusText *text, + gboolean use_extension) { g_assert (BUS_IS_INPUT_CONTEXT (context)); if (text == text_empty || text == NULL) return; - GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text); - bus_input_context_emit_signal (context, - "CommitText", - g_variant_new ("(v)", variant), - NULL); + if (use_extension && context->emoji_extension) { + bus_panel_proxy_commit_text_received (context->emoji_extension, text); + } else { + GVariant *variant = ibus_serializable_serialize ( + (IBusSerializable *)text); + bus_input_context_emit_signal (context, + "CommitText", + g_variant_new ("(v)", variant), + NULL); + } +} + +void +bus_input_context_commit_text (BusInputContext *context, + IBusText *text) +{ + bus_input_context_commit_text_use_extension (context, text, TRUE); +} + +void +bus_input_context_update_preedit_text (BusInputContext *context, + IBusText *text, + guint cursor_pos, + gboolean visible, + guint mode, + gboolean use_extension) +{ + gboolean extension_visible = FALSE; + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + if (context->preedit_text) { + g_object_unref (context->preedit_text); + } + + context->preedit_text = (IBusText *) g_object_ref_sink (text ? text : + text_empty); + context->preedit_cursor_pos = cursor_pos; + if (use_extension) + context->preedit_visible = visible; + if (use_extension) + context->preedit_mode = mode; + extension_visible = context->preedit_visible | + (context->emoji_extension != NULL); + + if (use_extension && context->emoji_extension) { + bus_panel_proxy_update_preedit_text (context->emoji_extension, + context->preedit_text, + context->preedit_cursor_pos, + context->preedit_visible); + } else if (PREEDIT_CONDITION) { + GVariant *variant = ibus_serializable_serialize ( + (IBusSerializable *)context->preedit_text); + bus_input_context_emit_signal (context, + "UpdatePreeditText", + g_variant_new ( + "(vub)", + variant, + context->preedit_cursor_pos, + extension_visible), + NULL); + } else { + g_signal_emit (context, + context_signals[UPDATE_PREEDIT_TEXT], + 0, + context->preedit_text, + context->preedit_cursor_pos, + extension_visible); + } +} + +void +bus_input_context_set_emoji_extension (BusInputContext *context, + BusPanelProxy *emoji_extension) +{ + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + if (context->emoji_extension) + g_object_unref (context->emoji_extension); + context->emoji_extension = emoji_extension; + if (emoji_extension) { + g_object_ref (context->emoji_extension); + if (!context->connection) + return; + bus_input_context_show_preedit_text (context, TRUE); + bus_panel_proxy_set_cursor_location (context->emoji_extension, + context->x, + context->y, + context->w, + context->h); + } else { + if (!context->connection) + return; + /* https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/113 + * Cannot use bus_input_context_hide_preedit_text () yet. + */ + if (!context->preedit_visible) { + bus_input_context_update_preedit_text (context, + text_empty, + 0, + FALSE, + IBUS_ENGINE_PREEDIT_CLEAR, + FALSE); + } + } +} + +void +bus_input_context_panel_extension_received (BusInputContext *context, + IBusExtensionEvent *event) +{ + g_assert (BUS_IS_INPUT_CONTEXT (context)); + + if (!context->engine) + return; + bus_engine_proxy_panel_extension_received (context->engine, event); } diff --git a/bus/inputcontext.h b/bus/inputcontext.h index 7674abd8..a46d5c06 100644 --- a/bus/inputcontext.h +++ b/bus/inputcontext.h @@ -28,6 +28,11 @@ #include "connection.h" #include "factoryproxy.h" +#ifndef __BUS_PANEL_PROXY_DEFINED +#define __BUS_PANEL_PROXY_DEFINED +typedef struct _BusPanelProxy BusPanelProxy; +#endif + /* * Type macros. */ @@ -63,6 +68,7 @@ BusInputContext *bus_input_context_new (BusConnection *connection, /** * bus_input_context_focus_in: + * @context: A #BusInputContext. * * Give a focus to the context. Call FocusIn, Enable, SetCapabilities, * and SetCursorLocation methods of the engine for the context, @@ -73,6 +79,7 @@ void bus_input_context_focus_in (BusInputContext *context); /** * bus_input_context_focus_out: + * @context: A #BusInputContext. * * Remove a focus from the context. Call FocusOut method of the engine for * the context. @@ -83,6 +90,7 @@ void bus_input_context_focus_out /** * bus_input_context_has_focus: + * @context: A #BusInputContext. * @returns: context->has_focus. */ gboolean bus_input_context_has_focus @@ -90,6 +98,7 @@ gboolean bus_input_context_has_focus /** * bus_input_context_enable: + * @context: A #BusInputContext. * * Enable the current engine for the context. Request an engine (if needed), * call FocusIn, Enable, SetCapabilities, and SetCursorLocation methods @@ -100,6 +109,7 @@ void bus_input_context_enable (BusInputContext *context); /** * bus_input_context_disable: + * @context: A #BusInputContext. * * Disable the current engine for the context. Request an engine (if needed), * call FocusIn, Enable, SetCapabilities, and SetCursorLocation methods @@ -110,6 +120,7 @@ void bus_input_context_disable (BusInputContext *context); /** * bus_input_context_page_up: + * @context: A #BusInputContext. * * Call page_up method of the current engine proxy. */ @@ -117,6 +128,7 @@ void bus_input_context_page_up (BusInputContext *context); /** * bus_input_context_page_down: + * @context: A #BusInputContext. * * Call page_down method of the current engine proxy. */ @@ -125,6 +137,7 @@ void bus_input_context_page_down /** * bus_input_context_cursor_up: + * @context: A #BusInputContext. * * Call cursor_up method of the current engine proxy. */ @@ -133,6 +146,7 @@ void bus_input_context_cursor_up /** * bus_input_context_cursor_down: + * @context: A #BusInputContext. * * Call cursor_down method of the current engine proxy. */ @@ -141,6 +155,10 @@ void bus_input_context_cursor_down /** * bus_input_context_candidate_clicked: + * @context: A #BusInputContext. + * @index: An index. + * @button: A button number. + * @state: A button state. * * Call candidate_clicked method of the current engine proxy. */ @@ -152,6 +170,8 @@ void bus_input_context_candidate_clicked /** * bus_input_context_set_engine: + * @context: A #BusInputContext. + * @engine: A #BusEngineProxy. * * Use the engine on the context. */ @@ -161,12 +181,14 @@ void bus_input_context_set_engine /** * bus_input_context_set_engine_by_desc: + * @context: A #BusInputContext. * @desc: the engine to use on the context. * @timeout: timeout (in ms) for D-Bus calls. * @callback: a function to be called when bus_input_context_set_engine_by_desc * is finished. if NULL, the default callback * function, which just calls * bus_input_context_set_engine_by_desc_finish, is used. + * @user_data: an argument of @callback. * * Create a new BusEngineProxy object and use it on the context. */ @@ -181,6 +203,9 @@ void bus_input_context_set_engine_by_desc /** * bus_input_context_set_engine_by_desc_finish: + * @context: A #BusInputContext. + * @res: A #GAsyncResult. + * @error: A #GError. * * A function to be called by the GAsyncReadyCallback function for * bus_input_context_set_engine_by_desc. @@ -192,6 +217,7 @@ gboolean bus_input_context_set_engine_by_desc_finish /** * bus_input_context_get_engine: + * @context: A #BusInputContext. * * Get a BusEngineProxy object of the current engine. */ @@ -200,6 +226,7 @@ BusEngineProxy *bus_input_context_get_engine /** * bus_input_context_get_engine_desc: + * @context: A #BusInputContext. * * Get an IBusEngineDesc object of the current engine. */ @@ -208,6 +235,9 @@ IBusEngineDesc *bus_input_context_get_engine_desc /** * bus_input_context_property_activate: + * @context: A #BusInputContext. + * @prop_name: A property name. + * @prop_state: A property state. * * Call property_activate method of the current engine proxy. */ @@ -219,6 +249,7 @@ void bus_input_context_property_activate /** * bus_input_context_get_capabilities: + * @context: A #BusInputContext. * @returns: context->capabilities. */ guint bus_input_context_get_capabilities @@ -226,6 +257,8 @@ guint bus_input_context_get_capabilities /** * bus_input_context_set_capabilities: + * @context: A #BusInputContext. + * @capabilities: capabilities. * * Call set_capabilities method of the current engine proxy. */ @@ -236,6 +269,7 @@ void bus_input_context_set_capabilities /** * bus_input_context_get_client: + * @context: A #BusInputContext. * @returns: context->client. */ const gchar *bus_input_context_get_client @@ -243,6 +277,7 @@ const gchar *bus_input_context_get_client /** * bus_input_context_get_content_type: + * @context: A #BusInputContext. * @purpose: Input purpose. * @hints: Input hints. */ @@ -253,6 +288,7 @@ void bus_input_context_get_content_type /** * bus_input_context_set_content_type: + * @context: A #BusInputContext. * @purpose: Input purpose. * @hints: Input hints. */ @@ -263,11 +299,83 @@ void bus_input_context_set_content_type /** * bus_input_context_commit_text: - * @text: a commited text. + * @context: A #BusInputContext. + * @text: A committed text. */ void bus_input_context_commit_text (BusInputContext *context, IBusText *text); +/** + * bus_input_context_commit_text: + * @context: A #BusInputContext. + * @text: A committed text. + * @use_extension: Use an extension if it's %TRUE and the extension is + * available. + */ +void bus_input_context_commit_text_use_extension + (BusInputContext *context, + IBusText *text, + gboolean use_extension); + +/** + * bus_input_context_set_emoji_extension: + * @context: A #BusInputContext. + * @extension: A #BusPanelProxy. + */ +void bus_input_context_set_emoji_extension + (BusInputContext *context, + BusPanelProxy *extension); + +/** + * bus_input_context_update_preedit_text: + * @context: A #BusInputContext. + * @text: An #IBusText. + * @cursor_pos: The cursor position. + * @visible: %TRUE if the preedit is visible. Otherwise %FALSE. + * @mode: The preedit commit mode. + * @use_extension: %TRUE if preedit text is sent to the extesion at first. + * + * Update a preedit text. Send D-Bus signal to update status of client or + * send glib signal to the panel, depending on capabilities of the client. + */ +void bus_input_context_update_preedit_text + (BusInputContext *context, + IBusText *text, + guint cursor_pos, + gboolean visible, + guint mode, + gboolean + use_extension); + +/** + * bus_input_context_update_lookup_table: + * @context: A #BusInputContext. + * @table: An #IBusTable. + * @visible: %TRUE if the lookup table is visible. Otherwise %FALSE. + * @is_extension: %TRUE if the lookup table is created by panel extensions. + * + * Update contents in the lookup table. + * Send D-Bus signal to update status of client or send glib signal to the + * panel, depending on capabilities of the client. + */ +void bus_input_context_update_lookup_table + (BusInputContext *context, + IBusLookupTable *table, + gboolean visible, + gboolean + is_extension); + + +/** + * bus_input_context_panel_extension_received: + * @context: A #BusInputContext. + * @event: An #IBusExtensionEvent. + * + * Send An #IBusExtensionEvent callback from an extension. + */ +void bus_input_context_panel_extension_received + (BusInputContext *context, + IBusExtensionEvent *event); G_END_DECLS #endif diff --git a/bus/panelproxy.c b/bus/panelproxy.c index c3908fcf..1c0fcca2 100644 --- a/bus/panelproxy.c +++ b/bus/panelproxy.c @@ -52,6 +52,10 @@ enum { PROPERTY_HIDE, COMMIT_TEXT, PANEL_EXTENSION, + PANEL_EXTENSION_REGISTER_KEYS, + UPDATE_PREEDIT_TEXT_RECEIVED, + UPDATE_LOOKUP_TABLE_RECEIVED, + UPDATE_AUXILIARY_TEXT_RECEIVED, LAST_SIGNAL, }; @@ -125,8 +129,8 @@ bus_panel_proxy_new (BusConnection *connection, case PANEL_TYPE_PANEL: path = IBUS_PATH_PANEL; break; - case PANEL_TYPE_EXTENSION: - path = IBUS_PATH_PANEL_EXTENSION; + case PANEL_TYPE_EXTENSION_EMOJI: + path = IBUS_PATH_PANEL_EXTENSION_EMOJI; break; default: g_return_val_if_reached (NULL); @@ -253,6 +257,16 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class) panel_signals[PANEL_EXTENSION] = g_signal_new (I_("panel-extension"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + IBUS_TYPE_EXTENSION_EVENT); + + panel_signals[PANEL_EXTENSION_REGISTER_KEYS] = + g_signal_new (I_("panel-extension-register-keys"), G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, 0, @@ -260,6 +274,40 @@ bus_panel_proxy_class_init (BusPanelProxyClass *class) bus_marshal_VOID__VARIANT, G_TYPE_NONE, 1, G_TYPE_VARIANT); + + panel_signals[UPDATE_PREEDIT_TEXT_RECEIVED] = + g_signal_new (I_("update-preedit-text-received"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT_UINT_BOOLEAN, + G_TYPE_NONE, 3, + IBUS_TYPE_TEXT, + G_TYPE_UINT, + G_TYPE_BOOLEAN); + + panel_signals[UPDATE_LOOKUP_TABLE_RECEIVED] = + g_signal_new (I_("update-lookup-table-received"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, + IBUS_TYPE_LOOKUP_TABLE, + G_TYPE_BOOLEAN); + + panel_signals[UPDATE_AUXILIARY_TEXT_RECEIVED] = + g_signal_new (I_("update-auxiliary-text-received"), + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + bus_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, + IBUS_TYPE_TEXT, + G_TYPE_BOOLEAN); } static void @@ -355,23 +403,83 @@ bus_panel_proxy_g_signal (GDBusProxy *proxy, if (g_strcmp0 ("CommitText", signal_name) == 0) { GVariant *arg0 = NULL; - g_variant_get (parameters, "(v)", &arg0); - g_return_if_fail (arg0 != NULL); + g_variant_get (parameters, "(v)", &arg0); + g_return_if_fail (arg0); IBusText *text = IBUS_TEXT (ibus_serializable_deserialize (arg0)); g_variant_unref (arg0); - g_return_if_fail (text != NULL); + g_return_if_fail (text); g_signal_emit (panel, panel_signals[COMMIT_TEXT], 0, text); _g_object_unref_if_floating (text); return; } if (g_strcmp0 ("PanelExtension", signal_name) == 0) { - if (panel->panel_type != PANEL_TYPE_PANEL) { - g_warning ("Wrong signal"); - return; - } - g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, parameters); + GVariant *arg0 = NULL; + + g_variant_get (parameters, "(v)", &arg0); + g_return_if_fail (arg0); + IBusExtensionEvent *event = IBUS_EXTENSION_EVENT ( + ibus_serializable_deserialize (arg0)); + g_variant_unref (arg0); + g_return_if_fail (event); + g_signal_emit (panel, panel_signals[PANEL_EXTENSION], 0, event); + _g_object_unref_if_floating (event); + return; + } + + if (g_strcmp0 ("PanelExtensionRegisterKeys", signal_name) == 0) { + g_signal_emit (panel, panel_signals[PANEL_EXTENSION_REGISTER_KEYS], 0, + parameters); + return; + } + + if (g_strcmp0 ("UpdatePreeditTextReceived", signal_name) == 0) { + GVariant *variant = NULL; + guint cursor_pos = 0; + gboolean visible = FALSE; + IBusText *text = NULL; + + g_variant_get (parameters, "(vub)", &variant, &cursor_pos, &visible); + g_return_if_fail (variant); + text = (IBusText *) ibus_serializable_deserialize (variant); + g_variant_unref (variant); + g_return_if_fail (text); + g_signal_emit (panel, panel_signals[UPDATE_PREEDIT_TEXT_RECEIVED], 0, + text, cursor_pos, visible); + _g_object_unref_if_floating (text); + return; + } + + if (g_strcmp0 ("UpdateLookupTableReceived", signal_name) == 0) { + GVariant *variant = NULL; + gboolean visible = FALSE; + IBusLookupTable *table = NULL; + + g_variant_get (parameters, "(vb)", &variant, &visible); + g_return_if_fail (variant); + table = (IBusLookupTable *) ibus_serializable_deserialize (variant); + g_variant_unref (variant); + g_return_if_fail (table); + g_signal_emit (panel, panel_signals[UPDATE_LOOKUP_TABLE_RECEIVED], 0, + table, visible); + _g_object_unref_if_floating (table); + return; + } + + if (g_strcmp0 ("UpdateAuxiliaryTextReceived", signal_name) == 0) { + GVariant *variant = NULL; + gboolean visible = FALSE; + IBusText *text = NULL; + + g_variant_get (parameters, "(vb)", &variant, &visible); + g_return_if_fail (variant); + text = (IBusText *) ibus_serializable_deserialize (variant); + g_variant_unref (variant); + g_return_if_fail (text); + g_signal_emit (panel, panel_signals[UPDATE_AUXILIARY_TEXT_RECEIVED], 0, + text, visible); + _g_object_unref_if_floating (text); return; } @@ -552,12 +660,17 @@ static void bus_panel_proxy_commit_text (BusPanelProxy *panel, IBusText *text) { + gboolean use_extension = TRUE; g_assert (BUS_IS_PANEL_PROXY (panel)); g_assert (text != NULL); - if (panel->focused_context) { - bus_input_context_commit_text (panel->focused_context, text); - } + if (!panel->focused_context) + return; + if (panel->panel_type != PANEL_TYPE_PANEL) + use_extension = FALSE; + bus_input_context_commit_text_use_extension (panel->focused_context, + text, + use_extension); } #define DEFINE_FUNCTION(Name, name) \ @@ -877,3 +990,74 @@ bus_panel_proxy_get_panel_type (BusPanelProxy *panel) g_assert (BUS_IS_PANEL_PROXY (panel)); return panel->panel_type; } + +void +bus_panel_proxy_panel_extension_received (BusPanelProxy *panel, + IBusExtensionEvent *event) +{ + GVariant *data; + + g_assert (BUS_IS_PANEL_PROXY (panel)); + g_assert (event); + + data = ibus_serializable_serialize (IBUS_SERIALIZABLE (event)); + g_return_if_fail (data); + g_dbus_proxy_call ((GDBusProxy *)panel, + "PanelExtensionReceived", + g_variant_new ("(v)", data), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +void +bus_panel_proxy_process_key_event (BusPanelProxy *panel, + guint keyval, + guint keycode, + guint state, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_assert (BUS_IS_PANEL_PROXY (panel)); + + g_dbus_proxy_call ((GDBusProxy *)panel, + "ProcessKeyEvent", + g_variant_new ("(uuu)", keyval, keycode, state), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + callback, + user_data); +} + +void +bus_panel_proxy_commit_text_received (BusPanelProxy *panel, + IBusText *text) +{ + GVariant *variant; + + g_assert (BUS_IS_PANEL_PROXY (panel)); + g_assert (IBUS_IS_TEXT (text)); + + variant = ibus_serializable_serialize (IBUS_SERIALIZABLE (text)); + g_dbus_proxy_call ((GDBusProxy *)panel, + "CommitTextReceived", + g_variant_new ("(v)", variant), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} + +void +bus_panel_proxy_candidate_clicked_lookup_table (BusPanelProxy *panel, + guint index, + guint button, + guint state) +{ + gboolean use_extension = TRUE; + g_assert (BUS_IS_PANEL_PROXY (panel)); + + g_dbus_proxy_call ((GDBusProxy *)panel, + "CandidateClickedLookupTable", + g_variant_new ("(uuu)", index, button, state), + G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); +} diff --git a/bus/panelproxy.h b/bus/panelproxy.h index b5a7af17..4d8afb98 100644 --- a/bus/panelproxy.h +++ b/bus/panelproxy.h @@ -55,7 +55,7 @@ typedef enum { PANEL_TYPE_NONE, PANEL_TYPE_PANEL, - PANEL_TYPE_EXTENSION + PANEL_TYPE_EXTENSION_EMOJI } PanelType; typedef struct _BusPanelProxy BusPanelProxy; @@ -135,6 +135,27 @@ void bus_panel_proxy_set_content_type guint hints); PanelType bus_panel_proxy_get_panel_type (BusPanelProxy *panel); +void bus_panel_proxy_panel_extension_received + (BusPanelProxy *panel, + IBusExtensionEvent + *event); +void bus_panel_proxy_process_key_event + (BusPanelProxy *panel, + guint keyval, + guint keycode, + guint state, + GAsyncReadyCallback + callback, + gpointer user_data); +void bus_panel_proxy_commit_text_received + (BusPanelProxy *panel, + IBusText *text); +void bus_panel_proxy_candidate_clicked_lookup_table + (BusPanelProxy *panel, + guint index, + guint button, + guint state); + G_END_DECLS #endif diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in index 3c6b6f69..f4a019d0 100644 --- a/data/ibus.schemas.in +++ b/data/ibus.schemas.in @@ -353,6 +353,18 @@ Custom font name for language panel + + /schemas/desktop/ibus/panel/emoji/unicode-hotkey + /desktop/ibus/panel/emoji/unicode-hotkey + ibus + list + string + [<Control><Shift>u] + + Unicode shortcut keys for gtk_accelerator_parse + The shortcut keys for turning Unicode typing on or off + + /schemas/desktop/ibus/panel/emoji/hotkey /desktop/ibus/panel/emoji/hotkey diff --git a/setup/main.py b/setup/main.py index f0eee996..f6adb098 100644 --- a/setup/main.py +++ b/setup/main.py @@ -4,7 +4,7 @@ # ibus - The Input Bus # # Copyright (c) 2007-2016 Peng Huang -# Copyright (c) 2010-2017 Takao Fujiwara +# Copyright (c) 2010-2018 Takao Fujiwara # Copyright (c) 2007-2016 Red Hat, Inc. # # This library is free software; you can redistribute it and/or @@ -123,10 +123,15 @@ class Setup(object): name = 'emoji' label = 'emoji_dialog' self.__init_hotkey(name, label) + name = 'unicode' + label = 'unicode_dialog' + self.__init_hotkey(name, label) def __init_hotkey(self, name, label, comment=None): if name == 'emoji': shortcuts = self.__settings_emoji.get_strv('hotkey') + elif name == 'unicode': + shortcuts = self.__settings_emoji.get_strv('unicode-hotkey') else: shortcuts = self.__settings_hotkey.get_strv(name) button = self.__builder.get_object("button_%s" % label) @@ -139,6 +144,9 @@ class Setup(object): if name == 'emoji': button.connect("clicked", self.__shortcut_button_clicked_cb, 'hotkey', 'panel/' + name, label, entry) + elif name == 'unicode': + button.connect("clicked", self.__shortcut_button_clicked_cb, + 'unicode-hotkey', 'panel/emoji', label, entry) else: button.connect("clicked", self.__shortcut_button_clicked_cb, name, "general/hotkey", label, entry) diff --git a/setup/setup.ui b/setup/setup.ui index e64b1046..f1beb1de 100644 --- a/setup/setup.ui +++ b/setup/setup.ui @@ -870,9 +870,9 @@ True False - The shortcut keys for showing emoji dialog + The shortcut keys to enable conversions of emoji annotations or Unicode names start - Emoji choice: + Emoji annotation: 0 @@ -920,6 +920,60 @@ 0 + + + True + False + The shortcut keys to enable Unicode code point conversions + start + Unicode code point: + + + 0 + 1 + + + + + horizontal + True + False + 6 + true + + + True + True + False + + + True + True + 0 + + + + + ... + False + True + True + False + False + True + + + False + True + 1 + + + + + 1 + 1 + + diff --git a/src/ibusengine.c b/src/ibusengine.c index fd61102a..a3ccd7dd 100644 --- a/src/ibusengine.c +++ b/src/ibusengine.c @@ -64,8 +64,6 @@ enum { }; -typedef struct _IBusEngineKeybinding IBusEngineKeybinding; - /* IBusEnginePriv */ struct _IBusEnginePrivate { gchar *engine_name; @@ -81,14 +79,11 @@ struct _IBusEnginePrivate { guint content_purpose; guint content_hints; - GSettings *settings_emoji; - IBusEngineKeybinding **emoji_keybindings; + GHashTable *extension_keybindings; + gboolean enable_extension; + gchar *current_extension_name; }; -struct _IBusEngineKeybinding { - guint keyval; - IBusModifierType modifiers; -}; static guint engine_signals[LAST_SIGNAL] = { 0 }; @@ -191,10 +186,6 @@ static void ibus_engine_dbus_property_changed const gchar *property_name, GVariant *value); static void ibus_engine_keybinding_free (IBusEngine *engine); -static void settings_emoji_hotkey_changed_cb - (GSettings *settings, - const gchar *key, - gpointer data); G_DEFINE_TYPE (IBusEngine, ibus_engine, IBUS_TYPE_SERVICE) @@ -253,6 +244,12 @@ static const gchar introspection_xml[] = " " " " " " + " " + " " + " " + " " + " " + " " /* FIXME signals */ " " " " @@ -309,16 +306,22 @@ ibus_engine_class_init (IBusEngineClass *class) GObjectClass *gobject_class = G_OBJECT_CLASS (class); IBusObjectClass *ibus_object_class = IBUS_OBJECT_CLASS (class); - gobject_class->set_property = (GObjectSetPropertyFunc) ibus_engine_set_property; - gobject_class->get_property = (GObjectGetPropertyFunc) ibus_engine_get_property; + gobject_class->set_property = + (GObjectSetPropertyFunc) ibus_engine_set_property; + gobject_class->get_property = + (GObjectGetPropertyFunc) ibus_engine_get_property; ibus_object_class->destroy = (IBusObjectDestroyFunc) ibus_engine_destroy; - IBUS_SERVICE_CLASS (class)->service_method_call = ibus_engine_service_method_call; - IBUS_SERVICE_CLASS (class)->service_get_property = ibus_engine_service_get_property; - IBUS_SERVICE_CLASS (class)->service_set_property = ibus_engine_service_set_property; + IBUS_SERVICE_CLASS (class)->service_method_call = + ibus_engine_service_method_call; + IBUS_SERVICE_CLASS (class)->service_get_property = + ibus_engine_service_get_property; + IBUS_SERVICE_CLASS (class)->service_set_property = + ibus_engine_service_set_property; - ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), introspection_xml); + ibus_service_class_add_interfaces (IBUS_SERVICE_CLASS (class), + introspection_xml); class->process_key_event = ibus_engine_process_key_event; class->focus_in = ibus_engine_focus_in; @@ -839,26 +842,25 @@ ibus_engine_init (IBusEngine *engine) { IBusEnginePrivate *priv; engine->priv = priv = IBUS_ENGINE_GET_PRIVATE (engine); - priv->surrounding_text = g_object_ref_sink (text_empty); - priv->settings_emoji = - g_settings_new ("org.freedesktop.ibus.panel.emoji"); - settings_emoji_hotkey_changed_cb (priv->settings_emoji, "hotkey", engine); - g_signal_connect (priv->settings_emoji, "changed::hotkey", - G_CALLBACK (settings_emoji_hotkey_changed_cb), engine); + priv->extension_keybindings = g_hash_table_new_full ( + g_str_hash, + g_str_equal, + g_free, + g_free); } static void ibus_engine_destroy (IBusEngine *engine) { - g_free (engine->priv->engine_name); - engine->priv->engine_name = NULL; + IBusEnginePrivate *priv = engine->priv; - if (engine->priv->surrounding_text) { - g_object_unref (engine->priv->surrounding_text); - engine->priv->surrounding_text = NULL; - } - ibus_engine_keybinding_free (engine); + g_clear_pointer (&priv->engine_name, g_free); + g_clear_pointer (&priv->current_extension_name, g_free); + if (priv->surrounding_text) + g_clear_object (&priv->surrounding_text); + if (priv->extension_keybindings) + g_clear_pointer (&priv->extension_keybindings, g_hash_table_destroy); IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine)); } @@ -895,19 +897,38 @@ ibus_engine_get_property (IBusEngine *engine, } static void -ibus_engine_panel_extension (IBusEngine *engine) +ibus_engine_panel_extension (IBusEngine *engine, + const gchar *name) { - IBusXEvent *xevent = ibus_x_event_new ( - "event-type", IBUS_X_EVENT_KEY_PRESS, - "purpose", "emoji", + IBusEnginePrivate *priv; + IBusExtensionEvent *event; + GVariant *data; + + g_assert (IBUS_IS_ENGINE (engine)); + g_assert (name); + + priv = engine->priv; + if (!g_strcmp0 (name, priv->current_extension_name)) + priv->enable_extension = !priv->enable_extension; + else + priv->enable_extension = TRUE; + if (priv->enable_extension) { + g_free (priv->current_extension_name); + priv->current_extension_name = g_strdup (name); + } + event = ibus_extension_event_new ( + "name", name, + "is-enabled", priv->enable_extension, NULL); - GVariant *data = ibus_serializable_serialize_object ( - IBUS_SERIALIZABLE (xevent)); + g_assert (IBUS_IS_EXTENSION_EVENT (event)); + data = ibus_serializable_serialize_object ( + IBUS_SERIALIZABLE (event)); g_assert (data != NULL); ibus_engine_emit_signal (engine, "PanelExtension", g_variant_new ("(v)", data)); + g_object_unref (event); } static gboolean @@ -917,7 +938,8 @@ ibus_engine_filter_key_event (IBusEngine *engine, guint state) { IBusEnginePrivate *priv; - int i; + GList *names, *n; + IBusProcessKeyEventData *keys; guint modifiers; if ((state & IBUS_RELEASE_MASK) != 0) @@ -925,22 +947,29 @@ ibus_engine_filter_key_event (IBusEngine *engine, g_return_val_if_fail (IBUS_IS_ENGINE (engine), FALSE); priv = engine->priv; - if (!priv->emoji_keybindings) - return FALSE; - modifiers = state & IBUS_MODIFIER_FILTER; if (keyval >= IBUS_KEY_A && keyval <= IBUS_KEY_Z && (modifiers & IBUS_SHIFT_MASK) != 0) { keyval = keyval - IBUS_KEY_A + IBUS_KEY_a; } - for (i = 0; priv->emoji_keybindings[i]; i++) { - IBusEngineKeybinding *binding = priv->emoji_keybindings[i]; - if (binding->keyval == keyval && - binding->modifiers == modifiers) { - ibus_engine_panel_extension (engine); - return TRUE; + names = g_hash_table_get_keys (priv->extension_keybindings); + if (!names) + return FALSE; + for (n = names; n; n = n->next) { + const gchar *name = (const gchar *)n->data; + keys = g_hash_table_lookup (priv->extension_keybindings, name); + for (; keys; keys++) { + if (keys->keyval == 0 && keys->keycode == 0 && keys->state == 0) + break; + if (keys->keyval == keyval && + keys->state == modifiers && + (keys->keycode == 0 || keys->keycode == keycode)) { + ibus_engine_panel_extension (engine, name); + return TRUE; + } } } + g_list_free (names); return FALSE; } @@ -953,6 +982,97 @@ ibus_engine_service_authorized_method (IBusService *service, return FALSE; } +static void +ibus_engine_service_panel_extension_register_keys (IBusEngine *engine, + GVariant *parameters, + GDBusMethodInvocation + *invocation) +{ + IBusEnginePrivate *priv = engine->priv; + GVariant *v1 = NULL; + GVariant *v2 = NULL; + GVariant *v3 = NULL; + GVariant *vkeys = NULL; + GVariantIter *iter1 = NULL; + GVariantIter *iter2 = NULL; + const gchar *name = NULL; + guint failure_id = 0; + + g_variant_get (parameters, "(v)", &v1); + if (v1) + g_variant_get (v1, "(v)", &v2); + else + failure_id = 1; + if (v2) + g_variant_get (v2, "a{sv}", &iter1); + else + failure_id = 2; + if (iter1) { + while (g_variant_iter_loop (iter1, "{&sv}", &name, &vkeys)) { + if (vkeys) + g_variant_get (vkeys, "av", &iter2); + if (name && iter2) { + IBusProcessKeyEventData *keys = NULL; + gint num = 0; + while (g_variant_iter_loop (iter2, "v", &v3)) { + if (v3) { + guint keyval = 0; + guint keycode = 0; + guint state = 0; + g_variant_get (v3, "(iii)", + &keyval, &keycode, &state); + if (!keys) + keys = g_new0 (IBusProcessKeyEventData, 2); + else + keys = g_renew (IBusProcessKeyEventData, + keys, + num + 2); + keys[num].keyval = keyval; + keys[num].keycode = keycode; + keys[num].state = state; + keys[num + 1].keyval = 0; + keys[num + 1].keycode = 0; + keys[num + 1].state = 0; + g_clear_pointer (&v3, g_variant_unref); + num++; + } else { + failure_id = 5; + } + } + if (num > 0) { + g_hash_table_replace (priv->extension_keybindings, + g_strdup (name), + keys); + } else { + g_hash_table_remove (priv->extension_keybindings, name); + } + g_clear_pointer (&iter2, g_variant_iter_free); + } else { + failure_id = 4; + } + g_clear_pointer (&vkeys, g_variant_unref); + name = NULL; + } + g_variant_iter_free (iter1); + } else { + failure_id = 3; + } + if (failure_id == 0) { + g_dbus_method_invocation_return_value (invocation, NULL); + } else { + g_dbus_method_invocation_return_error ( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "PanelExtensionRegisterKeys method gives NULL: %d", + failure_id); + } + if (v2) + g_variant_unref (v2); + if (v1) + g_variant_unref (v1); +} + static void ibus_engine_service_method_call (IBusService *service, GDBusConnection *connection, @@ -964,6 +1084,7 @@ ibus_engine_service_method_call (IBusService *service, GDBusMethodInvocation *invocation) { IBusEngine *engine = IBUS_ENGINE (service); + IBusEnginePrivate *priv = engine->priv; if (g_strcmp0 (interface_name, IBUS_INTERFACE_ENGINE) != 0) { IBUS_SERVICE_CLASS (ibus_engine_parent_class)-> @@ -1002,6 +1123,33 @@ ibus_engine_service_method_call (IBusService *service, g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", retval)); return; } + if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) { + GVariant *arg0 = NULL; + IBusExtensionEvent *event = NULL; + + g_variant_get (parameters, "(v)", &arg0); + if (arg0) { + event = (IBusExtensionEvent *)ibus_serializable_deserialize_object ( + arg0); + } + if (!event) { + g_dbus_method_invocation_return_error ( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "PanelExtensionReceived method gives NULL"); + return; + } + priv->enable_extension = ibus_extension_event_is_enabled (event); + g_dbus_method_invocation_return_value (invocation, NULL); + return; + } + if (g_strcmp0 (method_name, "PanelExtensionRegisterKeys") == 0) { + ibus_engine_service_panel_extension_register_keys (engine, + parameters, + invocation); + return; + } static const struct { gchar *member; @@ -1441,73 +1589,10 @@ static void ibus_engine_keybinding_free (IBusEngine *engine) { IBusEnginePrivate *priv; - int i; g_return_if_fail (IBUS_IS_ENGINE (engine)); priv = engine->priv; - if (priv->emoji_keybindings) { - for (i = 0; priv->emoji_keybindings[i]; i++) - g_slice_free (IBusEngineKeybinding, priv->emoji_keybindings[i]); - g_clear_pointer (&priv->emoji_keybindings, g_free); - } -} - -static IBusEngineKeybinding * -ibus_engine_keybinding_new (IBusEngine *engine, - const gchar *accelerator) -{ - guint keyval = 0U; - IBusModifierType modifiers = 0; - IBusEngineKeybinding *binding = NULL; - - ibus_accelerator_parse (accelerator, &keyval, &modifiers); - if (keyval == 0U && modifiers == 0) { - g_warning ("Failed to parse shortcut key '%s'", accelerator); - return NULL; - } - if (modifiers & IBUS_SUPER_MASK) { - modifiers^=IBUS_SUPER_MASK; - modifiers|=IBUS_MOD4_MASK; - } - - binding = g_slice_new0 (IBusEngineKeybinding); - binding->keyval = keyval; - binding->modifiers = modifiers; - return binding; -} - -static void -settings_emoji_hotkey_changed_cb (GSettings *settings, - const gchar *key, - gpointer data) -{ - IBusEngine *engine; - IBusEnginePrivate *priv; - gchar **accelerators; - int i, j, length; - g_return_if_fail (IBUS_IS_ENGINE (data)); - engine = IBUS_ENGINE (data); - priv = engine->priv; - - if (g_strcmp0 (key, "hotkey") != 0) - return; - accelerators = g_settings_get_strv (settings, key); - length = g_strv_length (accelerators); - ibus_engine_keybinding_free (engine); - if (length == 0) { - g_strfreev (accelerators); - return; - } - priv->emoji_keybindings = g_new0 (IBusEngineKeybinding*, length + 1); - for (i = 0, j = 0; i < length; i++) { - IBusEngineKeybinding *binding = - ibus_engine_keybinding_new (engine, accelerators[i]); - if (!binding) - continue; - priv->emoji_keybindings[j++] = binding; - } - g_strfreev (accelerators); } IBusEngine * diff --git a/src/ibuspanelservice.c b/src/ibuspanelservice.c index f37b91c3..71028ebf 100644 --- a/src/ibuspanelservice.c +++ b/src/ibuspanelservice.c @@ -57,6 +57,9 @@ enum { DESTROY_CONTEXT, SET_CONTENT_TYPE, PANEL_EXTENSION_RECEIVED, + PROCESS_KEY_EVENT, + COMMIT_TEXT_RECEIVED, + CANDIDATE_CLICKED_LOOKUP_TABLE, LAST_SIGNAL, }; @@ -153,7 +156,7 @@ static void ibus_panel_service_set_content_type guint hints); static void ibus_panel_service_panel_extension_received (IBusPanelService *panel, - GVariant *data); + IBusExtensionEvent *event); G_DEFINE_TYPE (IBusPanelService, ibus_panel_service, IBUS_TYPE_SERVICE) @@ -184,6 +187,11 @@ static const gchar introspection_xml[] = " " " " " " + " " + " " + " " + " " + " " " " " " " " @@ -221,7 +229,16 @@ static const gchar introspection_xml[] = " " " " " " - " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " " " /* Signals */ " " @@ -247,7 +264,23 @@ static const gchar introspection_xml[] = " " " " " " + " " + " " + " " " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " " " " " ""; @@ -927,10 +960,81 @@ ibus_panel_service_class_init (IBusPanelServiceClass *class) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (IBusPanelServiceClass, panel_extension_received), NULL, NULL, - _ibus_marshal_VOID__VARIANT, + _ibus_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + IBUS_TYPE_EXTENSION_EVENT); + + /** + * IBusPanelService::process-key-event: + * @panel: An #IBusPanelService + * @keyval: Key symbol of the key press. + * @keycode: KeyCode of the key press. + * @state: Key modifier flags. + * + * Emitted when a key event is received. + * Implement the member function IBusPanelServiceClass::process_key_event + * in extended class to receive this signal. + * Both the key symbol and keycode are passed to the member function. + * See ibus_input_context_process_key_event() for further explanation of + * key symbol, keycode and which to use. + * + * Returns: %TRUE for successfully process the key; %FALSE otherwise. + * See also: ibus_input_context_process_key_event(). + * + * Argument @user_data is ignored in this function. + * + */ + panel_signals[PROCESS_KEY_EVENT] = + g_signal_new (I_("process-key-event"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IBusPanelServiceClass, process_key_event), + g_signal_accumulator_true_handled, NULL, + _ibus_marshal_BOOL__UINT_UINT_UINT, + G_TYPE_BOOLEAN, + 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); + + /** + * IBusPanelService::commit-text-received: + * @panel: An #IBusPanelService + * @text: A #IBusText + * + * Emitted when the client application get the ::commit-text-received. + * Implement the member function + * IBusPanelServiceClass::commit_text_received in extended class to + * receive this signal. + * + * Argument @user_data is ignored in this function. + * + */ + panel_signals[COMMIT_TEXT_RECEIVED] = + g_signal_new (I_("commit-text-received"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IBusPanelServiceClass, commit_text_received), + NULL, NULL, + _ibus_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - G_TYPE_VARIANT); + IBUS_TYPE_TEXT); + + panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE] = + g_signal_new (I_("candidate-clicked-lookup-table"), + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (IBusPanelServiceClass, + candidate_clicked_lookup_table), + NULL, NULL, + _ibus_marshal_VOID__UINT_UINT_UINT, + G_TYPE_NONE, + 3, + G_TYPE_UINT, + G_TYPE_UINT, + G_TYPE_UINT); } static void @@ -1129,9 +1233,14 @@ ibus_panel_service_service_method_call (IBusService *service, } if (g_strcmp0 (method_name, "PanelExtensionReceived") == 0) { - GVariant *variant = NULL; - g_variant_get (parameters, "(v)", &variant); - if (variant == NULL) { + GVariant *arg0 = NULL; + IBusExtensionEvent *event = NULL; + g_variant_get (parameters, "(v)", &arg0); + if (arg0) { + event = IBUS_EXTENSION_EVENT (ibus_serializable_deserialize (arg0)); + g_variant_unref (arg0); + } + if (!event) { g_dbus_method_invocation_return_error ( invocation, G_DBUS_ERROR, @@ -1140,11 +1249,63 @@ ibus_panel_service_service_method_call (IBusService *service, return; } g_signal_emit (panel, panel_signals[PANEL_EXTENSION_RECEIVED], 0, - variant); - g_variant_unref (variant); + event); + _g_object_unref_if_floating (event); g_dbus_method_invocation_return_value (invocation, NULL); return; } + if (g_strcmp0 (method_name, "ProcessKeyEvent") == 0) { + guint keyval, keycode, state; + gboolean retval = FALSE; + + g_variant_get (parameters, "(uuu)", &keyval, &keycode, &state); + g_signal_emit (panel, + panel_signals[PROCESS_KEY_EVENT], + 0, + keyval, + keycode, + state, + &retval); + g_dbus_method_invocation_return_value (invocation, + g_variant_new ("(b)", retval)); + return; + } + if (g_strcmp0 (method_name, "CommitTextReceived") == 0) { + GVariant *arg0 = NULL; + IBusText *text = NULL; + + g_variant_get (parameters, "(v)", &arg0); + if (arg0) { + text = (IBusText *) ibus_serializable_deserialize (arg0); + g_variant_unref (arg0); + } + if (!text) { + g_dbus_method_invocation_return_error ( + invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "CommitTextReceived method gives NULL"); + return; + } + g_signal_emit (panel, + panel_signals[COMMIT_TEXT_RECEIVED], + 0, + text); + _g_object_unref_if_floating (text); + return; + } + if (g_strcmp0 (method_name, "CandidateClickedLookupTable") == 0) { + guint index = 0; + guint button = 0; + guint state = 0; + g_variant_get (parameters, "(uuu)", &index, &button, &state); + g_signal_emit (panel, + panel_signals[CANDIDATE_CLICKED_LOOKUP_TABLE], + 0, + index, button, state); + return; + } + const static struct { const gchar *name; @@ -1318,8 +1479,8 @@ ibus_panel_service_set_content_type (IBusPanelService *panel, } static void -ibus_panel_service_panel_extension_received (IBusPanelService *panel, - GVariant *data) +ibus_panel_service_panel_extension_received (IBusPanelService *panel, + IBusExtensionEvent *event) { ibus_panel_service_not_implemented(panel); } @@ -1396,10 +1557,11 @@ void ibus_panel_service_commit_text (IBusPanelService *panel, IBusText *text) { + GVariant *variant; g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); g_return_if_fail (IBUS_IS_TEXT (text)); - GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text); + variant = ibus_serializable_serialize ((IBusSerializable *)text); ibus_service_emit_signal ((IBusService *) panel, NULL, IBUS_INTERFACE_PANEL, @@ -1413,18 +1575,144 @@ ibus_panel_service_commit_text (IBusPanelService *panel, } void -ibus_panel_service_panel_extension (IBusPanelService *panel, - GVariant *variant) +ibus_panel_service_panel_extension (IBusPanelService *panel, + IBusExtensionEvent *event) { + GVariant *variant; g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); - g_return_if_fail (variant); + g_return_if_fail (IBUS_IS_EXTENSION_EVENT (event)); + variant = ibus_serializable_serialize ((IBusSerializable *)event); ibus_service_emit_signal ((IBusService *) panel, NULL, IBUS_INTERFACE_PANEL, "PanelExtension", g_variant_new ("(v)", variant), NULL); + + if (g_object_is_floating (event)) { + g_object_unref (event); + } +} + +void +ibus_panel_service_panel_extension_register_keys (IBusPanelService *panel, + const gchar + *first_property_name, + ...) +{ + GVariantBuilder builder; + GVariantBuilder child; + const gchar *name; + va_list var_args; + IBusProcessKeyEventData *keys; + + g_return_if_fail (first_property_name); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + name = first_property_name; + + va_start (var_args, first_property_name); + do { + keys = va_arg (var_args, IBusProcessKeyEventData *); + g_return_if_fail (keys != NULL); + g_variant_builder_init (&child, G_VARIANT_TYPE ("av")); + for (; keys; keys++) { + if (keys->keyval == 0 && keys->keycode == 0 && keys->state == 0) + break; + g_variant_builder_add (&child, "v", + g_variant_new ("(iii)", + keys->keyval, + keys->keycode, + keys->state)); + } + g_variant_builder_add (&builder, "{sv}", + g_strdup (name), g_variant_builder_end (&child)); + } while ((name = va_arg (var_args, const gchar *))); + va_end (var_args); + + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "PanelExtensionRegisterKeys", + g_variant_new ("(v)", + g_variant_builder_end (&builder)), + NULL); +} + +void +ibus_panel_service_update_preedit_text_received (IBusPanelService *panel, + IBusText *text, + guint cursor_pos, + gboolean visible) +{ + GVariant *variant; + + g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); + g_return_if_fail (IBUS_IS_TEXT (text)); + + variant = ibus_serializable_serialize ((IBusSerializable *)text); + g_return_if_fail (variant); + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "UpdatePreeditTextReceived", + g_variant_new ("(vub)", + variant, cursor_pos, visible), + NULL); + + if (g_object_is_floating (text)) { + g_object_unref (text); + } +} + +void +ibus_panel_service_update_auxiliary_text_received (IBusPanelService *panel, + IBusText *text, + gboolean visible) +{ + GVariant *variant; + g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); + g_return_if_fail (IBUS_IS_TEXT (text)); + + variant = ibus_serializable_serialize ((IBusSerializable *)text); + g_return_if_fail (variant); + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "UpdateAuxiliaryTextReceived", + g_variant_new ("(vb)", + variant, visible), + NULL); + + if (g_object_is_floating (text)) { + g_object_unref (text); + } +} + +void +ibus_panel_service_update_lookup_table_received (IBusPanelService *panel, + IBusLookupTable *table, + gboolean visible) +{ + GVariant *variant; + + g_return_if_fail (IBUS_IS_PANEL_SERVICE (panel)); + g_return_if_fail (IBUS_IS_LOOKUP_TABLE (table)); + + variant = ibus_serializable_serialize ((IBusSerializable *)table); + g_return_if_fail (variant); + ibus_service_emit_signal ((IBusService *) panel, + NULL, + IBUS_INTERFACE_PANEL, + "UpdateLookupTableReceived", + g_variant_new ("(vb)", + variant, visible), + NULL); + + if (g_object_is_floating (table)) { + g_object_unref (table); + } } #define DEFINE_FUNC(name, Name) \ diff --git a/src/ibuspanelservice.h b/src/ibuspanelservice.h index 60ef842b..d91f2309 100644 --- a/src/ibuspanelservice.h +++ b/src/ibuspanelservice.h @@ -38,6 +38,7 @@ #include "ibuslookuptable.h" #include "ibusservice.h" #include "ibusproplist.h" +#include "ibusxevent.h" /* * Type macros. @@ -130,11 +131,24 @@ struct _IBusPanelServiceClass { gint h); void (* panel_extension_received) (IBusPanelService *panel, - GVariant *data); + IBusExtensionEvent *event); + gboolean (* process_key_event) + (IBusPanelService *panel, + guint keyval, + guint keycode, + guint state); + void (* commit_text_received) + (IBusPanelService *panel, + IBusText *text); + void (* candidate_clicked_lookup_table) + (IBusPanelService *panel, + guint index, + guint button, + guint state); /*< private >*/ /* padding */ - gpointer pdummy[5]; // We can add 8 pointers without breaking the ABI. + gpointer pdummy[2]; // We can add 8 pointers without breaking the ABI. }; GType ibus_panel_service_get_type (void); @@ -248,12 +262,105 @@ void ibus_panel_service_commit_text (IBusPanelService *panel, /** * ibus_panel_service_panel_extension: * @panel: An #IBusPanelService - * @data: (transfer full): A #GVariant data which is sent to a panel extension. + * @event: (transfer full): A #PanelExtensionEvent which is sent to a + * panel extension. * + * Enable or disable a panel extension with #IBusExtensionEvent. * Notify that a data is sent * by sending a "PanelExtension" message to IBus panel extension service. */ -void ibus_panel_service_panel_extension (IBusPanelService *panel, - GVariant *data); +void ibus_panel_service_panel_extension (IBusPanelService *panel, + IBusExtensionEvent *event); + +/** + * ibus_panel_service_panel_extension_register_keys: + * @panel: An #IBusPanelService + * @first_property_name: the first name of the shortcut keys. This is %NULL + " terminated. + * + * Register shortcut keys to enable panel extensions with #IBusExtensionEvent. + * Notify that a data is sent + * by sending a "PanelExtensionRegisterKeys" message to IBus panel extension + * service. Seems Vala does not support uint[][3] and use + * IBusProcessKeyEventData[]. E.g. + * IBusProcessKeyEventData[] keys = {{ + * IBUS_KEY_e, 0, IBUS_SHIFT_MASK | IBUS_SUPER_MASK }}; + * ibus_panel_service_panel_extension_register_keys(panel, "emoji", keys, NULL); + */ +void ibus_panel_service_panel_extension_register_keys + (IBusPanelService *panel, + const gchar *first_property_name, + ...); + +/** + * ibus_panel_service_update_preedit_text_received: + * @panel: An #IBusPanelService + * @text: Update content. + * @cursor_pos: Current position of cursor + * @visible: Whether the pre-edit buffer is visible. + * + * Notify that the preedit is updated by the panel extension + * + * (Note: The table object will be released, if it is floating. + * If caller want to keep the object, caller should make the object + * sink by g_object_ref_sink.) + */ +void ibus_panel_service_update_preedit_text_received + (IBusPanelService *panel, + IBusText *text, + guint cursor_pos, + gboolean visible); + +/** + * ibus_panel_service_show_preedit_text_received: + * @panel: An IBusPanelService + * + * Notify that the preedit is shown by the panel extension + */ +void ibus_panel_service_show_preedit_text_received + (IBusPanelService *panel); + +/** + * ibus_panel_service_hide_preedit_text_received: + * @panel: An IBusPanelService + * + * Notify that the preedit is hidden by the panel extension + */ +void ibus_panel_service_hide_preedit_text_received + (IBusPanelService *panel); + +/** + * ibus_panel_service_update_auxiliary_text_received: + * @panel: An #IBusPanelService + * @text: An #IBusText + * @visible: Whether the auxilirary text is visible. + * + * Notify that the auxilirary is updated by the panel extension. + * + * (Note: The table object will be released, if it is floating. + * If caller want to keep the object, caller should make the object + * sink by g_object_ref_sink.) + */ +void ibus_panel_service_update_auxiliary_text_received + (IBusPanelService *panel, + IBusText *text, + gboolean visible); + +/** + * ibus_panel_service_update_lookup_table_received: + * @panel: An #IBusPanelService + * @table: An #IBusLookupTable + * @visible: Whether the lookup table is visible. + * + * Notify that the lookup table is updated by the panel extension. + * + * (Note: The table object will be released, if it is floating. + * If caller want to keep the object, caller should make the object + * sink by g_object_ref_sink.) + */ +void ibus_panel_service_update_lookup_table_received + (IBusPanelService *panel, + IBusLookupTable *table, + gboolean visible); G_END_DECLS #endif diff --git a/src/ibusshare.h b/src/ibusshare.h index 757d915b..4f5a306b 100644 --- a/src/ibusshare.h +++ b/src/ibusshare.h @@ -73,6 +73,15 @@ */ #define IBUS_SERVICE_PANEL_EXTENSION "org.freedesktop.IBus.Panel.Extension" +/** + * IBUS_SERVICE_PANEL_EXTENSION_EMOJI: + * + * Address of IBus panel extension service for emoji. + * This service provides emoji, Unicode code point, Unicode name features. + */ +#define IBUS_SERVICE_PANEL_EXTENSION_EMOJI \ + "org.freedesktop.IBus.Panel.Extension.Emoji" + /** * IBUS_SERVICE_CONFIG: * @@ -109,11 +118,13 @@ #define IBUS_PATH_PANEL "/org/freedesktop/IBus/Panel" /** - * IBUS_PATH_PANEL_EXTENSION: + * IBUS_PATH_PANEL_EXTENSION_EMOJI: * - * D-Bus path for IBus panel. + * D-Bus path for IBus extension panel for emoji. + * This service provides emoji, Unicode code point, Unicode name features. */ -#define IBUS_PATH_PANEL_EXTENSION "/org/freedesktop/IBus/Panel/Extension" +#define IBUS_PATH_PANEL_EXTENSION_EMOJI \ + "/org/freedesktop/IBus/Panel/Extension/Emoji" /** * IBUS_PATH_CONFIG: diff --git a/src/ibusxevent.c b/src/ibusxevent.c index dea80272..287bb99b 100644 --- a/src/ibusxevent.c +++ b/src/ibusxevent.c @@ -22,13 +22,23 @@ #include "ibusinternal.h" #include "ibusxevent.h" +#define IBUS_EXTENSION_EVENT_VERSION 1 +#define IBUS_EXTENSION_EVENT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEventPrivate)) + #define IBUS_X_EVENT_VERSION 1 -#define IBUS_X_EVENT_GET_PRIVATE(o) \ +#define IBUS_X_EVENT_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_X_EVENT, IBusXEventPrivate)) enum { PROP_0, PROP_VERSION, + PROP_NAME, + PROP_IS_ENABLED, + PROP_IS_EXTENSION, + PROP_PARAMS, PROP_EVENT_TYPE, PROP_WINDOW, PROP_SEND_EVENT, @@ -52,6 +62,14 @@ enum { }; +struct _IBusExtensionEventPrivate { + guint version; + gchar *name; + gboolean is_enabled; + gboolean is_extension; + gchar *params; +}; + struct _IBusXEventPrivate { guint version; guint32 time; @@ -73,24 +91,346 @@ struct _IBusXEventPrivate { }; /* functions prototype */ -static void ibus_x_event_destroy (IBusXEvent *event); -static void ibus_x_event_set_property (IBusXEvent *event, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void ibus_x_event_get_property (IBusXEvent *event, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static gboolean ibus_x_event_serialize (IBusXEvent *event, - GVariantBuilder *builder); -static gint ibus_x_event_deserialize (IBusXEvent *event, - GVariant *variant); -static gboolean ibus_x_event_copy (IBusXEvent *dest, - const IBusXEvent *src); - +static void ibus_extension_event_destroy (IBusExtensionEvent *event); +static void ibus_extension_event_set_property (IBusExtensionEvent *event, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void ibus_extension_event_get_property (IBusExtensionEvent *event, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static gboolean ibus_extension_event_serialize (IBusExtensionEvent *event, + GVariantBuilder + *builder); +static gint ibus_extension_event_deserialize (IBusExtensionEvent *event, + GVariant + *variant); +static gboolean ibus_extension_event_copy (IBusExtensionEvent + *dest, + const IBusExtensionEvent + *src); +static void ibus_x_event_destroy (IBusXEvent *event); +static void ibus_x_event_set_property (IBusXEvent *event, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void ibus_x_event_get_property (IBusXEvent *event, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static gboolean ibus_x_event_serialize (IBusXEvent *event, + GVariantBuilder + *builder); +static gint ibus_x_event_deserialize (IBusXEvent *event, + GVariant + *variant); +static gboolean ibus_x_event_copy (IBusXEvent *dest, + const IBusXEvent *src); + +G_DEFINE_TYPE (IBusExtensionEvent, ibus_extension_event, IBUS_TYPE_SERIALIZABLE) G_DEFINE_TYPE (IBusXEvent, ibus_x_event, IBUS_TYPE_SERIALIZABLE) +static void +ibus_extension_event_class_init (IBusExtensionEventClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class); + IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class); + + gobject_class->set_property = + (GObjectSetPropertyFunc) ibus_extension_event_set_property; + gobject_class->get_property = + (GObjectGetPropertyFunc) ibus_extension_event_get_property; + + object_class->destroy = + (IBusObjectDestroyFunc) ibus_extension_event_destroy; + + serializable_class->serialize = + (IBusSerializableSerializeFunc) ibus_extension_event_serialize; + serializable_class->deserialize = + (IBusSerializableDeserializeFunc) ibus_extension_event_deserialize; + serializable_class->copy = + (IBusSerializableCopyFunc) ibus_extension_event_copy; + + /* install properties */ + /** + * IBusExtensionEvent:version: + * + * Version of the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_VERSION, + g_param_spec_uint ("version", + "version", + "version", + 0, + G_MAXUINT32, + IBUS_EXTENSION_EVENT_VERSION, + G_PARAM_READABLE)); + + /** + * IBusExtensionEvent:name: + * + * Name of the extension in the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "name", + "name of the extension", + "", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusExtensionEvent:is-enabled: + * + * %TRUE if the extension is enabled in the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_IS_ENABLED, + g_param_spec_boolean ("is-enabled", + "is enabled", + "if the extension is enabled", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusExtensionEvent:is-extension: + * + * %TRUE if the #IBusExtensionEvent is called by an extension. + * %FALSE if the #IBusExtensionEvent is called by an active engine or + * panel. + * If this value is %TRUE, the event is send to ibus-daemon, an active + * engine. If it's %FALSE, the event is sned to ibus-daemon, panels. + */ + g_object_class_install_property (gobject_class, + PROP_IS_EXTENSION, + g_param_spec_boolean ("is-extension", + "is extension", + "if the event is called by an extension", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + /** + * IBusExtensionEvent:params: + * + * Parameters to enable the extension in the #IBusExtensionEvent. + */ + g_object_class_install_property (gobject_class, + PROP_PARAMS, + g_param_spec_string ("params", + "params", + "Parameters to enable the extension", + "", + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_type_class_add_private (class, sizeof (IBusExtensionEventPrivate)); +} + +static void +ibus_extension_event_init (IBusExtensionEvent *event) +{ + event->priv = IBUS_EXTENSION_EVENT_GET_PRIVATE (event); + event->priv->version = IBUS_EXTENSION_EVENT_VERSION; +} + +static void +ibus_extension_event_destroy (IBusExtensionEvent *event) +{ + g_clear_pointer (&event->priv->name, g_free); + + IBUS_OBJECT_CLASS(ibus_extension_event_parent_class)-> + destroy (IBUS_OBJECT (event)); +} + +static void +ibus_extension_event_set_property (IBusExtensionEvent *event, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + IBusExtensionEventPrivate *priv = event->priv; + + switch (prop_id) { + case PROP_NAME: + g_free (priv->name); + priv->name = g_value_dup_string (value); + break; + case PROP_IS_ENABLED: + priv->is_enabled = g_value_get_boolean (value); + break; + case PROP_IS_EXTENSION: + priv->is_extension = g_value_get_boolean (value); + break; + case PROP_PARAMS: + priv->params = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec); + } +} + +static void +ibus_extension_event_get_property (IBusExtensionEvent *event, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + IBusExtensionEventPrivate *priv = event->priv; + switch (prop_id) { + case PROP_VERSION: + g_value_set_uint (value, priv->version); + break; + case PROP_NAME: + g_value_set_string (value, priv->name); + break; + case PROP_IS_ENABLED: + g_value_set_boolean (value, priv->is_enabled); + break; + case PROP_IS_EXTENSION: + g_value_set_boolean (value, priv->is_extension); + break; + case PROP_PARAMS: + g_value_set_string (value, priv->params); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (event, prop_id, pspec); + } +} + +static gboolean +ibus_extension_event_serialize (IBusExtensionEvent *event, + GVariantBuilder *builder) +{ + gboolean retval; + IBusExtensionEventPrivate *priv; + + retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)-> + serialize ((IBusSerializable *)event, builder); + g_return_val_if_fail (retval, FALSE); + /* End dict iter */ + + priv = event->priv; +#define NOTNULL(s) ((s) != NULL ? (s) : "") + /* If you will add a new property, you can append it at the end and + * you should not change the serialized order of name, longname, + * description, ... because the order is also used in other applications + * likes ibus-qt. */ + g_variant_builder_add (builder, "u", priv->version); + g_variant_builder_add (builder, "s", NOTNULL (priv->name)); + g_variant_builder_add (builder, "b", priv->is_enabled); + g_variant_builder_add (builder, "b", priv->is_extension); + g_variant_builder_add (builder, "s", NOTNULL (priv->params)); +#undef NOTNULL + + return TRUE; +} + +static gint +ibus_extension_event_deserialize (IBusExtensionEvent *event, + GVariant *variant) +{ + gint retval; + IBusExtensionEventPrivate *priv; + + retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)-> + deserialize ((IBusSerializable *)event, variant); + g_return_val_if_fail (retval, 0); + + priv = event->priv; + /* If you will add a new property, you can append it at the end and + * you should not change the serialized order of name, longname, + * description, ... because the order is also used in other applications + * likes ibus-qt. */ + g_variant_get_child (variant, retval++, "u", &priv->version); + ibus_g_variant_get_child_string (variant, retval++, + &priv->name); + g_variant_get_child (variant, retval++, "b", &priv->is_enabled); + g_variant_get_child (variant, retval++, "b", &priv->is_extension); + ibus_g_variant_get_child_string (variant, retval++, + &priv->params); + + return retval; +} + +static gboolean +ibus_extension_event_copy (IBusExtensionEvent *dest, + const IBusExtensionEvent *src) +{ + gboolean retval; + IBusExtensionEventPrivate *dest_priv = dest->priv; + IBusExtensionEventPrivate *src_priv = src->priv; + + retval = IBUS_SERIALIZABLE_CLASS (ibus_extension_event_parent_class)-> + copy ((IBusSerializable *)dest, (IBusSerializable *)src); + g_return_val_if_fail (retval, FALSE); + + dest_priv->version = src_priv->version; + dest_priv->name = g_strdup (src_priv->name); + dest_priv->is_enabled = src_priv->is_enabled; + dest_priv->is_extension = src_priv->is_extension; + dest_priv->params = g_strdup (src_priv->params); + return TRUE; +} + +IBusExtensionEvent * +ibus_extension_event_new (const gchar *first_property_name, + ...) +{ + va_list var_args; + IBusExtensionEvent *event; + + va_start (var_args, first_property_name); + event = (IBusExtensionEvent *) g_object_new_valist ( + IBUS_TYPE_EXTENSION_EVENT, + first_property_name, + var_args); + va_end (var_args); + g_assert (event->priv->version != 0); + return event; +} + +guint +ibus_extension_event_get_version (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), 0); + return event->priv->version; +} + +const gchar * +ibus_extension_event_get_name (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), ""); + return event->priv->name; +} + +gboolean +ibus_extension_event_is_enabled (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), FALSE); + return event->priv->is_enabled; +} + +gboolean +ibus_extension_event_is_extension (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), FALSE); + return event->priv->is_extension; +} + +const gchar * +ibus_extension_event_get_params (IBusExtensionEvent *event) +{ + g_return_val_if_fail (IBUS_IS_EXTENSION_EVENT (event), ""); + return event->priv->params; +} + + static void ibus_x_event_class_init (IBusXEventClass *class) { @@ -454,6 +794,7 @@ static void ibus_x_event_destroy (IBusXEvent *event) { g_clear_pointer (&event->priv->string, g_free); + g_clear_pointer (&event->priv->purpose, g_free); IBUS_OBJECT_CLASS(ibus_x_event_parent_class)->destroy (IBUS_OBJECT (event)); } diff --git a/src/ibusxevent.h b/src/ibusxevent.h index f35f14e4..d44cc8f4 100644 --- a/src/ibusxevent.h +++ b/src/ibusxevent.h @@ -29,8 +29,8 @@ /** * SECTION: ibusxevent - * @short_description: XEvent wrapper object - * @title: IBusXEvent + * @short_description: Extension Event wrapper object + * @title: IBusExtensionEvent * @stability: Unstable * * An IBusXEvent provides a wrapper of XEvent. @@ -45,25 +45,150 @@ */ /* define GOBJECT macros */ -#define IBUS_TYPE_X_EVENT \ +#define IBUS_TYPE_EXTENSION_EVENT \ + (ibus_extension_event_get_type ()) +#define IBUS_EXTENSION_EVENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEvent)) +#define IBUS_EXTENSION_EVENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEventClass)) +#define IBUS_IS_EXTENSION_EVENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_EXTENSION_EVENT)) +#define IBUS_IS_EXTENSION_EVENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_EXTENSION_EVENT)) +#define IBUS_EXTENSION_EVENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + IBUS_TYPE_EXTENSION_EVENT, \ + IBusExtensionEventClass)) + +#define IBUS_TYPE_X_EVENT \ (ibus_x_event_get_type ()) -#define IBUS_X_EVENT(obj) \ +#define IBUS_X_EVENT(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), IBUS_TYPE_X_EVENT, IBusXEvent)) -#define IBUS_X_EVENT_CLASS(klass) \ +#define IBUS_X_EVENT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), IBUS_TYPE_X_EVENT, IBusXEventClass)) -#define IBUS_IS_X_EVENT(obj) \ +#define IBUS_IS_X_EVENT(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IBUS_TYPE_X_EVENT)) -#define IBUS_IS_X_EVENT_CLASS(klass) \ +#define IBUS_IS_X_EVENT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), IBUS_TYPE_X_EVENT)) -#define IBUS_X_EVENT_GET_CLASS(obj) \ +#define IBUS_X_EVENT_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), IBUS_TYPE_X_EVENT, IBusXEventClass)) G_BEGIN_DECLS +typedef struct _IBusProcessKeyEventData IBusProcessKeyEventData; +typedef struct _IBusExtensionEvent IBusExtensionEvent; +typedef struct _IBusExtensionEventClass IBusExtensionEventClass; +typedef struct _IBusExtensionEventPrivate IBusExtensionEventPrivate; typedef struct _IBusXEvent IBusXEvent; typedef struct _IBusXEventClass IBusXEventClass; typedef struct _IBusXEventPrivate IBusXEventPrivate; +/** + * IBusProcessKeyEventData: + * + * IBuProcessKeyEventData properties. + */ +struct _IBusProcessKeyEventData { + /*< public >*/ + guint keyval; + guint keycode; + guint state; +}; + +/** + * IBusExtensionEvent: + * + * IBusExtensionEvent properties. + */ +struct _IBusExtensionEvent { + /*< private >*/ + IBusSerializable parent; + IBusExtensionEventPrivate *priv; + + /* instance members */ + /*< public >*/ +}; + +struct _IBusExtensionEventClass { + /*< private >*/ + IBusSerializableClass parent; + + /* class members */ + /*< public >*/ + + /*< private >*/ + /* padding */ + gpointer pdummy[10]; +}; + + +GType ibus_extension_event_get_type (void); + +/** + * ibus_extension_event_new: + * @first_property_name: Name of the first property. + * @...: the NULL-terminated arguments of the properties and values. + * + * Create a new #IBusExtensionEvent. + * + * Returns: A newly allocated #IBusExtensionEvent. E.g. + * ibus_extension_event_new ("name", "emoji", "is-enabled", TRUE, NULL); + */ +IBusExtensionEvent *ibus_extension_event_new (const gchar + *first_property_name, + ...); + +/** + * ibus_extension_event_get_version: + * @event: An #IBusExtensionEvent. + * + * Returns: Version of #IBusExtensionEvent + */ +guint ibus_extension_event_get_version (IBusExtensionEvent *event); + +/** + * ibus_extension_event_get_purpose: + * @event: An #IBusExtensionEvent. + * + * Returns: name of the extension for #IBusXEvent + */ +const gchar * ibus_extension_event_get_name (IBusExtensionEvent *event); + +/** + * ibus_extension_event_is_enabled: + * @event: An #IBusExtensionEvent. + * + * Returns: %TRUE if the extension is enabled for #IBusExtensionEvent + */ +gboolean ibus_extension_event_is_enabled (IBusExtensionEvent *event); + +/** + * ibus_extension_event_is_extension: + * @event: An #IBusExtensionEvent. + * + * Returns: %TRUE if the #IBusExtensionEvent is called by an extension. + * %FALSE if the #IBusExtensionEvent is called by an active engine or + * panel. + * If this value is %TRUE, the event is send to ibus-daemon, an active + * engine. If it's %FALSE, the event is sned to ibus-daemon, panels. + */ +gboolean ibus_extension_event_is_extension + (IBusExtensionEvent *event); + +/** + * ibus_extension_event_get_params: + * @event: An #IBusExtensionEvent. + * + * Returns: Parameters to enable the extension for #IBusXEvent + */ +const gchar * ibus_extension_event_get_params (IBusExtensionEvent *event); + + + typedef enum { IBUS_X_EVENT_NOTHING = -1, IBUS_X_EVENT_KEY_PRESS = 0, @@ -76,7 +201,7 @@ typedef enum { * IBusXEvent: * @type: event type * - * IBusEngine properties. + * IBusXEvent properties. */ struct _IBusXEvent { /*< private >*/ diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am index bf9f98d7..aaba7a4d 100644 --- a/ui/gtk3/Makefile.am +++ b/ui/gtk3/Makefile.am @@ -78,6 +78,7 @@ AM_VALAFLAGS = \ --pkg=ibus-1.0 \ --pkg=config \ --pkg=xi \ + --pkg=gdk-wayland \ --target-glib="$(VALA_TARGET_GLIB_VERSION)" \ $(NULL) @@ -176,6 +177,7 @@ ibus_ui_emojier_VALASOURCES = \ emojier.vala \ iconwidget.vala \ separator.vala \ + pango.vala \ $(NULL) ibus_ui_emojier_SOURCES = \ $(ibus_ui_emojier_VALASOURCES:.vala=.c) \ @@ -213,6 +215,7 @@ ibus_extension_gtk3_VALASOURCES = \ iconwidget.vala \ keybindingmanager.vala \ panelbinding.vala \ + pango.vala \ $(NULL) ibus_extension_gtk3_SOURCES = \ $(ibus_extension_gtk3_VALASOURCES:.vala=.c) \ diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala index 0c0865f1..cd98c9d7 100644 --- a/ui/gtk3/emojier.vala +++ b/ui/gtk3/emojier.vala @@ -226,43 +226,6 @@ public class IBusEmojier : Gtk.ApplicationWindow { } } } - private class ETitleLabelBox : Gtk.HeaderBar { - private Gtk.Label m_lang_label; - private Gtk.Label m_title_label; - - public ETitleLabelBox(string title) { - GLib.Object( - name : "IBusEmojierTitleLabelBox", - show_close_button: true, - decoration_layout: ":close", - title: title - ); - var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - set_custom_title(vbox); - m_title_label = new Gtk.Label(title); - m_title_label.get_style_context().add_class(Gtk.STYLE_CLASS_TITLE); - vbox.pack_start(m_title_label, true, false, 0); - m_lang_label = new Gtk.Label(null); - m_lang_label.get_style_context().add_class( - Gtk.STYLE_CLASS_SUBTITLE); - vbox.pack_start(m_lang_label, true, false, 0); - - var menu = new GLib.Menu(); - menu.append(_("Show emoji variants"), "win.variant"); - var menu_button = new Gtk.MenuButton(); - menu_button.set_direction(Gtk.ArrowType.NONE); - menu_button.set_valign(Gtk.Align.CENTER); - menu_button.set_menu_model(menu); - menu_button.set_tooltip_text(_("Menu")); - pack_end(menu_button); - } - public new void set_title(string title) { - m_title_label.set_text(title); - } - public void set_lang_label(string str) { - m_lang_label.set_text(str); - } - } private class LoadProgressObject : GLib.Object { public LoadProgressObject() { } @@ -275,6 +238,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { BACKWARD, } + public const uint BUTTON_CLOSE_BUTTON = 1000; + private const uint EMOJI_GRID_PAGE = 10; private const string EMOJI_CATEGORY_FAVORITES = N_("Favorites"); private const string EMOJI_CATEGORY_OTHERS = N_("Others"); @@ -313,11 +278,19 @@ public class IBusEmojier : Gtk.ApplicationWindow { private static bool m_show_unicode = false; private static LoadProgressObject m_unicode_progress_object; private static bool m_loaded_unicode = false; + private static string m_warning_message = ""; private ThemedRGBA m_rgba; private Gtk.Box m_vbox; - private ETitleLabelBox m_title; private EEntry m_entry; + /* If emojier is emoji category list or Unicode category list, + * m_annotation is "" and preedit is also "". + * If emojier is candidate mode, m_annotation is an annotation and + * get_current_candidate() returns the current emoji. + * But the current preedit can be "" in candidate mode in case that + * Unicode candidate window has U+0000. + */ + private string m_annotation = ""; private string? m_backward; private int m_backward_index = -1; private EScrolledWindow? m_scrolled_window = null; @@ -326,8 +299,20 @@ public class IBusEmojier : Gtk.ApplicationWindow { private string m_input_context_path = ""; private GLib.MainLoop? m_loop; private string? m_result; - private string? m_unicode_point = null; + /* If m_candidate_panel_is_visible is true, emojier is candidate mode and + * the emoji lookup window is visible. + * If m_candidate_panel_is_visible is false, the emoji lookup window is + * not visible but the mode is not clear. + */ private bool m_candidate_panel_is_visible; + /* If m_candidate_panel_mode is true, emojier is candidate mode and + * it does not depend on whether the window is visible or not. + * I.E. the first candidate does not show the lookup window and the + * second one shows the window. + * If m_candidate_panel_mode is false, emojier is emoji category list or + * Unicode category list. + */ + private bool m_candidate_panel_mode; private int m_category_active_index = -1; private IBus.LookupTable m_lookup_table; private Gtk.Label[] m_candidates; @@ -337,23 +322,18 @@ public class IBusEmojier : Gtk.ApplicationWindow { protected static double m_mouse_x; protected static double m_mouse_y; private Gtk.ProgressBar m_unicode_progress_bar; + private uint m_unicode_progress_id; private Gtk.Label m_unicode_percent_label; private double m_unicode_percent; + private Gdk.Rectangle m_cursor_location; + private bool m_is_up_side_down = false; + private uint m_redraw_window_id; public signal void candidate_clicked(uint index, uint button, uint state); public IBusEmojier() { GLib.Object( - type : Gtk.WindowType.TOPLEVEL, - events : Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | - Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON_RELEASE_MASK, - window_position : Gtk.WindowPosition.CENTER, - icon_name: "ibus-setup", - accept_focus : true, - resizable : true, - focus_visible : true + type : Gtk.WindowType.POPUP ); // GLib.ActionEntry accepts const variables only. @@ -363,6 +343,9 @@ public class IBusEmojier : Gtk.ApplicationWindow { new GLib.Variant.boolean(m_show_emoji_variant)); action.activate.connect(check_action_variant_cb); add_action(action); + action = new GLib.SimpleAction("close", null); + action.activate.connect(action_close_cb); + add_action(action); if (m_current_lang_id == null) m_current_lang_id = "en"; if (m_emoji_font_family == null) @@ -448,14 +431,12 @@ public class IBusEmojier : Gtk.ApplicationWindow { css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - m_title = new ETitleLabelBox(_("Emoji Choice")); - set_titlebar(m_title); m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); add(m_vbox); m_entry = new EEntry(); m_entry.set_placeholder_text(_("Type annotation or choose emoji")); - m_vbox.add(m_entry); + //m_vbox.add(m_entry); m_entry.changed.connect(() => { update_candidate_window(); }); @@ -480,10 +461,16 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_loop.quit(); }); + size_allocate.connect((w, a) => { + adjust_window_position(); + }); + candidate_clicked.connect((i, b, s) => { - candidate_panel_select_index(i); + if (m_input_context_path != "") + candidate_panel_select_index(i, b); }); + if (m_annotation_to_emojis_dict == null) { reload_emoji_dict(); } @@ -814,6 +801,12 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void remove_all_children() { + if (m_list_box != null) { + foreach (Gtk.Widget w in m_list_box.get_children()) { + w.destroy(); + } + m_list_box = null; + } foreach (Gtk.Widget w in m_vbox.get_children()) { if (w.name == "IBusEmojierEntry" || w.name == "IBusEmojierTitleLabelBox") { @@ -824,15 +817,40 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + private void clamp_page() { + Gtk.ListBoxRow row; + if (m_category_active_index >= 0) { + row = m_list_box.get_row_at_index(m_category_active_index); + m_list_box.select_row(row); + } else { + row = m_list_box.get_row_at_index(0); + } + Gtk.Allocation alloc = { 0, 0, 0, 0 }; + row.get_allocation(out alloc); + var adjustment = m_scrolled_window.get_vadjustment(); + adjustment.clamp_page(alloc.y, alloc.y + alloc.height); + return_val_if_fail(m_category_active_index >= 0, false); + m_lookup_table.set_cursor_pos((uint)m_category_active_index); + } + + private void show_category_list() { + // Do not call remove_all_children() to work adjustment.clamp_page() + // with PageUp/Down. + // After show_candidate_panel() is called, m_category_active_index + // is saved for Escape key but m_list_box is null by + // remove_all_children(). + if (m_category_active_index >= 0 && m_list_box != null) { + var row = m_list_box.get_row_at_index(m_category_active_index); + m_list_box.select_row(row); + return; + } + if (m_category_active_index < 0) + m_category_active_index = 0; remove_all_children(); m_scrolled_window = new EScrolledWindow(); set_fixed_size(); - m_title.set_title(_("Emoji Choice")); - string language = - IBus.get_language_name(m_current_lang_id); - m_title.set_lang_label(language); m_vbox.add(m_scrolled_window); Gtk.Viewport viewport = new Gtk.Viewport(null, null); m_scrolled_window.add(viewport); @@ -842,53 +860,21 @@ public class IBusEmojier : Gtk.ApplicationWindow { Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment(); m_list_box.set_adjustment(adjustment); m_list_box.row_activated.connect((box, gtkrow) => { - m_category_active_index = -1; + m_category_active_index = gtkrow.get_index(); EBoxRow row = gtkrow as EBoxRow; show_emoji_for_category(row.text); + show_all(); }); - uint n = 0; - if (m_favorites.length > 0) { - EBoxRow row = new EBoxRow(EMOJI_CATEGORY_FAVORITES); - EPaddedLabelBox widget = - new EPaddedLabelBox(_(EMOJI_CATEGORY_FAVORITES), - Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) - m_list_box.select_row(row); - } - GLib.List categories = - m_category_to_emojis_dict.get_keys(); - // FIXME: How to cast GLib.CompareFunc to strcmp? - categories.sort((a, b) => { - if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS) - return 1; - else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS) - return -1; - return GLib.strcmp(_(a), _(b)); - }); - foreach (unowned string category in categories) { - // "Others" category includes next unicode chars and fonts do not support - // the base and varints yet. - if (category == EMOJI_CATEGORY_OTHERS) - continue; + uint ncandidates = m_lookup_table.get_number_of_candidates(); + for (uint i = 0; i < ncandidates; i++) { + string category = m_lookup_table.get_candidate(i).text; EBoxRow row = new EBoxRow(category); EPaddedLabelBox widget = new EPaddedLabelBox(_(category), Gtk.Align.CENTER); row.add(widget); m_list_box.add(row); - if (n++ == m_category_active_index) - m_list_box.select_row(row); - } - if (m_unicode_block_list.length() > 0) { - EBoxRow row = new EBoxRow(EMOJI_CATEGORY_UNICODE); - EPaddedLabelBox widget = - new EPaddedLabelBox(_(EMOJI_CATEGORY_UNICODE), - Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) + if (i == m_category_active_index) m_list_box.select_row(row); } @@ -903,6 +889,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void show_emoji_for_category(string category) { if (category == EMOJI_CATEGORY_FAVORITES) { m_lookup_table.clear(); + m_candidate_panel_mode = true; foreach (unowned string favorate in m_favorites) { IBus.Text text = new IBus.Text.from_string(favorate); m_lookup_table.append_candidate(text); @@ -911,25 +898,26 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else if (category == EMOJI_CATEGORY_UNICODE) { m_category_active_index = -1; m_show_unicode = true; - show_unicode_blocks(); + update_unicode_blocks(); return; } else { unowned GLib.SList emojis = m_category_to_emojis_dict.lookup(category); m_lookup_table.clear(); + m_candidate_panel_mode = true; foreach (unowned string emoji in emojis) { IBus.Text text = new IBus.Text.from_string(emoji); m_lookup_table.append_candidate(text); } m_backward = category; } + m_annotation = m_lookup_table.get_candidate(0).text; // Restore the cursor position before the special table of // emoji variants is shown. if (m_backward_index >= 0) { m_lookup_table.set_cursor_pos((uint)m_backward_index); m_backward_index = -1; } - show_candidate_panel(); } @@ -940,18 +928,28 @@ public class IBusEmojier : Gtk.ApplicationWindow { IBus.Text text = new IBus.Text.from_string(emoji); m_lookup_table.append_candidate(text); } - show_candidate_panel(); } private void show_unicode_blocks() { + // Do not call remove_all_children() to work adjustment.clamp_page() + // with PageUp/Down. + // After show_candidate_panel() is called, m_category_active_index + // is saved for Escape key but m_list_box is null by + // remove_all_children(). + if (m_category_active_index >= 0 && m_list_box != null) { + var row = m_list_box.get_row_at_index(m_category_active_index); + m_list_box.select_row(row); + return; + } + if (m_category_active_index < 0) + m_category_active_index = 0; m_show_unicode = true; if (m_default_window_width == 0 && m_default_window_height == 0) get_size(out m_default_window_width, out m_default_window_height); remove_all_children(); set_fixed_size(); - m_title.set_title(_("Unicode Choice")); EPaddedLabelBox label = new EPaddedLabelBox(_("Bring back emoji choice"), Gtk.Align.CENTER, @@ -964,10 +962,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_category_active_index = -1; m_show_unicode = false; hide_candidate_panel(); + show_all(); return true; }); m_scrolled_window = new EScrolledWindow(); - m_title.set_lang_label(""); m_vbox.add(m_scrolled_window); Gtk.Viewport viewport = new Gtk.Viewport(null, null); m_scrolled_window.add(viewport); @@ -977,9 +975,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment(); m_list_box.set_adjustment(adjustment); m_list_box.row_activated.connect((box, gtkrow) => { - m_category_active_index = -1; + m_category_active_index = gtkrow.get_index(); EBoxRow row = gtkrow as EBoxRow; show_unicode_for_block(row.text); + show_candidate_panel(); }); uint n = 0; @@ -1007,44 +1006,18 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_list_box.unselect_all(); m_list_box.invalidate_filter(); m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE); + Gtk.ListBoxRow row = m_list_box.get_row_at_index((int)n - 1); + + // If clamp_page() would be called without the allocation signal, + // the jumping page could be failed when returns from + // show_unicode_for_block() with Escape key. + row.size_allocate.connect((w, a) => { + clamp_page(); + }); } + private void show_unicode_for_block(string block_name) { - if (!m_loaded_unicode) { - remove_all_children(); - set_fixed_size(); - m_unicode_progress_bar = new Gtk.ProgressBar(); - m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE); - m_unicode_progress_bar.set_halign(Gtk.Align.CENTER); - m_unicode_progress_bar.set_valign(Gtk.Align.CENTER); - m_vbox.add(m_unicode_progress_bar); - m_unicode_progress_bar.show(); - var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); - hbox.set_halign(Gtk.Align.CENTER); - hbox.set_valign(Gtk.Align.CENTER); - m_vbox.add(hbox); - var label = new Gtk.Label(_("Loading a Unicode dictionary:")); - hbox.pack_start(label, false, true, 0); - m_unicode_percent_label = new Gtk.Label(""); - hbox.pack_start(m_unicode_percent_label, false, true, 0); - hbox.show_all(); - - m_unicode_progress_object.deserialize_unicode.connect((i, n) => { - m_unicode_percent = (double)i / n; - }); - GLib.Timeout.add(100, () => { - m_unicode_progress_bar.set_fraction(m_unicode_percent); - m_unicode_percent_label.set_text( - "%.0f%%\n".printf(m_unicode_percent * 100)); - m_unicode_progress_bar.show(); - m_unicode_percent_label.show(); - if (m_loaded_unicode) { - show_unicode_for_block(block_name); - } - return !m_loaded_unicode; - }); - return; - } unichar start = 0; unichar end = 0; foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) { @@ -1055,6 +1028,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { } } m_lookup_table.clear(); + m_candidate_panel_mode = true; for (unichar ch = start; ch < end; ch++) { unowned IBus.UnicodeData? data = m_unicode_to_data_dict.lookup(ch); @@ -1064,7 +1038,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_lookup_table.append_candidate(text); } m_backward = block_name; - show_candidate_panel(); + m_annotation = m_lookup_table.get_candidate(0).text; } @@ -1091,6 +1065,41 @@ public class IBusEmojier : Gtk.ApplicationWindow { prev_button.set_relief(Gtk.ReliefStyle.NONE); prev_button.set_tooltip_text(_("Page Up")); + var menu = new GLib.Menu(); + menu.append(_("Show emoji variants"), "win.variant"); + menu.append(_("Close"), "win.close"); + var menu_button = new Gtk.MenuButton(); + menu_button.set_direction(Gtk.ArrowType.NONE); + menu_button.set_valign(Gtk.Align.CENTER); + menu_button.set_menu_model(menu); + menu_button.set_relief(Gtk.ReliefStyle.NONE); + menu_button.set_tooltip_text(_("Menu")); + + IBus.Text text = this.get_title_text(); + Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(text); + Gtk.Label title_label = new Gtk.Label(text.get_text()); + title_label.set_attributes(attrs); + + Gtk.Button? warning_button = null; + if (m_warning_message != "") { + warning_button = new Gtk.Button(); + warning_button.set_tooltip_text( + _("Click to view a warning message")); + warning_button.set_image(new Gtk.Image.from_icon_name( + "dialog-warning", + Gtk.IconSize.MENU)); + warning_button.set_relief(Gtk.ReliefStyle.NONE); + warning_button.clicked.connect(() => { + Gtk.Label warning_label = new Gtk.Label(m_warning_message); + warning_label.set_line_wrap(true); + warning_label.set_max_width_chars(40); + Gtk.Popover popover = new Gtk.Popover(warning_button); + popover.add(warning_label); + popover.show_all(); + popover.popup(); + }); + } + Gtk.Box buttons_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0); Gtk.Label state_label = new Gtk.Label(null); state_label.set_size_request(10, -1); @@ -1099,14 +1108,55 @@ public class IBusEmojier : Gtk.ApplicationWindow { buttons_hbox.pack_start(state_label, false, true, 0); buttons_hbox.pack_start(prev_button, false, false, 0); buttons_hbox.pack_start(next_button, false, false, 0); + buttons_hbox.pack_start(title_label, false, false, 0); + if (warning_button != null) + buttons_hbox.pack_start(warning_button, false, false, 0); + buttons_hbox.pack_end(menu_button, false, false, 0); m_vbox.pack_start(buttons_hbox, false, false, 0); buttons_hbox.show_all(); } - private bool check_unicode_point() { - string annotation = m_entry.get_text(); - m_unicode_point = null; + private void show_unicode_progress_bar() { + m_unicode_progress_bar = new Gtk.ProgressBar(); + m_unicode_progress_bar.set_ellipsize(Pango.EllipsizeMode.MIDDLE); + m_unicode_progress_bar.set_halign(Gtk.Align.CENTER); + m_unicode_progress_bar.set_valign(Gtk.Align.CENTER); + m_vbox.add(m_unicode_progress_bar); + m_unicode_progress_bar.show(); + var hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); + hbox.set_halign(Gtk.Align.CENTER); + hbox.set_valign(Gtk.Align.CENTER); + m_vbox.add(hbox); + var label = new Gtk.Label(_("Loading a Unicode dictionary:")); + hbox.pack_start(label, false, true, 0); + m_unicode_percent_label = new Gtk.Label(""); + hbox.pack_start(m_unicode_percent_label, false, true, 0); + hbox.show_all(); + + m_unicode_progress_object.deserialize_unicode.connect((i, n) => { + m_unicode_percent = (double)i / n; + }); + if (m_unicode_progress_id > 0) { + GLib.Source.remove(m_unicode_progress_id); + } + m_unicode_progress_id = GLib.Timeout.add(100, () => { + m_unicode_progress_id = 0; + m_unicode_progress_bar.set_fraction(m_unicode_percent); + m_unicode_percent_label.set_text( + "%.0f%%\n".printf(m_unicode_percent * 100)); + m_unicode_progress_bar.show(); + m_unicode_percent_label.show(); + if (m_loaded_unicode) { + show_candidate_panel(); + } + return !m_loaded_unicode; + }); + } + + + private static string? check_unicode_point(string annotation) { + string unicode_point = null; // Add "0x" because uint64.ascii_strtoull() is not accessible // and need to use uint64.parse() var buff = new GLib.StringBuilder("0x"); @@ -1114,33 +1164,31 @@ public class IBusEmojier : Gtk.ApplicationWindow { for (int i = 0; i < annotation.char_count(); i++) { unichar ch = annotation.get_char(i); if (ch == 0) - return false; + return null; if (ch.isspace()) { unichar code = (unichar)uint64.parse(buff.str); buff.assign("0x"); if (!code.validate()) - return false; + return null; retval.append(code.to_string()); continue; } if (!ch.isxdigit()) - return false; + return null; buff.append_unichar(ch); } unichar code = (unichar)uint64.parse(buff.str); if (!code.validate()) - return false; + return null; retval.append(code.to_string()); - m_unicode_point = retval.str; - if (m_unicode_point == null) - return true; - IBus.Text text = new IBus.Text.from_string(m_unicode_point); - m_lookup_table.append_candidate(text); - return true; + unicode_point = retval.str; + if (unicode_point == null) + return null; + return unicode_point; } - private GLib.SList? + private static GLib.SList? lookup_emojis_from_annotation(string annotation) { GLib.SList? total_emojis = null; unowned GLib.SList? sub_emojis = null; @@ -1221,19 +1269,19 @@ public class IBusEmojier : Gtk.ApplicationWindow { return total_emojis; } + private void update_candidate_window() { - string annotation = m_entry.get_text(); + string annotation = m_annotation; if (annotation.length == 0) { - hide_candidate_panel(); m_backward = null; return; } + m_lookup_table.clear(); + m_category_active_index = -1; if (annotation.length > m_emoji_max_seq_len) { - hide_candidate_panel(); return; } - // Call check_unicode_point() to get m_unicode_point - check_unicode_point(); + string? unicode_point = check_unicode_point(annotation); GLib.SList? total_emojis = lookup_emojis_from_annotation(annotation); if (total_emojis == null) { @@ -1246,18 +1294,75 @@ public class IBusEmojier : Gtk.ApplicationWindow { annotation = annotation.down(); total_emojis = lookup_emojis_from_annotation(annotation); } - if (total_emojis == null && m_unicode_point == null) { - hide_candidate_panel(); + if (total_emojis == null && unicode_point == null) { return; } - m_lookup_table.clear(); - // Call check_unicode_point() to update m_lookup_table - check_unicode_point(); + if (unicode_point != null) { + IBus.Text text = new IBus.Text.from_string(unicode_point); + m_lookup_table.append_candidate(text); + } foreach (unowned string emoji in total_emojis) { IBus.Text text = new IBus.Text.from_string(emoji); m_lookup_table.append_candidate(text); } - show_candidate_panel(); + m_candidate_panel_is_visible = + (m_lookup_table.get_number_of_candidates() > 0) ? true : false; + m_candidate_panel_mode = true; + } + + + private void update_category_list() { + // Always update m_lookup_table even if the contents are same + // because m_category_active_index needs to be kept after + // bring back this API from show_emoji_for_category(). + reset_window_mode(); + m_lookup_table.clear(); + IBus.Text text; + if (m_favorites.length > 0) { + text = new IBus.Text.from_string(EMOJI_CATEGORY_FAVORITES); + m_lookup_table.append_candidate(text); + } + GLib.List categories = + m_category_to_emojis_dict.get_keys(); + // FIXME: How to cast GLib.CompareFunc to strcmp? + categories.sort((a, b) => { + if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS) + return 1; + else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS) + return -1; + return GLib.strcmp(_(a), _(b)); + }); + foreach (unowned string category in categories) { + // "Others" category includes next unicode chars and fonts do not + // support the base and varints yet. + if (category == EMOJI_CATEGORY_OTHERS) + continue; + text = new IBus.Text.from_string(category); + m_lookup_table.append_candidate(text); + } + if (m_unicode_block_list.length() > 0) { + text = new IBus.Text.from_string(EMOJI_CATEGORY_UNICODE); + m_lookup_table.append_candidate(text); + } + // Do not set m_category_active_index to 0 here so that + // show_category_list() handles it. + } + + + private void update_unicode_blocks() { + // Always update m_lookup_table even if the contents are same + // because m_category_active_index needs to be kept after + // bring back this API from show_emoji_for_category(). + reset_window_mode(); + m_lookup_table.clear(); + m_show_unicode = true; + foreach (unowned IBus.UnicodeBlock block in m_unicode_block_list) { + string name = block.get_name(); + IBus.Text text = new IBus.Text.from_string(name); + m_lookup_table.append_candidate(text); + } + // Do not set m_category_active_index to 0 here so that + // show_unicode_blocks() handles it. } @@ -1283,27 +1388,27 @@ public class IBusEmojier : Gtk.ApplicationWindow { uint page_size = m_lookup_table.get_page_size(); uint ncandidates = m_lookup_table.get_number_of_candidates(); uint cursor = m_lookup_table.get_cursor_pos(); - uint page_start_pos = cursor / page_size * page_size; uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates); + Gtk.Button? backward_button = null; if (m_backward != null) { - string backward_desc = - "%s (%u / %u)".printf(_(m_backward), cursor, ncandidates - 1); + string backward_desc = _(m_backward); EPaddedLabelBox label = new EPaddedLabelBox(backward_desc, Gtk.Align.CENTER, TravelDirection.BACKWARD); - Gtk.Button button = new Gtk.Button(); - button.add(label); - m_vbox.add(button); - button.show_all(); - button.button_press_event.connect((w, e) => { + backward_button = new Gtk.Button(); + backward_button.add(label); + backward_button.button_press_event.connect((w, e) => { // Bring back to emoji candidate panel in case // m_show_emoji_variant is enabled and shows variants. - if (m_backward_index >= 0 && m_backward != null) + if (m_backward_index >= 0 && m_backward != null) { show_emoji_for_category(m_backward); - else + show_candidate_panel(); + } else { hide_candidate_panel(); + show_all(); + } return true; }); } @@ -1385,34 +1490,60 @@ public class IBusEmojier : Gtk.ApplicationWindow { } if (n > 0) { m_candidate_panel_is_visible = true; - show_arrow_buttons(); - m_vbox.add(grid); - grid.show_all(); - string text = m_lookup_table.get_candidate(cursor).text; - unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text); - if (data != null) { - show_emoji_description(data, text); - return; + if (!m_is_up_side_down) { + show_arrow_buttons(); + if (backward_button != null) { + m_vbox.add(backward_button); + backward_button.show_all(); + } + m_vbox.add(grid); + grid.show_all(); + show_description(); + if (!m_loaded_unicode) + show_unicode_progress_bar(); } - if (text.char_count() <= 1) { - unichar code = text.get_char(); - unowned IBus.UnicodeData? udata = - m_unicode_to_data_dict.lookup(code); - if (udata != null) { - show_unicode_description(udata, text); - return; + if (m_is_up_side_down) { + if (!m_loaded_unicode) + show_unicode_progress_bar(); + show_description(); + m_vbox.add(grid); + grid.show_all(); + if (backward_button != null) { + m_vbox.add(backward_button); + backward_button.show_all(); } + show_arrow_buttons(); } - EPaddedLabelBox widget = new EPaddedLabelBox( - _("Description: %s").printf(_("None")), - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); - show_code_point_description(text); } } + private void show_description() { + uint cursor = m_lookup_table.get_cursor_pos(); + string text = m_lookup_table.get_candidate(cursor).text; + unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text); + if (data != null) { + show_emoji_description(data, text); + return; + } + if (text.char_count() <= 1) { + unichar code = text.get_char(); + unowned IBus.UnicodeData? udata = + m_unicode_to_data_dict.lookup(code); + if (udata != null) { + show_unicode_description(udata, text); + return; + } + } + EPaddedLabelBox widget = new EPaddedLabelBox( + _("Description: %s").printf(_("None")), + Gtk.Align.START); + m_vbox.add(widget); + widget.show_all(); + show_code_point_description(text); + } + + private void show_emoji_description(IBus.EmojiData data, string text) { unowned string description = data.get_description(); @@ -1473,14 +1604,17 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void hide_candidate_panel() { + hide(); m_enter_notify_enable = true; - m_candidate_panel_is_visible = false; - if (m_loop.is_running()) { - if (m_show_unicode) - show_unicode_blocks(); - else - show_category_list(); - } + m_annotation = ""; + // Call remove_all_children() instead of show_category_list() + // so that show_category_list do not remove children with + // PageUp/PageDown. + remove_all_children(); + if (m_show_unicode) + update_unicode_blocks(); + else + update_category_list(); } @@ -1498,20 +1632,34 @@ public class IBusEmojier : Gtk.ApplicationWindow { } - private void candidate_panel_select_index(uint index) { + private void candidate_panel_select_index(uint index, + uint button) { + if (button == BUTTON_CLOSE_BUTTON) { + hide(); + if (m_candidate_panel_mode && + m_lookup_table.get_number_of_candidates() > 0) { + // Call remove_all_children() instead of show_category_list() + // so that show_category_list do not remove children with + // PageUp/PageDown. + remove_all_children(); + } + m_result = ""; + return; + } string text = m_lookup_table.get_candidate(index).text; unowned GLib.SList? emojis = m_emoji_to_emoji_variants_dict.lookup(text); if (m_show_emoji_variant && emojis != null && m_backward_index < 0) { show_emoji_variants(emojis); + show_all(); } else { m_result = text; - m_loop.quit(); - hide_candidate_panel(); + hide(); } } + private void candidate_panel_cursor_down() { enter_notify_disable_with_timer(); uint ncandidates = m_lookup_table.get_number_of_candidates(); @@ -1523,7 +1671,6 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else { m_lookup_table.set_cursor_pos(0); } - show_candidate_panel(); } @@ -1541,7 +1688,6 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else { m_lookup_table.set_cursor_pos(0); } - show_candidate_panel(); } @@ -1558,7 +1704,9 @@ public class IBusEmojier : Gtk.ApplicationWindow { return page_num; } + private bool category_list_cursor_move(uint keyval) { + return_val_if_fail (m_list_box != null, false); GLib.List list = m_list_box.get_children(); int length = (int)list.length(); if (length == 0) @@ -1600,32 +1748,37 @@ public class IBusEmojier : Gtk.ApplicationWindow { var row = m_list_box.get_selected_row(); if (row != null) m_list_box.unselect_row(row); - if (m_category_active_index >= 0) { - row = m_list_box.get_row_at_index(m_category_active_index); - m_list_box.select_row(row); - } else { - row = m_list_box.get_row_at_index(0); - } - Gtk.Allocation alloc = { 0, 0, 0, 0 }; - row.get_allocation(out alloc); - var adjustment = m_scrolled_window.get_vadjustment(); - adjustment.clamp_page(alloc.y, alloc.y + alloc.height); + clamp_page (); return true; } - private bool key_press_cursor_horizontal(uint keyval, - uint modifiers) { + public bool has_variants(uint index) { + if (index >= m_lookup_table.get_number_of_candidates()) + return false; + string text = m_lookup_table.get_candidate(index).text; + unowned GLib.SList? emojis = + m_emoji_to_emoji_variants_dict.lookup(text); + if (m_show_emoji_variant && emojis != null && + m_backward_index < 0) { + show_emoji_variants(emojis); + return true; + } + return false; + } + + + public bool key_press_cursor_horizontal(uint keyval, + uint modifiers) { assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right); - uint ncandidates = m_lookup_table.get_number_of_candidates(); - if (m_candidate_panel_is_visible && ncandidates > 1) { + if (m_candidate_panel_mode && + m_lookup_table.get_number_of_candidates() > 0) { enter_notify_disable_with_timer(); if (keyval == Gdk.Key.Left) m_lookup_table.cursor_up(); else if (keyval == Gdk.Key.Right) m_lookup_table.cursor_down(); - show_candidate_panel(); } else if (m_entry.get_text().length > 0) { int step = 0; if (keyval == Gdk.Key.Left) @@ -1650,8 +1803,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { } - private bool key_press_cursor_vertical(uint keyval, - uint modifiers) { + public bool key_press_cursor_vertical(uint keyval, + uint modifiers) { assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up || keyval == Gdk.Key.Page_Down || keyval == Gdk.Key.Page_Up); @@ -1661,8 +1814,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { else if (keyval == Gdk.Key.Up) keyval = Gdk.Key.Page_Up; } - uint ncandidates = m_lookup_table.get_number_of_candidates(); - if (m_candidate_panel_is_visible && ncandidates > 1) { + if ((m_candidate_panel_is_visible || m_annotation.length > 0) + && m_lookup_table.get_number_of_candidates() > 0) { switch (keyval) { case Gdk.Key.Down: candidate_panel_cursor_down(); @@ -1673,12 +1826,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { case Gdk.Key.Page_Down: enter_notify_disable_with_timer(); m_lookup_table.page_down(); - show_candidate_panel(); break; case Gdk.Key.Page_Up: enter_notify_disable_with_timer(); m_lookup_table.page_up(); - show_candidate_panel(); break; } } else { @@ -1688,19 +1839,18 @@ public class IBusEmojier : Gtk.ApplicationWindow { } - private bool key_press_cursor_home_end(uint keyval, - uint modifiers) { + public bool key_press_cursor_home_end(uint keyval, + uint modifiers) { assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End); uint ncandidates = m_lookup_table.get_number_of_candidates(); - if (m_candidate_panel_is_visible && ncandidates > 1) { + if (m_candidate_panel_mode && ncandidates > 0) { enter_notify_disable_with_timer(); if (keyval == Gdk.Key.Home) { m_lookup_table.set_cursor_pos(0); } else if (keyval == Gdk.Key.End) { m_lookup_table.set_cursor_pos(ncandidates - 1); } - show_candidate_panel(); return true; } if (m_entry.get_text().length > 0) { @@ -1717,44 +1867,41 @@ public class IBusEmojier : Gtk.ApplicationWindow { ? true : false); return true; } - if (!m_candidate_panel_is_visible) - return category_list_cursor_move(keyval); - return false; + return category_list_cursor_move(keyval); } - private bool key_press_escape() { + public bool key_press_escape() { if (m_show_unicode) { - if (m_candidate_panel_is_visible) { - m_candidate_panel_is_visible = false; - show_unicode_blocks(); - return true; - } else { + if (!m_candidate_panel_is_visible) { m_show_unicode = false; m_category_active_index = -1; - hide_candidate_panel(); - return true; } + hide_candidate_panel(); + return true; } else if (m_backward_index >= 0 && m_backward != null) { show_emoji_for_category(m_backward); return true; - } else if (m_candidate_panel_is_visible) { - hide_candidate_panel(); - return true; - } else if (m_entry.get_text().length == 0) { - m_loop.quit(); + } else if (m_candidate_panel_is_visible && m_backward != null) { hide_candidate_panel(); return true; } - m_entry.delete_text(0, -1); - return true; + hide(); + if (m_candidate_panel_mode && + m_lookup_table.get_number_of_candidates() > 0) { + // Call remove_all_children() instead of show_category_list() + // so that show_category_list do not remove children with + // PageUp/PageDown. + remove_all_children(); + } + return false; } - private bool key_press_enter() { + public bool key_press_enter() { if (m_candidate_panel_is_visible) { uint index = m_lookup_table.get_cursor_pos(); - candidate_panel_select_index(index); + return has_variants(index); } else if (m_category_active_index >= 0) { Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row(); EBoxRow row = gtkrow as EBoxRow; @@ -1789,13 +1936,111 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + private Gdk.Rectangle get_monitor_geometry() { + Gdk.Rectangle monitor_area = { 0, }; + + // Use get_monitor_geometry() instead of get_monitor_area(). + // get_monitor_area() excludes docks, but the lookup window should be + // shown over them. +#if VALA_0_34 + Gdk.Monitor monitor = Gdk.Display.get_default().get_monitor_at_point( + m_cursor_location.x, + m_cursor_location.y); + monitor_area = monitor.get_geometry(); +#else + Gdk.Screen screen = Gdk.Screen.get_default(); + int monitor_num = screen.get_monitor_at_point(m_cursor_location.x, + m_cursor_location.y); + screen.get_monitor_geometry(monitor_num, out monitor_area); +#endif + return monitor_area; + } + + + private void adjust_window_position() { + Gdk.Point cursor_right_bottom = { + m_cursor_location.x + m_cursor_location.width, + m_cursor_location.y + m_cursor_location.height + }; + + Gtk.Allocation allocation; + get_allocation(out allocation); + Gdk.Point window_right_bottom = { + cursor_right_bottom.x + allocation.width, + cursor_right_bottom.y + allocation.height + }; + + Gdk.Rectangle monitor_area = get_monitor_geometry(); + int monitor_right = monitor_area.x + monitor_area.width; + int monitor_bottom = monitor_area.y + monitor_area.height; + + int x, y; + if (window_right_bottom.x > monitor_right) + x = monitor_right - allocation.width; + else + x = cursor_right_bottom.x; + if (x < 0) + x = 0; + + bool changed = false; + if (window_right_bottom.y > monitor_bottom) { + y = m_cursor_location.y - allocation.height; + // Do not up side down in Wayland + if (m_input_context_path == "") { + changed = (m_is_up_side_down == false); + m_is_up_side_down = true; + } else { + changed = (m_is_up_side_down == true); + m_is_up_side_down = false; + } + } else { + y = cursor_right_bottom.y; + changed = (m_is_up_side_down == true); + m_is_up_side_down = false; + } + if (y < 0) + y = 0; + + move(x, y); + if (changed) { + if (m_redraw_window_id > 0) + GLib.Source.remove(m_redraw_window_id); + m_redraw_window_id = GLib.Timeout.add(100, () => { + m_redraw_window_id = 0; + this.show_all(); + return false; + }); + } + } + + +#if 0 + private void check_action_variant_cb(Gtk.MenuItem item) { + Gtk.CheckMenuItem check = item as Gtk.CheckMenuItem; + m_show_emoji_variant = check.get_active(); + // Redraw emoji candidate panel for m_show_emoji_variant + if (m_candidate_panel_is_visible) { + // DOTO: queue_draw() does not effect at the real time. + this.queue_draw(); + } + } +#else private void check_action_variant_cb(GLib.SimpleAction action, GLib.Variant? parameter) { m_show_emoji_variant = !action.get_state().get_boolean(); action.set_state(new GLib.Variant.boolean(m_show_emoji_variant)); // Redraw emoji candidate panel for m_show_emoji_variant - if (m_candidate_panel_is_visible) - show_candidate_panel(); + if (m_candidate_panel_is_visible) { + // DOTO: queue_draw() does not effect at the real time. + this.queue_draw(); + } + } +#endif + + + private void action_close_cb(GLib.SimpleAction action, + GLib.Variant? parameter) { + candidate_clicked(0, BUTTON_CLOSE_BUTTON, 0); } @@ -1842,6 +2087,123 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public void set_annotation(string annotation) { + m_annotation = annotation; + remove_all_children(); + if (annotation.length > 0) { + update_candidate_window(); + } else { + if (m_show_unicode) + update_unicode_blocks(); + else + update_category_list(); + } + } + + + public IBus.LookupTable get_one_dimension_lookup_table() { + var lookup_table = new IBus.LookupTable(EMOJI_GRID_PAGE, 0, true, true); + uint i = 0; + for (; i < m_lookup_table.get_number_of_candidates(); i++) { + IBus.Text text = new IBus.Text.from_string(""); + text.copy(m_lookup_table.get_candidate(i)); + lookup_table.append_candidate(text); + } + if (i > 0) + lookup_table.set_cursor_pos(m_lookup_table.get_cursor_pos()); + return lookup_table; + } + + + public uint get_number_of_candidates() { + return m_lookup_table.get_number_of_candidates(); + } + + + public uint get_cursor_pos() { + return m_lookup_table.get_cursor_pos(); + } + + + public void set_cursor_pos(uint cursor_pos) { + m_lookup_table.set_cursor_pos(cursor_pos); + } + + + public string get_current_candidate() { + // If category_list mode, do not show the category name on preedit. + // If candidate_panel mode, the first space key does not show the + // lookup table but the first candidate is avaiable on preedit. + if (!m_candidate_panel_mode) + return ""; + uint cursor = m_lookup_table.get_cursor_pos(); + return m_lookup_table.get_candidate(cursor).text; + } + + + public IBus.Text get_title_text() { + var language = _(IBus.get_language_name(m_current_lang_id)); + uint ncandidates = this.get_number_of_candidates(); + string main_title = _("Emoji Choice"); + if (m_show_unicode) + main_title = _("Unicode Choice"); + var text = new IBus.Text.from_string( + "%s (%s) (%u / %u)".printf( + main_title, + language, + this.get_cursor_pos() + 1, + ncandidates)); + int char_count = text.text.char_count(); + int start_index = -1; + for (int i = 0; i < char_count; i++) { + if (text.text.utf8_offset(i).has_prefix(language)) { + start_index = i; + break; + } + } + if (start_index >= 0) { + var attr = new IBus.Attribute( + IBus.AttrType.FOREGROUND, + 0x808080, + start_index, + start_index + language.char_count()); + var attrs = new IBus.AttrList(); + attrs.append(attr); + text.set_attributes(attrs); + } + return text; + } + + +#if 0 + public GLib.SList? get_candidates() { + if (m_annotation.length == 0) { + return null; + } + if (m_annotation.length > m_emoji_max_seq_len) { + return null; + } + string? unicode_point = check_unicode_point(m_annotation); + GLib.SList? total_emojis = + lookup_emojis_from_annotation(m_annotation); + if (total_emojis == null) { + /* Users can type title strings against lower case. + * E.g. "Smile" against "smile" + * But the dictionary has the case sensitive annotations. + * E.g. ":D" and ":q" + * So need to call lookup_emojis_from_annotation() twice. + */ + string lower_annotation = m_annotation.down(); + total_emojis = lookup_emojis_from_annotation(lower_annotation); + } + if (unicode_point != null) + total_emojis.prepend(unicode_point); + return total_emojis; + } +#endif + + +#if 0 public string run(string input_context_path, Gdk.Event event) { assert (m_loop == null); @@ -1915,12 +2277,34 @@ public class IBusEmojier : Gtk.ApplicationWindow { return m_result; } +#endif /* override virtual functions */ - public override void show() { - base.show(); - set_focus_visible(true); + public override void show_all() { + base.show_all(); + if (m_candidate_panel_mode) + show_candidate_panel(); + else if (m_show_unicode) + show_unicode_blocks(); + else + show_category_list(); + } + + + public override void hide() { + base.hide(); + m_candidate_panel_is_visible = false; + // m_candidate_panel_mode is not false in when you type something + // during enabling the candidate panel. + if (m_redraw_window_id > 0) { + GLib.Source.remove(m_redraw_window_id); + m_redraw_window_id = 0; + } + if (m_unicode_progress_id > 0) { + GLib.Source.remove(m_unicode_progress_id); + m_unicode_progress_id = 0; + } } @@ -1935,11 +2319,16 @@ public class IBusEmojier : Gtk.ApplicationWindow { switch (keyval) { case Gdk.Key.Escape: if (key_press_escape()) - return true; - break; + show_all(); + return true; case Gdk.Key.Return: case Gdk.Key.KP_Enter: - key_press_enter(); + if (key_press_enter()) { + show_all(); + } else { + m_result = get_current_candidate(); + hide(); + } return true; case Gdk.Key.BackSpace: if (m_entry.get_text().length > 0) { @@ -1977,42 +2366,49 @@ public class IBusEmojier : Gtk.ApplicationWindow { } else { category_list_cursor_move(Gdk.Key.Down); + show_all(); } return true; case Gdk.Key.Right: case Gdk.Key.KP_Right: key_press_cursor_horizontal(Gdk.Key.Right, modifiers); + show_all(); return true; case Gdk.Key.Left: case Gdk.Key.KP_Left: key_press_cursor_horizontal(Gdk.Key.Left, modifiers); + show_all(); return true; case Gdk.Key.Down: case Gdk.Key.KP_Down: key_press_cursor_vertical(Gdk.Key.Down, modifiers); + show_all(); return true; case Gdk.Key.Up: case Gdk.Key.KP_Up: key_press_cursor_vertical(Gdk.Key.Up, modifiers); + show_all(); return true; case Gdk.Key.Page_Down: case Gdk.Key.KP_Page_Down: key_press_cursor_vertical(Gdk.Key.Page_Down, modifiers); + show_all(); return true; case Gdk.Key.Page_Up: case Gdk.Key.KP_Page_Up: key_press_cursor_vertical(Gdk.Key.Page_Up, modifiers); + show_all(); return true; case Gdk.Key.Home: case Gdk.Key.KP_Home: - if (key_press_cursor_home_end(Gdk.Key.Home, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.Home, modifiers); + show_all(); + return true; case Gdk.Key.End: case Gdk.Key.KP_End: - if (key_press_cursor_home_end(Gdk.Key.End, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.End, modifiers); + show_all(); + return true; case Gdk.Key.Insert: case Gdk.Key.KP_Insert: GLib.Signal.emit_by_name(m_entry, "toggle-overwrite"); @@ -2023,26 +2419,30 @@ public class IBusEmojier : Gtk.ApplicationWindow { switch (keyval) { case Gdk.Key.f: key_press_cursor_horizontal(Gdk.Key.Right, modifiers); + show_all(); return true; case Gdk.Key.b: key_press_cursor_horizontal(Gdk.Key.Left, modifiers); + show_all(); return true; case Gdk.Key.n: case Gdk.Key.N: key_press_cursor_vertical(Gdk.Key.Down, modifiers); + show_all(); return true; case Gdk.Key.p: case Gdk.Key.P: key_press_cursor_vertical(Gdk.Key.Up, modifiers); + show_all(); return true; case Gdk.Key.h: - if (key_press_cursor_home_end(Gdk.Key.Home, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.Home, modifiers); + show_all(); + return true; case Gdk.Key.e: - if (key_press_cursor_home_end(Gdk.Key.End, modifiers)) - return true; - break; + key_press_cursor_home_end(Gdk.Key.End, modifiers); + show_all(); + return true; case Gdk.Key.u: if (m_entry.get_text().length > 0) { GLib.Signal.emit_by_name(m_entry, @@ -2103,14 +2503,41 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public void set_input_context_path(string input_context_path) { + m_input_context_path = input_context_path; + if (input_context_path == "") { + m_warning_message = _("" + + "Failed to get the current text application. " + + "Please re-focus your application. E.g. Press Esc key " + + "several times to release the emoji typing mode, " + + "click your desktop and click your text application again." + ); + } else { + m_warning_message = ""; + } + } + + public string get_selected_string() { return m_result; } + private void reset_window_mode() { + m_backward_index = -1; + m_backward = null; + m_candidate_panel_is_visible = false; + m_candidate_panel_mode = false; + // Do not clear m_lookup_table to work with space key later. + } + + public void reset() { + reset_window_mode(); m_input_context_path = ""; m_result = null; + m_category_active_index = -1; + m_show_unicode = false; } @@ -2145,6 +2572,23 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public void set_cursor_location(int x, + int y, + int width, + int height) { + Gdk.Rectangle location = Gdk.Rectangle(){ + x = x, y = y, width = width, height = height }; + if (m_cursor_location == location) + return; + m_cursor_location = location; + } + + + public bool is_candidate_panel_mode() { + return m_candidate_panel_mode; + } + + public static bool has_loaded_emoji_dict() { if (m_emoji_to_data_dict == null) return false; @@ -2165,6 +2609,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + public static string get_annotation_lang() { + return m_current_lang_id; + } + public static void set_emoji_font(string? emoji_font) { return_if_fail(emoji_font != null && emoji_font != ""); Pango.FontDescription font_desc = @@ -2182,18 +2630,21 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_has_partial_match = has_partial_match; } + public static void set_partial_match_length(int length) { if (length < 1) return; m_partial_match_length = length; } + public static void set_partial_match_condition(int condition) { if (condition < 0) return; m_partial_match_condition = condition; } + public static void set_favorites(string[]? unowned_favorites, string[]? unowned_favorite_annotations) { m_favorites = {}; diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala index 9506a945..787d448f 100644 --- a/ui/gtk3/emojierapp.vala +++ b/ui/gtk3/emojierapp.vala @@ -28,8 +28,9 @@ int partial_match_condition = -1; public class EmojiApplication : Gtk.Application { private IBusEmojier? m_emojier; - GLib.Settings m_settings_emoji = + private GLib.Settings m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji"); + private ApplicationCommandLine? m_command_line = null; private EmojiApplication() { @@ -40,25 +41,39 @@ public class EmojiApplication : Gtk.Application { private void show_dialog(ApplicationCommandLine command_line) { - m_emojier = new IBusEmojier(); - // For title handling in gnome-shell - add_window(m_emojier); - Gdk.Event event = Gtk.get_current_event(); - // Plasma and GNOME3 desktop returns null event - if (event == null) { - event = new Gdk.Event(Gdk.EventType.KEY_PRESS); - event.key.time = Gdk.CURRENT_TIME; - // event.get_seat() refers event.any.window - event.key.window = Gdk.get_default_root_window(); - event.key.window.ref(); + m_command_line = command_line; + m_emojier.reset(); + m_emojier.set_annotation(""); + m_emojier.show_all(); + } + + + public void candidate_clicked_lookup_table(uint index, + uint button, + uint state) { + if (m_command_line == null) + return; + if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) { + m_emojier.hide(); + m_command_line.print("%s\n", _("Canceled to choose an emoji.")); + m_command_line = null; + return; } - string emoji = m_emojier.run("", event); - remove_window(m_emojier); - if (emoji == null) { - m_emojier = null; - command_line.print("%s\n", _("Canceled to choose an emoji.")); + if (m_emojier == null) + return; + bool show_candidate = false; + uint ncandidates = m_emojier.get_number_of_candidates(); + if (ncandidates > 0 && ncandidates >= index) { + m_emojier.set_cursor_pos(index); + show_candidate = m_emojier.has_variants(index); + } else { + return; + } + if (show_candidate) { return; } + string emoji = m_emojier.get_current_candidate(); + m_emojier.hide(); Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); clipboard.set_text(emoji, -1); clipboard.store(); @@ -75,9 +90,8 @@ public class EmojiApplication : Gtk.Application { emojier_favorites += emoji; m_settings_emoji.set_strv("favorites", emojier_favorites); } - - m_emojier = null; - command_line.print("%s\n", _("Copied an emoji to your clipboard.")); + m_command_line.print("%s\n", _("Copied an emoji to your clipboard.")); + m_command_line = null; } @@ -88,7 +102,7 @@ public class EmojiApplication : Gtk.Application { } - private int _command_line (ApplicationCommandLine command_line) { + private int _command_line(ApplicationCommandLine command_line) { // Set default font size IBusEmojier.set_emoji_font(m_settings_emoji.get_string("font")); @@ -181,13 +195,22 @@ public class EmojiApplication : Gtk.Application { IBusEmojier.load_unicode_dict(); + if (m_emojier == null) { + m_emojier = new IBusEmojier(); + // For title handling in gnome-shell + add_window(m_emojier); + m_emojier.candidate_clicked.connect((i, b, s) => { + candidate_clicked_lookup_table(i, b, s); + }); + } + activate_dialog(command_line); return Posix.EXIT_SUCCESS; } - public override int command_line (ApplicationCommandLine command_line) { + public override int command_line(ApplicationCommandLine command_line) { // keep the application running until we are done with this commandline this.hold(); int result = _command_line(command_line); @@ -196,6 +219,13 @@ public class EmojiApplication : Gtk.Application { } + public override void shutdown() { + base.shutdown(); + remove_window(m_emojier); + m_emojier = null; + } + + public static int main (string[] args) { GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE, Config.GLIB_LOCALE_DIR); diff --git a/ui/gtk3/extension.vala b/ui/gtk3/extension.vala index 7d6d76e7..c729fd7e 100644 --- a/ui/gtk3/extension.vala +++ b/ui/gtk3/extension.vala @@ -50,20 +50,20 @@ class ExtensionGtk : Gtk.Application { "org.freedesktop.DBus", "NameAcquired", "/org/freedesktop/DBus", - IBus.SERVICE_PANEL_EXTENSION, + IBus.SERVICE_PANEL_EXTENSION_EMOJI, DBusSignalFlags.NONE, bus_name_acquired_cb); connection.signal_subscribe("org.freedesktop.DBus", "org.freedesktop.DBus", "NameLost", "/org/freedesktop/DBus", - IBus.SERVICE_PANEL_EXTENSION, + IBus.SERVICE_PANEL_EXTENSION_EMOJI, DBusSignalFlags.NONE, bus_name_lost_cb); var flags = IBus.BusNameFlag.ALLOW_REPLACEMENT | IBus.BusNameFlag.REPLACE_EXISTING; - m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION, flags); + m_bus.request_name(IBus.SERVICE_PANEL_EXTENSION_EMOJI, flags); } diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala index d9238c89..4c3b00ca 100644 --- a/ui/gtk3/panel.vala +++ b/ui/gtk3/panel.vala @@ -1148,26 +1148,15 @@ class Panel : IBus.PanelService { #if EMOJI_DICT item = new Gtk.MenuItem.with_label(_("Emoji Choice")); item.activate.connect((i) => { - Gdk.Event event = Gtk.get_current_event(); - if (event == null) { - event = new Gdk.Event(Gdk.EventType.KEY_PRESS); - event.key.time = Gdk.CURRENT_TIME; - // event.get_seat() refers event.any.window - event.key.window = Gdk.get_default_root_window(); - event.key.window.ref(); - } - IBus.XEvent xevent = new IBus.XEvent( - "event-type", IBus.XEventType.KEY_PRESS, - "window", - (event.key.window as Gdk.X11.Window).get_xid(), - "time", event.key.time, - "purpose", "emoji"); - /* new GLib.Variant("(sv)", "emoji", xevent.serialize_object()) + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", "emoji", "is-enabled", true, + "params", "category-list"); + /* new GLib.Variant("(sv)", "emoji", event.serialize_object()) * will call g_variant_unref() for the child variant by vala. * I have no idea not to unref the object so integrated - * the purpose to IBus.XEvent above. + * the purpose to IBus.ExtensionEvent above. */ - panel_extension(xevent.serialize_object()); + panel_extension(event); }); m_sys_menu.append(item); #endif diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala index 581f721e..52b78c17 100644 --- a/ui/gtk3/panelbinding.vala +++ b/ui/gtk3/panelbinding.vala @@ -21,7 +21,193 @@ * USA */ +class Preedit : Gtk.Window { + private Gtk.Label m_extension_preedit_text; + private Gtk.Label m_extension_preedit_emoji; + private IBus.Text? m_engine_preedit_text; + private bool m_engine_preedit_text_show; + private uint m_engine_preedit_cursor_pos; + private string m_prefix = "@"; + private bool m_is_shown = true; + + + public Preedit() { + GLib.Object( + name : "IBusPreedit", + type: Gtk.WindowType.POPUP + ); + m_extension_preedit_text = new Gtk.Label(""); + m_extension_preedit_emoji = new Gtk.Label(""); + } + + + public new void hide() { + reset(); + base.hide(); + m_is_shown = false; + } + + + public bool is_shown() { + return m_is_shown; + } + + + public void reset() { + set_emoji(""); + set_text(""); + resize(1, 1); + m_is_shown = true; + } + + public void append_text(string text) { + if (text.length == 0) + return; + string total = m_extension_preedit_text.get_text(); + total += text; + m_extension_preedit_text.set_text(total); + } + + + public string get_text() { + return m_extension_preedit_text.get_text(); + } + + + public void set_text(string text) { + m_extension_preedit_text.set_text(text); + } + + + public string get_emoji() { + return m_extension_preedit_emoji.get_text(); + } + + + public void set_emoji(string text) { + m_extension_preedit_emoji.set_text(text); + } + + + public bool backspace() { + string total = m_extension_preedit_emoji.get_text(); + if (total.length > 0) { + m_extension_preedit_emoji.set_text(""); + resize(1, 1); + return false; + } + total = m_extension_preedit_text.get_text(); + int char_count = total.char_count(); + if (char_count == 0) + return true; + total = total[0:total.index_of_nth_char(char_count - 1)]; + resize(1, 1); + m_extension_preedit_text.set_text(total); + if (total.length == 0) + resize(1, 1); + return true; + } + + + private string get_extension_text () { + string extension_text = m_extension_preedit_emoji.get_text(); + if (extension_text.length == 0) + extension_text = m_extension_preedit_text.get_text(); + return m_prefix + extension_text; + } + + + private void set_preedit_color(IBus.Text text, + uint start_index, + uint end_index) { + text.append_attribute(IBus.AttrType.UNDERLINE, + IBus.AttrUnderline.SINGLE, + start_index, (int)end_index); + } + + + public IBus.Text get_engine_preedit_text() { + string extension_text = get_extension_text(); + uint char_count = extension_text.char_count(); + IBus.Text retval; + if (m_engine_preedit_text == null || !m_engine_preedit_text_show) { + retval = new IBus.Text.from_string(extension_text); + set_preedit_color(retval, 0, char_count); + return retval; + } + retval = new IBus.Text.from_string( + extension_text + m_engine_preedit_text.get_text()); + set_preedit_color(retval, 0, char_count); + + unowned IBus.AttrList attrs = m_engine_preedit_text.get_attributes(); + + if (attrs == null) + return retval; + + int i = 0; + while (true) { + IBus.Attribute attr = attrs.get(i++); + if (attr == null) + break; + long start_index = attr.start_index; + long end_index = attr.end_index; + if (start_index < 0) + start_index = 0; + if (end_index < 0) + end_index = m_engine_preedit_text.get_length(); + retval.append_attribute(attr.type, attr.value, + char_count + (uint)start_index, + (int)char_count + (int)end_index); + } + return retval; + } + + + public void set_engine_preedit_text(IBus.Text? text) { + m_engine_preedit_text = text; + } + + + public void show_engine_preedit_text() { + m_engine_preedit_text_show = true; + } + + + public void hide_engine_preedit_text() { + m_engine_preedit_text_show = false; + } + + + public uint get_engine_preedit_cursor_pos() { + return get_extension_text().char_count() + m_engine_preedit_cursor_pos; + } + + + public void set_engine_preedit_cursor_pos(uint cursor_pos) { + m_engine_preedit_cursor_pos = cursor_pos; + } + + + public IBus.Text get_commit_text() { + string extension_text = m_extension_preedit_emoji.get_text(); + if (extension_text.length == 0) + extension_text = m_extension_preedit_text.get_text(); + return new IBus.Text.from_string(extension_text); + } + + + public void set_extension_name(string extension_name) { + if (extension_name.length == 0) + m_prefix = "@"; + else + m_prefix = extension_name[0:1]; + } +} + + class PanelBinding : IBus.PanelService { + private bool m_is_wayland; + private bool m_wayland_lookup_table_is_visible; private IBus.Bus m_bus; private Gtk.Application m_application; private GLib.Settings m_settings_panel = null; @@ -38,18 +224,26 @@ class PanelBinding : IBus.PanelService { private bool m_loaded_emoji = false; private bool m_load_unicode_at_startup; private bool m_loaded_unicode = false; + private bool m_enable_extension; + private string m_extension_name = ""; + private Preedit m_preedit; public PanelBinding(IBus.Bus bus, Gtk.Application application) { GLib.assert(bus.is_connected()); // Chain up base class constructor GLib.Object(connection : bus.get_connection(), - object_path : IBus.PATH_PANEL_EXTENSION); + object_path : IBus.PATH_PANEL_EXTENSION_EMOJI); + + Type instance_type = Gdk.Display.get_default().get_type(); + Type wayland_type = typeof(GdkWayland.Display); + m_is_wayland = instance_type.is_a(wayland_type); m_bus = bus; m_application = application; init_settings(); + m_preedit = new Preedit(); } @@ -69,12 +263,20 @@ class PanelBinding : IBus.PanelService { ref m_css_provider); }); + m_settings_emoji.changed["unicode-hotkey"].connect((key) => { + set_emoji_hotkey(); + }); + m_settings_emoji.changed["font"].connect((key) => { BindingCommon.set_custom_font(m_settings_panel, m_settings_emoji, ref m_css_provider); }); + m_settings_emoji.changed["hotkey"].connect((key) => { + set_emoji_hotkey(); + }); + m_settings_emoji.changed["favorites"].connect((key) => { set_emoji_favorites(); }); @@ -109,6 +311,54 @@ class PanelBinding : IBus.PanelService { } + private unowned + IBus.ProcessKeyEventData? parse_accelerator(string accelerator) { + IBus.ProcessKeyEventData key = {}; + uint keysym = 0; + IBus.ModifierType modifiers = 0; + IBus.accelerator_parse(accelerator, + out keysym, out modifiers); + if (keysym == 0U && modifiers == 0) { + warning("Failed to parse shortcut key '%s'".printf(accelerator)); + return null; + } + if ((modifiers & IBus.ModifierType.SUPER_MASK) != 0) { + modifiers ^= IBus.ModifierType.SUPER_MASK; + modifiers |= IBus.ModifierType.MOD4_MASK; + } + key.keyval = keysym; + key.state = modifiers; + return key; + } + + + private void set_emoji_hotkey() { + IBus.ProcessKeyEventData[] emoji_keys = {}; + IBus.ProcessKeyEventData key; + string[] accelerators = m_settings_emoji.get_strv("hotkey"); + foreach (var accelerator in accelerators) { + key = parse_accelerator(accelerator); + emoji_keys += key; + } + + /* Since {} is not allocated, parse_accelerator() should be unowned. */ + key = {}; + emoji_keys += key; + + IBus.ProcessKeyEventData[] unicode_keys = {}; + accelerators = m_settings_emoji.get_strv("unicode-hotkey"); + foreach (var accelerator in accelerators) { + key = parse_accelerator(accelerator); + unicode_keys += key; + } + key = {}; + unicode_keys += key; + + panel_extension_register_keys("emoji", emoji_keys, + "unicode", unicode_keys); + } + + private void set_emoji_favorites() { m_emojier_favorites = m_settings_emoji.get_strv("favorites"); IBusEmojier.set_favorites( @@ -159,6 +409,7 @@ class PanelBinding : IBus.PanelService { public void load_settings() { + set_emoji_hotkey(); set_load_emoji_at_startup(); set_load_unicode_at_startup(); BindingCommon.set_custom_font(m_settings_panel, @@ -181,36 +432,37 @@ class PanelBinding : IBus.PanelService { GLib.Source.remove(m_emojier_set_emoji_lang_id); m_emojier_set_emoji_lang_id = 0; } - m_application = null; - } - - - private void show_emojier(Gdk.Event event) { - if (!m_loaded_emoji) - set_emoji_lang(); - if (!m_loaded_unicode && m_loaded_emoji) { - IBusEmojier.load_unicode_dict(); - m_loaded_unicode = true; - } - m_emojier = new IBusEmojier(); - // For title handling in gnome-shell - m_application.add_window(m_emojier); - string emoji = m_emojier.run(m_real_current_context_path, event); - m_application.remove_window(m_emojier); - if (emoji == null) { + if (m_emojier != null) { + m_application.remove_window(m_emojier); m_emojier = null; - return; } - this.emojier_focus_commit(); + m_application = null; } - private void handle_emoji_typing(Gdk.Event event) { - if (m_emojier != null && m_emojier.is_running()) { - m_emojier.present_centralize(event); + private void commit_text_update_favorites(IBus.Text text) { + commit_text(text); + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", m_extension_name, + "is-enabled", false, + "is-extension", true); + panel_extension(event); + string committed_string = text.text; + string preedit_string = m_preedit.get_text(); + m_preedit.hide(); + if (preedit_string == committed_string) return; + bool has_favorite = false; + foreach (unowned string favorite in m_emojier_favorites) { + if (favorite == committed_string) { + has_favorite = true; + break; + } + } + if (!has_favorite) { + m_emojier_favorites += committed_string; + m_settings_emoji.set_strv("favorites", m_emojier_favorites); } - show_emojier(event); } @@ -223,19 +475,8 @@ class PanelBinding : IBus.PanelService { prev_context_path != "" && prev_context_path == m_current_context_path) { IBus.Text text = new IBus.Text.from_string(selected_string); - commit_text(text); - m_emojier = null; - bool has_favorite = false; - foreach (unowned string favorite in m_emojier_favorites) { - if (favorite == selected_string) { - has_favorite = true; - break; - } - } - if (!has_favorite) { - m_emojier_favorites += selected_string; - m_settings_emoji.set_strv("favorites", m_emojier_favorites); - } + commit_text_update_favorites(text); + m_emojier.reset(); return true; } @@ -249,8 +490,7 @@ class PanelBinding : IBus.PanelService { string selected_string = m_emojier.get_selected_string(); string prev_context_path = m_emojier.get_input_context_path(); if (selected_string == null && - prev_context_path != "" && - m_emojier.is_running()) { + prev_context_path != "") { var context = GLib.MainContext.default(); if (m_emojier_focus_commit_text_id > 0 && context.find_source_by_id(m_emojier_focus_commit_text_id) @@ -277,6 +517,243 @@ class PanelBinding : IBus.PanelService { } + private bool key_press_escape() { + if (is_emoji_lookup_table()) { + bool show_candidate = m_emojier.key_press_escape(); + convert_preedit_text(); + return show_candidate; + } + if (m_preedit.get_emoji() != "") { + m_preedit.set_emoji(""); + string annotation = m_preedit.get_text(); + m_emojier.set_annotation(annotation); + return false; + } + m_enable_extension = false; + hide_emoji_lookup_table(); + m_preedit.hide(); + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", m_extension_name, + "is-enabled", false, + "is-extension", true); + panel_extension(event); + return false; + } + + + private bool key_press_enter() { + if (m_extension_name != "unicode" && is_emoji_lookup_table()) { + // Check if variats exist + if (m_emojier.key_press_enter()) + return true; + } + IBus.Text text = m_preedit.get_commit_text(); + commit_text_update_favorites(text); + return false; + } + + + private void convert_preedit_text() { + if (m_emojier.get_number_of_candidates() > 0) + m_preedit.set_emoji(m_emojier.get_current_candidate()); + else + m_preedit.set_emoji(""); + } + + + private bool key_press_space() { + bool show_candidate = false; + if (m_preedit.get_emoji() != "") { + m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0); + show_candidate = true; + } else { + string annotation = m_preedit.get_text(); + if (annotation.length == 0) { + show_candidate = true; + if (is_emoji_lookup_table()) + m_emojier.key_press_cursor_horizontal(Gdk.Key.Right, 0); + } else { + m_emojier.set_annotation(annotation); + } + } + convert_preedit_text(); + return show_candidate; + } + + + private bool key_press_cursor_horizontal(uint keyval, + uint modifiers) { + if (is_emoji_lookup_table()) { + m_emojier.key_press_cursor_horizontal(keyval, modifiers); + convert_preedit_text(); + return true; + } + return false; + } + + + private bool key_press_cursor_vertical(uint keyval, + uint modifiers) { + if (is_emoji_lookup_table()) { + m_emojier.key_press_cursor_vertical(keyval, modifiers); + convert_preedit_text(); + return true; + } + return false; + } + + + private bool key_press_cursor_home_end(uint keyval, + uint modifiers) { + if (is_emoji_lookup_table()) { + m_emojier.key_press_cursor_home_end(keyval, modifiers); + convert_preedit_text(); + return true; + } + return false; + } + + + private bool key_press_control_keyval(uint keyval, + uint modifiers) { + bool show_candidate = false; + switch(keyval) { + case Gdk.Key.f: + show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, + modifiers); + break; + case Gdk.Key.b: + show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, + modifiers); + break; + case Gdk.Key.n: + case Gdk.Key.N: + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, modifiers); + break; + case Gdk.Key.p: + case Gdk.Key.P: + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, modifiers); + break; + case Gdk.Key.h: + show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers); + break; + case Gdk.Key.e: + show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers); + break; + case Gdk.Key.u: + m_preedit.reset(); + m_emojier.set_annotation(""); + hide_emoji_lookup_table(); + break; + case Gdk.Key.C: + case Gdk.Key.c: + if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) { + if (!m_is_wayland && m_emojier != null && + m_emojier.get_number_of_candidates() > 0) { + var text = m_emojier.get_current_candidate(); + Gtk.Clipboard clipboard = + Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); + clipboard.set_text(text, -1); + clipboard.store(); + } + show_candidate = is_emoji_lookup_table(); + } + break; + default: + show_candidate = is_emoji_lookup_table(); + break; + } + return show_candidate; + } + + + private void hide_wayland_lookup_table() { + m_wayland_lookup_table_is_visible = false; + var text = new IBus.Text.from_string(""); + update_auxiliary_text_received(text, false); + update_lookup_table_received( + new IBus.LookupTable(1, 0, false, true), + false); + } + + + private void show_wayland_lookup_table(IBus.Text text) { + m_wayland_lookup_table_is_visible = true; + var table = m_emojier.get_one_dimension_lookup_table(); + uint ncandidates = table.get_number_of_candidates(); + update_auxiliary_text_received( + text, + ncandidates > 0 ? true : false); + update_lookup_table_received( + table, + ncandidates > 0 ? true : false); + } + + + private bool is_visible_wayland_lookup_table() { + return m_wayland_lookup_table_is_visible; + } + + + private void hide_emoji_lookup_table() { + if (m_emojier == null) + return; + if (m_is_wayland) + hide_wayland_lookup_table(); + else + m_emojier.hide(); + } + + + private void show_emoji_lookup_table() { + /* Emojier category_list is shown in both Xorg and Wayland + * because the annotation information is useful but the Wayland lookup + * window is alway one dimension. So the category_list is shown + * when the user annotation is null. + */ + if (m_is_wayland && m_preedit.get_text() != "") { + var text = m_emojier.get_title_text(); + show_wayland_lookup_table(text); + } else { + // POPUP window takes the focus in Wayland. + if (m_is_wayland) + m_emojier.set_input_context_path(m_real_current_context_path); + m_emojier.show_all(); + } + } + + + private bool is_emoji_lookup_table() { + if (m_is_wayland) + return is_visible_wayland_lookup_table(); + else + return m_emojier.get_visible(); + } + + + private void show_preedit_and_candidate(bool show_candidate) { + uint cursor_pos = 0; + if (!show_candidate) + cursor_pos = m_preedit.get_engine_preedit_cursor_pos(); + update_preedit_text_received( + m_preedit.get_engine_preedit_text(), + cursor_pos, + true); + if (!show_candidate) { + hide_emoji_lookup_table(); + return; + } + if (m_emojier == null) + return; + /* Wayland gives the focus on Emojir which is a GTK popup window + * and move the focus fom the current input context to Emojier. + * This forwards the lookup table to gnome-shell's lookup table + * but it enables one dimension lookup table only. + */ + show_emoji_lookup_table(); + } + + public override void focus_in(string input_context_path) { m_current_context_path = input_context_path; @@ -299,48 +776,280 @@ class PanelBinding : IBus.PanelService { } - public override void panel_extension_received(GLib.Variant data) { - IBus.XEvent? xevent = IBus.Serializable.deserialize_object(data) - as IBus.XEvent; - if (xevent == null) { - warning ("Failed to deserialize IBusXEvent"); + public override void panel_extension_received(IBus.ExtensionEvent event) { + m_extension_name = event.get_name(); + if (m_extension_name != "emoji" && m_extension_name != "unicode") { + string format = "The name %s is not implemented in PanelExtension"; + warning (format.printf(m_extension_name)); + m_extension_name = ""; return; } - if (xevent.get_purpose() != "emoji") { - string format = "The purpose %s is not implemented in PanelExtension"; - warning (format.printf(xevent.get_purpose())); + m_enable_extension = event.is_enabled; + if (!m_enable_extension) { + hide_emoji_lookup_table(); + return; + } + if (!m_loaded_emoji) + set_emoji_lang(); + if (!m_loaded_unicode && m_loaded_emoji) { + IBusEmojier.load_unicode_dict(); + m_loaded_unicode = true; + } + if (m_emojier == null) { + m_emojier = new IBusEmojier(); + // For title handling in gnome-shell + m_application.add_window(m_emojier); + m_emojier.candidate_clicked.connect((i, b, s) => { + if (!m_is_wayland) + candidate_clicked_lookup_table(i, b, s); + }); + } + m_emojier.reset(); + m_emojier.set_annotation(""); + m_preedit.set_extension_name(m_extension_name); + m_preedit.reset(); + update_preedit_text_received( + m_preedit.get_engine_preedit_text(), + m_preedit.get_engine_preedit_cursor_pos(), + true); + string params = event.get_params(); + if (params == "category-list") { + key_press_space(); + show_preedit_and_candidate(true); + } + } + + + public override void set_cursor_location(int x, + int y, + int width, + int height) { + if (m_emojier != null) + m_emojier.set_cursor_location(x, y, width, height); + } + + + public override void update_preedit_text(IBus.Text text, + uint cursor_pos, + bool visible) { + m_preedit.set_engine_preedit_text(text); + if (visible) + m_preedit.show_engine_preedit_text(); + else + m_preedit.hide_engine_preedit_text(); + m_preedit.set_engine_preedit_cursor_pos(cursor_pos); + update_preedit_text_received(m_preedit.get_engine_preedit_text(), + m_preedit.get_engine_preedit_cursor_pos(), + visible); + } + + + public override void show_preedit_text() { + m_preedit.show_engine_preedit_text(); + show_preedit_and_candidate(false); + } + + + public override void hide_preedit_text() { + m_preedit.hide_engine_preedit_text(); + show_preedit_and_candidate(false); + } + + + public override bool process_key_event(uint keyval, + uint keycode, + uint state) { + if ((state & IBus.ModifierType.RELEASE_MASK) != 0) + return false; + uint modifiers = state; + bool show_candidate = false; + switch(keyval) { + case Gdk.Key.Escape: + show_candidate = key_press_escape(); + if (!m_preedit.is_shown()) + return true; + break; + case Gdk.Key.Return: + case Gdk.Key.KP_Enter: + if (m_extension_name == "unicode") + key_press_space(); + show_candidate = key_press_enter(); + if (!m_preedit.is_shown()) { + hide_emoji_lookup_table(); + return true; + } + break; + case Gdk.Key.BackSpace: + m_preedit.backspace(); + string annotation = m_preedit.get_text(); + if (annotation == "" && m_extension_name == "unicode") { + key_press_escape(); + return true; + } + m_emojier.set_annotation(annotation); + break; + case Gdk.Key.space: + case Gdk.Key.KP_Space: + show_candidate = key_press_space(); + if (m_extension_name == "unicode") { + key_press_enter(); + return true; + } + break; + case Gdk.Key.Right: + case Gdk.Key.KP_Right: + /* one dimension in Wayland, two dimensions in X11 */ + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, + modifiers); + } else { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, + modifiers); + } + break; + case Gdk.Key.Left: + case Gdk.Key.KP_Left: + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, + modifiers); + } else { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, + modifiers); + } + break; + case Gdk.Key.Down: + case Gdk.Key.KP_Down: + if (m_is_wayland) { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, + modifiers); + } + break; + case Gdk.Key.Up: + case Gdk.Key.KP_Up: + if (m_is_wayland) { + show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, + modifiers); + } + break; + case Gdk.Key.Page_Down: + case Gdk.Key.KP_Page_Down: + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Down, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Down, + modifiers); + } + break; + case Gdk.Key.Page_Up: + case Gdk.Key.KP_Page_Up: + if (m_is_wayland) { + show_candidate = key_press_cursor_vertical(Gdk.Key.Up, + modifiers); + } else { + show_candidate = key_press_cursor_vertical(Gdk.Key.Page_Up, + modifiers); + } + break; + case Gdk.Key.Home: + case Gdk.Key.KP_Home: + show_candidate = key_press_cursor_home_end(Gdk.Key.Home, modifiers); + break; + case Gdk.Key.End: + case Gdk.Key.KP_End: + show_candidate = key_press_cursor_home_end(Gdk.Key.End, modifiers); + break; + default: + if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) { + show_candidate = key_press_control_keyval(keyval, modifiers); + break; + } + unichar ch = IBus.keyval_to_unicode(keyval); + if (ch.iscntrl()) + return true; + string str = ch.to_string(); + m_preedit.append_text(str); + string annotation = m_preedit.get_text(); + m_emojier.set_annotation(annotation); + m_preedit.set_emoji(""); + show_candidate = is_emoji_lookup_table(); + break; + } + show_preedit_and_candidate(show_candidate); + return true; + } + + public override void commit_text_received(IBus.Text text) { + unowned string? str = text.text; + if (str == null) + return; + /* Do not call convert_preedit_text() because it depends on + * each IME whether process_key_event() receives Shift-space or not. + */ + m_preedit.append_text(str); + m_preedit.set_emoji(""); + string annotation = m_preedit.get_text(); + m_emojier.set_annotation(annotation); + show_preedit_and_candidate(false); + } + + public override void page_up_lookup_table() { + bool show_candidate = key_press_cursor_vertical(Gdk.Key.Up, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void page_down_lookup_table() { + bool show_candidate = key_press_cursor_vertical(Gdk.Key.Down, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void cursor_up_lookup_table() { + bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Left, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void cursor_down_lookup_table() { + bool show_candidate = key_press_cursor_horizontal(Gdk.Key.Right, 0); + show_preedit_and_candidate(show_candidate); + } + + public override void candidate_clicked_lookup_table(uint index, + uint button, + uint state) { + if (button == IBusEmojier.BUTTON_CLOSE_BUTTON) { + m_enable_extension = false; + hide_emoji_lookup_table(); + m_preedit.hide(); + IBus.ExtensionEvent event = new IBus.ExtensionEvent( + "name", m_extension_name, + "is-enabled", false, + "is-extension", true); + panel_extension(event); return; } - Gdk.EventType event_type; - if (xevent.get_event_type() == IBus.XEventType.KEY_PRESS) { - event_type = Gdk.EventType.KEY_PRESS; - } else if (xevent.get_event_type() == IBus.XEventType.KEY_RELEASE) { - event_type = Gdk.EventType.KEY_RELEASE; + if (m_emojier == null) + return; + bool show_candidate = false; + uint ncandidates = m_emojier.get_number_of_candidates(); + if (ncandidates > 0 && ncandidates >= index) { + m_emojier.set_cursor_pos(index); + show_candidate = m_emojier.has_variants(index); + m_preedit.set_emoji(m_emojier.get_current_candidate()); } else { - warning ("Not supported type %d".printf(xevent.get_event_type())); return; } - Gdk.Event event = new Gdk.Event(event_type); - uint32 time = xevent.get_time(); - if (time == 0) - time = Gtk.get_current_event_time(); - event.key.time = time; - X.Window xid = xevent.get_window(); - Gdk.Display? display = Gdk.Display.get_default(); - Gdk.Window? window = null; - if (window == null && xid != 0) { - window = Gdk.X11.Window.lookup_for_display( - display as Gdk.X11.Display, xid); - } - if (window == null && xid != 0) { - window = new Gdk.X11.Window.foreign_for_display( - display as Gdk.X11.Display, xid); - } - if (window == null) { - window = Gdk.get_default_root_window(); - window.ref(); - } - event.key.window = window; - handle_emoji_typing(event); + if (!show_candidate) { + IBus.Text text = m_preedit.get_commit_text(); + commit_text_update_favorites(text); + hide_emoji_lookup_table(); + return; + } + show_preedit_and_candidate(show_candidate); } } -- 2.14.3 From 7cef5bf572596361bc502e8fa917569676a80372 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Wed, 20 Jun 2018 19:01:59 +0900 Subject: [PATCH] setup: Replace emoji font with Unicode font Now the font settings of emoji is configurable in the session base but not the application base and the current font setting on ibus-setup effects on Unicode characters. Also fixed the progress bar on Unicode candidate table. --- setup/setup.ui | 4 +- src/tests/runtest | 2 +- ui/gtk3/emojier.vala | 213 ++++++++++++++++++++++++++++----------------------- 3 files changed, 120 insertions(+), 99 deletions(-) diff --git a/setup/setup.ui b/setup/setup.ui index f1beb1de..9d9d7ee9 100644 --- a/setup/setup.ui +++ b/setup/setup.ui @@ -1010,9 +1010,9 @@ True False - Set a font of emoji candidates on the emoji dialog + Set a font of Unicode candidates on the emoji dialog start - Emoji font: + Unicode font: right diff --git a/src/tests/runtest b/src/tests/runtest index b6b845d6..5c163083 100755 --- a/src/tests/runtest +++ b/src/tests/runtest @@ -142,7 +142,7 @@ run_test_case() --daemonize \ --cache=none \ --panel=disable \ - --panel-extension=disable \ + --emoji-extension=disable \ --config=default \ --verbose; diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala index cd98c9d7..7beb6f0a 100644 --- a/ui/gtk3/emojier.vala +++ b/ui/gtk3/emojier.vala @@ -253,6 +253,7 @@ public class IBusEmojier : Gtk.ApplicationWindow { private static string m_current_lang_id; private static string m_emoji_font_family; private static int m_emoji_font_size; + private static bool m_emoji_font_changed = false; private static string[] m_favorites; private static string[] m_favorite_annotations; private static int m_emoji_max_seq_len; @@ -348,88 +349,20 @@ public class IBusEmojier : Gtk.ApplicationWindow { add_action(action); if (m_current_lang_id == null) m_current_lang_id = "en"; - if (m_emoji_font_family == null) + if (m_emoji_font_family == null) { m_emoji_font_family = "Monospace"; - if (m_emoji_font_size == 0) + m_emoji_font_changed = true; + } + if (m_emoji_font_size == 0) { m_emoji_font_size = 16; + m_emoji_font_changed = true; + } if (m_favorites == null) m_favorites = {}; if (m_favorite_annotations == null) m_favorite_annotations = {}; - Gdk.Display display = Gdk.Display.get_default(); - Gdk.Screen screen = (display != null) ? - display.get_default_screen() : null; - - if (screen == null) { - warning("Could not open display."); - return; - } - // Set en locale because de_DE's decimal_point is ',' instead of '.' - string? backup_locale = - Intl.setlocale(LocaleCategory.NUMERIC, null).dup(); - if (Intl.setlocale(LocaleCategory.NUMERIC, "en_US.UTF-8") == null) { - if (Intl.setlocale(LocaleCategory.NUMERIC, "C.UTF-8") == null) { - if (Intl.setlocale(LocaleCategory.NUMERIC, "C") == null) { - warning("You don't install either en_US.UTF-8 or C.UTF-8 " + - "or C locale"); - } - } - } - m_rgba = new ThemedRGBA(this); - uint bg_red = (uint)(m_rgba.normal_bg.red * 255); - uint bg_green = (uint)(m_rgba.normal_bg.green * 255); - uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255); - double bg_alpha = m_rgba.normal_bg.alpha; - string data = - "#IBusEmojierWhiteLabel { background-color: " + - "rgba(%u, %u, %u, %lf); ".printf( - bg_red, bg_green, bg_blue, bg_alpha) + - "font-family: %s; font-size: %dpt; ".printf( - m_emoji_font_family, m_emoji_font_size) + - "border-width: 4px; border-radius: 3px; } "; - - uint fg_red = (uint)(m_rgba.selected_fg.red * 255); - uint fg_green = (uint)(m_rgba.selected_fg.green * 255); - uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255); - double fg_alpha = m_rgba.selected_fg.alpha; - bg_red = (uint)(m_rgba.selected_bg.red * 255); - bg_green = (uint)(m_rgba.selected_bg.green * 255); - bg_blue = (uint)(m_rgba.selected_bg.blue * 255); - bg_alpha = m_rgba.selected_bg.alpha; - data += "#IBusEmojierSelectedLabel { color: " + - "rgba(%u, %u, %u, %lf); ".printf( - fg_red, fg_green, fg_blue, fg_alpha) + - "font-family: %s; font-size: %dpt; ".printf( - m_emoji_font_family, m_emoji_font_size) + - "background-color: " + - "rgba(%u, %u, %u, %lf); ".printf( - bg_red, bg_green, bg_blue, bg_alpha) + - "border-width: 4px; border-radius: 3px; }"; - data += "#IBusEmojierGoldLabel { color: " + - "rgba(%u, %u, %u, %lf); ".printf( - fg_red, fg_green, fg_blue, fg_alpha) + - "font-family: %s; font-size: %dpt; ".printf( - m_emoji_font_family, m_emoji_font_size) + - "background-color: #b09c5f; " + - "border-width: 4px; border-radius: 3px; }"; - - Gtk.CssProvider css_provider = new Gtk.CssProvider(); - try { - css_provider.load_from_data(data, -1); - } catch (GLib.Error e) { - warning("Failed css_provider_from_data: %s", e.message); - return; - } - if (backup_locale != null) - Intl.setlocale(LocaleCategory.NUMERIC, backup_locale); - else - Intl.setlocale(LocaleCategory.NUMERIC, ""); - - Gtk.StyleContext.add_provider_for_screen( - screen, - css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + set_css_data(); m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); add(m_vbox); @@ -795,6 +728,84 @@ public class IBusEmojier : Gtk.ApplicationWindow { } + private void set_css_data() { + Gdk.Display display = Gdk.Display.get_default(); + Gdk.Screen screen = (display != null) ? + display.get_default_screen() : null; + + if (screen == null) { + warning("Could not open display."); + return; + } + // Set en locale because de_DE's decimal_point is ',' instead of '.' + string? backup_locale = + Intl.setlocale(LocaleCategory.NUMERIC, null).dup(); + if (Intl.setlocale(LocaleCategory.NUMERIC, "en_US.UTF-8") == null) { + if (Intl.setlocale(LocaleCategory.NUMERIC, "C.UTF-8") == null) { + if (Intl.setlocale(LocaleCategory.NUMERIC, "C") == null) { + warning("You don't install either en_US.UTF-8 or C.UTF-8 " + + "or C locale"); + } + } + } + if (m_rgba == null) + m_rgba = new ThemedRGBA(this); + uint bg_red = (uint)(m_rgba.normal_bg.red * 255); + uint bg_green = (uint)(m_rgba.normal_bg.green * 255); + uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255); + double bg_alpha = m_rgba.normal_bg.alpha; + string data = + "#IBusEmojierWhiteLabel { background-color: " + + "rgba(%u, %u, %u, %lf); ".printf( + bg_red, bg_green, bg_blue, bg_alpha) + + "font-family: %s; font-size: %dpt; ".printf( + m_emoji_font_family, m_emoji_font_size) + + "border-width: 4px; border-radius: 3px; } "; + + uint fg_red = (uint)(m_rgba.selected_fg.red * 255); + uint fg_green = (uint)(m_rgba.selected_fg.green * 255); + uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255); + double fg_alpha = m_rgba.selected_fg.alpha; + bg_red = (uint)(m_rgba.selected_bg.red * 255); + bg_green = (uint)(m_rgba.selected_bg.green * 255); + bg_blue = (uint)(m_rgba.selected_bg.blue * 255); + bg_alpha = m_rgba.selected_bg.alpha; + data += "#IBusEmojierSelectedLabel { color: " + + "rgba(%u, %u, %u, %lf); ".printf( + fg_red, fg_green, fg_blue, fg_alpha) + + "font-family: %s; font-size: %dpt; ".printf( + m_emoji_font_family, m_emoji_font_size) + + "background-color: " + + "rgba(%u, %u, %u, %lf); ".printf( + bg_red, bg_green, bg_blue, bg_alpha) + + "border-width: 4px; border-radius: 3px; }"; + data += "#IBusEmojierGoldLabel { color: " + + "rgba(%u, %u, %u, %lf); ".printf( + fg_red, fg_green, fg_blue, fg_alpha) + + "font-family: %s; font-size: %dpt; ".printf( + m_emoji_font_family, m_emoji_font_size) + + "background-color: #b09c5f; " + + "border-width: 4px; border-radius: 3px; }"; + + Gtk.CssProvider css_provider = new Gtk.CssProvider(); + try { + css_provider.load_from_data(data, -1); + } catch (GLib.Error e) { + warning("Failed css_provider_from_data: %s", e.message); + return; + } + if (backup_locale != null) + Intl.setlocale(LocaleCategory.NUMERIC, backup_locale); + else + Intl.setlocale(LocaleCategory.NUMERIC, ""); + + Gtk.StyleContext.add_provider_for_screen( + screen, + css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + private void set_fixed_size() { resize(20, 1); } @@ -1038,7 +1049,8 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_lookup_table.append_candidate(text); } m_backward = block_name; - m_annotation = m_lookup_table.get_candidate(0).text; + if (m_lookup_table.get_number_of_candidates() > 0) + m_annotation = m_lookup_table.get_candidate(0).text; } @@ -1385,6 +1397,10 @@ public class IBusEmojier : Gtk.ApplicationWindow { private void show_candidate_panel() { remove_all_children(); set_fixed_size(); + if (m_emoji_font_changed) { + set_css_data(); + m_emoji_font_changed = false; + } uint page_size = m_lookup_table.get_page_size(); uint ncandidates = m_lookup_table.get_number_of_candidates(); uint cursor = m_lookup_table.get_cursor_pos(); @@ -1488,32 +1504,33 @@ public class IBusEmojier : Gtk.ApplicationWindow { m_candidates += label; } - if (n > 0) { - m_candidate_panel_is_visible = true; - if (!m_is_up_side_down) { - show_arrow_buttons(); - if (backward_button != null) { - m_vbox.add(backward_button); - backward_button.show_all(); - } + m_candidate_panel_is_visible = true; + if (!m_is_up_side_down) { + show_arrow_buttons(); + if (backward_button != null) { + m_vbox.add(backward_button); + backward_button.show_all(); + } + if (n > 0) { m_vbox.add(grid); grid.show_all(); show_description(); - if (!m_loaded_unicode) - show_unicode_progress_bar(); } - if (m_is_up_side_down) { - if (!m_loaded_unicode) - show_unicode_progress_bar(); + if (!m_loaded_unicode) + show_unicode_progress_bar(); + } else { + if (!m_loaded_unicode) + show_unicode_progress_bar(); + if (n > 0) { show_description(); m_vbox.add(grid); grid.show_all(); - if (backward_button != null) { - m_vbox.add(backward_button); - backward_button.show_all(); - } - show_arrow_buttons(); } + if (backward_button != null) { + m_vbox.add(backward_button); + backward_button.show_all(); + } + show_arrow_buttons(); } } @@ -2618,11 +2635,15 @@ public class IBusEmojier : Gtk.ApplicationWindow { Pango.FontDescription font_desc = Pango.FontDescription.from_string(emoji_font); string font_family = font_desc.get_family(); - if (font_family != null) + if (font_family != null) { m_emoji_font_family = font_family; + m_emoji_font_changed = true; + } int font_size = font_desc.get_size() / Pango.SCALE; - if (font_size != 0) + if (font_size != 0) { m_emoji_font_size = font_size; + m_emoji_font_changed = true; + } } -- 2.14.3 From f9e30359d328054793e1e225dcf2fe537e6c8c48 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Wed, 27 Jun 2018 12:11:41 +0900 Subject: [PATCH] ibusenginesimple: Enable preedit for compose keys BUG=https://github.com/ibus/ibus/issues/1935 --- src/ibusenginesimple.c | 166 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 115 insertions(+), 51 deletions(-) diff --git a/src/ibusenginesimple.c b/src/ibusenginesimple.c index 94ce53b7..61dfb89f 100644 --- a/src/ibusenginesimple.c +++ b/src/ibusenginesimple.c @@ -189,11 +189,12 @@ ibus_engine_simple_reset (IBusEngine *engine) priv->tentative_match = 0; priv->tentative_match_len = 0; ibus_engine_hide_preedit_text ((IBusEngine *)simple); - } - if (priv->tentative_emoji || priv->in_emoji_sequence) { + } else if (priv->tentative_emoji || priv->in_emoji_sequence) { priv->in_emoji_sequence = FALSE; g_clear_pointer (&priv->tentative_emoji, g_free); ibus_engine_hide_preedit_text ((IBusEngine *)simple); + } else if (!priv->in_hex_sequence && !priv->in_emoji_sequence) { + ibus_engine_hide_preedit_text ((IBusEngine *)simple); } } @@ -209,18 +210,78 @@ ibus_engine_simple_commit_char (IBusEngineSimple *simple, priv->in_hex_sequence = FALSE; priv->tentative_match = 0; priv->tentative_match_len = 0; - ibus_engine_simple_update_preedit_text (simple); } if (priv->tentative_emoji || priv->in_emoji_sequence) { priv->in_emoji_sequence = FALSE; g_clear_pointer (&priv->tentative_emoji, g_free); - ibus_engine_simple_update_preedit_text (simple); } - ibus_engine_commit_text ((IBusEngine *)simple, ibus_text_new_from_unichar (ch)); } +#define COMPOSE_KEYSYM_TO_UNICHAR(keysym, unichar) { \ + +static gunichar +ibus_keysym_to_unicode (guint16 keysym) { +#define CASE(keysym_suffix, unicode) \ + case IBUS_KEY_dead_##keysym_suffix: return unicode + switch (keysym) { + CASE(a, 0x03041); + CASE(A, 0x03042); + CASE(i, 0x03043); + CASE(I, 0x03044); + CASE(u, 0x03045); + CASE(U, 0x03046); + CASE(e, 0x03047); + CASE(E, 0x03048); + CASE(o, 0x03049); + CASE(O, 0x0304a); + CASE(abovecomma, 0x0313); + CASE(abovedot, 0x0307); + CASE(abovereversedcomma, 0x0314); + CASE(abovering, 0x030a); + CASE(acute, 0x0301); + CASE(belowbreve, 0x032e); + CASE(belowcircumflex, 0x032d); + CASE(belowcomma, 0x0326); + CASE(belowdiaeresis, 0x0324); + CASE(belowdot, 0x0323); + CASE(belowmacron, 0x0331); + CASE(belowring, 0x030a); + CASE(belowtilde, 0x0330); + CASE(breve, 0x0306); + CASE(capital_schwa, 0x018f); + CASE(caron, 0x030c); + CASE(cedilla, 0x0327); + CASE(circumflex, 0x0302); + CASE(currency, 0x00a4); + // IBUS_KEY_dead_dasia == IBUS_KEY_dead_abovereversedcomma + CASE(diaeresis, 0x0308); + CASE(doubleacute, 0x030b); + CASE(doublegrave, 0x030f); + CASE(grave, 0x0300); + CASE(greek, 0x03b1); + CASE(hook, 0x0309); + CASE(horn, 0x031b); + CASE(invertedbreve, 0x032f); + CASE(iota, 0x0345); + CASE(macron, 0x0304); + CASE(ogonek, 0x0328); + // IBUS_KEY_dead_perispomeni == IBUS_KEY_dead_tilde + // IBUS_KEY_dead_psili == IBUS_KEY_dead_abovecomma + CASE(semivoiced_sound, 0x309a); + CASE(small_schwa, 0x1d4a); + CASE(stroke, 0x29f8); + CASE(tilde, 0x0303); + CASE(voiced_sound, 0x3099); + case IBUS_KEY_Multi_key: + return 0x2384; + default:; + } + return 0x0; +#undef CASE +} + static void ibus_engine_simple_commit_str (IBusEngineSimple *simple, const gchar *str) @@ -278,8 +339,7 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple) g_assert (len <= IBUS_MAX_COMPOSE_LEN + 1); else g_assert (len <= EMOJI_SOURCE_LEN + 1); - } - else if (priv->tentative_match) { + } else if (priv->tentative_match) { outbuf[len++] = priv->tentative_match; } else if (priv->tentative_emoji && *priv->tentative_emoji) { IBusText *text = ibus_text_new_from_string (priv->tentative_emoji); @@ -288,6 +348,24 @@ ibus_engine_simple_update_preedit_text (IBusEngineSimple *simple) IBUS_ATTR_TYPE_UNDERLINE, IBUS_ATTR_UNDERLINE_SINGLE, 0, len); ibus_engine_update_preedit_text ((IBusEngine *)simple, text, len, TRUE); return; + } else { + int hexchars = 0; + while (priv->compose_buffer[hexchars] != 0) { + guint16 keysym= priv->compose_buffer[hexchars]; + gunichar unichar = ibus_keysym_to_unicode (keysym); + if (unichar > 0) + outbuf[len] = unichar; + else + outbuf[len] = ibus_keyval_to_unicode (keysym); + if (!outbuf[len]) { + g_warning ( + "Not found alternative character of compose key 0x%X", + priv->compose_buffer[hexchars]); + } + ++len; + ++hexchars; + } + g_assert (len <= IBUS_MAX_COMPOSE_LEN + 1); } outbuf[len] = L'\0'; @@ -569,8 +647,9 @@ check_table (IBusEngineSimple *simple, } ibus_engine_simple_commit_char (simple, value); - // g_debug ("U+%04X\n", value); priv->compose_buffer[0] = 0; + ibus_engine_simple_update_preedit_text (simple); + // g_debug ("U+%04X\n", value); } return TRUE; } @@ -768,44 +847,10 @@ ibus_check_algorithmically (const guint16 *compose_buffer, combination_buffer[n_compose] = 0; i--; while (i >= 0) { - switch (compose_buffer[i]) { -#define CASE(keysym, unicode) \ - case IBUS_KEY_dead_##keysym: \ - combination_buffer[i+1] = unicode; \ - break - CASE (grave, 0x0300); - CASE (acute, 0x0301); - CASE (circumflex, 0x0302); - CASE (tilde, 0x0303); /* Also used with perispomeni, 0x342. */ - CASE (macron, 0x0304); - CASE (breve, 0x0306); - CASE (abovedot, 0x0307); - CASE (diaeresis, 0x0308); - CASE (hook, 0x0309); - CASE (abovering, 0x030A); - CASE (doubleacute, 0x030B); - CASE (caron, 0x030C); - CASE (abovecomma, 0x0313); /* Equivalent to psili */ - CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */ - CASE (horn, 0x031B); /* Legacy use for psili, 0x313 (or 0x343). */ - CASE (belowdot, 0x0323); - CASE (cedilla, 0x0327); - CASE (ogonek, 0x0328); /* Legacy use for dasia, 0x314.*/ - CASE (iota, 0x0345); - CASE (voiced_sound, 0x3099); /* Per Markus Kuhn keysyms.txt file. */ - CASE (semivoiced_sound, 0x309A); /* Per Markus Kuhn keysyms.txt file. */ - - /* The following cases are to be removed once xkeyboard-config, - * xorg are fully updated. - */ - /* Workaround for typo in 1.4.x xserver-xorg */ - case 0xfe66: combination_buffer[i+1] = 0x314; break; - /* CASE (dasia, 0x314); */ - /* CASE (perispomeni, 0x342); */ - /* CASE (psili, 0x343); */ -#undef CASE - default: - combination_buffer[i+1] = ibus_keyval_to_unicode (compose_buffer[i]); + combination_buffer[i+1] = ibus_keysym_to_unicode (compose_buffer[i]); + if (!combination_buffer[i+1]) { + combination_buffer[i+1] = + ibus_keyval_to_unicode (compose_buffer[i]); } i--; } @@ -853,6 +898,7 @@ no_sequence_matches (IBusEngineSimple *simple, ibus_engine_simple_commit_char (simple, priv->tentative_match); priv->compose_buffer[0] = 0; + ibus_engine_simple_update_preedit_text (simple); for (i=0; i < n_compose - len - 1; i++) { ibus_engine_simple_process_key_event ( @@ -872,20 +918,21 @@ no_sequence_matches (IBusEngineSimple *simple, if (n_compose > 1) { /* Invalid sequence */ // FIXME beep_window (event->window); + ibus_engine_simple_update_preedit_text (simple); return TRUE; } + ibus_engine_simple_update_preedit_text (simple); ch = ibus_keyval_to_unicode (keyval); /* IBUS_CHANGE: RH#769133 * Since we use ibus xkb engines as the disable state, * do not commit the characters locally without in_hex_sequence. */ if (ch != 0 && !g_unichar_iscntrl (ch) && priv->in_hex_sequence) { - ibus_engine_simple_commit_char (simple, ch); return TRUE; - } - else + } else { return FALSE; + } } return FALSE; } @@ -1027,6 +1074,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, if (priv->tentative_match && g_unichar_validate (priv->tentative_match)) { ibus_engine_simple_commit_char (simple, priv->tentative_match); + ibus_engine_simple_update_preedit_text (simple); } else if (n_compose == 0) { priv->modifiers_dropped = TRUE; } else { @@ -1176,12 +1224,21 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, return TRUE; } + if (!priv->in_hex_sequence && !priv->in_emoji_sequence && is_backspace) { + if (n_compose > 0) { + n_compose--; + priv->compose_buffer[n_compose] = 0; + ibus_engine_simple_update_preedit_text (simple); + return TRUE; + } + } /* Check for hex sequence restart */ if (priv->in_hex_sequence && have_hex_mods && is_hex_start) { if (priv->tentative_match && g_unichar_validate (priv->tentative_match)) { ibus_engine_simple_commit_char (simple, priv->tentative_match); + ibus_engine_simple_update_preedit_text (simple); } else { /* invalid hex sequence */ @@ -1283,6 +1340,12 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, return TRUE; } } else { + if (is_escape) { + if (n_compose > 0) { + ibus_engine_simple_reset (engine); + return TRUE; + } + } SET_COMPOSE_BUFFER_ELEMENT_NEXT (priv->compose_buffer, n_compose, keyval); @@ -1302,6 +1365,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, ibus_engine_simple_commit_char (simple, priv->tentative_match); priv->compose_buffer[0] = 0; + ibus_engine_simple_update_preedit_text (simple); } else { // FIXME /* invalid hex sequence */ @@ -1417,9 +1481,8 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, if (compose_finish) { ibus_engine_simple_commit_char (simple, output_char); priv->compose_buffer[0] = 0; - } else { - ibus_engine_simple_update_preedit_text (simple); } + ibus_engine_simple_update_preedit_text (simple); return TRUE; } @@ -1430,6 +1493,7 @@ ibus_engine_simple_process_key_event (IBusEngine *engine, ibus_engine_simple_commit_char (simple, output_char); priv->compose_buffer[0] = 0; } + ibus_engine_simple_update_preedit_text (simple); return TRUE; } } -- 2.14.3 From 9b26a4b46fa2635033d315e8babb8c4ca9869898 Mon Sep 17 00:00:00 2001 From: fujiwarat Date: Wed, 27 Jun 2018 12:18:26 +0900 Subject: [PATCH] panelbinding: Fix SEGV in panel_binding_parse_accelerator panel_binding_parse_accelerator() could return NULL of the unowned IBus.ProcessKeyEventData with gcc optimization. Since Vala does not provice a static local variable, the variable is moved to the class member to fix this SEGV. Also a NULL preedit is fixed in the first emoji candidate from the emoji category window. --- ui/gtk3/panelbinding.vala | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/ui/gtk3/panelbinding.vala b/ui/gtk3/panelbinding.vala index 52b78c17..95115b13 100644 --- a/ui/gtk3/panelbinding.vala +++ b/ui/gtk3/panelbinding.vala @@ -227,6 +227,8 @@ class PanelBinding : IBus.PanelService { private bool m_enable_extension; private string m_extension_name = ""; private Preedit m_preedit; + private IBus.ProcessKeyEventData m_key_event_data = + IBus.ProcessKeyEventData(); public PanelBinding(IBus.Bus bus, Gtk.Application application) { @@ -311,24 +313,24 @@ class PanelBinding : IBus.PanelService { } - private unowned - IBus.ProcessKeyEventData? parse_accelerator(string accelerator) { - IBus.ProcessKeyEventData key = {}; + // Returning unowned IBus.KeyEventData causes NULL with gcc optimization + // and use m_key_event_data. + private void parse_accelerator(string accelerator) { + m_key_event_data = {}; uint keysym = 0; IBus.ModifierType modifiers = 0; IBus.accelerator_parse(accelerator, out keysym, out modifiers); if (keysym == 0U && modifiers == 0) { warning("Failed to parse shortcut key '%s'".printf(accelerator)); - return null; + return; } if ((modifiers & IBus.ModifierType.SUPER_MASK) != 0) { modifiers ^= IBus.ModifierType.SUPER_MASK; modifiers |= IBus.ModifierType.MOD4_MASK; } - key.keyval = keysym; - key.state = modifiers; - return key; + m_key_event_data.keyval = keysym; + m_key_event_data.state = modifiers; } @@ -337,8 +339,8 @@ class PanelBinding : IBus.PanelService { IBus.ProcessKeyEventData key; string[] accelerators = m_settings_emoji.get_strv("hotkey"); foreach (var accelerator in accelerators) { - key = parse_accelerator(accelerator); - emoji_keys += key; + parse_accelerator(accelerator); + emoji_keys += m_key_event_data; } /* Since {} is not allocated, parse_accelerator() should be unowned. */ @@ -348,8 +350,8 @@ class PanelBinding : IBus.PanelService { IBus.ProcessKeyEventData[] unicode_keys = {}; accelerators = m_settings_emoji.get_strv("unicode-hotkey"); foreach (var accelerator in accelerators) { - key = parse_accelerator(accelerator); - unicode_keys += key; + parse_accelerator(accelerator); + unicode_keys += m_key_event_data; } key = {}; unicode_keys += key; @@ -544,8 +546,10 @@ class PanelBinding : IBus.PanelService { private bool key_press_enter() { if (m_extension_name != "unicode" && is_emoji_lookup_table()) { // Check if variats exist - if (m_emojier.key_press_enter()) + if (m_emojier.key_press_enter()) { + convert_preedit_text(); return true; + } } IBus.Text text = m_preedit.get_commit_text(); commit_text_update_favorites(text); -- 2.14.3