pax_global_header00006660000000000000000000000064135622122360014514gustar00rootroot0000000000000052 comment=6f9ef6417ab04f4a7f3f9af8d16c3a165df7ea82 fcitx5-gtk-0.0~git20191111.6f9ef64/000077500000000000000000000000001356221223600161525ustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/.formatignore000066400000000000000000000000071356221223600206440ustar00rootroot00000000000000gtk3/* fcitx5-gtk-0.0~git20191111.6f9ef64/.gitignore000066400000000000000000000001621356221223600201410ustar00rootroot00000000000000build*/ .* !.git* .git/ *.tar.* *.kdev4 *.kate-swp *.orig tags astyle.sh cscope.* *.part XF86keysym.h keysymdef.h fcitx5-gtk-0.0~git20191111.6f9ef64/CMakeLists.txt000066400000000000000000000032111356221223600207070ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) set(FCITX5_GTK_VERSION 4.99.0) project(fcitx5-gtk VERSION ${FCITX5_GTK_VERSION}) find_package(ECM REQUIRED 1.0.0) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) include(FindPkgConfig) include(ECMSetupVersion) include(GenerateExportHeader) include(FeatureSummary) include(GNUInstallDirs) include(FcitxCompilerSettings) include(ECMUninstallTarget) option(ENABLE_GIR "GObject Introspection" ON) option(ENABLE_GTK2_IM_MODULE "Enable GTK2 IM Module" ON) option(ENABLE_GTK3_IM_MODULE "Enable GTK3 IM Module" ON) option(ENABLE_SNOOPER "Enable Key Snooper for gtk app" ON) if(NOT NO_SNOOPER_APPS) set(NO_SNOOPER_APPS ".*chrome.*,.*chromium.*,firefox.*,Do.*" CACHE STRING "Disable Key Snooper for following app") endif() configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h") include_directories("${CMAKE_CURRENT_BINARY_DIR}") find_package(PkgConfig) find_package(XKBCommon) find_package(Fcitx5Utils) pkg_check_modules(GLib2 REQUIRED IMPORTED_TARGET "glib-2.0") pkg_check_modules(Gio2 REQUIRED IMPORTED_TARGET "gio-2.0") pkg_check_modules(GObject2 REQUIRED IMPORTED_TARGET "gobject-2.0") add_subdirectory(fcitx-gclient) if (ENABLE_GTK2_IM_MODULE OR ENABLE_GTK3_IM_MODULE) find_package(X11 REQUIRED) add_library(X11Import UNKNOWN IMPORTED) set_target_properties(X11Import PROPERTIES IMPORTED_LOCATION "${X11_X11_LIB}" INTERFACE_INCLUDE_DIRECTORIES "${X11_X11_INCLUDE_PATH}") endif() if (ENABLE_GTK2_IM_MODULE) add_subdirectory(gtk2) endif() if (ENABLE_GTK3_IM_MODULE) add_subdirectory(gtk3) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) fcitx5-gtk-0.0~git20191111.6f9ef64/README.md000066400000000000000000000005771356221223600174420ustar00rootroot00000000000000Gtk im module for fcitx5 and glib based dbus client library ========================================== [![Jenkins Build Status](https://img.shields.io/jenkins/s/https/jenkins.fcitx-im.org/job/fcitx5-gtk.svg)](https://jenkins.fcitx-im.org/job/fcitx5-gtk/) [![Coverity Scan Status](https://img.shields.io/coverity/scan/12702.svg)](https://scan.coverity.com/projects/fcitx-fcitx5-gtk) fcitx5-gtk-0.0~git20191111.6f9ef64/cmake/000077500000000000000000000000001356221223600172325ustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/cmake/FcitxCompilerSettings.cmake000066400000000000000000000024671356221223600245360ustar00rootroot00000000000000 set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_STANDARD 14) set(CMAKE_C_STANDARD_REQUIRED TRUE) set(CMAKE_C_STANDARD 99) set(CMAKE_C_FLAGS "-Wall -Wextra -fvisibility=hidden ${CMAKE_C_FLAGS}") set(CMAKE_CXX_FLAGS "-Wall -Wextra -fvisibility=hidden ${CMAKE_CXX_FLAGS}") set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined -Wl,--as-needed ${CMAKE_SHARED_LINKER_FLAGS}") set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined -Wl,--as-needed ${CMAKE_MODULE_LINKER_FLAGS}") if(ENABLE_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") endif() # RPATH list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" _isSystemPlatformLibDir) list(FIND CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" _isSystemCxxLibDir) if("${_isSystemPlatformLibDir}" STREQUAL "-1" AND "${_isSystemCxxLibDir}" STREQUAL "-1") set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) endif("${_isSystemPlatformLibDir}" STREQUAL "-1" AND "${_isSystemCxxLibDir}" STREQUAL "-1") fcitx5-gtk-0.0~git20191111.6f9ef64/cmake/FindXKBCommon.cmake000066400000000000000000000022421356221223600226320ustar00rootroot00000000000000 include(ECMFindModuleHelpersStub) ecm_find_package_version_check(XKBCommon) # Note that this list needs to be ordered such that any component # appears after its dependencies set(XKBCommon_known_components XKBCommon X11) set(XKBCommon_XKBCommon_component_deps) set(XKBCommon_XKBCommon_pkg_config "xkbcommon") set(XKBCommon_XKBCommon_lib "xkbcommon") set(XKBCommon_XKBCommon_header "xkbcommon/xkbcommon.h") set(XKBCommon_X11_component_deps XKBCommon) set(XKBCommon_X11_pkg_config "xkbcommon-x11") set(XKBCommon_X11_lib "xkbcommon-x11") set(XKBCommon_X11_header "xkbcommon/xkbcommon-x11.h") ecm_find_package_parse_components(XKBCommon RESULT_VAR XKBCommon_components KNOWN_COMPONENTS ${XKBCommon_known_components} ) ecm_find_package_handle_library_components(XKBCommon COMPONENTS ${XKBCommon_components} ) find_package_handle_standard_args(XKBCommon FOUND_VAR XKBCommon_FOUND REQUIRED_VARS XKBCommon_LIBRARIES VERSION_VAR XKBCommon_VERSION HANDLE_COMPONENTS ) include(FeatureSummary) set_package_properties(XKBCommon PROPERTIES URL "http://xkbcommon.org" DESCRIPTION "Keyboard handling library using XKB data" ) fcitx5-gtk-0.0~git20191111.6f9ef64/cmake/GObjectIntrospection.cmake000066400000000000000000000157671356221223600243520ustar00rootroot00000000000000include(CMakeParseArguments) include(FindPkgConfig) pkg_check_modules(GOBJECT_INTROSPECTION REQUIRED gobject-introspection-1.0) _pkgconfig_invoke("gobject-introspection-1.0" GOBJECT_INTROSPECTION GIRDIR "" "--variable=girdir") _pkgconfig_invoke("gobject-introspection-1.0" GOBJECT_INTROSPECTION TYPELIBDIR "" "--variable=typelibdir") find_program(GIR_SCANNER NAMES g-ir-scanner DOC "g-ir-scanner executable") mark_as_advanced(GIR_SCANNER) find_program(GIR_COMPILER NAMES g-ir-compiler DOC "g-ir-compiler executable") mark_as_advanced(GIR_COMPILER) find_program(GIR_GENERATE NAMES g-ir-generate DOC "g-ir-generate executable") mark_as_advanced(GIR_GENERATE) function(_gir_list_prefix _newlist _list _prefix) set(newlist) foreach(_item IN LISTS ${_list}) list(APPEND newlist ${_prefix}${_item}) endforeach() set(${_newlist} ${newlist} PARENT_SCOPE) endfunction() function(__GIR_GET_UNIQUE_TARGET_NAME _name _unique_name) set(propertyName "_GOBJECT_INTROSPECTION_UNIQUE_COUNTER_${_name}") get_property(currentCounter GLOBAL PROPERTY "${propertyName}") if(NOT currentCounter) set(currentCounter 1) endif() set("${_unique_name}" "${_name}_${currentCounter}" PARENT_SCOPE) math(EXPR currentCounter "${currentCounter} + 1") set_property(GLOBAL PROPERTY "${propertyName}" "${currentCounter}") endfunction() function(gobject_introspection _FIRST_ARG) set(options QUIET VERBOSE) set(oneValueArgs FILENAME FORMAT LIBRARY NAMESPACE NSVERSION PROGRAM PROGRAM_ARG PACKAGE_EXPORT ) set(multiValueArgs BUILT_SOURCES CFLAGS COMPILER_ARGS HEADERS IDENTIFIER_PREFIXES PACKAGES INCLUDE SCANNER_ARGS SOURCES SYMBOL_PREFIXES ) cmake_parse_arguments(GIR "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) if(ADD_GIR_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keys given to ADD_GIR_INTROSPECTION(): \"${ADD_GIR_UNPARSED_ARGUMENTS}\"") endif() ########################################################################### # make sure that the user set some variables... ########################################################################### if(NOT GIR_FILENAME) message(FATAL_ERROR "No gir filename given") endif() if(NOT GIR_NAMESPACE) # the caller didn't give us a namespace, try to grab it from the filename string(REGEX REPLACE "([^-]+)-.*" "\\1" GIR_NAMESPACE "${GIR_FILENAME}") if(NOT GIR_NAMESPACE) message(FATAL_ERROR "No namespace given and couldn't find one in FILENAME") endif() endif() if(NOT GIR_NSVERSION) # the caller didn't give us a namespace version, # try to grab it from the filemenu string(REGEX REPLACE ".*-([^-]+).gir" "\\1" GIR_NSVERSION "${GIR_FILENAME}") if(NOT GIR_NSVERSION) message(FATAL_ERROR "No namespace version given and couldn't find one in FILENAME") endif() endif() if(NOT GIR_CFLAGS) get_directory_property(GIR_CFLAGS INCLUDE_DIRECTORIES) _gir_list_prefix(GIR_REAL_CFLAGS GIR_CFLAGS "-I") endif() ########################################################################### # Fix up some of our arguments ########################################################################### if(GIR_VERBOSE) set(GIR_VERBOSE "--verbose") else() set(GIR_VERBOSE "") endif() if(GIR_QUIET) set(GIR_QUIET "--quiet") else() set(GIR_QUIET "") endif() if(GIR_FORMAT) set(GIR_FORMAT "--format=${GIR_FORMAT}") endif() # if library is set, we need to prepend --library= on to it if(GIR_LIBRARY) set(GIR_REAL_LIBRARY "--library=${GIR_LIBRARY}") endif() # if program has been set, we prepend --program= on to it if(GIR_PROGRAM) set(GIR_PROGRAM "--program=${GIR_PROGRAM}") endif() # if program_arg has been set, we prepend --program-arg= on to it if(GIR_PROGRAM_ARG) set(GIR_PROGRAM_ARG "--program-arg=${GIR_PROGRAM_ARG}") endif() # if the user specified PACKAGE_EXPORT, # we need to prefix each with --pkg-export if(GIR_PACKAGE_EXPORT) set(GIR_REAL_PACKAGE_EXPORT GIR_PACKAGE_EXPORT "--pkg-export=") endif() ########################################################################### # Clean up any of the multivalue items that all need to be prefixed ########################################################################### # if the user specified IDENTIFIER_PREFIXES, # we need to prefix each with --identifier-prefix if(GIR_IDENTIFIER_PREFIXES) _gir_list_prefix(GIR_REAL_IDENTIFIER_PREFIXES GIR_IDENTIFIER_PREFIXES "--identifier-prefix=") endif() # if the user specified SYMBOL_PREFIXES, # we need to prefix each with --symbol-prefix= if(GIR_SYMBOL_PREFIXES) _gir_list_prefix(GIR_REAL_SYMBOL_PREFIXES GIR_SYMBOL_PREFIXES "--symbol-prefix=") endif() # if the user specified PACKAGES we need to prefix each with --pkg if(GIR_PACKAGES) _gir_list_prefix(GIR_REAL_PACKAGES GIR_PACKAGES "--pkg=") endif() # if the user specified PACKAGES we need to prefix each with --pkg if(GIR_INCLUDE) _gir_list_prefix(GIR_REAL_INCLUDE GIR_INCLUDE "--include=") endif() # if the user specified BUILT_SOURCES, we need to get their paths since # they could be in CMAKE_CURRENT_BUILD_DIR if(GIR_BUILT_SOURCES) set(GIR_REAL_BUILT_SOURCES) foreach(ITEM ${GIR_BUILT_SOURCES}) get_source_file_property(LOCATION "${ITEM}" LOCATION) list(APPEND GIR_REAL_BUILT_SOURCES "${LOCATION}") endforeach() endif() ########################################################################### # Add the custom commands ########################################################################### set(ENV{CFLAGS} ${GIR_REAL_CFLAGS}) add_custom_command( COMMAND env "LD_LIBRARY_PATH=${CMAKE_CURRENT_BINARY_DIR}" "${GIR_SCANNER}" ${GIR_SCANNER_ARGS} "--namespace=${GIR_NAMESPACE}" "--nsversion=${GIR_NSVERSION}" ${GIR_REAL_CFLAGS} ${GIR_FORMAT} ${GIR_REAL_LIBRARY} ${GIR_PROGRAM} ${GIR_PROGRAM_ARGS} ${GIR_QUIET} ${GIR_VERBOSE} ${GIR_REAL_IDENTIFIER_PREFIXES} ${GIR_REAL_SYMBOL_PREFIXES} ${GIR_REAL_PACKAGES} ${GIR_REAL_INCLUDE} --no-libtool -L${CMAKE_CURRENT_BINARY_DIR} "--output=${CMAKE_CURRENT_BINARY_DIR}/${GIR_FILENAME}" ${GIR_PACKAGE_EXPORT} ${GIR_SCANNER_FLAGS} ${GIR_SOURCES} ${GIR_REAL_BUILT_SOURCES} OUTPUT "${GIR_FILENAME}" DEPENDS ${GIR_LIBRARY} "${GIR_SCANNER}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" VERBATIM ) # create the name of the typelib string(REPLACE ".gir" ".typelib" GIR_TYPELIB "${GIR_FILENAME}") add_custom_command( COMMAND "${GIR_COMPILER}" ${GIR_COMPILER_ARGS} "${CMAKE_CURRENT_BINARY_DIR}/${GIR_FILENAME}" "--output=${CMAKE_CURRENT_BINARY_DIR}/${GIR_TYPELIB}" OUTPUT "${GIR_TYPELIB}" DEPENDS "${GIR_FILENAME}" "${GIR_COMPILER}" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) __gir_get_unique_target_name(gobject_introspection_compile_target _gir_compile_target) add_custom_target(${_gir_compile_target} ALL DEPENDS "${GIR_TYPELIB}") endfunction() fcitx5-gtk-0.0~git20191111.6f9ef64/config.h.in000066400000000000000000000004121356221223600201720ustar00rootroot00000000000000#ifndef ___CONFIG_H___ #define ___CONFIG_H___ #define LOCALEDIR "@CMAKE_INSTALL_PREFIX@/locale" #define NO_SNOOPER_APPS "@NO_SNOOPER_APPS@" #cmakedefine ENABLE_SNOOPER #ifdef ENABLE_SNOOPER #define _ENABLE_SNOOPER 1 #else #define _ENABLE_SNOOPER 0 #endif #endif fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/000077500000000000000000000000001356221223600207125ustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/CMakeLists.txt000066400000000000000000000067001356221223600234550ustar00rootroot00000000000000set(FCITX_GCLIENT_SOURCES fcitxgwatcher.c fcitxgclient.c ) set(FCITX_GCLIENT_BUILT_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/marshall.c ) set(FCITX_GCLIENT_HEADERS fcitxgclient.h fcitxgwatcher.h ) set(FCITX_GCLIENT_BUILT_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/marshall.h ) pkg_get_variable(GLIB2_GLIB_GENMARSHAL "glib-2.0" "glib_genmarshal") find_program(GLIB_GENMARSHAL ${GLIB2_GLIB_GENMARSHAL}) add_custom_command(OUTPUT marshall.c COMMAND ${GLIB_GENMARSHAL} --body --prefix=fcitx_marshall ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list > marshall.c DEPENDS ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list) add_custom_command(OUTPUT marshall.h COMMAND ${GLIB_GENMARSHAL} --header --prefix=fcitx_marshall ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list > marshall.h DEPENDS ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list) add_library(Fcitx5GClient SHARED ${FCITX_GCLIENT_SOURCES} ${FCITX_GCLIENT_BUILT_SOURCES} ${FCITX_GCLIENT_BUILT_HEADERS}) set_target_properties(Fcitx5GClient PROPERTIES VERSION 1.0 SOVERSION 1 COMPILE_FLAGS "-fvisibility=hidden" LINK_FLAGS "-Wl,--no-undefined" EXPORT_NAME GClient ) target_include_directories(Fcitx5GClient PUBLIC "$" "$/Fcitx5/GClient") target_link_libraries(Fcitx5GClient PRIVATE PkgConfig::Gio2 PkgConfig::GLib2 PkgConfig::GObject2) configure_file(Fcitx5GClient.pc.in ${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClient.pc @ONLY) install(TARGETS Fcitx5GClient EXPORT Fcitx5GClientTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClient.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") install(FILES ${FCITX_UTILS_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/Fcitx5/GClient/fcitx-gclient") add_library(Fcitx5::GClient ALIAS Fcitx5GClient) ecm_setup_version(PROJECT PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfigVersion.cmake" SOVERSION 1) configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Fcitx5GClientConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fcitx5GClient ) generate_export_header(Fcitx5GClient BASE_NAME FcitxGClient) install(EXPORT Fcitx5GClientTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fcitx5GClient FILE Fcitx5GClientTargets.cmake NAMESPACE Fcitx5::) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fcitx5GClient COMPONENT Devel ) if(ENABLE_GIR) include(GObjectIntrospection) gobject_introspection( FILENAME FcitxG-1.0.gir NSVERSION 1.0 INCLUDE Gio-2.0 GObject-2.0 GLib-2.0 PACKAGE_EXPORT Fcitx5GClient LIBRARY Fcitx5GClient NAMESPACE FcitxG SCANNER_ARGS --warn-all --add-include-path=${CMAKE_CURRENT_SOURCE_DIR} COMPILER_ARGS "--includedir=${CMAKE_CURRENT_SOURCE_DIR}" SYMBOL_PREFIXES fcitx_g SOURCES ${FCITX_GCLIENT_SOURCES} ${FCITX_GCLIENT_HEADERS} QUIET ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/FcitxG-1.0.gir" DESTINATION "${GOBJECT_INTROSPECTION_GIRDIR}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/FcitxG-1.0.typelib" DESTINATION "${GOBJECT_INTROSPECTION_TYPELIBDIR}") endif() fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/Fcitx5GClient.pc.in000066400000000000000000000004431356221223600242540ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@/Fcitx5/GClient Name: Fcitx5GClient Description: Fcitx GLib Client Library Version: @FCITX5_GTK_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lFcitx5Utils fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/Fcitx5GClientConfig.cmake.in000066400000000000000000000000171356221223600260550ustar00rootroot00000000000000@PACKAGE_INIT@ fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/fcitxgclient.c000066400000000000000000000711751356221223600235540ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2012~2012 by CSSlayer * * * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "fcitxgclient.h" #include "fcitxgclient_export.h" #include "fcitxgwatcher.h" #include "marshall.h" typedef struct _ProcessKeyStruct ProcessKeyStruct; /** * FcitxGClient: * * A #FcitxGClient allow to create a input context via DBus */ enum { PROP_0, PROP_WATCHER, }; struct _ProcessKeyStruct { FcitxGClient *self; GAsyncReadyCallback callback; void *user_data; }; struct _FcitxGClientPrivate { GDBusProxy *improxy; GDBusProxy *icproxy; gchar *icname; guint8 uuid[16]; gchar *display; GCancellable *cancellable; FcitxGWatcher *watcher; guint watch_id; }; static const gchar introspection_xml[] = "" " " " \n" " \n" " \n" " \n" " \n" " " ""; static const gchar ic_introspection_xml[] = "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"; FCITXGCLIENT_EXPORT GType fcitx_g_client_get_type(void) G_GNUC_CONST; G_DEFINE_TYPE(FcitxGClient, fcitx_g_client, G_TYPE_OBJECT); #define FCITX_G_CLIENT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE((obj), FCITX_G_TYPE_CLIENT, FcitxGClientPrivate)) enum { CONNECTED_SIGNAL, FORWARD_KEY_SIGNAL, COMMIT_STRING_SIGNAL, DELETE_SURROUNDING_TEXT_SIGNAL, UPDATED_FORMATTED_PREEDIT_SIGNAL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; static GDBusInterfaceInfo *_fcitx_g_client_get_interface_info(void); static GDBusInterfaceInfo *_fcitx_g_client_get_clientic_info(void); static void _fcitx_g_client_update_availability(FcitxGClient *self); static void _fcitx_g_client_availability_changed(FcitxGWatcher *connection, gboolean avail, gpointer user_data); static void _fcitx_g_client_service_vanished(GDBusConnection *conn, const gchar *name, gpointer user_data); static void _fcitx_g_client_create_ic(FcitxGClient *self); static void _fcitx_g_client_create_ic_phase1_finished(GObject *source_object, GAsyncResult *res, gpointer user_data); static void _fcitx_g_client_create_ic_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); static void _fcitx_g_client_create_ic_phase2_finished(GObject *source_object, GAsyncResult *res, gpointer user_data); static void _fcitx_g_client_g_signal(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data); static void _fcitx_g_client_clean_up(FcitxGClient *self); static gboolean _fcitx_g_client_recheck(gpointer user_data); static void fcitx_g_client_finalize(GObject *object); static void fcitx_g_client_dispose(GObject *object); static void fcitx_g_client_constructed(GObject *object); static void fcitx_g_client_set_property(GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec); static void _item_free(gpointer arg); #define STATIC_INTERFACE_INFO(FUNCTION, XML) \ static GDBusInterfaceInfo *FUNCTION(void) { \ static gsize has_info = 0; \ static GDBusInterfaceInfo *info = NULL; \ if (g_once_init_enter(&has_info)) { \ GDBusNodeInfo *introspection_data; \ introspection_data = g_dbus_node_info_new_for_xml(XML, NULL); \ info = introspection_data->interfaces[0]; \ g_once_init_leave(&has_info, 1); \ } \ return info; \ } STATIC_INTERFACE_INFO(_fcitx_g_client_get_interface_info, introspection_xml) STATIC_INTERFACE_INFO(_fcitx_g_client_get_clientic_info, ic_introspection_xml) static void fcitx_g_client_class_init(FcitxGClientClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS(klass); gobject_class->set_property = fcitx_g_client_set_property; gobject_class->dispose = fcitx_g_client_dispose; gobject_class->finalize = fcitx_g_client_finalize; gobject_class->constructed = fcitx_g_client_constructed; g_type_class_add_private(klass, sizeof(FcitxGClientPrivate)); g_object_class_install_property( gobject_class, PROP_WATCHER, g_param_spec_object("watcher", "Fcitx Watcher", "Fcitx Watcher", FCITX_G_TYPE_WATCHER, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); /* install signals */ /** * FcitxGClient::connected: * @self: A #FcitxGClient * * Emit when connected to fcitx and created ic */ signals[CONNECTED_SIGNAL] = g_signal_new("connected", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FcitxGClient::forward-key: * @self: A #FcitxGClient * @keyval: key value * @state: key state * @type: event type * * Emit when input method ask for forward a key */ signals[FORWARD_KEY_SIGNAL] = g_signal_new("forward-key", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL, fcitx_marshall_VOID__UINT_UINT_INT, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INT); /** * FcitxGClient::commit-string: * @self: A #FcitxGClient * @string: string to be commited * * Emit when input method commit one string */ signals[COMMIT_STRING_SIGNAL] = g_signal_new( "commit-string", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); /** * FcitxGClient::delete-surrounding-text: * @self: A #FcitxGClient * @cursor: deletion start * @len: deletion length * * Emit when input method need to delete surrounding text */ signals[DELETE_SURROUNDING_TEXT_SIGNAL] = g_signal_new( "delete-surrounding-text", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL, fcitx_marshall_VOID__INT_UINT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_UINT); /** * FcitxGClient::update-formatted-preedit: * @self: A #FcitxGClient * @preedit: (transfer none) (element-type FcitxGPreeditItem): An * #FcitxGPreeditItem List * @cursor: cursor postion by utf8 byte * * Emit when input method need to delete surrounding text */ signals[UPDATED_FORMATTED_PREEDIT_SIGNAL] = g_signal_new( "update-formatted-preedit", FCITX_G_TYPE_CLIENT, G_SIGNAL_RUN_LAST, 0, NULL, NULL, fcitx_marshall_VOID__BOXED_INT, G_TYPE_NONE, 2, G_TYPE_PTR_ARRAY, G_TYPE_INT); } static void fcitx_g_client_init(FcitxGClient *self) { self->priv = FCITX_G_CLIENT_GET_PRIVATE(self); self->priv->watcher = NULL; self->priv->cancellable = NULL; self->priv->improxy = NULL; self->priv->icproxy = NULL; self->priv->icname = NULL; self->priv->display = NULL; self->priv->watch_id = 0; } static void fcitx_g_client_constructed(GObject *object) { FcitxGClient *self = FCITX_G_CLIENT(object); G_OBJECT_CLASS(fcitx_g_client_parent_class)->constructed(object); if (!self->priv->watcher) { self->priv->watcher = fcitx_g_watcher_new(); g_object_ref_sink(self->priv->watcher); fcitx_g_watcher_watch(self->priv->watcher); } g_signal_connect(self->priv->watcher, "availability-changed", (GCallback)_fcitx_g_client_availability_changed, self); _fcitx_g_client_availability_changed( self->priv->watcher, fcitx_g_watcher_is_service_available(self->priv->watcher), self); } static void fcitx_g_client_finalize(GObject *object) { if (G_OBJECT_CLASS(fcitx_g_client_parent_class)->finalize != NULL) G_OBJECT_CLASS(fcitx_g_client_parent_class)->finalize(object); } static void fcitx_g_client_dispose(GObject *object) { FcitxGClient *self = FCITX_G_CLIENT(object); if (self->priv->icproxy) { g_dbus_proxy_call(self->priv->icproxy, "DestroyIC", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } g_signal_handlers_disconnect_by_data(self->priv->watcher, self); _fcitx_g_client_clean_up(self); g_clear_pointer(&self->priv->display, g_free); if (G_OBJECT_CLASS(fcitx_g_client_parent_class)->dispose != NULL) G_OBJECT_CLASS(fcitx_g_client_parent_class)->dispose(object); } /** * fcitx_g_client_get_uuid * @self: a #FcitxGWatcher * * Returns: (transfer none): the current uuid of input context. */ FCITXGCLIENT_EXPORT const guint8 *fcitx_g_client_get_uuid(FcitxGClient *self) { return self->priv->uuid; } /** * fcitx_g_client_focus_in: * @self: A #FcitxGClient * * tell fcitx current client has focus **/ FCITXGCLIENT_EXPORT void fcitx_g_client_focus_in(FcitxGClient *self) { g_return_if_fail(fcitx_g_client_is_valid(self)); g_dbus_proxy_call(self->priv->icproxy, "FocusIn", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } /** * fcitx_g_client_focus_out: * @self: A #FcitxGClient * * tell fcitx current client has lost focus **/ FCITXGCLIENT_EXPORT void fcitx_g_client_focus_out(FcitxGClient *self) { g_return_if_fail(fcitx_g_client_is_valid(self)); g_dbus_proxy_call(self->priv->icproxy, "FocusOut", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } /** * fcitx_g_client_reset: * @self: A #FcitxGClient * * tell fcitx current client is reset from client side **/ FCITXGCLIENT_EXPORT void fcitx_g_client_reset(FcitxGClient *self) { g_return_if_fail(fcitx_g_client_is_valid(self)); g_dbus_proxy_call(self->priv->icproxy, "Reset", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } /** * fcitx_g_client_set_capability: * @self: A #FcitxGClient * @flags: capability * * set client capability of input context. **/ FCITXGCLIENT_EXPORT void fcitx_g_client_set_capability(FcitxGClient *self, guint64 flags) { g_return_if_fail(fcitx_g_client_is_valid(self)); g_dbus_proxy_call(self->priv->icproxy, "SetCapability", g_variant_new("(t)", flags), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } /** * fcitx_g_client_set_cursor_rect: * @self: A #FcitxGClient * @x: x of cursor * @y: y of cursor * @w: width of cursor * @h: height of cursor * * tell fcitx current client's cursor geometry info **/ FCITXGCLIENT_EXPORT void fcitx_g_client_set_cursor_rect(FcitxGClient *self, gint x, gint y, gint w, gint h) { g_return_if_fail(fcitx_g_client_is_valid(self)); g_dbus_proxy_call(self->priv->icproxy, "SetCursorRect", g_variant_new("(iiii)", x, y, w, h), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } /** * fcitx_g_client_set_surrounding_text: * @self: A #FcitxGClient * @text: (transfer none) (allow-none): surroundng text * @cursor: cursor position coresponding to text * @anchor: anchor position coresponding to text **/ FCITXGCLIENT_EXPORT void fcitx_g_client_set_surrounding_text(FcitxGClient *self, gchar *text, guint cursor, guint anchor) { g_return_if_fail(fcitx_g_client_is_valid(self)); if (text) { g_dbus_proxy_call(self->priv->icproxy, "SetSurroundingText", g_variant_new("(suu)", text, cursor, anchor), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } else { g_dbus_proxy_call(self->priv->icproxy, "SetSurroundingTextPosition", g_variant_new("(uu)", cursor, anchor), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } } /** * fcitx_g_client_process_key_finish: * @self: A #FcitxGClient * @res: result * * use this function with #fcitx_g_client_process_key_async * * Returns: process key result **/ FCITXGCLIENT_EXPORT gboolean fcitx_g_client_process_key_finish(FcitxGClient *self, GAsyncResult *res) { g_return_val_if_fail(fcitx_g_client_is_valid(self), FALSE); gboolean ret = FALSE; GVariant *result = g_dbus_proxy_call_finish(self->priv->icproxy, res, NULL); if (result) { g_variant_get(result, "(b)", &ret); g_variant_unref(result); } return ret; } void _process_key_data_free(ProcessKeyStruct *pk) { g_object_unref(pk->self); g_free(pk); } void _fcitx_g_client_process_key_cb(G_GNUC_UNUSED GObject *source_object, GAsyncResult *res, gpointer user_data) { ProcessKeyStruct *pk = user_data; pk->callback(G_OBJECT(pk->self), res, pk->user_data); _process_key_data_free(pk); } void _fcitx_g_client_process_key_cancelled( G_GNUC_UNUSED GCancellable *cancellable, gpointer user_data) { ProcessKeyStruct *pk = user_data; _process_key_data_free(pk); } /** * fcitx_g_client_process_key: * @self: A #FcitxGClient * @keyval: key value * @keycode: hardware key code * @state: key state * @isRelease: event type is key release * @t: timestamp * @timeout_msec: timeout in millisecond * @cancellable: cancellable * @callback: (scope async) (closure user_data): callback * @user_data: (closure): user data * * use this function with #fcitx_g_client_process_key_finish **/ FCITXGCLIENT_EXPORT void fcitx_g_client_process_key(FcitxGClient *self, guint32 keyval, guint32 keycode, guint32 state, gboolean isRelease, guint32 t, gint timeout_msec, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_return_if_fail(fcitx_g_client_is_valid(self)); ProcessKeyStruct *pk = g_new(ProcessKeyStruct, 1); pk->self = g_object_ref(self); pk->callback = callback; pk->user_data = user_data; g_dbus_proxy_call( self->priv->icproxy, "ProcessKeyEvent", g_variant_new("(uuubu)", keyval, keycode, state, isRelease, t), G_DBUS_CALL_FLAGS_NONE, timeout_msec, cancellable, _fcitx_g_client_process_key_cb, pk); } /** * fcitx_g_client_process_key_sync: * @self: A #FcitxGClient * @keyval: key value * @keycode: hardware key code * @state: key state * @isRelease: is key release * @t: timestamp * * send a key event to fcitx synchronizely * * Returns: the key is processed or not */ FCITXGCLIENT_EXPORT gboolean fcitx_g_client_process_key_sync(FcitxGClient *self, guint32 keyval, guint32 keycode, guint32 state, gboolean isRelease, guint32 t) { g_return_val_if_fail(fcitx_g_client_is_valid(self), FALSE); gboolean ret = FALSE; GVariant *result = g_dbus_proxy_call_sync( self->priv->icproxy, "ProcessKeyEvent", g_variant_new("(uuubu)", keyval, keycode, state, isRelease, t), G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); if (result) { g_variant_get(result, "(b)", &ret); g_variant_unref(result); } return ret; } static void _fcitx_g_client_availability_changed(G_GNUC_UNUSED FcitxGWatcher *connection, G_GNUC_UNUSED gboolean avail, gpointer user_data) { FcitxGClient *self = user_data; _fcitx_g_client_update_availability(self); } static void _fcitx_g_client_update_availability(FcitxGClient *self) { g_timeout_add_full(G_PRIORITY_DEFAULT, 100, _fcitx_g_client_recheck, g_object_ref(self), g_object_unref); } static gboolean _fcitx_g_client_recheck(gpointer user_data) { FcitxGClient *self = user_data; // Check we are not valid or in the process of create ic. if (!fcitx_g_client_is_valid(self) && self->priv->cancellable == NULL && fcitx_g_watcher_is_service_available(self->priv->watcher)) { _fcitx_g_client_create_ic(self); } if (!fcitx_g_watcher_is_service_available(self->priv->watcher)) { _fcitx_g_client_clean_up(self); } return FALSE; } static void _fcitx_g_client_create_ic(FcitxGClient *self) { g_return_if_fail(fcitx_g_watcher_is_service_available(self->priv->watcher)); _fcitx_g_client_clean_up(self); const gchar *service_name = fcitx_g_watcher_get_service_name(self->priv->watcher); GDBusConnection *connection = fcitx_g_watcher_get_connection(self->priv->watcher); self->priv->watch_id = g_bus_watch_name_on_connection( connection, service_name, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, _fcitx_g_client_service_vanished, self, NULL); self->priv->cancellable = g_cancellable_new(); g_dbus_proxy_new(connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, _fcitx_g_client_get_interface_info(), service_name, "/org/freedesktop/portal/inputmethod", "org.fcitx.Fcitx.InputMethod1", self->priv->cancellable, _fcitx_g_client_create_ic_phase1_finished, g_object_ref(self)); } static void _fcitx_g_client_service_vanished(G_GNUC_UNUSED GDBusConnection *conn, G_GNUC_UNUSED const gchar *name, gpointer user_data) { FcitxGClient *self = user_data; _fcitx_g_client_clean_up(self); _fcitx_g_client_update_availability(self); } static void _fcitx_g_client_create_ic_phase1_finished(G_GNUC_UNUSED GObject *source_object, GAsyncResult *res, gpointer user_data) { FcitxGClient *self = user_data; g_return_if_fail(user_data != NULL); g_return_if_fail(FCITX_G_IS_CLIENT(user_data)); g_clear_object(&self->priv->cancellable); g_clear_object(&self->priv->improxy); self->priv->improxy = g_dbus_proxy_new_finish(res, NULL); if (!self->priv->improxy) { _fcitx_g_client_clean_up(self); g_object_unref(self); return; } self->priv->cancellable = g_cancellable_new(); GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("a(ss)")); if (self->priv->display) { g_variant_builder_add(&builder, "(ss)", "display,", self->priv->display); } g_dbus_proxy_call(self->priv->improxy, "CreateInputContext", g_variant_new("(a(ss))", &builder), G_DBUS_CALL_FLAGS_NONE, -1, /* timeout */ self->priv->cancellable, _fcitx_g_client_create_ic_cb, self); } static void _fcitx_g_client_create_ic_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { FcitxGClient *self = (FcitxGClient *)user_data; g_clear_object(&self->priv->cancellable); g_autoptr(GVariant) result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, NULL); if (!result) { _fcitx_g_client_clean_up(self); g_object_unref(self); return; } GVariantIter iter, inner; g_variant_iter_init(&iter, result); GVariant *pathVariant = g_variant_iter_next_value(&iter); const gchar *path = g_variant_get_string(pathVariant, NULL); GVariant *uuidVariant = g_variant_iter_next_value(&iter); size_t size = g_variant_iter_init(&inner, uuidVariant); if (size == 16) { int i = 0; GVariant *byte; while ((byte = g_variant_iter_next_value(&inner))) { self->priv->uuid[i] = g_variant_get_byte(byte); i++; } } self->priv->icname = g_strdup(path); self->priv->cancellable = g_cancellable_new(); g_dbus_proxy_new(g_dbus_proxy_get_connection(self->priv->improxy), G_DBUS_PROXY_FLAGS_NONE, _fcitx_g_client_get_clientic_info(), g_dbus_proxy_get_name(self->priv->improxy), self->priv->icname, "org.fcitx.Fcitx.InputContext1", self->priv->cancellable, _fcitx_g_client_create_ic_phase2_finished, self); } static void _fcitx_g_client_create_ic_phase2_finished(G_GNUC_UNUSED GObject *source_object, GAsyncResult *res, gpointer user_data) { g_return_if_fail(user_data != NULL); g_return_if_fail(FCITX_G_IS_CLIENT(user_data)); FcitxGClient *self = (FcitxGClient *)user_data; g_clear_object(&self->priv->cancellable); g_clear_object(&self->priv->icproxy); self->priv->icproxy = g_dbus_proxy_new_finish(res, NULL); if (!self->priv->icproxy) { _fcitx_g_client_clean_up(self); g_object_unref(self); return; } g_signal_connect(self->priv->icproxy, "g-signal", G_CALLBACK(_fcitx_g_client_g_signal), self); g_signal_emit(self, signals[CONNECTED_SIGNAL], 0); /* unref for _fcitx_g_client_create_ic_cb */ g_object_unref(self); } static void _item_free(gpointer arg) { FcitxGPreeditItem *item = arg; g_free(item->string); g_free(item); } static void _fcitx_g_client_g_signal(G_GNUC_UNUSED GDBusProxy *proxy, G_GNUC_UNUSED gchar *sender_name, gchar *signal_name, GVariant *parameters, gpointer user_data) { if (g_strcmp0(signal_name, "CommitString") == 0) { const gchar *data = NULL; g_variant_get(parameters, "(s)", &data); if (data) { g_signal_emit(user_data, signals[COMMIT_STRING_SIGNAL], 0, data); } } else if (g_strcmp0(signal_name, "ForwardKey") == 0) { guint32 key, state; gboolean isRelease; g_variant_get(parameters, "(uub)", &key, &state, &isRelease); g_signal_emit(user_data, signals[FORWARD_KEY_SIGNAL], 0, key, state, isRelease); } else if (g_strcmp0(signal_name, "DeleteSurroundingText") == 0) { guint32 nchar; gint32 offset; g_variant_get(parameters, "(iu)", &offset, &nchar); g_signal_emit(user_data, signals[DELETE_SURROUNDING_TEXT_SIGNAL], 0, offset, nchar); } else if (g_strcmp0(signal_name, "UpdateFormattedPreedit") == 0) { int cursor_pos; GPtrArray *array = g_ptr_array_new_with_free_func(_item_free); GVariantIter *iter; g_variant_get(parameters, "(a(si)i)", &iter, &cursor_pos); gchar *string; int type; while (g_variant_iter_next(iter, "(si)", &string, &type, NULL)) { FcitxGPreeditItem *item = g_malloc0(sizeof(FcitxGPreeditItem)); item->string = g_strdup(string); item->type = type; g_ptr_array_add(array, item); g_free(string); } g_variant_iter_free(iter); g_signal_emit(user_data, signals[UPDATED_FORMATTED_PREEDIT_SIGNAL], 0, array, cursor_pos); g_ptr_array_free(array, TRUE); } } /** * fcitx_g_client_new: * * New a #FcitxGClient * * Returns: A newly allocated #FcitxGClient **/ FCITXGCLIENT_EXPORT FcitxGClient *fcitx_g_client_new() { FcitxGClient *self = g_object_new(FCITX_G_TYPE_CLIENT, NULL); return FCITX_G_CLIENT(self); } /** * fcitx_g_client_new_with_connection: * @connection: the #FcitxConnection to be used with this client * * New a #FcitxGClient * * Returns: A newly allocated #FcitxGClient **/ FCITXGCLIENT_EXPORT FcitxGClient *fcitx_g_client_new_with_watcher(FcitxGWatcher *watcher) { FcitxGClient *self = g_object_new(FCITX_G_TYPE_CLIENT, "watcher", watcher, NULL); return FCITX_G_CLIENT(self); } FCITXGCLIENT_EXPORT void fcitx_g_client_set_display(FcitxGClient *self, const gchar *display) { g_free(self->priv->display); self->priv->display = g_strdup(display); } /** * fcitx_g_client_is_valid: * @self: A #FcitxGClient * * Check #FcitxGClient is valid to communicate with Fcitx * * Returns: #FcitxGClient is valid or not **/ FCITXGCLIENT_EXPORT gboolean fcitx_g_client_is_valid(FcitxGClient *self) { return self->priv->icproxy != NULL; } static void fcitx_g_client_set_property(GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { FcitxGClient *self = FCITX_G_CLIENT(gobject); FcitxGWatcher *watcher; switch (prop_id) { case PROP_WATCHER: watcher = g_value_get_object(value); if (watcher) { self->priv->watcher = watcher; g_object_ref_sink(self->priv->watcher); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec); break; } } static void _fcitx_g_client_clean_up(FcitxGClient *self) { if (self->priv->cancellable) { g_cancellable_cancel(self->priv->cancellable); } g_clear_object(&self->priv->cancellable); g_clear_object(&self->priv->improxy); g_clear_pointer(&self->priv->icname, g_free); if (self->priv->icproxy) { g_signal_handlers_disconnect_by_func( self->priv->icproxy, G_CALLBACK(_fcitx_g_client_g_signal), self); } g_clear_object(&self->priv->icproxy); if (self->priv->watch_id) { g_bus_unwatch_name(self->priv->watch_id); self->priv->watch_id = 0; } } // kate: indent-mode cstyle; replace-tabs on; fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/fcitxgclient.h000066400000000000000000000103171356221223600235500ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2012~2012 by CSSlayer * * * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef CLIENT_IM_H #define CLIENT_IM_H #include "fcitxgwatcher.h" #include G_BEGIN_DECLS /* * Type macros */ /* define GOBJECT macros */ #define FCITX_G_TYPE_CLIENT (fcitx_g_client_get_type()) #define FCITX_G_CLIENT(o) \ (G_TYPE_CHECK_INSTANCE_CAST((o), FCITX_G_TYPE_CLIENT, FcitxGClient)) #define FCITX_G_IS_CLIENT(object) \ (G_TYPE_CHECK_INSTANCE_TYPE((object), FCITX_G_TYPE_CLIENT)) #define FCITX_G_CLIENT_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST((k), FCITX_G_TYPE_CLIENT, FcitxGClientClass)) #define FCITX_G_CLIENT_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS((o), FCITX_G_TYPE_CLIENT, FcitxGClientClass)) typedef struct _FcitxGClient FcitxGClient; typedef struct _FcitxGClientClass FcitxGClientClass; typedef struct _FcitxGClientPrivate FcitxGClientPrivate; typedef struct _FcitxGPreeditItem FcitxGPreeditItem; struct _FcitxGClient { GObject parent_instance; /* instance member */ FcitxGClientPrivate *priv; }; struct _FcitxGClientClass { GObjectClass parent_class; /* signals */ /*< private >*/ /* padding */ }; struct _FcitxGPreeditItem { gchar *string; gint32 type; }; GType fcitx_g_client_get_type(void) G_GNUC_CONST; FcitxGClient *fcitx_g_client_new(); FcitxGClient *fcitx_g_client_new_with_watcher(FcitxGWatcher *watcher); gboolean fcitx_g_client_is_valid(FcitxGClient *self); gboolean fcitx_g_client_process_key_sync(FcitxGClient *self, guint32 keyval, guint32 keycode, guint32 state, gboolean isRelease, guint32 t); void fcitx_g_client_process_key(FcitxGClient *self, guint32 keyval, guint32 keycode, guint32 state, gboolean isRelease, guint32 t, gint timeout_msec, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean fcitx_g_client_process_key_finish(FcitxGClient *self, GAsyncResult *res); const guint8 *fcitx_g_client_get_uuid(FcitxGClient *self); void fcitx_g_client_focus_in(FcitxGClient *self); void fcitx_g_client_focus_out(FcitxGClient *self); void fcitx_g_client_set_display(FcitxGClient *self, const gchar *display); void fcitx_g_client_set_cursor_rect(FcitxGClient *self, gint x, gint y, gint w, gint h); void fcitx_g_client_set_surrounding_text(FcitxGClient *self, gchar *text, guint cursor, guint anchor); void fcitx_g_client_set_capability(FcitxGClient *self, guint64 flags); void fcitx_g_client_reset(FcitxGClient *self); G_END_DECLS #endif // CLIENT_IM_H fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/fcitxgwatcher.c000066400000000000000000000217041356221223600237240ustar00rootroot00000000000000/* * Copyright (C) 2017~2017 by CSSlayer * wengxt@gmail.com * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; see the file COPYING. If not, * see . */ #include "fcitxgwatcher.h" #include "fcitxgclient_export.h" #define FCITX_MAIN_SERVICE_NAME "org.fcitx.Fcitx5" #define FCITX_PORTAL_SERVICE_NAME "org.freedesktop.portal.Fcitx" /** * FcitxGWatcher: * * A FcitxGWatcher allow to create a input context via DBus */ struct _FcitxGWatcherPrivate { gboolean watched; guint watch_id; guint portal_watch_id; gchar *main_owner, *portal_owner; gboolean watch_portal; gboolean available; GCancellable *cancellable; GDBusConnection *connection; }; FCITXGCLIENT_EXPORT GType fcitx_g_watcher_get_type(void) G_GNUC_CONST; G_DEFINE_TYPE(FcitxGWatcher, fcitx_g_watcher, G_TYPE_OBJECT); #define FCITX_G_WATCHER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE((obj), FCITX_G_TYPE_WATCHER, \ FcitxGWatcherPrivate)) enum { AVAILABLITY_CHANGED_SIGNAL, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; static void _fcitx_g_watcher_clean_up(FcitxGWatcher *self); static void _fcitx_g_watcher_get_bus_finished(GObject *source_object, GAsyncResult *res, gpointer user_data); static void _fcitx_g_watcher_update_availability(FcitxGWatcher *self); static void fcitx_g_watcher_finalize(GObject *object); static void fcitx_g_watcher_dispose(GObject *object); static void fcitx_g_watcher_class_init(FcitxGWatcherClass *klass) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS(klass); gobject_class->dispose = fcitx_g_watcher_dispose; gobject_class->finalize = fcitx_g_watcher_finalize; g_type_class_add_private(klass, sizeof(FcitxGWatcherPrivate)); /* install signals */ /** * FcitxGWatcher::availability-changed: * @watcher: A FcitxGWatcher * @available: whether fcitx service is available. * * Emit when connected to fcitx and created ic */ signals[AVAILABLITY_CHANGED_SIGNAL] = g_signal_new( "availability-changed", FCITX_G_TYPE_WATCHER, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); } static void fcitx_g_watcher_init(FcitxGWatcher *self) { self->priv = FCITX_G_WATCHER_GET_PRIVATE(self); self->priv->connection = NULL; self->priv->cancellable = NULL; self->priv->watch_id = 0; self->priv->portal_watch_id = 0; self->priv->main_owner = NULL; self->priv->portal_owner = NULL; self->priv->watched = FALSE; } static void fcitx_g_watcher_finalize(GObject *object) { if (G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->finalize != NULL) G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->finalize(object); } static void fcitx_g_watcher_dispose(GObject *object) { FcitxGWatcher *self = FCITX_G_WATCHER(object); if (self->priv->watched) { fcitx_g_watcher_unwatch(self); } if (G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->dispose != NULL) G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->dispose(object); } static void _fcitx_g_watcher_appear(G_GNUC_UNUSED GDBusConnection *conn, const gchar *name, const gchar *name_owner, gpointer user_data) { g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); FcitxGWatcher *self = FCITX_G_WATCHER(user_data); if (g_strcmp0(name, FCITX_MAIN_SERVICE_NAME) == 0) { g_free(self->priv->main_owner); self->priv->main_owner = g_strdup(name_owner); } else if (g_strcmp0(name, FCITX_PORTAL_SERVICE_NAME) == 0) { g_free(self->priv->portal_owner); self->priv->portal_owner = g_strdup(name_owner); } _fcitx_g_watcher_update_availability(self); } static void _fcitx_g_watcher_vanish(G_GNUC_UNUSED GDBusConnection *conn, const gchar *name, gpointer user_data) { g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); FcitxGWatcher *self = FCITX_G_WATCHER(user_data); if (g_strcmp0(name, FCITX_MAIN_SERVICE_NAME) == 0) { g_free(self->priv->main_owner); self->priv->main_owner = NULL; } else if (g_strcmp0(name, FCITX_PORTAL_SERVICE_NAME) == 0) { g_free(self->priv->portal_owner); self->priv->portal_owner = NULL; } _fcitx_g_watcher_update_availability(self); } /** * fcitx_g_watcher_watch * @self: a #FcitxGWatcher * * Watch for the fcitx serivce. */ FCITXGCLIENT_EXPORT void fcitx_g_watcher_watch(FcitxGWatcher *self) { g_return_if_fail(!self->priv->watched); g_object_ref(self); g_bus_get(G_BUS_TYPE_SESSION, self->priv->cancellable, _fcitx_g_watcher_get_bus_finished, self); self->priv->watched = TRUE; }; static void _fcitx_g_watcher_get_bus_finished(G_GNUC_UNUSED GObject *source_object, GAsyncResult *res, gpointer user_data) { g_return_if_fail(user_data != NULL); g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); FcitxGWatcher *self = FCITX_G_WATCHER(user_data); _fcitx_g_watcher_clean_up(self); self->priv->connection = g_bus_get_finish(res, NULL); if (!self->priv->connection) { return; } self->priv->watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, FCITX_MAIN_SERVICE_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, _fcitx_g_watcher_appear, _fcitx_g_watcher_vanish, self, NULL); if (self->priv->watch_portal) { self->priv->portal_watch_id = g_bus_watch_name(G_BUS_TYPE_SESSION, FCITX_PORTAL_SERVICE_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, _fcitx_g_watcher_appear, _fcitx_g_watcher_vanish, self, NULL); } _fcitx_g_watcher_update_availability(self); /* unref for fcitx_g_watcher_watch */ g_object_unref(self); } /** * fcitx_g_watcher_unwatch * @self: a #FcitxGWatcher * * Unwatch for the fcitx serivce, should only be called after * calling fcitx_g_watcher_watch. */ FCITXGCLIENT_EXPORT void fcitx_g_watcher_unwatch(FcitxGWatcher *self) { g_return_if_fail(self->priv->watched); self->priv->watched = FALSE; _fcitx_g_watcher_clean_up(self); } /** * fcitx_g_watcher_new: * * New a #FcitxGWatcher * * Returns: A newly allocated #FcitxGWatcher **/ FCITXGCLIENT_EXPORT FcitxGWatcher *fcitx_g_watcher_new() { FcitxGWatcher *self = g_object_new(FCITX_G_TYPE_WATCHER, NULL); return FCITX_G_WATCHER(self); } /** * fcitx_g_watcher_is_valid: * @connection: A #FcitxGWatcher * * Check #FcitxGWatcher is valid to communicate with Fcitx * * Returns: #FcitxGWatcher is valid or not **/ FCITXGCLIENT_EXPORT gboolean fcitx_g_watcher_is_service_available(FcitxGWatcher *self) { return self->priv->available; } /** * fcitx_g_watcher_get_connection: * self: A #FcitxGWatcher * * Return the current #GDBusConnection * * Returns: (transfer none): #GDBusConnection for current connection **/ FCITXGCLIENT_EXPORT GDBusConnection *fcitx_g_watcher_get_connection(FcitxGWatcher *self) { return self->priv->connection; } void _fcitx_g_watcher_clean_up(FcitxGWatcher *self) { if (self->priv->watch_id) { g_bus_unwatch_name(self->priv->watch_id); self->priv->watch_id = 0; } if (self->priv->portal_watch_id) { g_bus_unwatch_name(self->priv->portal_watch_id); self->priv->portal_watch_id = 0; } g_clear_pointer(&self->priv->main_owner, g_free); g_clear_pointer(&self->priv->portal_owner, g_free); g_clear_object(&self->priv->cancellable); g_clear_object(&self->priv->connection); } /** * fcitx_g_watcher_set_watch_portal: * self: A #FcitxGWatcher * watch: to monitor the portal service or not. * **/ FCITXGCLIENT_EXPORT void fcitx_g_watcher_set_watch_portal(FcitxGWatcher *self, gboolean watch) { self->priv->watch_portal = watch; } void _fcitx_g_watcher_update_availability(FcitxGWatcher *self) { gboolean available = self->priv->connection && (self->priv->main_owner || self->priv->portal_owner); if (available != self->priv->available) { self->priv->available = available; g_signal_emit(self, signals[AVAILABLITY_CHANGED_SIGNAL], 0); } } /** * fcitx_g_watcher_get_service_name: * @self: A #FcitxGWatcher * * Returns: (transfer none): an available service name. * **/ FCITXGCLIENT_EXPORT const gchar *fcitx_g_watcher_get_service_name(FcitxGWatcher *self) { if (self->priv->main_owner) { return self->priv->main_owner; } if (self->priv->portal_owner) { return self->priv->portal_owner; } return NULL; } fcitx5-gtk-0.0~git20191111.6f9ef64/fcitx-gclient/fcitxgwatcher.h000066400000000000000000000047151356221223600237340ustar00rootroot00000000000000/* * Copyright (C) 2017~2017 by CSSlayer * wengxt@gmail.com * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; see the file COPYING. If not, * see . */ #ifndef _FCITX_GCLIENT_FCITXWATCHER_H_ #define _FCITX_GCLIENT_FCITXWATCHER_H_ #include /* * Type macros */ /* define GOBJECT macros */ #define FCITX_G_TYPE_WATCHER (fcitx_g_watcher_get_type()) #define FCITX_G_WATCHER(o) \ (G_TYPE_CHECK_INSTANCE_CAST((o), FCITX_G_TYPE_WATCHER, FcitxGWatcher)) #define FCITX_G_IS_WATCHER(object) \ (G_TYPE_CHECK_INSTANCE_TYPE((object), FCITX_G_TYPE_WATCHER)) #define FCITX_G_WATCHER_CLASS(k) \ (G_TYPE_CHECK_CLASS_CAST((k), FCITX_G_TYPE_WATCHER, FcitxGWatcherClass)) #define FCITX_G_WATCHER_GET_CLASS(o) \ (G_TYPE_INSTANCE_GET_CLASS((o), FCITX_G_TYPE_WATCHER, FcitxGWatcherClass)) G_BEGIN_DECLS typedef struct _FcitxGWatcher FcitxGWatcher; typedef struct _FcitxGWatcherClass FcitxGWatcherClass; typedef struct _FcitxGWatcherPrivate FcitxGWatcherPrivate; struct _FcitxGWatcher { GObject parent_instance; /* instance member */ FcitxGWatcherPrivate *priv; }; struct _FcitxGWatcherClass { GObjectClass parent_class; /* signals */ /*< private >*/ /* padding */ }; GType fcitx_g_watcher_get_type(void) G_GNUC_CONST; FcitxGWatcher *fcitx_g_watcher_new(); void fcitx_g_watcher_watch(FcitxGWatcher *self); void fcitx_g_watcher_unwatch(FcitxGWatcher *self); void fcitx_g_watcher_set_watch_portal(FcitxGWatcher *self, gboolean watch); gboolean fcitx_g_watcher_is_service_available(FcitxGWatcher *self); const gchar *fcitx_g_watcher_get_service_name(FcitxGWatcher *self); GDBusConnection *fcitx_g_watcher_get_connection(FcitxGWatcher *self); G_END_DECLS #endif // _FCITX_GCLIENT_FCITXWATCHER_H_ fcitx5-gtk-0.0~git20191111.6f9ef64/gtk-common/000077500000000000000000000000001356221223600202255ustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/gtk-common/marshall.list000066400000000000000000000002061356221223600227230ustar00rootroot00000000000000VOID:UINT,UINT,INT VOID:STRING,STRING,STRING VOID:STRING,INT VOID:BOXED,INT VOID:INT,UINT VOID:STRING,STRING,STRING,STRING,STRING,INT fcitx5-gtk-0.0~git20191111.6f9ef64/gtk2/000077500000000000000000000000001356221223600170215ustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/gtk2/CMakeLists.txt000066400000000000000000000017241356221223600215650ustar00rootroot00000000000000set(FCITX_GTK2_IM_MODULE_SOURCES fcitxim.c fcitximcontext.cpp ) pkg_check_modules(Gtk2 REQUIRED IMPORTED_TARGET "gtk+-2.0") pkg_check_modules(Gdk2 REQUIRED IMPORTED_TARGET "gdk-2.0") pkg_check_modules(Gdk2X11 REQUIRED IMPORTED_TARGET "gdk-x11-2.0") pkg_get_variable(GTK2_BINARY_VERSION "gtk+-2.0" "gtk_binary_version") if (NOT DEFINED GTK2_IM_MODULEDIR) set(GTK2_IM_MODULEDIR "${CMAKE_INSTALL_LIBDIR}/gtk-2.0/${GTK2_BINARY_VERSION}/immodules" CACHE PATH "Gtk2 im module directory") endif() set(FCITX_GTK2_IM_MODULE_SOURCES ${FCITX_GTK2_IM_MODULE_SOURCES}) add_library(im-fcitx5 MODULE ${FCITX_GTK2_IM_MODULE_SOURCES}) set_target_properties( im-fcitx5 PROPERTIES PREFIX "" COMPILE_FLAGS "-fvisibility=hidden -fno-exceptions" LINK_FLAGS "-Wl,--no-undefined") target_link_libraries(im-fcitx5 Fcitx5::Utils Fcitx5::GClient XKBCommon::XKBCommon PkgConfig::Gtk2 PkgConfig::Gdk2 PkgConfig::Gdk2X11 X11Import) install(TARGETS im-fcitx5 DESTINATION "${GTK2_IM_MODULEDIR}") fcitx5-gtk-0.0~git20191111.6f9ef64/gtk2/fcitxim.c000066400000000000000000000055731356221223600206420ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2010~2012 by CSSlayer * * * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "config.h" #include "fcitxgclient_export.h" #include "fcitximcontext.h" #include #include static const GtkIMContextInfo fcitx5_im_info = { "fcitx5", "Fcitx5 (Flexible Input Method Framework5)", "fcitx5", LOCALEDIR, "ja:ko:zh:*"}; static const GtkIMContextInfo fcitx_im_info = { "fcitx", "Fcitx5 (Flexible Input Method Framework5)", "fcitx5", LOCALEDIR, "ja:ko:zh:*"}; static const GtkIMContextInfo *info_list[] = {&fcitx_im_info, &fcitx5_im_info}; FCITXGCLIENT_EXPORT G_MODULE_EXPORT const gchar * g_module_check_init(G_GNUC_UNUSED GModule *module) { return glib_check_version(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, 0); } FCITXGCLIENT_EXPORT G_MODULE_EXPORT void im_module_init(GTypeModule *type_module) { /* make module resident */ g_type_module_use(type_module); fcitx_im_context_register_type(type_module); } FCITXGCLIENT_EXPORT G_MODULE_EXPORT void im_module_exit(void) {} FCITXGCLIENT_EXPORT G_MODULE_EXPORT GtkIMContext * im_module_create(const gchar *context_id) { if (context_id != NULL && (g_strcmp0(context_id, "fcitx5") == 0 || g_strcmp0(context_id, "fcitx") == 0)) { FcitxIMContext *context; context = fcitx_im_context_new(); return (GtkIMContext *)context; } return NULL; } FCITXGCLIENT_EXPORT G_MODULE_EXPORT void im_module_list(const GtkIMContextInfo ***contexts, gint *n_contexts) { *contexts = info_list; *n_contexts = G_N_ELEMENTS(info_list); } // kate: indent-mode cstyle; space-indent on; indent-width 0; fcitx5-gtk-0.0~git20191111.6f9ef64/gtk2/fcitximcontext.cpp000066400000000000000000001640771356221223600226140ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2010~2012 by CSSlayer * * * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ /** * @file fcitximcontext.c * * This is a gtk im module for fcitx, using DBus as a protocol. * This is compromise to gtk and firefox, users are being sucked by them * again and again. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef GDK_WINDOWING_WAYLAND #include #endif #include "config.h" #include "fcitx-gclient/fcitxgclient.h" #include "fcitx-gclient/fcitxgwatcher.h" #include "fcitximcontext.h" #if !GTK_CHECK_VERSION(2, 91, 0) #define DEPRECATED_GDK_KEYSYMS 1 #endif #if GTK_CHECK_VERSION(2, 24, 0) #define NEW_GDK_WINDOW_GET_DISPLAY #endif static const fcitx::CapabilityFlags purpose_related_capability = { fcitx::CapabilityFlag::Alpha, fcitx::CapabilityFlag::Digit, fcitx::CapabilityFlag::Number, fcitx::CapabilityFlag::Dialable, fcitx::CapabilityFlag::Url, fcitx::CapabilityFlag::Email, fcitx::CapabilityFlag::Password}; static const fcitx::CapabilityFlags hints_related_capability = { fcitx::CapabilityFlag::SpellCheck, fcitx::CapabilityFlag::NoSpellCheck, fcitx::CapabilityFlag::WordCompletion, fcitx::CapabilityFlag::Lowercase, fcitx::CapabilityFlag::Uppercase, fcitx::CapabilityFlag::UppercaseWords, fcitx::CapabilityFlag::UppwercaseSentences, fcitx::CapabilityFlag::NoOnScreenKeyboard}; extern "C" { static bool get_boolean_env(const char *name, bool defval) { const char *value = getenv(name); if (value == nullptr) { return defval; } if (g_strcmp0(value, "") == 0 || g_strcmp0(value, "0") == 0 || g_strcmp0(value, "false") == 0 || g_strcmp0(value, "False") == 0 || g_strcmp0(value, "FALSE") == 0) { return false; } return true; } struct _FcitxIMContext { GtkIMContext parent; GdkWindow *client_window; GdkRectangle area; FcitxGClient *client; GtkIMContext *slave; int has_focus; guint32 time; gboolean use_preedit; gboolean support_surrounding_text; gboolean is_inpreedit; gboolean is_wayland; gchar *preedit_string; gchar *surrounding_text; int cursor_pos; guint64 capability_from_toolkit; guint64 last_updated_capability; PangoAttrList *attrlist; gint last_cursor_pos; gint last_anchor_pos; struct xkb_compose_state *xkbComposeState; }; struct _FcitxIMContextClass { GtkIMContextClass parent; /* klass members */ }; /* functions prototype */ static void fcitx_im_context_class_init(FcitxIMContextClass *klass); static void fcitx_im_context_class_fini(FcitxIMContextClass *klass); static void fcitx_im_context_init(FcitxIMContext *im_context); static void fcitx_im_context_finalize(GObject *obj); static void fcitx_im_context_set_client_window(GtkIMContext *context, GdkWindow *client_window); static gboolean fcitx_im_context_filter_keypress(GtkIMContext *context, GdkEventKey *key); static void fcitx_im_context_reset(GtkIMContext *context); static void fcitx_im_context_focus_in(GtkIMContext *context); static void fcitx_im_context_focus_out(GtkIMContext *context); static void fcitx_im_context_set_cursor_location(GtkIMContext *context, GdkRectangle *area); static void fcitx_im_context_set_use_preedit(GtkIMContext *context, gboolean use_preedit); static void fcitx_im_context_set_surrounding(GtkIMContext *context, const gchar *text, gint len, gint cursor_index); static void fcitx_im_context_get_preedit_string(GtkIMContext *context, gchar **str, PangoAttrList **attrs, gint *cursor_pos); static gboolean _set_cursor_location_internal(FcitxIMContext *fcitxcontext); static gboolean _defer_request_surrounding_text(FcitxIMContext *fcitxcontext); static void _slave_commit_cb(GtkIMContext *slave, gchar *string, FcitxIMContext *context); static void _slave_preedit_changed_cb(GtkIMContext *slave, FcitxIMContext *context); static void _slave_preedit_start_cb(GtkIMContext *slave, FcitxIMContext *context); static void _slave_preedit_end_cb(GtkIMContext *slave, FcitxIMContext *context); static gboolean _slave_retrieve_surrounding_cb(GtkIMContext *slave, FcitxIMContext *context); static gboolean _slave_delete_surrounding_cb(GtkIMContext *slave, gint offset_from_cursor, guint nchars, FcitxIMContext *context); static void _fcitx_im_context_commit_string_cb(FcitxGClient *client, char *str, void *user_data); static void _fcitx_im_context_forward_key_cb(FcitxGClient *client, guint keyval, guint state, gint type, void *user_data); static void _fcitx_im_context_delete_surrounding_text_cb(FcitxGClient *client, gint offset_from_cursor, guint nchars, void *user_data); static void _fcitx_im_context_connect_cb(FcitxGClient *client, void *user_data); static void _fcitx_im_context_update_formatted_preedit_cb(FcitxGClient *im, GPtrArray *array, int cursor_pos, void *user_data); static void _fcitx_im_context_process_key_cb(GObject *source_object, GAsyncResult *res, gpointer user_data); static void _fcitx_im_context_set_capability(FcitxIMContext *fcitxcontext, gboolean force); #if GTK_CHECK_VERSION(3, 6, 0) static void _fcitx_im_context_input_hints_changed_cb(GObject *gobject, GParamSpec *pspec, gpointer user_data); static void _fcitx_im_context_input_purpose_changed_cb(GObject *gobject, GParamSpec *pspec, gpointer user_data); #endif static GdkEventKey *_create_gdk_event(FcitxIMContext *fcitxcontext, guint keyval, guint state, gboolean isRelease); static gboolean _key_is_modifier(guint keyval); static void _request_surrounding_text(FcitxIMContext **context); static gint _key_snooper_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data); static GType _fcitx_type_im_context = 0; static GtkIMContextClass *parent_class = NULL; static guint _signal_commit_id = 0; static guint _signal_preedit_changed_id = 0; static guint _signal_preedit_start_id = 0; static guint _signal_preedit_end_id = 0; static guint _signal_delete_surrounding_id = 0; static guint _signal_retrieve_surrounding_id = 0; static gboolean _use_sync_mode = 0; static GtkIMContext *_focus_im_context = NULL; static const gchar *_no_snooper_apps = NO_SNOOPER_APPS; static gboolean _use_key_snooper = _ENABLE_SNOOPER; static guint _key_snooper_id = 0; static FcitxGWatcher *_watcher = NULL; static struct xkb_context *xkbContext = NULL; static struct xkb_compose_table *xkbComposeTable = NULL; void fcitx_im_context_register_type(GTypeModule *type_module) { static const GTypeInfo fcitx_im_context_info = { sizeof(FcitxIMContextClass), (GBaseInitFunc)NULL, (GBaseFinalizeFunc)NULL, (GClassInitFunc)fcitx_im_context_class_init, (GClassFinalizeFunc)fcitx_im_context_class_fini, NULL, /* klass data */ sizeof(FcitxIMContext), 0, (GInstanceInitFunc)fcitx_im_context_init, 0}; if (_fcitx_type_im_context) { return; } if (type_module) { _fcitx_type_im_context = g_type_module_register_type( type_module, GTK_TYPE_IM_CONTEXT, "FcitxIMContext", &fcitx_im_context_info, (GTypeFlags)0); } else { _fcitx_type_im_context = g_type_register_static(GTK_TYPE_IM_CONTEXT, "FcitxIMContext", &fcitx_im_context_info, (GTypeFlags)0); } } GType fcitx_im_context_get_type(void) { if (_fcitx_type_im_context == 0) { fcitx_im_context_register_type(NULL); } g_assert(_fcitx_type_im_context != 0); return _fcitx_type_im_context; } FcitxIMContext *fcitx_im_context_new(void) { GObject *obj = (GObject *)g_object_new(FCITX_TYPE_IM_CONTEXT, NULL); return FCITX_IM_CONTEXT(obj); } /// static void fcitx_im_context_class_init(FcitxIMContextClass *klass) { GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS(klass); GObjectClass *gobject_class = G_OBJECT_CLASS(klass); parent_class = (GtkIMContextClass *)g_type_class_peek_parent(klass); im_context_class->set_client_window = fcitx_im_context_set_client_window; im_context_class->filter_keypress = fcitx_im_context_filter_keypress; im_context_class->reset = fcitx_im_context_reset; im_context_class->get_preedit_string = fcitx_im_context_get_preedit_string; im_context_class->focus_in = fcitx_im_context_focus_in; im_context_class->focus_out = fcitx_im_context_focus_out; im_context_class->set_cursor_location = fcitx_im_context_set_cursor_location; im_context_class->set_use_preedit = fcitx_im_context_set_use_preedit; im_context_class->set_surrounding = fcitx_im_context_set_surrounding; gobject_class->finalize = fcitx_im_context_finalize; _signal_commit_id = g_signal_lookup("commit", G_TYPE_FROM_CLASS(klass)); g_assert(_signal_commit_id != 0); _signal_preedit_changed_id = g_signal_lookup("preedit-changed", G_TYPE_FROM_CLASS(klass)); g_assert(_signal_preedit_changed_id != 0); _signal_preedit_start_id = g_signal_lookup("preedit-start", G_TYPE_FROM_CLASS(klass)); g_assert(_signal_preedit_start_id != 0); _signal_preedit_end_id = g_signal_lookup("preedit-end", G_TYPE_FROM_CLASS(klass)); g_assert(_signal_preedit_end_id != 0); _signal_delete_surrounding_id = g_signal_lookup("delete-surrounding", G_TYPE_FROM_CLASS(klass)); g_assert(_signal_delete_surrounding_id != 0); _signal_retrieve_surrounding_id = g_signal_lookup("retrieve-surrounding", G_TYPE_FROM_CLASS(klass)); g_assert(_signal_retrieve_surrounding_id != 0); _use_key_snooper = !get_boolean_env("IBUS_DISABLE_SNOOPER", !(_ENABLE_SNOOPER)) && !get_boolean_env("FCITX_DISABLE_SNOOPER", !(_ENABLE_SNOOPER)); /* env IBUS_DISABLE_SNOOPER does not exist */ if (_use_key_snooper) { /* disable snooper if app is in _no_snooper_apps */ const gchar *prgname = g_get_prgname(); if (g_getenv("IBUS_NO_SNOOPER_APPS")) { _no_snooper_apps = g_getenv("IBUS_NO_SNOOPER_APPS"); } if (g_getenv("FCITX_NO_SNOOPER_APPS")) { _no_snooper_apps = g_getenv("FCITX_NO_SNOOPER_APPS"); } gchar **p; gchar **apps = g_strsplit(_no_snooper_apps, ",", 0); for (p = apps; *p != NULL; p++) { if (g_regex_match_simple(*p, prgname, (GRegexCompileFlags)0, (GRegexMatchFlags)0)) { _use_key_snooper = FALSE; break; } } g_strfreev(apps); } /* make ibus fix benefits us */ _use_sync_mode = get_boolean_env("IBUS_ENABLE_SYNC_MODE", FALSE) || get_boolean_env("FCITX_ENABLE_SYNC_MODE", FALSE); /* always install snooper */ if (_key_snooper_id == 0) _key_snooper_id = gtk_key_snooper_install(_key_snooper_cb, NULL); } static void fcitx_im_context_class_fini(G_GNUC_UNUSED FcitxIMContextClass *klass) { if (_key_snooper_id != 0) { gtk_key_snooper_remove(_key_snooper_id); _key_snooper_id = 0; } } static void fcitx_im_context_init(FcitxIMContext *context) { context->client = NULL; context->area.x = -1; context->area.y = -1; context->area.width = 0; context->area.height = 0; context->use_preedit = TRUE; context->cursor_pos = 0; context->last_anchor_pos = -1; context->last_cursor_pos = -1; context->preedit_string = NULL; context->attrlist = NULL; context->last_updated_capability = (guint64)fcitx::CapabilityFlag::SurroundingText; #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) { context->is_wayland = TRUE; } #endif context->slave = gtk_im_context_simple_new(); g_signal_connect(context->slave, "commit", G_CALLBACK(_slave_commit_cb), context); g_signal_connect(context->slave, "preedit-start", G_CALLBACK(_slave_preedit_start_cb), context); g_signal_connect(context->slave, "preedit-end", G_CALLBACK(_slave_preedit_end_cb), context); g_signal_connect(context->slave, "preedit-changed", G_CALLBACK(_slave_preedit_changed_cb), context); g_signal_connect(context->slave, "retrieve-surrounding", G_CALLBACK(_slave_retrieve_surrounding_cb), context); g_signal_connect(context->slave, "delete-surrounding", G_CALLBACK(_slave_delete_surrounding_cb), context); #if GTK_CHECK_VERSION(3, 6, 0) g_signal_connect(context, "notify::input-hints", G_CALLBACK(_fcitx_im_context_input_hints_changed_cb), NULL); g_signal_connect(context, "notify::input-purpose", G_CALLBACK(_fcitx_im_context_input_purpose_changed_cb), NULL); #endif context->time = GDK_CURRENT_TIME; static gsize has_info = 0; if (g_once_init_enter(&has_info)) { _watcher = fcitx_g_watcher_new(); fcitx_g_watcher_set_watch_portal(_watcher, TRUE); fcitx_g_watcher_watch(_watcher); g_object_ref_sink(_watcher); xkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (xkbContext) { xkb_context_set_log_level(xkbContext, XKB_LOG_LEVEL_CRITICAL); } const char *locale = getenv("LC_ALL"); if (!locale) locale = getenv("LC_CTYPE"); if (!locale) locale = getenv("LANG"); if (!locale) locale = "C"; xkbComposeTable = xkbContext ? xkb_compose_table_new_from_locale( xkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS) : NULL; g_once_init_leave(&has_info, 1); } context->client = fcitx_g_client_new_with_watcher(_watcher); if (context->is_wayland) { fcitx_g_client_set_display(context->client, "wayland:"); } else { #if GTK_CHECK_VERSION(3, 0, 0) if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) { #endif fcitx_g_client_set_display(context->client, "x11:"); #if GTK_CHECK_VERSION(3, 0, 0) } #endif } g_signal_connect(context->client, "connected", G_CALLBACK(_fcitx_im_context_connect_cb), context); g_signal_connect(context->client, "forward-key", G_CALLBACK(_fcitx_im_context_forward_key_cb), context); g_signal_connect(context->client, "commit-string", G_CALLBACK(_fcitx_im_context_commit_string_cb), context); g_signal_connect(context->client, "delete-surrounding-text", G_CALLBACK(_fcitx_im_context_delete_surrounding_text_cb), context); g_signal_connect(context->client, "update-formatted-preedit", G_CALLBACK(_fcitx_im_context_update_formatted_preedit_cb), context); context->xkbComposeState = xkbComposeTable ? xkb_compose_state_new(xkbComposeTable, XKB_COMPOSE_STATE_NO_FLAGS) : NULL; } static void fcitx_im_context_finalize(GObject *obj) { FcitxIMContext *context = FCITX_IM_CONTEXT(obj); fcitx_im_context_set_client_window(GTK_IM_CONTEXT(context), NULL); #ifndef g_signal_handlers_disconnect_by_data #define g_signal_handlers_disconnect_by_data(instance, data) \ g_signal_handlers_disconnect_matched((instance), G_SIGNAL_MATCH_DATA, 0, \ 0, NULL, NULL, (data)) #endif g_clear_pointer(&context->xkbComposeState, xkb_compose_state_unref); if (context->client) { g_signal_handlers_disconnect_by_data(context->client, context); } g_clear_object(&context->client); g_clear_pointer(&context->preedit_string, g_free); g_clear_pointer(&context->surrounding_text, g_free); g_clear_pointer(&context->attrlist, pango_attr_list_unref); G_OBJECT_CLASS(parent_class)->finalize(obj); } /// static void fcitx_im_context_set_client_window(GtkIMContext *context, GdkWindow *client_window) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (!client_window) return; g_clear_object(&fcitxcontext->client_window); fcitxcontext->client_window = GDK_WINDOW(g_object_ref(client_window)); } static gboolean fcitx_im_context_filter_keypress_fallback(FcitxIMContext *context, GdkEventKey *event) { if (!context->xkbComposeState || event->type == GDK_KEY_RELEASE) { return gtk_im_context_filter_keypress(context->slave, event); } struct xkb_compose_state *xkbComposeState = context->xkbComposeState; enum xkb_compose_feed_result result = xkb_compose_state_feed(xkbComposeState, event->keyval); if (result == XKB_COMPOSE_FEED_IGNORED) { return gtk_im_context_filter_keypress(context->slave, event); } enum xkb_compose_status status = xkb_compose_state_get_status(xkbComposeState); if (status == XKB_COMPOSE_NOTHING) { return gtk_im_context_filter_keypress(context->slave, event); } else if (status == XKB_COMPOSE_COMPOSED) { char buffer[] = {'\0', '\0', '\0', '\0', '\0', '\0', '\0'}; int length = xkb_compose_state_get_utf8(xkbComposeState, buffer, sizeof(buffer)); xkb_compose_state_reset(xkbComposeState); if (length != 0) { g_signal_emit(context, _signal_commit_id, 0, buffer); } } else if (status == XKB_COMPOSE_CANCELLED) { xkb_compose_state_reset(xkbComposeState); } return TRUE; } /// static gboolean fcitx_im_context_filter_keypress(GtkIMContext *context, GdkEventKey *event) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); /* check this first, since we use key snooper, most key will be handled. */ if (fcitx_g_client_is_valid(fcitxcontext->client)) { /* XXX it is a workaround for some applications do not set client * window. */ if (fcitxcontext->client_window == NULL && event->window != NULL) { gtk_im_context_set_client_window((GtkIMContext *)fcitxcontext, event->window); /* set_cursor_location_internal() will get origin from X server, * it blocks UI. So delay it to idle callback. */ gdk_threads_add_idle_full( G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)_set_cursor_location_internal, g_object_ref(fcitxcontext), (GDestroyNotify)g_object_unref); } } if (event->state & (guint64)fcitx::KeyState::HandledMask) { return TRUE; } if (event->state & (guint64)fcitx::KeyState::IgnoredMask) { return fcitx_im_context_filter_keypress_fallback(fcitxcontext, event); } if (fcitx_g_client_is_valid(fcitxcontext->client) && fcitxcontext->has_focus) { _request_surrounding_text(&fcitxcontext); if (G_UNLIKELY(!fcitxcontext)) return FALSE; fcitxcontext->time = event->time; if (_use_sync_mode) { gboolean ret = fcitx_g_client_process_key_sync( fcitxcontext->client, event->keyval, event->hardware_keycode, event->state, (event->type != GDK_KEY_PRESS), event->time); if (ret) { event->state |= (guint32)fcitx::KeyState::HandledMask; return TRUE; } else { event->state |= (guint32)fcitx::KeyState::IgnoredMask; return fcitx_im_context_filter_keypress_fallback(fcitxcontext, event); } } else { fcitx_g_client_process_key( fcitxcontext->client, event->keyval, event->hardware_keycode, event->state, (event->type != GDK_KEY_PRESS), event->time, -1, NULL, _fcitx_im_context_process_key_cb, gdk_event_copy((GdkEvent *)event)); event->state |= (guint32)fcitx::KeyState::HandledMask; return TRUE; } } else { return fcitx_im_context_filter_keypress_fallback(fcitxcontext, event); } return FALSE; } static void _fcitx_im_context_process_key_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { GdkEventKey *event = (GdkEventKey *)user_data; gboolean ret = fcitx_g_client_process_key_finish(FCITX_G_CLIENT(source_object), res); if (!ret) { event->state |= (guint32)fcitx::KeyState::IgnoredMask; gdk_event_put((GdkEvent *)event); } gdk_event_free((GdkEvent *)event); } static void _fcitx_im_context_update_formatted_preedit_cb(FcitxGClient *im, GPtrArray *array, int cursor_pos, void *user_data) { FCITX_UNUSED(im); FcitxIMContext *context = FCITX_IM_CONTEXT(user_data); gboolean visible = false; if (cursor_pos < 0) { cursor_pos = 0; } if (context->preedit_string != NULL) { if (strlen(context->preedit_string) != 0) visible = true; g_clear_pointer(&context->preedit_string, g_free); } if (context->attrlist != NULL) { pango_attr_list_unref(context->attrlist); } context->attrlist = pango_attr_list_new(); GString *gstr = g_string_new(NULL); unsigned int i = 0; for (i = 0; i < array->len; i++) { size_t bytelen = strlen(gstr->str); FcitxGPreeditItem *preedit = (FcitxGPreeditItem *)g_ptr_array_index(array, i); const gchar *s = preedit->string; gint type = preedit->type; PangoAttribute *pango_attr = NULL; if ((type & (guint32)fcitx::TextFormatFlag::Underline)) { pango_attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); pango_attr->start_index = bytelen; pango_attr->end_index = bytelen + strlen(s); pango_attr_list_insert(context->attrlist, pango_attr); } if ((type & (guint32)fcitx::TextFormatFlag::Strike)) { pango_attr = pango_attr_strikethrough_new(true); pango_attr->start_index = bytelen; pango_attr->end_index = bytelen + strlen(s); pango_attr_list_insert(context->attrlist, pango_attr); } if ((type & (guint32)fcitx::TextFormatFlag::Bold)) { pango_attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD); pango_attr->start_index = bytelen; pango_attr->end_index = bytelen + strlen(s); pango_attr_list_insert(context->attrlist, pango_attr); } if ((type & (guint32)fcitx::TextFormatFlag::Italic)) { pango_attr = pango_attr_style_new(PANGO_STYLE_ITALIC); pango_attr->start_index = bytelen; pango_attr->end_index = bytelen + strlen(s); pango_attr_list_insert(context->attrlist, pango_attr); } if (type & (guint32)fcitx::TextFormatFlag::HighLight) { gboolean hasColor = false; GdkColor fg; GdkColor bg; memset(&fg, 0, sizeof(GdkColor)); memset(&bg, 0, sizeof(GdkColor)); if (context->client_window) { GtkWidget *widget; gdk_window_get_user_data(context->client_window, (gpointer *)&widget); if (GTK_IS_WIDGET(widget)) { hasColor = true; #if GTK_CHECK_VERSION(3, 0, 0) GtkStyleContext *styleContext = gtk_widget_get_style_context(widget); GdkRGBA fg_rgba, bg_rgba; hasColor = gtk_style_context_lookup_color( styleContext, "theme_selected_bg_color", &bg_rgba) && gtk_style_context_lookup_color( styleContext, "theme_selected_fg_color", &fg_rgba); if (hasColor) { fg.pixel = 0; fg.red = CLAMP((gint)(fg_rgba.red * 65535), 0, 65535); fg.green = CLAMP((gint)(fg_rgba.green * 65535), 0, 65535); fg.blue = CLAMP((gint)(fg_rgba.blue * 65535), 0, 65535); bg.pixel = 0; bg.red = CLAMP((gint)(bg_rgba.red * 65535), 0, 65535); bg.green = CLAMP((gint)(bg_rgba.green * 65535), 0, 65535); bg.blue = CLAMP((gint)(bg_rgba.blue * 65535), 0, 65535); } #else GtkStyle *style = gtk_widget_get_style(widget); fg = style->text[GTK_STATE_SELECTED]; bg = style->base[GTK_STATE_SELECTED]; #endif } } if (!hasColor) { fg.red = 0xffff; fg.green = 0xffff; fg.blue = 0xffff; bg.red = 0x43ff; bg.green = 0xacff; bg.blue = 0xe8ff; } pango_attr = pango_attr_foreground_new(fg.red, fg.green, fg.blue); pango_attr->start_index = bytelen; pango_attr->end_index = bytelen + strlen(s); pango_attr_list_insert(context->attrlist, pango_attr); pango_attr = pango_attr_background_new(bg.red, bg.green, bg.blue); pango_attr->start_index = bytelen; pango_attr->end_index = bytelen + strlen(s); pango_attr_list_insert(context->attrlist, pango_attr); } gstr = g_string_append(gstr, s); } gchar *str = g_string_free(gstr, FALSE); context->preedit_string = str; char *tempstr = g_strndup(str, cursor_pos); context->cursor_pos = fcitx_utf8_strlen(tempstr); g_free(tempstr); if (context->preedit_string != NULL && context->preedit_string[0] == 0) { g_clear_pointer(&context->preedit_string, g_free); } gboolean new_visible = context->preedit_string != NULL; gboolean flag = new_visible != visible; if (new_visible) { if (flag) { /* invisible => visible */ g_signal_emit(context, _signal_preedit_start_id, 0); } g_signal_emit(context, _signal_preedit_changed_id, 0); } else { if (flag) { /* visible => invisible */ g_signal_emit(context, _signal_preedit_changed_id, 0); g_signal_emit(context, _signal_preedit_end_id, 0); } else { /* still invisible */ /* do nothing */ } } } /// static void fcitx_im_context_focus_in(GtkIMContext *context) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (fcitxcontext->has_focus) { return; } _fcitx_im_context_set_capability(fcitxcontext, FALSE); fcitxcontext->has_focus = true; /* * Do not call gtk_im_context_focus_out() here. * This might workaround some chrome issue */ #if 0 if (_focus_im_context != NULL) { g_assert (_focus_im_context != context); gtk_im_context_focus_out (_focus_im_context); g_assert (_focus_im_context == NULL); } #endif if (fcitx_g_client_is_valid(fcitxcontext->client)) { fcitx_g_client_focus_in(fcitxcontext->client); } gtk_im_context_focus_in(fcitxcontext->slave); /* set_cursor_location_internal() will get origin from X server, * it blocks UI. So delay it to idle callback. */ gdk_threads_add_idle_full( G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)_set_cursor_location_internal, g_object_ref(fcitxcontext), (GDestroyNotify)g_object_unref); /* _request_surrounding_text may trigger freeze in Libreoffice. After * focus in, the request is not as urgent as key event. Delay it to main * idle callback. */ gdk_threads_add_idle_full( G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)_defer_request_surrounding_text, g_object_ref(fcitxcontext), (GDestroyNotify)g_object_unref); g_object_add_weak_pointer((GObject *)context, (gpointer *)&_focus_im_context); _focus_im_context = context; return; } static void fcitx_im_context_focus_out(GtkIMContext *context) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (!fcitxcontext->has_focus) { return; } g_object_remove_weak_pointer((GObject *)context, (gpointer *)&_focus_im_context); _focus_im_context = NULL; fcitxcontext->has_focus = false; if (fcitx_g_client_is_valid(fcitxcontext->client)) { fcitx_g_client_focus_out(fcitxcontext->client); } fcitxcontext->cursor_pos = 0; if (fcitxcontext->preedit_string != NULL) { g_clear_pointer(&fcitxcontext->preedit_string, g_free); g_signal_emit(fcitxcontext, _signal_preedit_changed_id, 0); g_signal_emit(fcitxcontext, _signal_preedit_end_id, 0); } gtk_im_context_focus_out(fcitxcontext->slave); return; } /// static void fcitx_im_context_set_cursor_location(GtkIMContext *context, GdkRectangle *area) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (fcitxcontext->area.x == area->x && fcitxcontext->area.y == area->y && fcitxcontext->area.width == area->width && fcitxcontext->area.height == area->height) { return; } fcitxcontext->area = *area; if (fcitx_g_client_is_valid(fcitxcontext->client)) { _set_cursor_location_internal(fcitxcontext); } gtk_im_context_set_cursor_location(fcitxcontext->slave, area); return; } static gboolean _set_cursor_location_internal(FcitxIMContext *fcitxcontext) { GdkRectangle area; if (fcitxcontext->client_window == NULL || !fcitx_g_client_is_valid(fcitxcontext->client)) { return FALSE; } area = fcitxcontext->area; #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) { gdouble px, py; GdkWindow *parent; GdkWindow *window = fcitxcontext->client_window; while ((parent = gdk_window_get_effective_parent(window)) != NULL) { gdk_window_coords_to_parent(window, area.x, area.y, &px, &py); area.x = px; area.y = py; window = parent; } } else #endif { if (area.x == -1 && area.y == -1 && area.width == 0 && area.height == 0) { #if GTK_CHECK_VERSION(2, 91, 0) area.x = 0; area.y += gdk_window_get_height(fcitxcontext->client_window); #else gint w, h; gdk_drawable_get_size(fcitxcontext->client_window, &w, &h); area.y += h; area.x = 0; #endif } #if GTK_CHECK_VERSION(2, 18, 0) gdk_window_get_root_coords(fcitxcontext->client_window, area.x, area.y, &area.x, &area.y); #else { int rootx, rooty; gdk_window_get_origin(fcitxcontext->client_window, &rootx, &rooty); area.x += rootx; area.y += rooty; } #endif } int scale = 1; #if GTK_CHECK_VERSION(3, 10, 0) scale = gdk_window_get_scale_factor(fcitxcontext->client_window); #endif area.x *= scale; area.y *= scale; area.width *= scale; area.height *= scale; fcitx_g_client_set_cursor_rect(fcitxcontext->client, area.x, area.y, area.width, area.height); return FALSE; } static gboolean _defer_request_surrounding_text(FcitxIMContext *fcitxcontext) { _request_surrounding_text(&fcitxcontext); return FALSE; } /// static void fcitx_im_context_set_use_preedit(GtkIMContext *context, gboolean use_preedit) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); fcitxcontext->use_preedit = use_preedit; _fcitx_im_context_set_capability(fcitxcontext, FALSE); gtk_im_context_set_use_preedit(fcitxcontext->slave, use_preedit); } static guint get_selection_anchor_point(FcitxIMContext *fcitxcontext, guint cursor_pos, guint surrounding_text_len) { GtkWidget *widget; if (fcitxcontext->client_window == NULL) { return cursor_pos; } gdk_window_get_user_data(fcitxcontext->client_window, (gpointer *)&widget); if (!GTK_IS_TEXT_VIEW(widget)) { return cursor_pos; } GtkTextView *text_view = GTK_TEXT_VIEW(widget); GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view); if (!gtk_text_buffer_get_has_selection(buffer)) { return cursor_pos; } GtkTextIter start_iter, end_iter, cursor_iter; if (!gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter)) { return cursor_pos; } gtk_text_buffer_get_iter_at_mark(buffer, &cursor_iter, gtk_text_buffer_get_insert(buffer)); guint start_index = gtk_text_iter_get_offset(&start_iter); guint end_index = gtk_text_iter_get_offset(&end_iter); guint cursor_index = gtk_text_iter_get_offset(&cursor_iter); guint anchor; if (start_index == cursor_index) { anchor = end_index; } else if (end_index == cursor_index) { anchor = start_index; } else { return cursor_pos; } // Change absolute index to relative position. guint relative_origin = cursor_index - cursor_pos; if (anchor < relative_origin) { return cursor_pos; } anchor -= relative_origin; if (anchor > surrounding_text_len) { return cursor_pos; } return anchor; } static void fcitx_im_context_set_surrounding(GtkIMContext *context, const gchar *text, gint l, gint cursor_index) { g_return_if_fail(context != NULL); g_return_if_fail(FCITX_IS_IM_CONTEXT(context)); g_return_if_fail(text != NULL); gint len = l; if (len < 0) { len = strlen(text); } g_return_if_fail(0 <= cursor_index && cursor_index <= len); FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (fcitx_g_client_is_valid(fcitxcontext->client) && !(fcitxcontext->last_updated_capability & (guint64)fcitx::CapabilityFlag::Password)) { gint cursor_pos; guint utf8_len; gchar *p; p = g_strndup(text, len); cursor_pos = g_utf8_strlen(p, cursor_index); utf8_len = g_utf8_strlen(p, len); gint anchor_pos = get_selection_anchor_point(fcitxcontext, cursor_pos, utf8_len); if (g_strcmp0(fcitxcontext->surrounding_text, p) == 0) { g_clear_pointer(&p, g_free); } else { g_free(fcitxcontext->surrounding_text); fcitxcontext->surrounding_text = p; } if (p || fcitxcontext->last_cursor_pos != cursor_pos || fcitxcontext->last_anchor_pos != anchor_pos) { fcitxcontext->last_cursor_pos = cursor_pos; fcitxcontext->last_anchor_pos = anchor_pos; fcitx_g_client_set_surrounding_text(fcitxcontext->client, p, cursor_pos, anchor_pos); } } gtk_im_context_set_surrounding(fcitxcontext->slave, text, l, cursor_index); } void _fcitx_im_context_set_capability(FcitxIMContext *fcitxcontext, gboolean force) { if (fcitx_g_client_is_valid(fcitxcontext->client)) { guint64 flags = fcitxcontext->capability_from_toolkit; // toolkit hint always not have preedit / surrounding hint // no need to check them if (fcitxcontext->use_preedit) { flags |= (guint64)fcitx::CapabilityFlag::Preedit | (guint64)fcitx::CapabilityFlag::FormattedPreedit; } if (fcitxcontext->support_surrounding_text) { flags |= (guint64)fcitx::CapabilityFlag::SurroundingText; } if (fcitxcontext->is_wayland) { flags |= (guint64)fcitx::CapabilityFlag::RelativeRect; } // always run this code against all gtk version // seems visibility != PASSWORD hint if (fcitxcontext->client_window != NULL) { GtkWidget *widget; gdk_window_get_user_data(fcitxcontext->client_window, (gpointer *)&widget); if (GTK_IS_ENTRY(widget) && !gtk_entry_get_visibility(GTK_ENTRY(widget))) { flags |= (guint64)fcitx::CapabilityFlag::Password; } } gboolean update = FALSE; if (G_UNLIKELY(fcitxcontext->last_updated_capability != flags)) { fcitxcontext->last_updated_capability = flags; update = TRUE; } if (G_UNLIKELY(update || force)) fcitx_g_client_set_capability( fcitxcontext->client, fcitxcontext->last_updated_capability); } } /// static void fcitx_im_context_reset(GtkIMContext *context) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (fcitx_g_client_is_valid(fcitxcontext->client)) { fcitx_g_client_reset(fcitxcontext->client); } if (fcitxcontext->xkbComposeState) { xkb_compose_state_reset(fcitxcontext->xkbComposeState); } gtk_im_context_reset(fcitxcontext->slave); } static void fcitx_im_context_get_preedit_string(GtkIMContext *context, gchar **str, PangoAttrList **attrs, gint *cursor_pos) { FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(context); if (fcitx_g_client_is_valid(fcitxcontext->client)) { if (str) { *str = g_strdup(fcitxcontext->preedit_string ? fcitxcontext->preedit_string : ""); } if (attrs) { if (fcitxcontext->attrlist == NULL) { *attrs = pango_attr_list_new(); if (str) { PangoAttribute *pango_attr; pango_attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); pango_attr->start_index = 0; pango_attr->end_index = strlen(*str); pango_attr_list_insert(*attrs, pango_attr); } } else { *attrs = pango_attr_list_ref(fcitxcontext->attrlist); } } if (cursor_pos) *cursor_pos = fcitxcontext->cursor_pos; } else { gtk_im_context_get_preedit_string(fcitxcontext->slave, str, attrs, cursor_pos); } return; } /* Callback functions for slave context */ static void _slave_commit_cb(GtkIMContext *slave, gchar *string, FcitxIMContext *context) { FCITX_UNUSED(slave); g_signal_emit(context, _signal_commit_id, 0, string); } static void _slave_preedit_changed_cb(GtkIMContext *slave, FcitxIMContext *context) { FCITX_UNUSED(slave); if (context->client) { return; } g_signal_emit(context, _signal_preedit_changed_id, 0); } static void _slave_preedit_start_cb(GtkIMContext *slave, FcitxIMContext *context) { FCITX_UNUSED(slave); if (context->client) { return; } g_signal_emit(context, _signal_preedit_start_id, 0); } static void _slave_preedit_end_cb(GtkIMContext *slave, FcitxIMContext *context) { FCITX_UNUSED(slave); if (context->client) { return; } g_signal_emit(context, _signal_preedit_end_id, 0); } static gboolean _slave_retrieve_surrounding_cb(GtkIMContext *slave, FcitxIMContext *context) { FCITX_UNUSED(slave); gboolean return_value; if (context->client) { return FALSE; } g_signal_emit(context, _signal_retrieve_surrounding_id, 0, &return_value); return return_value; } static gboolean _slave_delete_surrounding_cb(GtkIMContext *slave, gint offset_from_cursor, guint nchars, FcitxIMContext *context) { FCITX_UNUSED(slave); gboolean return_value; if (context->client) { return FALSE; } g_signal_emit(context, _signal_delete_surrounding_id, 0, offset_from_cursor, nchars, &return_value); return return_value; } void _fcitx_im_context_commit_string_cb(FcitxGClient *im, char *str, void *user_data) { FCITX_UNUSED(im); FcitxIMContext *context = FCITX_IM_CONTEXT(user_data); g_signal_emit(context, _signal_commit_id, 0, str); // Better request surrounding after commit. gdk_threads_add_idle_full( G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)_defer_request_surrounding_text, g_object_ref(context), (GDestroyNotify)g_object_unref); } void _fcitx_im_context_forward_key_cb(FcitxGClient *im, guint keyval, guint state, gboolean isRelease, void *user_data) { FCITX_UNUSED(im); FcitxIMContext *context = FCITX_IM_CONTEXT(user_data); GdkEventKey *event = _create_gdk_event(context, keyval, state, isRelease); event->state |= (guint32)fcitx::KeyState::IgnoredMask; gdk_event_put((GdkEvent *)event); gdk_event_free((GdkEvent *)event); } static void _fcitx_im_context_delete_surrounding_text_cb( FcitxGClient *im, gint offset_from_cursor, guint nchars, void *user_data) { FCITX_UNUSED(im); FcitxIMContext *context = FCITX_IM_CONTEXT(user_data); gboolean return_value; g_signal_emit(context, _signal_delete_surrounding_id, 0, offset_from_cursor, nchars, &return_value); } /* Copy from gdk */ static GdkEventKey *_create_gdk_event(FcitxIMContext *fcitxcontext, guint keyval, guint state, gboolean isRelease) { gunichar c = 0; gchar buf[8]; GdkEventKey *event = (GdkEventKey *)gdk_event_new( isRelease ? GDK_KEY_RELEASE : GDK_KEY_PRESS); if (fcitxcontext && fcitxcontext->client_window) event->window = (GdkWindow *)g_object_ref(fcitxcontext->client_window); /* The time is copied the latest value from the previous * GdkKeyEvent in filter_keypress(). * * We understand the best way would be to pass the all time value * to Fcitx functions process_key_event() and Fcitx DBus functions * ProcessKeyEvent() in IM clients and IM engines so that the * _create_gdk_event() could get the correct time values. * However it would causes to change many functions and the time value * would not provide the useful meanings for each Fcitx engines but just * pass the original value to ForwardKeyEvent(). * We use the saved value at the moment. * * Another idea might be to have the time implementation in X servers * but some Xorg uses clock_gettime() and others use gettimeofday() * and the values would be different in each implementation and * locale/remote X server. So probably that idea would not work. */ if (fcitxcontext) { event->time = fcitxcontext->time; } else { event->time = GDK_CURRENT_TIME; } event->send_event = FALSE; event->state = state; event->keyval = keyval; event->string = NULL; event->length = 0; event->hardware_keycode = 0; if (event->window) { #ifndef NEW_GDK_WINDOW_GET_DISPLAY GdkDisplay *display = gdk_display_get_default(); #else GdkDisplay *display = gdk_window_get_display(event->window); #endif GdkKeymap *keymap = gdk_keymap_get_for_display(display); GdkKeymapKey *keys; gint n_keys = 0; if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) { if (n_keys) event->hardware_keycode = keys[0].keycode; g_free(keys); } } event->group = 0; event->is_modifier = _key_is_modifier(keyval); #ifdef DEPRECATED_GDK_KEYSYMS if (keyval != GDK_VoidSymbol) #else if (keyval != GDK_KEY_VoidSymbol) #endif c = gdk_keyval_to_unicode(keyval); if (c) { gsize bytes_written; gint len; /* Apply the control key - Taken from Xlib */ if (event->state & GDK_CONTROL_MASK) { if ((c >= '@' && c < '\177') || c == ' ') c &= 0x1F; else if (c == '2') { event->string = (gchar *)g_memdup("\0\0", 2); event->length = 1; buf[0] = '\0'; goto out; } else if (c >= '3' && c <= '7') c -= ('3' - '\033'); else if (c == '8') c = '\177'; else if (c == '/') c = '_' & 0x1F; } len = g_unichar_to_utf8(c, buf); buf[len] = '\0'; event->string = g_locale_from_utf8(buf, len, NULL, &bytes_written, NULL); if (event->string) event->length = bytes_written; #ifdef DEPRECATED_GDK_KEYSYMS } else if (keyval == GDK_Escape) { #else } else if (keyval == GDK_KEY_Escape) { #endif event->length = 1; event->string = g_strdup("\033"); } #ifdef DEPRECATED_GDK_KEYSYMS else if (keyval == GDK_Return || keyval == GDK_KP_Enter) { #else else if (keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter) { #endif event->length = 1; event->string = g_strdup("\r"); } if (!event->string) { event->length = 0; event->string = g_strdup(""); } out: return event; } static gboolean _key_is_modifier(guint keyval) { /* See gdkkeys-x11.c:_gdk_keymap_key_is_modifier() for how this * really should be implemented */ switch (keyval) { #ifdef DEPRECATED_GDK_KEYSYMS case GDK_Shift_L: case GDK_Shift_R: case GDK_Control_L: case GDK_Control_R: case GDK_Caps_Lock: case GDK_Shift_Lock: case GDK_Meta_L: case GDK_Meta_R: case GDK_Alt_L: case GDK_Alt_R: case GDK_Super_L: case GDK_Super_R: case GDK_Hyper_L: case GDK_Hyper_R: case GDK_ISO_Lock: case GDK_ISO_Level2_Latch: case GDK_ISO_Level3_Shift: case GDK_ISO_Level3_Latch: case GDK_ISO_Level3_Lock: case GDK_ISO_Group_Shift: case GDK_ISO_Group_Latch: case GDK_ISO_Group_Lock: return TRUE; #else case GDK_KEY_Shift_L: case GDK_KEY_Shift_R: case GDK_KEY_Control_L: case GDK_KEY_Control_R: case GDK_KEY_Caps_Lock: case GDK_KEY_Shift_Lock: case GDK_KEY_Meta_L: case GDK_KEY_Meta_R: case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: case GDK_KEY_Super_L: case GDK_KEY_Super_R: case GDK_KEY_Hyper_L: case GDK_KEY_Hyper_R: case GDK_KEY_ISO_Lock: case GDK_KEY_ISO_Level2_Latch: case GDK_KEY_ISO_Level3_Shift: case GDK_KEY_ISO_Level3_Latch: case GDK_KEY_ISO_Level3_Lock: case GDK_KEY_ISO_Level5_Shift: case GDK_KEY_ISO_Level5_Latch: case GDK_KEY_ISO_Level5_Lock: case GDK_KEY_ISO_Group_Shift: case GDK_KEY_ISO_Group_Latch: case GDK_KEY_ISO_Group_Lock: return TRUE; #endif default: return FALSE; } } void send_uuid_to_x11(Display *xdisplay, const guint8 *uuid) { Atom atom = XInternAtom(xdisplay, "_FCITX_SERVER", False); if (!atom) { return; } Window window = XGetSelectionOwner(xdisplay, atom); if (!window) { return; } XEvent ev; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = window; ev.xclient.message_type = atom; ev.xclient.format = 8; memcpy(ev.xclient.data.b, uuid, 16); XSendEvent(xdisplay, window, False, NoEventMask, &ev); XSync(xdisplay, False); } void _fcitx_im_context_connect_cb(FcitxGClient *im, void *user_data) { FCITX_UNUSED(im); FcitxIMContext *context = FCITX_IM_CONTEXT(user_data); Display *display = NULL; if (context->client_window) { #if GTK_CHECK_VERSION(3, 0, 0) if (GDK_IS_X11_WINDOW(context->client_window)) { display = GDK_WINDOW_XDISPLAY(context->client_window); } #else if (GDK_IS_WINDOW(context->client_window)) { auto drawable = gdk_x11_window_get_drawable_impl(context->client_window); // We use the logic in gtk's get_impl_drawable. if (GDK_IS_WINDOW (drawable) || GDK_IS_PIXMAP (drawable)) { display = gdk_x11_drawable_get_xdisplay(drawable); } } #endif } else { GdkDisplay *gdkDisplay = gdk_display_get_default(); #if GTK_CHECK_VERSION(3, 0, 0) if (GDK_IS_X11_DISPLAY(gdkDisplay)) { #endif display = GDK_DISPLAY_XDISPLAY(gdkDisplay); #if GTK_CHECK_VERSION(3, 0, 0) } #endif } if (display) { send_uuid_to_x11(display, fcitx_g_client_get_uuid(im)); } _fcitx_im_context_set_capability(context, TRUE); if (context->has_focus && _focus_im_context == (GtkIMContext *)context && fcitx_g_client_is_valid(context->client)) fcitx_g_client_focus_in(context->client); /* set_cursor_location_internal() will get origin from X server, * it blocks UI. So delay it to idle callback. */ gdk_threads_add_idle_full( G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)_set_cursor_location_internal, g_object_ref(context), (GDestroyNotify)g_object_unref); } static void _request_surrounding_text(FcitxIMContext **context) { if (*context && fcitx_g_client_is_valid((*context)->client) && (*context)->has_focus) { gboolean return_value; /* according to RH#859879, something bad could happen here. */ g_object_add_weak_pointer((GObject *)*context, (gpointer *)context); /* some unref can happen here */ g_signal_emit(*context, _signal_retrieve_surrounding_id, 0, &return_value); if (*context) g_object_remove_weak_pointer((GObject *)*context, (gpointer *)context); else return; if (return_value) { (*context)->support_surrounding_text = TRUE; _fcitx_im_context_set_capability(*context, FALSE); } else { (*context)->support_surrounding_text = FALSE; _fcitx_im_context_set_capability(*context, FALSE); } } } static gint _key_snooper_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { FCITX_UNUSED(widget); FCITX_UNUSED(user_data); gboolean retval = FALSE; FcitxIMContext *fcitxcontext = (FcitxIMContext *)_focus_im_context; if (G_UNLIKELY(!_use_key_snooper)) return FALSE; if (fcitxcontext == NULL || !fcitxcontext->has_focus) return FALSE; if (G_UNLIKELY(event->state & (guint32)fcitx::KeyState::HandledMask)) return TRUE; if (G_UNLIKELY(event->state & (guint32)fcitx::KeyState::IgnoredMask)) return FALSE; do { if (!fcitx_g_client_is_valid(fcitxcontext->client)) { break; } _request_surrounding_text(&fcitxcontext); if (G_UNLIKELY(!fcitxcontext)) return FALSE; fcitxcontext->time = event->time; if (_use_sync_mode) { retval = fcitx_g_client_process_key_sync( fcitxcontext->client, event->keyval, event->hardware_keycode, event->state, (event->type == GDK_KEY_RELEASE), event->time); } else { fcitx_g_client_process_key( fcitxcontext->client, event->keyval, event->hardware_keycode, event->state, (event->type == GDK_KEY_RELEASE), event->time, -1, NULL, _fcitx_im_context_process_key_cb, gdk_event_copy((GdkEvent *)event)); retval = TRUE; } } while (0); if (!retval) { event->state |= (guint32)fcitx::KeyState::IgnoredMask; return FALSE; } else { event->state |= (guint32)fcitx::KeyState::HandledMask; return TRUE; } return retval; } #if GTK_CHECK_VERSION(3, 6, 0) void _fcitx_im_context_input_purpose_changed_cb(GObject *gobject, GParamSpec *pspec, gpointer user_data) { FCITX_UNUSED(pspec); FCITX_UNUSED(user_data); FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(gobject); GtkInputPurpose purpose; g_object_get(gobject, "input-purpose", &purpose, NULL); fcitxcontext->capability_from_toolkit &= ~purpose_related_capability; #define CASE_PURPOSE(_PURPOSE, _CAPABILITY) \ case _PURPOSE: \ fcitxcontext->capability_from_toolkit |= (guint64)_CAPABILITY; \ break; switch (purpose) { CASE_PURPOSE(GTK_INPUT_PURPOSE_ALPHA, fcitx::CapabilityFlag::Alpha) CASE_PURPOSE(GTK_INPUT_PURPOSE_DIGITS, fcitx::CapabilityFlag::Digit); CASE_PURPOSE(GTK_INPUT_PURPOSE_NUMBER, fcitx::CapabilityFlag::Number) CASE_PURPOSE(GTK_INPUT_PURPOSE_PHONE, fcitx::CapabilityFlag::Dialable) CASE_PURPOSE(GTK_INPUT_PURPOSE_URL, fcitx::CapabilityFlag::Url) CASE_PURPOSE(GTK_INPUT_PURPOSE_EMAIL, fcitx::CapabilityFlag::Email) CASE_PURPOSE(GTK_INPUT_PURPOSE_NAME, fcitx::CapabilityFlag::Name) CASE_PURPOSE(GTK_INPUT_PURPOSE_PASSWORD, fcitx::CapabilityFlag::Password) CASE_PURPOSE(GTK_INPUT_PURPOSE_PIN, (guint64)fcitx::CapabilityFlag::Password | (guint64)fcitx::CapabilityFlag::Digit) case GTK_INPUT_PURPOSE_FREE_FORM: default: break; } _fcitx_im_context_set_capability(fcitxcontext, FALSE); } void _fcitx_im_context_input_hints_changed_cb(GObject *gobject, GParamSpec *pspec, gpointer user_data) { FCITX_UNUSED(pspec); FCITX_UNUSED(user_data); FcitxIMContext *fcitxcontext = FCITX_IM_CONTEXT(gobject); GtkInputHints hints; g_object_get(gobject, "input-hints", &hints, NULL); fcitxcontext->capability_from_toolkit &= ~hints_related_capability; #define CHECK_HINTS(_HINTS, _CAPABILITY) \ if (hints & _HINTS) \ fcitxcontext->capability_from_toolkit |= (guint64)_CAPABILITY; CHECK_HINTS(GTK_INPUT_HINT_SPELLCHECK, fcitx::CapabilityFlag::SpellCheck) CHECK_HINTS(GTK_INPUT_HINT_NO_SPELLCHECK, fcitx::CapabilityFlag::NoSpellCheck); CHECK_HINTS(GTK_INPUT_HINT_WORD_COMPLETION, fcitx::CapabilityFlag::WordCompletion) CHECK_HINTS(GTK_INPUT_HINT_LOWERCASE, fcitx::CapabilityFlag::Lowercase) CHECK_HINTS(GTK_INPUT_HINT_UPPERCASE_CHARS, fcitx::CapabilityFlag::Uppercase) CHECK_HINTS(GTK_INPUT_HINT_UPPERCASE_WORDS, fcitx::CapabilityFlag::UppercaseWords) CHECK_HINTS(GTK_INPUT_HINT_UPPERCASE_SENTENCES, fcitx::CapabilityFlag::UppwercaseSentences) CHECK_HINTS(GTK_INPUT_HINT_INHIBIT_OSK, fcitx::CapabilityFlag::NoOnScreenKeyboard) _fcitx_im_context_set_capability(fcitxcontext, FALSE); } #endif } // kate: indent-mode cstyle; replace-tabs on; fcitx5-gtk-0.0~git20191111.6f9ef64/gtk2/fcitximcontext.h000066400000000000000000000051511356221223600222440ustar00rootroot00000000000000/*************************************************************************** * Copyright (C) 2010~2012 by CSSlayer * * * * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef __FCITX_IM_CONTEXT_H_ #define __FCITX_IM_CONTEXT_H_ #include /* * Type macros. */ #define FCITX_TYPE_IM_CONTEXT (fcitx_im_context_get_type()) #define FCITX_IM_CONTEXT(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContext)) #define FCITX_IM_CONTEXT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), FCITX_TYPE_IM_CONTEXT, FcitxIMContextClass)) #define FCITX_IS_IM_CONTEXT(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj), FCITX_TYPE_IM_CONTEXT)) #define FCITX_IS_IM_CONTEXT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass), FCITX_TYPE_IM_CONTEXT)) #define FCITX_IM_CONTEXT_GET_CLASS(obj) \ (G_TYPE_CHECK_GET_CLASS((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContextClass)) G_BEGIN_DECLS typedef struct _FcitxIMContext FcitxIMContext; typedef struct _FcitxIMContextClass FcitxIMContextClass; GType fcitx_im_context_get_type(void); FcitxIMContext *fcitx_im_context_new(void); void fcitx_im_context_register_type(GTypeModule *type_module); G_END_DECLS #endif // kate: indent-mode cstyle; space-indent on; indent-width 0; fcitx5-gtk-0.0~git20191111.6f9ef64/gtk3/000077500000000000000000000000001356221223600170225ustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/gtk3/CMakeLists.txt000066400000000000000000000017771356221223600215760ustar00rootroot00000000000000set(FCITX_GTK3_IM_MODULE_SOURCES fcitxim.c fcitximcontext.cpp ) pkg_check_modules(Gtk3 REQUIRED IMPORTED_TARGET "gtk+-3.0") pkg_check_modules(Gdk3 REQUIRED IMPORTED_TARGET "gdk-3.0") pkg_check_modules(Gdk3X11 REQUIRED IMPORTED_TARGET "gdk-x11-3.0") pkg_get_variable(GTK3_BINARY_VERSION "gtk+-3.0" "gtk_binary_version") if (NOT DEFINED GTK3_IM_MODULEDIR) set(GTK3_IM_MODULEDIR "${CMAKE_INSTALL_LIBDIR}/gtk-3.0/${GTK3_BINARY_VERSION}/immodules" CACHE PATH "Gtk3 im module directory") endif() set(FCITX_GTK3_IM_MODULE_SOURCES ${FCITX_GTK3_IM_MODULE_SOURCES}) add_library(im-fcitx5-gtk3 MODULE ${FCITX_GTK3_IM_MODULE_SOURCES}) set_target_properties(im-fcitx5-gtk3 PROPERTIES PREFIX "" OUTPUT_NAME "im-fcitx5" COMPILE_FLAGS "-fvisibility=hidden -fno-exceptions" LINK_FLAGS "-Wl,--no-undefined") target_link_libraries(im-fcitx5-gtk3 Fcitx5::Utils Fcitx5::GClient XKBCommon::XKBCommon PkgConfig::Gtk3 PkgConfig::Gdk3 PkgConfig::Gdk3X11 X11Import) install(TARGETS im-fcitx5-gtk3 DESTINATION "${GTK3_IM_MODULEDIR}") fcitx5-gtk-0.0~git20191111.6f9ef64/gtk3/fcitxim.c000077700000000000000000000000001356221223600235152../gtk2/fcitxim.custar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/gtk3/fcitximcontext.cpp000077700000000000000000000000001356221223600274272../gtk2/fcitximcontext.cppustar00rootroot00000000000000fcitx5-gtk-0.0~git20191111.6f9ef64/gtk3/fcitximcontext.h000077700000000000000000000000001356221223600265412../gtk2/fcitximcontext.hustar00rootroot00000000000000