├── .clang-format ├── .formatignore ├── .github └── workflows │ └── check.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSES └── LGPL-2.1-or-later.txt ├── README.md ├── cmake ├── CompilerSettings.cmake ├── FindXKBCommon.cmake └── GObjectIntrospection.cmake ├── config.h.in ├── fcitx-gclient ├── CMakeLists.txt ├── Fcitx5GClient.pc.in ├── Fcitx5GClientConfig.cmake.in ├── fcitxgclient.c ├── fcitxgclient.h ├── fcitxgwatcher.c └── fcitxgwatcher.h ├── gtk-common └── marshall.list ├── gtk2 ├── CMakeLists.txt ├── fcitxflags.h ├── fcitxim.c ├── fcitximcontext.cpp ├── fcitximcontext.h ├── immodule-probing.cpp ├── utils.cpp └── utils.h ├── gtk3 ├── CMakeLists.txt ├── fcitxflags.h ├── fcitxim.c ├── fcitximcontext.cpp ├── fcitximcontext.h ├── fcitxtheme.cpp ├── fcitxtheme.h ├── gtk3inputwindow.cpp ├── gtk3inputwindow.h ├── immodule-probing.cpp ├── inputwindow.cpp ├── inputwindow.h ├── utils.cpp └── utils.h └── gtk4 ├── CMakeLists.txt ├── fcitx5imcontext.h ├── fcitxflags.h ├── fcitxim.c ├── fcitximcontext.cpp ├── fcitximcontext.h ├── fcitximcontext5.cpp ├── fcitximcontext5.h ├── fcitximcontextprivate.h ├── fcitxtheme.cpp ├── fcitxtheme.h ├── gtk4inputwindow.cpp ├── gtk4inputwindow.h ├── immodule-probing.cpp ├── inputwindow.cpp ├── inputwindow.h ├── utils.cpp └── utils.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | ConstructorInitializerIndentWidth: 4 6 | AlignEscapedNewlinesLeft: false 7 | AlignTrailingComments: true 8 | AllowAllParametersOfDeclarationOnNextLine: true 9 | AllowShortBlocksOnASingleLine: false 10 | AllowShortIfStatementsOnASingleLine: false 11 | AllowShortLoopsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AlwaysBreakTemplateDeclarations: true 14 | AlwaysBreakBeforeMultilineStrings: false 15 | BreakBeforeBinaryOperators: false 16 | BreakBeforeTernaryOperators: true 17 | BreakConstructorInitializersBeforeComma: false 18 | BinPackParameters: true 19 | ColumnLimit: 80 20 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 21 | DerivePointerAlignment: false 22 | ExperimentalAutoDetectBinPacking: false 23 | IndentCaseLabels: false 24 | IndentWrappedFunctionNames: false 25 | IndentFunctionDeclarationAfterType: false 26 | MaxEmptyLinesToKeep: 1 27 | KeepEmptyLinesAtTheStartOfBlocks: true 28 | NamespaceIndentation: None 29 | ObjCSpaceAfterProperty: false 30 | ObjCSpaceBeforeProtocolList: true 31 | PenaltyBreakBeforeFirstCallParameter: 19 32 | PenaltyBreakComment: 300 33 | PenaltyBreakString: 1000 34 | PenaltyBreakFirstLessLess: 120 35 | PenaltyExcessCharacter: 1000000 36 | PenaltyReturnTypeOnItsOwnLine: 60 37 | PointerAlignment: Right 38 | SpacesBeforeTrailingComments: 1 39 | Cpp11BracedListStyle: true 40 | Standard: Cpp11 41 | IndentWidth: 4 42 | TabWidth: 4 43 | UseTab: Never 44 | BreakBeforeBraces: Attach 45 | SpacesInParentheses: false 46 | SpacesInAngles: false 47 | SpaceInEmptyParentheses: false 48 | SpacesInCStyleCastParentheses: false 49 | SpacesInContainerLiterals: true 50 | SpaceBeforeAssignmentOperators: true 51 | ContinuationIndentWidth: 4 52 | CommentPragmas: '^ IWYU pragma:' 53 | ForEachMacros: [ Q_FOREACH, BOOST_FOREACH ] 54 | SpaceBeforeParens: ControlStatements 55 | DisableFormat: false 56 | SortIncludes: true 57 | ... 58 | 59 | -------------------------------------------------------------------------------- /.formatignore: -------------------------------------------------------------------------------- 1 | gtk3/* 2 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | clang-format: 11 | name: Check clang-format 12 | runs-on: ubuntu-latest 13 | container: archlinux:latest 14 | steps: 15 | - name: Install dependencies 16 | run: | 17 | pacman -Syu --noconfirm git clang diffutils 18 | git config --global --add safe.directory $GITHUB_WORKSPACE 19 | - uses: actions/checkout@v4 20 | - uses: fcitx/github-actions@clang-format 21 | check: 22 | name: Build and test 23 | needs: clang-format 24 | runs-on: ubuntu-latest 25 | container: archlinux:latest 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | compiler: [gcc, clang] 30 | include: 31 | - compiler: gcc 32 | cxx_compiler: g++ 33 | - compiler: clang 34 | cxx_compiler: clang++ 35 | env: 36 | CC: ${{ matrix.compiler }} 37 | CXX: ${{ matrix.cxx_compiler }} 38 | steps: 39 | - name: Install dependencies 40 | run: | 41 | pacman -Syu --noconfirm base-devel clang cmake ninja extra-cmake-modules fmt libuv git glib2-devel gtk2 gtk3 gtk4 libx11 gobject-introspection 42 | - uses: actions/checkout@v4 43 | with: 44 | path: fcitx5-gtk 45 | - name: Init CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: cpp 49 | source-root: fcitx5-gtk 50 | - name: Build and Install fcitx5-gtk 51 | uses: fcitx/github-actions@cmake 52 | with: 53 | path: fcitx5-gtk 54 | cmake-option: >- 55 | -DENABLE_GIR=On 56 | -DENABLE_GTK2_IM_MODULE=On 57 | -DENABLE_GTK3_IM_MODULE=On 58 | -DENABLE_GTK4_IM_MODULE=On 59 | - name: CodeQL Analysis 60 | uses: github/codeql-action/analyze@v2 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | .* 3 | !.git* 4 | .git/ 5 | *.tar.* 6 | *.kdev4 7 | *.kate-swp 8 | *.orig 9 | tags 10 | astyle.sh 11 | cscope.* 12 | *.part 13 | XF86keysym.h 14 | keysymdef.h 15 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(fcitx5-gtk VERSION 5.1.3) 3 | 4 | find_package(ECM REQUIRED 1.0.0) 5 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 6 | 7 | include(FindPkgConfig) 8 | include(ECMSetupVersion) 9 | include(GenerateExportHeader) 10 | include(FeatureSummary) 11 | include(GNUInstallDirs) 12 | include(ECMUninstallTarget) 13 | 14 | option(ENABLE_GIR "GObject Introspection" ON) 15 | option(ENABLE_GTK2_IM_MODULE "Enable GTK2 IM Module" ON) 16 | option(ENABLE_GTK3_IM_MODULE "Enable GTK3 IM Module" ON) 17 | option(ENABLE_GTK4_IM_MODULE "Enable GTK4 IM Module" ON) 18 | option(ENABLE_SNOOPER "Enable Key Snooper for gtk app" ON) 19 | option(BUILD_ONLY_PLUGIN "Build only IM Module" OFF) 20 | 21 | set(NO_SNOOPER_APPS ".*chrome.*,.*chromium.*,firefox.*,Do.*" 22 | CACHE STRING "Disable Key Snooper for following app by default.") 23 | set(NO_PREEDIT_APPS "gvim.*" CACHE STRING "Disable preedit for follwing app by default.") 24 | set(SYNC_MODE_APPS "firefox.*" CACHE STRING "Use sync mode for following app by default.") 25 | 26 | configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h") 27 | include_directories("${CMAKE_CURRENT_BINARY_DIR}") 28 | find_package(PkgConfig) 29 | find_package(XKBCommon) 30 | pkg_check_modules(GLib2 REQUIRED IMPORTED_TARGET "glib-2.0>=2.56") 31 | pkg_check_modules(Gio2 REQUIRED IMPORTED_TARGET "gio-2.0") 32 | pkg_check_modules(GioUnix2 REQUIRED IMPORTED_TARGET "gio-unix-2.0") 33 | pkg_check_modules(GObject2 REQUIRED IMPORTED_TARGET "gobject-2.0") 34 | 35 | set(CMAKE_C_VISIBILITY_PRESET default) 36 | 37 | include(cmake/CompilerSettings.cmake) 38 | 39 | if (BUILD_ONLY_PLUGIN) 40 | set(GCLIENT_LIBRARY_TYPE STATIC) 41 | else() 42 | set(GCLIENT_LIBRARY_TYPE SHARED) 43 | endif() 44 | 45 | add_subdirectory(fcitx-gclient) 46 | 47 | set(NEED_X11 FALSE) 48 | if (ENABLE_GTK2_IM_MODULE) 49 | pkg_check_modules(Gtk2 REQUIRED IMPORTED_TARGET "gtk+-2.0") 50 | pkg_check_modules(Gdk2 REQUIRED IMPORTED_TARGET "gdk-2.0") 51 | pkg_check_modules(Gdk2X11 REQUIRED IMPORTED_TARGET "gdk-x11-2.0") 52 | pkg_get_variable(GTK2_BINARY_VERSION "gtk+-2.0" "gtk_binary_version") 53 | set(NEED_X11 TRUE) 54 | endif() 55 | 56 | if (ENABLE_GTK3_IM_MODULE) 57 | pkg_check_modules(Gtk3 REQUIRED IMPORTED_TARGET "gtk+-3.0") 58 | pkg_check_modules(Gdk3 REQUIRED IMPORTED_TARGET "gdk-3.0") 59 | pkg_get_variable(GTK3_BINARY_VERSION "gtk+-3.0" "gtk_binary_version") 60 | pkg_get_variable(GTK3_TARGETS "gtk+-3.0" "targets") 61 | if (GTK3_TARGETS MATCHES "x11") 62 | set(NEED_X11 TRUE) 63 | pkg_check_modules(Gdk3X11 REQUIRED IMPORTED_TARGET "gdk-x11-3.0") 64 | endif() 65 | endif() 66 | 67 | if (ENABLE_GTK4_IM_MODULE) 68 | pkg_check_modules(Gtk4 REQUIRED IMPORTED_TARGET "gtk4>=4.2") 69 | pkg_get_variable(GTK4_BINARY_VERSION "gtk4" "gtk_binary_version") 70 | pkg_get_variable(GTK4_TARGETS "gtk4" "targets") 71 | if (GTK4_TARGETS MATCHES "x11") 72 | set(NEED_X11 TRUE) 73 | pkg_check_modules(Gtk4X11 REQUIRED IMPORTED_TARGET "gtk4-x11") 74 | endif() 75 | endif() 76 | 77 | if (NEED_X11) 78 | find_package(X11 REQUIRED) 79 | add_library(X11Import UNKNOWN IMPORTED) 80 | set_target_properties(X11Import PROPERTIES 81 | IMPORTED_LOCATION "${X11_X11_LIB}" 82 | INTERFACE_INCLUDE_DIRECTORIES "${X11_X11_INCLUDE_PATH}") 83 | endif() 84 | 85 | if (ENABLE_GTK2_IM_MODULE) 86 | add_subdirectory(gtk2) 87 | endif() 88 | 89 | if (ENABLE_GTK3_IM_MODULE) 90 | add_subdirectory(gtk3) 91 | endif() 92 | 93 | if (ENABLE_GTK4_IM_MODULE) 94 | add_subdirectory(gtk4) 95 | endif() 96 | 97 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 98 | -------------------------------------------------------------------------------- /LICENSES/LGPL-2.1-or-later.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 2.1, February 1999 4 | 5 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 6 | 7 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 8 | 9 | Everyone is permitted to copy and distribute verbatim copies of this license 10 | document, but changing it is not allowed. 11 | 12 | [This is the first released version of the Lesser GPL. It also counts as the 13 | successor of the GNU Library Public License, version 2, hence the version 14 | number 2.1.] 15 | 16 | Preamble 17 | 18 | The licenses for most software are designed to take away your freedom to share 19 | and change it. By contrast, the GNU General Public Licenses are intended to 20 | guarantee your freedom to share and change free software--to make sure the 21 | software is free for all its users. 22 | 23 | This license, the Lesser General Public License, applies to some specially 24 | designated software packages--typically libraries--of the Free Software Foundation 25 | and other authors who decide to use it. You can use it too, but we suggest 26 | you first think carefully about whether this license or the ordinary General 27 | Public License is the better strategy to use in any particular case, based 28 | on the explanations below. 29 | 30 | When we speak of free software, we are referring to freedom of use, not price. 31 | Our General Public Licenses are designed to make sure that you have the freedom 32 | to distribute copies of free software (and charge for this service if you 33 | wish); that you receive source code or can get it if you want it; that you 34 | can change the software and use pieces of it in new free programs; and that 35 | you are informed that you can do these things. 36 | 37 | To protect your rights, we need to make restrictions that forbid distributors 38 | to deny you these rights or to ask you to surrender these rights. These restrictions 39 | translate to certain responsibilities for you if you distribute copies of 40 | the library or if you modify it. 41 | 42 | For example, if you distribute copies of the library, whether gratis or for 43 | a fee, you must give the recipients all the rights that we gave you. You must 44 | make sure that they, too, receive or can get the source code. If you link 45 | other code with the library, you must provide complete object files to the 46 | recipients, so that they can relink them with the library after making changes 47 | to the library and recompiling it. And you must show them these terms so they 48 | know their rights. 49 | 50 | We protect your rights with a two-step method: (1) we copyright the library, 51 | and (2) we offer you this license, which gives you legal permission to copy, 52 | distribute and/or modify the library. 53 | 54 | To protect each distributor, we want to make it very clear that there is no 55 | warranty for the free library. Also, if the library is modified by someone 56 | else and passed on, the recipients should know that what they have is not 57 | the original version, so that the original author's reputation will not be 58 | affected by problems that might be introduced by others. 59 | 60 | Finally, software patents pose a constant threat to the existence of any free 61 | program. We wish to make sure that a company cannot effectively restrict the 62 | users of a free program by obtaining a restrictive license from a patent holder. 63 | Therefore, we insist that any patent license obtained for a version of the 64 | library must be consistent with the full freedom of use specified in this 65 | license. 66 | 67 | Most GNU software, including some libraries, is covered by the ordinary GNU 68 | General Public License. This license, the GNU Lesser General Public License, 69 | applies to certain designated libraries, and is quite different from the ordinary 70 | General Public License. We use this license for certain libraries in order 71 | to permit linking those libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using a shared 74 | library, the combination of the two is legally speaking a combined work, a 75 | derivative of the original library. The ordinary General Public License therefore 76 | permits such linking only if the entire combination fits its criteria of freedom. 77 | The Lesser General Public License permits more lax criteria for linking other 78 | code with the library. 79 | 80 | We call this license the "Lesser" General Public License because it does Less 81 | to protect the user's freedom than the ordinary General Public License. It 82 | also provides other free software developers Less of an advantage over competing 83 | non-free programs. These disadvantages are the reason we use the ordinary 84 | General Public License for many libraries. However, the Lesser license provides 85 | advantages in certain special circumstances. 86 | 87 | For example, on rare occasions, there may be a special need to encourage the 88 | widest possible use of a certain library, so that it becomes a de-facto standard. 89 | To achieve this, non-free programs must be allowed to use the library. A more 90 | frequent case is that a free library does the same job as widely used non-free 91 | libraries. In this case, there is little to gain by limiting the free library 92 | to free software only, so we use the Lesser General Public License. 93 | 94 | In other cases, permission to use a particular library in non-free programs 95 | enables a greater number of people to use a large body of free software. For 96 | example, permission to use the GNU C Library in non-free programs enables 97 | many more people to use the whole GNU operating system, as well as its variant, 98 | the GNU/Linux operating system. 99 | 100 | Although the Lesser General Public License is Less protective of the users' 101 | freedom, it does ensure that the user of a program that is linked with the 102 | Library has the freedom and the wherewithal to run that program using a modified 103 | version of the Library. 104 | 105 | The precise terms and conditions for copying, distribution and modification 106 | follow. Pay close attention to the difference between a "work based on the 107 | library" and a "work that uses the library". The former contains code derived 108 | from the library, whereas the latter must be combined with the library in 109 | order to run. 110 | 111 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 112 | 113 | 0. This License Agreement applies to any software library or other program 114 | which contains a notice placed by the copyright holder or other authorized 115 | party saying it may be distributed under the terms of this Lesser General 116 | Public License (also called "this License"). Each licensee is addressed as 117 | "you". 118 | 119 | A "library" means a collection of software functions and/or data prepared 120 | so as to be conveniently linked with application programs (which use some 121 | of those functions and data) to form executables. 122 | 123 | The "Library", below, refers to any such software library or work which has 124 | been distributed under these terms. A "work based on the Library" means either 125 | the Library or any derivative work under copyright law: that is to say, a 126 | work containing the Library or a portion of it, either verbatim or with modifications 127 | and/or translated straightforwardly into another language. (Hereinafter, translation 128 | is included without limitation in the term "modification".) 129 | 130 | "Source code" for a work means the preferred form of the work for making modifications 131 | to it. For a library, complete source code means all the source code for all 132 | modules it contains, plus any associated interface definition files, plus 133 | the scripts used to control compilation and installation of the library. 134 | 135 | Activities other than copying, distribution and modification are not covered 136 | by this License; they are outside its scope. The act of running a program 137 | using the Library is not restricted, and output from such a program is covered 138 | only if its contents constitute a work based on the Library (independent of 139 | the use of the Library in a tool for writing it). Whether that is true depends 140 | on what the Library does and what the program that uses the Library does. 141 | 142 | 1. You may copy and distribute verbatim copies of the Library's complete source 143 | code as you receive it, in any medium, provided that you conspicuously and 144 | appropriately publish on each copy an appropriate copyright notice and disclaimer 145 | of warranty; keep intact all the notices that refer to this License and to 146 | the absence of any warranty; and distribute a copy of this License along with 147 | the Library. 148 | 149 | You may charge a fee for the physical act of transferring a copy, and you 150 | may at your option offer warranty protection in exchange for a fee. 151 | 152 | 2. You may modify your copy or copies of the Library or any portion of it, 153 | thus forming a work based on the Library, and copy and distribute such modifications 154 | or work under the terms of Section 1 above, provided that you also meet all 155 | of these conditions: 156 | 157 | a) The modified work must itself be a software library. 158 | 159 | b) You must cause the files modified to carry prominent notices stating that 160 | you changed the files and the date of any change. 161 | 162 | c) You must cause the whole of the work to be licensed at no charge to all 163 | third parties under the terms of this License. 164 | 165 | d) If a facility in the modified Library refers to a function or a table of 166 | data to be supplied by an application program that uses the facility, other 167 | than as an argument passed when the facility is invoked, then you must make 168 | a good faith effort to ensure that, in the event an application does not supply 169 | such function or table, the facility still operates, and performs whatever 170 | part of its purpose remains meaningful. 171 | 172 | (For example, a function in a library to compute square roots has a purpose 173 | that is entirely well-defined independent of the application. Therefore, Subsection 174 | 2d requires that any application-supplied function or table used by this function 175 | must be optional: if the application does not supply it, the square root function 176 | must still compute square roots.) 177 | 178 | These requirements apply to the modified work as a whole. If identifiable 179 | sections of that work are not derived from the Library, and can be reasonably 180 | considered independent and separate works in themselves, then this License, 181 | and its terms, do not apply to those sections when you distribute them as 182 | separate works. But when you distribute the same sections as part of a whole 183 | which is a work based on the Library, the distribution of the whole must be 184 | on the terms of this License, whose permissions for other licensees extend 185 | to the entire whole, and thus to each and every part regardless of who wrote 186 | it. 187 | 188 | Thus, it is not the intent of this section to claim rights or contest your 189 | rights to work written entirely by you; rather, the intent is to exercise 190 | the right to control the distribution of derivative or collective works based 191 | on the Library. 192 | 193 | In addition, mere aggregation of another work not based on the Library with 194 | the Library (or with a work based on the Library) on a volume of a storage 195 | or distribution medium does not bring the other work under the scope of this 196 | License. 197 | 198 | 3. You may opt to apply the terms of the ordinary GNU General Public License 199 | instead of this License to a given copy of the Library. To do this, you must 200 | alter all the notices that refer to this License, so that they refer to the 201 | ordinary GNU General Public License, version 2, instead of to this License. 202 | (If a newer version than version 2 of the ordinary GNU General Public License 203 | has appeared, then you can specify that version instead if you wish.) Do not 204 | make any other change in these notices. 205 | 206 | Once this change is made in a given copy, it is irreversible for that copy, 207 | so the ordinary GNU General Public License applies to all subsequent copies 208 | and derivative works made from that copy. 209 | 210 | This option is useful when you wish to copy part of the code of the Library 211 | into a program that is not a library. 212 | 213 | 4. You may copy and distribute the Library (or a portion or derivative of 214 | it, under Section 2) in object code or executable form under the terms of 215 | Sections 1 and 2 above provided that you accompany it with the complete corresponding 216 | machine-readable source code, which must be distributed under the terms of 217 | Sections 1 and 2 above on a medium customarily used for software interchange. 218 | 219 | If distribution of object code is made by offering access to copy from a designated 220 | place, then offering equivalent access to copy the source code from the same 221 | place satisfies the requirement to distribute the source code, even though 222 | third parties are not compelled to copy the source along with the object code. 223 | 224 | 5. A program that contains no derivative of any portion of the Library, but 225 | is designed to work with the Library by being compiled or linked with it, 226 | is called a "work that uses the Library". Such a work, in isolation, is not 227 | a derivative work of the Library, and therefore falls outside the scope of 228 | this License. 229 | 230 | However, linking a "work that uses the Library" with the Library creates an 231 | executable that is a derivative of the Library (because it contains portions 232 | of the Library), rather than a "work that uses the library". The executable 233 | is therefore covered by this License. Section 6 states terms for distribution 234 | of such executables. 235 | 236 | When a "work that uses the Library" uses material from a header file that 237 | is part of the Library, the object code for the work may be a derivative work 238 | of the Library even though the source code is not. Whether this is true is 239 | especially significant if the work can be linked without the Library, or if 240 | the work is itself a library. The threshold for this to be true is not precisely 241 | defined by law. 242 | 243 | If such an object file uses only numerical parameters, data structure layouts 244 | and accessors, and small macros and small inline functions (ten lines or less 245 | in length), then the use of the object file is unrestricted, regardless of 246 | whether it is legally a derivative work. (Executables containing this object 247 | code plus portions of the Library will still fall under Section 6.) 248 | 249 | Otherwise, if the work is a derivative of the Library, you may distribute 250 | the object code for the work under the terms of Section 6. Any executables 251 | containing that work also fall under Section 6, whether or not they are linked 252 | directly with the Library itself. 253 | 254 | 6. As an exception to the Sections above, you may also combine or link a "work 255 | that uses the Library" with the Library to produce a work containing portions 256 | of the Library, and distribute that work under terms of your choice, provided 257 | that the terms permit modification of the work for the customer's own use 258 | and reverse engineering for debugging such modifications. 259 | 260 | You must give prominent notice with each copy of the work that the Library 261 | is used in it and that the Library and its use are covered by this License. 262 | You must supply a copy of this License. If the work during execution displays 263 | copyright notices, you must include the copyright notice for the Library among 264 | them, as well as a reference directing the user to the copy of this License. 265 | Also, you must do one of these things: 266 | 267 | a) Accompany the work with the complete corresponding machine-readable source 268 | code for the Library including whatever changes were used in the work (which 269 | must be distributed under Sections 1 and 2 above); and, if the work is an 270 | executable linked with the Library, with the complete machine-readable "work 271 | that uses the Library", as object code and/or source code, so that the user 272 | can modify the Library and then relink to produce a modified executable containing 273 | the modified Library. (It is understood that the user who changes the contents 274 | of definitions files in the Library will not necessarily be able to recompile 275 | the application to use the modified definitions.) 276 | 277 | b) Use a suitable shared library mechanism for linking with the Library. A 278 | suitable mechanism is one that (1) uses at run time a copy of the library 279 | already present on the user's computer system, rather than copying library 280 | functions into the executable, and (2) will operate properly with a modified 281 | version of the library, if the user installs one, as long as the modified 282 | version is interface-compatible with the version that the work was made with. 283 | 284 | c) Accompany the work with a written offer, valid for at least three years, 285 | to give the same user the materials specified in Subsection 6a, above, for 286 | a charge no more than the cost of performing this distribution. 287 | 288 | d) If distribution of the work is made by offering access to copy from a designated 289 | place, offer equivalent access to copy the above specified materials from 290 | the same place. 291 | 292 | e) Verify that the user has already received a copy of these materials or 293 | that you have already sent this user a copy. 294 | 295 | For an executable, the required form of the "work that uses the Library" must 296 | include any data and utility programs needed for reproducing the executable 297 | from it. However, as a special exception, the materials to be distributed 298 | need not include anything that is normally distributed (in either source or 299 | binary form) with the major components (compiler, kernel, and so on) of the 300 | operating system on which the executable runs, unless that component itself 301 | accompanies the executable. 302 | 303 | It may happen that this requirement contradicts the license restrictions of 304 | other proprietary libraries that do not normally accompany the operating system. 305 | Such a contradiction means you cannot use both them and the Library together 306 | in an executable that you distribute. 307 | 308 | 7. You may place library facilities that are a work based on the Library side-by-side 309 | in a single library together with other library facilities not covered by 310 | this License, and distribute such a combined library, provided that the separate 311 | distribution of the work based on the Library and of the other library facilities 312 | is otherwise permitted, and provided that you do these two things: 313 | 314 | a) Accompany the combined library with a copy of the same work based on the 315 | Library, uncombined with any other library facilities. This must be distributed 316 | under the terms of the Sections above. 317 | 318 | b) Give prominent notice with the combined library of the fact that part of 319 | it is a work based on the Library, and explaining where to find the accompanying 320 | uncombined form of the same work. 321 | 322 | 8. You may not copy, modify, sublicense, link with, or distribute the Library 323 | except as expressly provided under this License. Any attempt otherwise to 324 | copy, modify, sublicense, link with, or distribute the Library is void, and 325 | will automatically terminate your rights under this License. However, parties 326 | who have received copies, or rights, from you under this License will not 327 | have their licenses terminated so long as such parties remain in full compliance. 328 | 329 | 9. You are not required to accept this License, since you have not signed 330 | it. However, nothing else grants you permission to modify or distribute the 331 | Library or its derivative works. These actions are prohibited by law if you 332 | do not accept this License. Therefore, by modifying or distributing the Library 333 | (or any work based on the Library), you indicate your acceptance of this License 334 | to do so, and all its terms and conditions for copying, distributing or modifying 335 | the Library or works based on it. 336 | 337 | 10. Each time you redistribute the Library (or any work based on the Library), 338 | the recipient automatically receives a license from the original licensor 339 | to copy, distribute, link with or modify the Library subject to these terms 340 | and conditions. You may not impose any further restrictions on the recipients' 341 | exercise of the rights granted herein. You are not responsible for enforcing 342 | compliance by third parties with this License. 343 | 344 | 11. If, as a consequence of a court judgment or allegation of patent infringement 345 | or for any other reason (not limited to patent issues), conditions are imposed 346 | on you (whether by court order, agreement or otherwise) that contradict the 347 | conditions of this License, they do not excuse you from the conditions of 348 | this License. If you cannot distribute so as to satisfy simultaneously your 349 | obligations under this License and any other pertinent obligations, then as 350 | a consequence you may not distribute the Library at all. For example, if a 351 | patent license would not permit royalty-free redistribution of the Library 352 | by all those who receive copies directly or indirectly through you, then the 353 | only way you could satisfy both it and this License would be to refrain entirely 354 | from distribution of the Library. 355 | 356 | If any portion of this section is held invalid or unenforceable under any 357 | particular circumstance, the balance of the section is intended to apply, 358 | and the section as a whole is intended to apply in other circumstances. 359 | 360 | It is not the purpose of this section to induce you to infringe any patents 361 | or other property right claims or to contest validity of any such claims; 362 | this section has the sole purpose of protecting the integrity of the free 363 | software distribution system which is implemented by public license practices. 364 | Many people have made generous contributions to the wide range of software 365 | distributed through that system in reliance on consistent application of that 366 | system; it is up to the author/donor to decide if he or she is willing to 367 | distribute software through any other system and a licensee cannot impose 368 | that choice. 369 | 370 | This section is intended to make thoroughly clear what is believed to be a 371 | consequence of the rest of this License. 372 | 373 | 12. If the distribution and/or use of the Library is restricted in certain 374 | countries either by patents or by copyrighted interfaces, the original copyright 375 | holder who places the Library under this License may add an explicit geographical 376 | distribution limitation excluding those countries, so that distribution is 377 | permitted only in or among countries not thus excluded. In such case, this 378 | License incorporates the limitation as if written in the body of this License. 379 | 380 | 13. The Free Software Foundation may publish revised and/or new versions of 381 | the Lesser General Public License from time to time. Such new versions will 382 | be similar in spirit to the present version, but may differ in detail to address 383 | new problems or concerns. 384 | 385 | Each version is given a distinguishing version number. If the Library specifies 386 | a version number of this License which applies to it and "any later version", 387 | you have the option of following the terms and conditions either of that version 388 | or of any later version published by the Free Software Foundation. If the 389 | Library does not specify a license version number, you may choose any version 390 | ever published by the Free Software Foundation. 391 | 392 | 14. If you wish to incorporate parts of the Library into other free programs 393 | whose distribution conditions are incompatible with these, write to the author 394 | to ask for permission. For software which is copyrighted by the Free Software 395 | Foundation, write to the Free Software Foundation; we sometimes make exceptions 396 | for this. Our decision will be guided by the two goals of preserving the free 397 | status of all derivatives of our free software and of promoting the sharing 398 | and reuse of software generally. 399 | 400 | NO WARRANTY 401 | 402 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR 403 | THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE 404 | STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY 405 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 406 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 407 | FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE 408 | OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 409 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 410 | 411 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 412 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 413 | THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 414 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE 415 | OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA 416 | OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES 417 | OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH 418 | HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 419 | END OF TERMS AND CONDITIONS 420 | 421 | How to Apply These Terms to Your New Libraries 422 | 423 | If you develop a new library, and you want it to be of the greatest possible 424 | use to the public, we recommend making it free software that everyone can 425 | redistribute and change. You can do so by permitting redistribution under 426 | these terms (or, alternatively, under the terms of the ordinary General Public 427 | License). 428 | 429 | To apply these terms, attach the following notices to the library. It is safest 430 | to attach them to the start of each source file to most effectively convey 431 | the exclusion of warranty; and each file should have at least the "copyright" 432 | line and a pointer to where the full notice is found. 433 | 434 | 435 | 436 | Copyright (C) 437 | 438 | This library is free software; you can redistribute it and/or modify it under 439 | the terms of the GNU Lesser General Public License as published by the Free 440 | Software Foundation; either version 2.1 of the License, or (at your option) 441 | any later version. 442 | 443 | This library is distributed in the hope that it will be useful, but WITHOUT 444 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 445 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 446 | details. 447 | 448 | You should have received a copy of the GNU Lesser General Public License along 449 | with this library; if not, write to the Free Software Foundation, Inc., 51 450 | Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 451 | 452 | Also add information on how to contact you by electronic and paper mail. 453 | 454 | You should also get your employer (if you work as a programmer) or your school, 455 | if any, to sign a "copyright disclaimer" for the library, if necessary. Here 456 | is a sample; alter the names: 457 | 458 | Yoyodyne, Inc., hereby disclaims all copyright interest in 459 | 460 | the library `Frob' (a library for tweaking knobs) written 461 | 462 | by James Random Hacker. 463 | 464 | < signature of Ty Coon > , 1 April 1990 465 | 466 | Ty Coon, President of Vice 467 | 468 | That's all there is to it! 469 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gtk im module for fcitx5 and glib based dbus client library 2 | ========================================== 3 | [![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/) 4 | 5 | [![Coverity Scan Status](https://img.shields.io/coverity/scan/12702.svg)](https://scan.coverity.com/projects/fcitx-fcitx5-gtk) 6 | -------------------------------------------------------------------------------- /cmake/CompilerSettings.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_EXTENSIONS OFF) 2 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_C_STANDARD_REQUIRED TRUE) 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | set(CMAKE_C_FLAGS "-Wall -Wextra ${CMAKE_C_FLAGS}") 8 | set(CMAKE_CXX_FLAGS "-Wall -Wextra ${CMAKE_CXX_FLAGS}") 9 | set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined -Wl,--as-needed ${CMAKE_SHARED_LINKER_FLAGS}") 10 | set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--no-undefined -Wl,--as-needed ${CMAKE_MODULE_LINKER_FLAGS}") 11 | 12 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 13 | set(CMAKE_VISIBILITY_INLINES_HIDDEN On) 14 | 15 | # RPATH 16 | list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" _isSystemPlatformLibDir) 17 | list(FIND CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" _isSystemCxxLibDir) 18 | if("${_isSystemPlatformLibDir}" STREQUAL "-1" AND "${_isSystemCxxLibDir}" STREQUAL "-1") 19 | set(CMAKE_SKIP_BUILD_RPATH FALSE) 20 | set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 21 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") 22 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 23 | endif("${_isSystemPlatformLibDir}" STREQUAL "-1" AND "${_isSystemCxxLibDir}" STREQUAL "-1") 24 | -------------------------------------------------------------------------------- /cmake/FindXKBCommon.cmake: -------------------------------------------------------------------------------- 1 | 2 | include(ECMFindModuleHelpersStub) 3 | 4 | ecm_find_package_version_check(XKBCommon) 5 | 6 | # Note that this list needs to be ordered such that any component 7 | # appears after its dependencies 8 | set(XKBCommon_known_components 9 | XKBCommon 10 | X11) 11 | 12 | set(XKBCommon_XKBCommon_component_deps) 13 | set(XKBCommon_XKBCommon_pkg_config "xkbcommon") 14 | set(XKBCommon_XKBCommon_lib "xkbcommon") 15 | set(XKBCommon_XKBCommon_header "xkbcommon/xkbcommon.h") 16 | 17 | set(XKBCommon_X11_component_deps XKBCommon) 18 | set(XKBCommon_X11_pkg_config "xkbcommon-x11") 19 | set(XKBCommon_X11_lib "xkbcommon-x11") 20 | set(XKBCommon_X11_header "xkbcommon/xkbcommon-x11.h") 21 | 22 | ecm_find_package_parse_components(XKBCommon 23 | RESULT_VAR XKBCommon_components 24 | KNOWN_COMPONENTS ${XKBCommon_known_components} 25 | ) 26 | ecm_find_package_handle_library_components(XKBCommon 27 | COMPONENTS ${XKBCommon_components} 28 | ) 29 | 30 | find_package_handle_standard_args(XKBCommon 31 | FOUND_VAR 32 | XKBCommon_FOUND 33 | REQUIRED_VARS 34 | XKBCommon_LIBRARIES 35 | VERSION_VAR 36 | XKBCommon_VERSION 37 | HANDLE_COMPONENTS 38 | ) 39 | 40 | include(FeatureSummary) 41 | set_package_properties(XKBCommon PROPERTIES 42 | URL "http://xkbcommon.org" 43 | DESCRIPTION "Keyboard handling library using XKB data" 44 | ) 45 | -------------------------------------------------------------------------------- /cmake/GObjectIntrospection.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeParseArguments) 2 | include(FindPkgConfig) 3 | 4 | pkg_check_modules(GOBJECT_INTROSPECTION REQUIRED gobject-introspection-1.0) 5 | _pkgconfig_invoke("gobject-introspection-1.0" GOBJECT_INTROSPECTION GIRDIR 6 | "" "--variable=girdir") 7 | _pkgconfig_invoke("gobject-introspection-1.0" GOBJECT_INTROSPECTION TYPELIBDIR 8 | "" "--variable=typelibdir") 9 | 10 | find_program(GIR_SCANNER NAMES g-ir-scanner DOC "g-ir-scanner executable") 11 | mark_as_advanced(GIR_SCANNER) 12 | find_program(GIR_COMPILER NAMES g-ir-compiler DOC "g-ir-compiler executable") 13 | mark_as_advanced(GIR_COMPILER) 14 | find_program(GIR_GENERATE NAMES g-ir-generate DOC "g-ir-generate executable") 15 | mark_as_advanced(GIR_GENERATE) 16 | 17 | function(_gir_list_prefix _newlist _list _prefix) 18 | set(newlist) 19 | foreach(_item IN LISTS ${_list}) 20 | list(APPEND newlist ${_prefix}${_item}) 21 | endforeach() 22 | set(${_newlist} ${newlist} PARENT_SCOPE) 23 | endfunction() 24 | 25 | function(__GIR_GET_UNIQUE_TARGET_NAME _name _unique_name) 26 | set(propertyName "_GOBJECT_INTROSPECTION_UNIQUE_COUNTER_${_name}") 27 | get_property(currentCounter GLOBAL PROPERTY "${propertyName}") 28 | if(NOT currentCounter) 29 | set(currentCounter 1) 30 | endif() 31 | set("${_unique_name}" "${_name}_${currentCounter}" PARENT_SCOPE) 32 | math(EXPR currentCounter "${currentCounter} + 1") 33 | set_property(GLOBAL PROPERTY "${propertyName}" "${currentCounter}") 34 | endfunction() 35 | 36 | function(gobject_introspection _FIRST_ARG) 37 | set(options QUIET VERBOSE) 38 | set(oneValueArgs 39 | FILENAME 40 | FORMAT 41 | LIBRARY 42 | NAMESPACE 43 | NSVERSION 44 | PROGRAM 45 | PROGRAM_ARG 46 | PACKAGE_EXPORT 47 | ) 48 | set(multiValueArgs 49 | BUILT_SOURCES 50 | CFLAGS 51 | COMPILER_ARGS 52 | HEADERS 53 | IDENTIFIER_PREFIXES 54 | PACKAGES 55 | INCLUDE 56 | SCANNER_ARGS 57 | SOURCES 58 | SYMBOL_PREFIXES 59 | ) 60 | 61 | cmake_parse_arguments(GIR "${options}" "${oneValueArgs}" "${multiValueArgs}" 62 | ${_FIRST_ARG} ${ARGN}) 63 | 64 | if(ADD_GIR_UNPARSED_ARGUMENTS) 65 | message(FATAL_ERROR "Unknown keys given to ADD_GIR_INTROSPECTION(): \"${ADD_GIR_UNPARSED_ARGUMENTS}\"") 66 | endif() 67 | 68 | ########################################################################### 69 | # make sure that the user set some variables... 70 | ########################################################################### 71 | if(NOT GIR_FILENAME) 72 | message(FATAL_ERROR "No gir filename given") 73 | endif() 74 | 75 | if(NOT GIR_NAMESPACE) 76 | # the caller didn't give us a namespace, try to grab it from the filename 77 | string(REGEX REPLACE "([^-]+)-.*" "\\1" GIR_NAMESPACE "${GIR_FILENAME}") 78 | if(NOT GIR_NAMESPACE) 79 | message(FATAL_ERROR 80 | "No namespace given and couldn't find one in FILENAME") 81 | endif() 82 | endif() 83 | 84 | if(NOT GIR_NSVERSION) 85 | # the caller didn't give us a namespace version, 86 | # try to grab it from the filemenu 87 | string(REGEX REPLACE ".*-([^-]+).gir" "\\1" GIR_NSVERSION "${GIR_FILENAME}") 88 | if(NOT GIR_NSVERSION) 89 | message(FATAL_ERROR 90 | "No namespace version given and couldn't find one in FILENAME") 91 | endif() 92 | endif() 93 | 94 | if(NOT GIR_CFLAGS) 95 | get_directory_property(GIR_CFLAGS INCLUDE_DIRECTORIES) 96 | _gir_list_prefix(GIR_REAL_CFLAGS GIR_CFLAGS "-I") 97 | endif() 98 | 99 | ########################################################################### 100 | # Fix up some of our arguments 101 | ########################################################################### 102 | if(GIR_VERBOSE) 103 | set(GIR_VERBOSE "--verbose") 104 | else() 105 | set(GIR_VERBOSE "") 106 | endif() 107 | 108 | if(GIR_QUIET) 109 | set(GIR_QUIET "--quiet") 110 | else() 111 | set(GIR_QUIET "") 112 | endif() 113 | 114 | if(GIR_FORMAT) 115 | set(GIR_FORMAT "--format=${GIR_FORMAT}") 116 | endif() 117 | 118 | # if library is set, we need to prepend --library= on to it 119 | if(GIR_LIBRARY) 120 | set(GIR_REAL_LIBRARY "--library=${GIR_LIBRARY}") 121 | endif() 122 | 123 | # if program has been set, we prepend --program= on to it 124 | if(GIR_PROGRAM) 125 | set(GIR_PROGRAM "--program=${GIR_PROGRAM}") 126 | endif() 127 | 128 | # if program_arg has been set, we prepend --program-arg= on to it 129 | if(GIR_PROGRAM_ARG) 130 | set(GIR_PROGRAM_ARG "--program-arg=${GIR_PROGRAM_ARG}") 131 | endif() 132 | 133 | # if the user specified PACKAGE_EXPORT, 134 | # we need to prefix each with --pkg-export 135 | if(GIR_PACKAGE_EXPORT) 136 | set(GIR_REAL_PACKAGE_EXPORT GIR_PACKAGE_EXPORT "--pkg-export=") 137 | endif() 138 | 139 | ########################################################################### 140 | # Clean up any of the multivalue items that all need to be prefixed 141 | ########################################################################### 142 | 143 | # if the user specified IDENTIFIER_PREFIXES, 144 | # we need to prefix each with --identifier-prefix 145 | if(GIR_IDENTIFIER_PREFIXES) 146 | _gir_list_prefix(GIR_REAL_IDENTIFIER_PREFIXES 147 | GIR_IDENTIFIER_PREFIXES "--identifier-prefix=") 148 | endif() 149 | 150 | # if the user specified SYMBOL_PREFIXES, 151 | # we need to prefix each with --symbol-prefix= 152 | if(GIR_SYMBOL_PREFIXES) 153 | _gir_list_prefix(GIR_REAL_SYMBOL_PREFIXES 154 | GIR_SYMBOL_PREFIXES "--symbol-prefix=") 155 | endif() 156 | 157 | # if the user specified PACKAGES we need to prefix each with --pkg 158 | if(GIR_PACKAGES) 159 | _gir_list_prefix(GIR_REAL_PACKAGES GIR_PACKAGES "--pkg=") 160 | endif() 161 | 162 | # if the user specified PACKAGES we need to prefix each with --pkg 163 | if(GIR_INCLUDE) 164 | _gir_list_prefix(GIR_REAL_INCLUDE GIR_INCLUDE "--include=") 165 | endif() 166 | 167 | # if the user specified BUILT_SOURCES, we need to get their paths since 168 | # they could be in CMAKE_CURRENT_BUILD_DIR 169 | if(GIR_BUILT_SOURCES) 170 | set(GIR_REAL_BUILT_SOURCES) 171 | foreach(ITEM ${GIR_BUILT_SOURCES}) 172 | get_source_file_property(LOCATION "${ITEM}" LOCATION) 173 | list(APPEND GIR_REAL_BUILT_SOURCES "${LOCATION}") 174 | endforeach() 175 | endif() 176 | 177 | ########################################################################### 178 | # Add the custom commands 179 | ########################################################################### 180 | set(ENV{CFLAGS} ${GIR_REAL_CFLAGS}) 181 | add_custom_command( 182 | COMMAND env "LD_LIBRARY_PATH=${CMAKE_CURRENT_BINARY_DIR}" 183 | "${GIR_SCANNER}" ${GIR_SCANNER_ARGS} 184 | "--namespace=${GIR_NAMESPACE}" 185 | "--nsversion=${GIR_NSVERSION}" 186 | ${GIR_REAL_CFLAGS} 187 | ${GIR_FORMAT} 188 | ${GIR_REAL_LIBRARY} 189 | ${GIR_PROGRAM} ${GIR_PROGRAM_ARGS} 190 | ${GIR_QUIET} ${GIR_VERBOSE} 191 | ${GIR_REAL_IDENTIFIER_PREFIXES} 192 | ${GIR_REAL_SYMBOL_PREFIXES} 193 | ${GIR_REAL_PACKAGES} 194 | ${GIR_REAL_INCLUDE} 195 | --no-libtool 196 | -L${CMAKE_CURRENT_BINARY_DIR} 197 | "--output=${CMAKE_CURRENT_BINARY_DIR}/${GIR_FILENAME}" 198 | ${GIR_PACKAGE_EXPORT} 199 | ${GIR_SCANNER_FLAGS} 200 | ${GIR_SOURCES} 201 | ${GIR_REAL_BUILT_SOURCES} 202 | OUTPUT "${GIR_FILENAME}" 203 | DEPENDS ${GIR_LIBRARY} "${GIR_SCANNER}" 204 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 205 | VERBATIM 206 | ) 207 | 208 | # create the name of the typelib 209 | string(REPLACE ".gir" ".typelib" GIR_TYPELIB "${GIR_FILENAME}") 210 | 211 | add_custom_command( 212 | COMMAND "${GIR_COMPILER}" ${GIR_COMPILER_ARGS} 213 | "${CMAKE_CURRENT_BINARY_DIR}/${GIR_FILENAME}" 214 | "--output=${CMAKE_CURRENT_BINARY_DIR}/${GIR_TYPELIB}" 215 | OUTPUT "${GIR_TYPELIB}" 216 | DEPENDS "${GIR_FILENAME}" "${GIR_COMPILER}" 217 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 218 | ) 219 | 220 | __gir_get_unique_target_name(gobject_introspection_compile_target 221 | _gir_compile_target) 222 | add_custom_target(${_gir_compile_target} ALL DEPENDS "${GIR_TYPELIB}") 223 | endfunction() 224 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef ___CONFIG_H___ 8 | #define ___CONFIG_H___ 9 | 10 | #define LOCALEDIR "@CMAKE_INSTALL_PREFIX@/locale" 11 | 12 | #define NO_SNOOPER_APPS "@NO_SNOOPER_APPS@" 13 | #define NO_PREEDIT_APPS "@NO_PREEDIT_APPS@" 14 | #define SYNC_MODE_APPS "@SYNC_MODE_APPS@" 15 | #cmakedefine ENABLE_SNOOPER 16 | 17 | #ifdef ENABLE_SNOOPER 18 | #define _ENABLE_SNOOPER 1 19 | #else 20 | #define _ENABLE_SNOOPER 0 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /fcitx-gclient/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(FCITX_GCLIENT_SOURCES 2 | fcitxgwatcher.c 3 | fcitxgclient.c 4 | ) 5 | 6 | set(FCITX_GCLIENT_BUILT_SOURCES 7 | ${CMAKE_CURRENT_BINARY_DIR}/marshall.c 8 | ) 9 | 10 | set(FCITX_GCLIENT_HEADERS 11 | fcitxgclient.h 12 | fcitxgwatcher.h 13 | ) 14 | 15 | set(FCITX_GCLIENT_BUILT_HEADERS 16 | ${CMAKE_CURRENT_BINARY_DIR}/marshall.h 17 | ) 18 | 19 | ecm_setup_version(PROJECT 20 | VARIABLE_PREFIX Fcitx5GClient 21 | PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfigVersion.cmake" 22 | SOVERSION 2) 23 | 24 | pkg_get_variable(GLIB2_GLIB_GENMARSHAL "glib-2.0" "glib_genmarshal") 25 | find_program(GLIB_GENMARSHAL ${GLIB2_GLIB_GENMARSHAL}) 26 | 27 | add_custom_command(OUTPUT marshall.c 28 | COMMAND ${GLIB_GENMARSHAL} --body --prefix=fcitx_marshall --internal --include-header=marshall.h 29 | ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list > marshall.c 30 | DEPENDS ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list) 31 | add_custom_command(OUTPUT marshall.h 32 | COMMAND ${GLIB_GENMARSHAL} --header --prefix=fcitx_marshall --internal 33 | ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list > marshall.h 34 | DEPENDS ${PROJECT_SOURCE_DIR}/gtk-common/marshall.list) 35 | 36 | add_library(Fcitx5GClient ${GCLIENT_LIBRARY_TYPE} ${FCITX_GCLIENT_SOURCES} 37 | ${FCITX_GCLIENT_BUILT_SOURCES} ${FCITX_GCLIENT_BUILT_HEADERS}) 38 | set_target_properties(Fcitx5GClient 39 | PROPERTIES VERSION ${Fcitx5GClient_VERSION} 40 | SOVERSION ${Fcitx5GClient_SOVERSION} 41 | EXPORT_NAME GClient 42 | C_VISIBILITY_PRESET default 43 | POSITION_INDEPENDENT_CODE ON 44 | ) 45 | target_include_directories(Fcitx5GClient 46 | PUBLIC 47 | "$" 48 | "$/Fcitx5/GClient" 49 | INTERFACE 50 | "${Gio2_INCLUDE_DIRS}" 51 | "${GObject2_INCLUDE_DIRS}" 52 | ) 53 | target_link_libraries(Fcitx5GClient LINK_PRIVATE PkgConfig::Gio2 PkgConfig::GLib2 PkgConfig::GObject2) 54 | 55 | add_library(Fcitx5::GClient ALIAS Fcitx5GClient) 56 | 57 | if (NOT BUILD_ONLY_PLUGIN) 58 | configure_file(Fcitx5GClient.pc.in ${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClient.pc @ONLY) 59 | 60 | install(TARGETS Fcitx5GClient EXPORT Fcitx5GClientTargets LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") 61 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClient.pc 62 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") 63 | install(FILES ${FCITX_GCLIENT_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/Fcitx5/GClient/fcitx-gclient") 64 | 65 | 66 | configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/Fcitx5GClientConfig.cmake.in" 67 | "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfig.cmake" 68 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fcitx5GClient 69 | ) 70 | 71 | install(EXPORT Fcitx5GClientTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fcitx5GClient FILE Fcitx5GClientTargets.cmake NAMESPACE Fcitx5::) 72 | 73 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfig.cmake" 74 | "${CMAKE_CURRENT_BINARY_DIR}/Fcitx5GClientConfigVersion.cmake" 75 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fcitx5GClient 76 | COMPONENT Devel ) 77 | 78 | if(ENABLE_GIR) 79 | include(GObjectIntrospection) 80 | gobject_introspection( 81 | FILENAME FcitxG-1.0.gir 82 | NSVERSION 1.0 83 | INCLUDE Gio-2.0 GObject-2.0 GLib-2.0 84 | PACKAGE_EXPORT Fcitx5GClient 85 | LIBRARY Fcitx5GClient 86 | NAMESPACE FcitxG 87 | SCANNER_ARGS --warn-all --add-include-path=${CMAKE_CURRENT_SOURCE_DIR} 88 | COMPILER_ARGS "--includedir=${CMAKE_CURRENT_SOURCE_DIR}" 89 | SYMBOL_PREFIXES fcitx_g 90 | SOURCES ${FCITX_GCLIENT_SOURCES} ${FCITX_GCLIENT_HEADERS} 91 | QUIET 92 | ) 93 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/FcitxG-1.0.gir" 94 | DESTINATION "${GOBJECT_INTROSPECTION_GIRDIR}") 95 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/FcitxG-1.0.typelib" 96 | DESTINATION "${GOBJECT_INTROSPECTION_TYPELIBDIR}") 97 | endif() 98 | 99 | endif() 100 | -------------------------------------------------------------------------------- /fcitx-gclient/Fcitx5GClient.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@/Fcitx5/GClient 5 | 6 | Name: Fcitx5GClient 7 | Description: Fcitx GLib Client Library 8 | Version: @FCITX5_GTK_VERSION@ 9 | Requires: gobject-2.0, gio-2.0 10 | Cflags: -I${includedir} 11 | Libs: -L${libdir} -lFcitx5GClient 12 | -------------------------------------------------------------------------------- /fcitx-gclient/Fcitx5GClientConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/Fcitx5GClientTargets.cmake") 4 | -------------------------------------------------------------------------------- /fcitx-gclient/fcitxgclient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012~2012 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef CLIENT_IM_H 8 | #define CLIENT_IM_H 9 | 10 | #include "fcitxgwatcher.h" 11 | #include 12 | 13 | G_BEGIN_DECLS 14 | 15 | /* 16 | * Type macros 17 | */ 18 | 19 | /* define GOBJECT macros */ 20 | #define FCITX_G_TYPE_CLIENT (fcitx_g_client_get_type()) 21 | 22 | G_DECLARE_FINAL_TYPE(FcitxGClient, fcitx_g_client, FCITX_G, CLIENT, GObject) 23 | 24 | typedef struct _FcitxGClientPrivate FcitxGClientPrivate; 25 | typedef struct _FcitxGPreeditItem FcitxGPreeditItem; 26 | typedef struct _FcitxGCandidateItem FcitxGCandidateItem; 27 | 28 | struct _FcitxGPreeditItem { 29 | gchar *string; 30 | gint32 type; 31 | }; 32 | 33 | struct _FcitxGCandidateItem { 34 | gchar *label; 35 | gchar *candidate; 36 | }; 37 | 38 | FcitxGClient *fcitx_g_client_new(); 39 | FcitxGClient *fcitx_g_client_new_with_watcher(FcitxGWatcher *watcher); 40 | gboolean fcitx_g_client_is_valid(FcitxGClient *self); 41 | gboolean fcitx_g_client_process_key_sync(FcitxGClient *self, guint32 keyval, 42 | guint32 keycode, guint32 state, 43 | gboolean isRelease, guint32 t); 44 | void fcitx_g_client_process_key(FcitxGClient *self, guint32 keyval, 45 | guint32 keycode, guint32 state, 46 | gboolean isRelease, guint32 t, 47 | gint timeout_msec, GCancellable *cancellable, 48 | GAsyncReadyCallback callback, 49 | gpointer user_data); 50 | gboolean fcitx_g_client_process_key_finish(FcitxGClient *self, 51 | GAsyncResult *res); 52 | const guint8 *fcitx_g_client_get_uuid(FcitxGClient *self); 53 | void fcitx_g_client_focus_in(FcitxGClient *self); 54 | void fcitx_g_client_focus_out(FcitxGClient *self); 55 | void fcitx_g_client_set_display(FcitxGClient *self, const gchar *display); 56 | void fcitx_g_client_set_program(FcitxGClient *self, const gchar *program); 57 | void fcitx_g_client_set_use_batch_process_key_event(FcitxGClient *self, 58 | gboolean batch); 59 | void fcitx_g_client_set_cursor_rect(FcitxGClient *self, gint x, gint y, gint w, 60 | gint h); 61 | void fcitx_g_client_set_cursor_rect_with_scale_factor(FcitxGClient *self, 62 | gint x, gint y, gint w, 63 | gint h, gdouble scale); 64 | void fcitx_g_client_set_surrounding_text(FcitxGClient *self, gchar *text, 65 | guint cursor, guint anchor); 66 | void fcitx_g_client_set_capability(FcitxGClient *self, guint64 flags); 67 | void fcitx_g_client_prev_page(FcitxGClient *self); 68 | void fcitx_g_client_next_page(FcitxGClient *self); 69 | void fcitx_g_client_select_candidate(FcitxGClient *self, int index); 70 | 71 | void fcitx_g_client_reset(FcitxGClient *self); 72 | 73 | G_END_DECLS 74 | 75 | #endif // CLIENT_IM_H 76 | -------------------------------------------------------------------------------- /fcitx-gclient/fcitxgwatcher.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017~2017 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "fcitxgwatcher.h" 8 | 9 | #define FCITX_MAIN_SERVICE_NAME "org.fcitx.Fcitx5" 10 | #define FCITX_PORTAL_SERVICE_NAME "org.freedesktop.portal.Fcitx" 11 | 12 | typedef struct _FcitxGWatcherPrivate FcitxGWatcherPrivate; 13 | 14 | struct _FcitxGWatcher { 15 | GObject parent_instance; 16 | /* instance member */ 17 | FcitxGWatcherPrivate *priv; 18 | }; 19 | 20 | /** 21 | * FcitxGWatcher: 22 | * 23 | * A FcitxGWatcher allow to create a input context via DBus 24 | */ 25 | struct _FcitxGWatcherPrivate { 26 | gboolean watched; 27 | guint watch_id; 28 | guint portal_watch_id; 29 | gchar *main_owner, *portal_owner; 30 | gboolean watch_portal; 31 | gboolean available; 32 | 33 | GCancellable *cancellable; 34 | GDBusConnection *connection; 35 | }; 36 | 37 | G_DEFINE_TYPE_WITH_PRIVATE(FcitxGWatcher, fcitx_g_watcher, G_TYPE_OBJECT); 38 | 39 | enum { AVAILABLITY_CHANGED_SIGNAL, LAST_SIGNAL }; 40 | 41 | static guint signals[LAST_SIGNAL] = {0}; 42 | 43 | static void _fcitx_g_watcher_clean_up(FcitxGWatcher *self); 44 | static void _fcitx_g_watcher_get_bus_finished(GObject *source_object, 45 | GAsyncResult *res, 46 | gpointer user_data); 47 | static void _fcitx_g_watcher_update_availability(FcitxGWatcher *self); 48 | 49 | static void fcitx_g_watcher_finalize(GObject *object); 50 | static void fcitx_g_watcher_dispose(GObject *object); 51 | 52 | static void fcitx_g_watcher_class_init(FcitxGWatcherClass *klass) { 53 | GObjectClass *gobject_class; 54 | 55 | gobject_class = G_OBJECT_CLASS(klass); 56 | gobject_class->dispose = fcitx_g_watcher_dispose; 57 | gobject_class->finalize = fcitx_g_watcher_finalize; 58 | 59 | /* install signals */ 60 | /** 61 | * FcitxGWatcher::availability-changed: 62 | * @watcher: A FcitxGWatcher 63 | * @available: whether fcitx service is available. 64 | * 65 | * Emit when connected to fcitx and created ic 66 | */ 67 | signals[AVAILABLITY_CHANGED_SIGNAL] = g_signal_new( 68 | "availability-changed", FCITX_G_TYPE_WATCHER, G_SIGNAL_RUN_LAST, 0, 69 | NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, 70 | G_TYPE_BOOLEAN); 71 | } 72 | 73 | static void fcitx_g_watcher_init(FcitxGWatcher *self) { 74 | self->priv = fcitx_g_watcher_get_instance_private(self); 75 | 76 | self->priv->connection = NULL; 77 | self->priv->cancellable = NULL; 78 | self->priv->watch_id = 0; 79 | self->priv->portal_watch_id = 0; 80 | self->priv->main_owner = NULL; 81 | self->priv->portal_owner = NULL; 82 | self->priv->watched = FALSE; 83 | } 84 | 85 | static void fcitx_g_watcher_finalize(GObject *object) { 86 | if (G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->finalize != NULL) 87 | G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->finalize(object); 88 | } 89 | 90 | static void fcitx_g_watcher_dispose(GObject *object) { 91 | FcitxGWatcher *self = FCITX_G_WATCHER(object); 92 | 93 | if (self->priv->watched) { 94 | fcitx_g_watcher_unwatch(self); 95 | } 96 | 97 | if (G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->dispose != NULL) 98 | G_OBJECT_CLASS(fcitx_g_watcher_parent_class)->dispose(object); 99 | } 100 | 101 | static void _fcitx_g_watcher_appear(G_GNUC_UNUSED GDBusConnection *conn, 102 | const gchar *name, const gchar *name_owner, 103 | gpointer user_data) { 104 | g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); 105 | 106 | FcitxGWatcher *self = FCITX_G_WATCHER(user_data); 107 | if (g_strcmp0(name, FCITX_MAIN_SERVICE_NAME) == 0) { 108 | g_free(self->priv->main_owner); 109 | self->priv->main_owner = g_strdup(name_owner); 110 | } else if (g_strcmp0(name, FCITX_PORTAL_SERVICE_NAME) == 0) { 111 | g_free(self->priv->portal_owner); 112 | self->priv->portal_owner = g_strdup(name_owner); 113 | } 114 | _fcitx_g_watcher_update_availability(self); 115 | } 116 | 117 | static void _fcitx_g_watcher_vanish(G_GNUC_UNUSED GDBusConnection *conn, 118 | const gchar *name, gpointer user_data) { 119 | g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); 120 | 121 | FcitxGWatcher *self = FCITX_G_WATCHER(user_data); 122 | if (g_strcmp0(name, FCITX_MAIN_SERVICE_NAME) == 0) { 123 | g_free(self->priv->main_owner); 124 | self->priv->main_owner = NULL; 125 | } else if (g_strcmp0(name, FCITX_PORTAL_SERVICE_NAME) == 0) { 126 | g_free(self->priv->portal_owner); 127 | self->priv->portal_owner = NULL; 128 | } 129 | _fcitx_g_watcher_update_availability(self); 130 | } 131 | 132 | static void _fcitx_g_watcher_start_watch(FcitxGWatcher *self) { 133 | if (!self->priv->watched) { 134 | return; 135 | } 136 | g_clear_object(&self->priv->cancellable); 137 | self->priv->cancellable = g_cancellable_new(); 138 | gchar *address = g_dbus_address_get_for_bus_sync( 139 | G_BUS_TYPE_SESSION, self->priv->cancellable, NULL); 140 | if (!address) { 141 | g_clear_object(&self->priv->cancellable); 142 | return; 143 | } 144 | g_object_ref(self); 145 | g_dbus_connection_new_for_address( 146 | address, 147 | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | 148 | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, 149 | NULL, self->priv->cancellable, _fcitx_g_watcher_get_bus_finished, self); 150 | g_free(address); 151 | } 152 | 153 | /** 154 | * fcitx_g_watcher_watch 155 | * @self: a #FcitxGWatcher 156 | * 157 | * Watch for the fcitx serivce. 158 | */ 159 | void fcitx_g_watcher_watch(FcitxGWatcher *self) { 160 | g_return_if_fail(!self->priv->watched); 161 | 162 | self->priv->watched = TRUE; 163 | _fcitx_g_watcher_start_watch(self); 164 | }; 165 | 166 | static gboolean _fcitx_g_watcher_recheck(gpointer user_data) { 167 | FcitxGWatcher *self = user_data; 168 | 169 | _fcitx_g_watcher_start_watch(self); 170 | return FALSE; 171 | } 172 | 173 | static void 174 | _fcitx_g_watcher_connection_closed(GDBusConnection *connection G_GNUC_UNUSED, 175 | gboolean remote_peer_vanished G_GNUC_UNUSED, 176 | GError *error G_GNUC_UNUSED, 177 | gpointer user_data) { 178 | 179 | g_return_if_fail(user_data != NULL); 180 | g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); 181 | 182 | FcitxGWatcher *self = FCITX_G_WATCHER(user_data); 183 | _fcitx_g_watcher_clean_up(self); 184 | if (self->priv->watched) { 185 | _fcitx_g_watcher_update_availability(self); 186 | 187 | g_timeout_add_full(G_PRIORITY_DEFAULT, 100, _fcitx_g_watcher_recheck, 188 | g_object_ref(self), g_object_unref); 189 | } 190 | } 191 | 192 | static void 193 | _fcitx_g_watcher_get_bus_finished(G_GNUC_UNUSED GObject *source_object, 194 | GAsyncResult *res, gpointer user_data) { 195 | g_return_if_fail(user_data != NULL); 196 | g_return_if_fail(FCITX_G_IS_WATCHER(user_data)); 197 | 198 | FcitxGWatcher *self = FCITX_G_WATCHER(user_data); 199 | // Need to call finish because clean up, otherwise cancellable will set the 200 | // return value. 201 | GDBusConnection *connection = 202 | g_dbus_connection_new_for_address_finish(res, NULL); 203 | _fcitx_g_watcher_clean_up(self); 204 | if (!connection) { 205 | g_object_unref(self); 206 | return; 207 | } 208 | self->priv->connection = connection; 209 | g_dbus_connection_set_exit_on_close(self->priv->connection, FALSE); 210 | g_signal_connect(self->priv->connection, "closed", 211 | (GCallback)_fcitx_g_watcher_connection_closed, self); 212 | 213 | self->priv->watch_id = 214 | g_bus_watch_name(G_BUS_TYPE_SESSION, FCITX_MAIN_SERVICE_NAME, 215 | G_BUS_NAME_WATCHER_FLAGS_NONE, _fcitx_g_watcher_appear, 216 | _fcitx_g_watcher_vanish, self, NULL); 217 | 218 | if (self->priv->watch_portal) { 219 | self->priv->portal_watch_id = g_bus_watch_name( 220 | G_BUS_TYPE_SESSION, FCITX_PORTAL_SERVICE_NAME, 221 | G_BUS_NAME_WATCHER_FLAGS_NONE, _fcitx_g_watcher_appear, 222 | _fcitx_g_watcher_vanish, self, NULL); 223 | } 224 | 225 | _fcitx_g_watcher_update_availability(self); 226 | 227 | /* unref for _fcitx_g_watcher_start_watch */ 228 | g_object_unref(self); 229 | } 230 | 231 | /** 232 | * fcitx_g_watcher_unwatch 233 | * @self: a #FcitxGWatcher 234 | * 235 | * Unwatch for the fcitx serivce, should only be called after 236 | * calling fcitx_g_watcher_watch. 237 | */ 238 | void fcitx_g_watcher_unwatch(FcitxGWatcher *self) { 239 | g_return_if_fail(self->priv->watched); 240 | self->priv->watched = FALSE; 241 | _fcitx_g_watcher_clean_up(self); 242 | } 243 | 244 | /** 245 | * fcitx_g_watcher_new: 246 | * 247 | * New a #FcitxGWatcher 248 | * 249 | * Returns: A newly allocated #FcitxGWatcher 250 | **/ 251 | FcitxGWatcher *fcitx_g_watcher_new() { 252 | FcitxGWatcher *self = g_object_new(FCITX_G_TYPE_WATCHER, NULL); 253 | return FCITX_G_WATCHER(self); 254 | } 255 | 256 | /** 257 | * fcitx_g_watcher_is_valid: 258 | * @connection: A #FcitxGWatcher 259 | * 260 | * Check #FcitxGWatcher is valid to communicate with Fcitx 261 | * 262 | * Returns: #FcitxGWatcher is valid or not 263 | **/ 264 | gboolean fcitx_g_watcher_is_service_available(FcitxGWatcher *self) { 265 | return self->priv->available; 266 | } 267 | 268 | /** 269 | * fcitx_g_watcher_get_connection: 270 | * self: A #FcitxGWatcher 271 | * 272 | * Return the current #GDBusConnection 273 | * 274 | * Returns: (transfer none): #GDBusConnection for current connection 275 | **/ 276 | GDBusConnection *fcitx_g_watcher_get_connection(FcitxGWatcher *self) { 277 | return self->priv->connection; 278 | } 279 | 280 | void _fcitx_g_watcher_clean_up(FcitxGWatcher *self) { 281 | if (self->priv->cancellable) { 282 | g_cancellable_cancel(self->priv->cancellable); 283 | } 284 | if (self->priv->watch_id) { 285 | g_bus_unwatch_name(self->priv->watch_id); 286 | self->priv->watch_id = 0; 287 | } 288 | if (self->priv->portal_watch_id) { 289 | g_bus_unwatch_name(self->priv->portal_watch_id); 290 | self->priv->portal_watch_id = 0; 291 | } 292 | 293 | if (self->priv->connection) { 294 | g_signal_handlers_disconnect_by_data(self->priv->connection, self); 295 | } 296 | 297 | g_clear_pointer(&self->priv->main_owner, g_free); 298 | g_clear_pointer(&self->priv->portal_owner, g_free); 299 | g_clear_object(&self->priv->cancellable); 300 | g_clear_object(&self->priv->connection); 301 | } 302 | 303 | /** 304 | * fcitx_g_watcher_set_watch_portal: 305 | * self: A #FcitxGWatcher 306 | * watch: to monitor the portal service or not. 307 | * 308 | **/ 309 | void fcitx_g_watcher_set_watch_portal(FcitxGWatcher *self, gboolean watch) { 310 | self->priv->watch_portal = watch; 311 | } 312 | 313 | void _fcitx_g_watcher_update_availability(FcitxGWatcher *self) { 314 | gboolean available = self->priv->connection && 315 | (self->priv->main_owner || self->priv->portal_owner); 316 | if (available != self->priv->available) { 317 | self->priv->available = available; 318 | g_signal_emit(self, signals[AVAILABLITY_CHANGED_SIGNAL], 0); 319 | } 320 | } 321 | 322 | /** 323 | * fcitx_g_watcher_get_service_name: 324 | * @self: A #FcitxGWatcher 325 | * 326 | * Returns: (transfer none): an available service name. 327 | * 328 | **/ 329 | const gchar *fcitx_g_watcher_get_service_name(FcitxGWatcher *self) { 330 | if (self->priv->main_owner) { 331 | return self->priv->main_owner; 332 | } 333 | if (self->priv->portal_owner) { 334 | return self->priv->portal_owner; 335 | } 336 | return NULL; 337 | } 338 | -------------------------------------------------------------------------------- /fcitx-gclient/fcitxgwatcher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017~2017 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | #ifndef _FCITX_GCLIENT_FCITXWATCHER_H_ 7 | #define _FCITX_GCLIENT_FCITXWATCHER_H_ 8 | 9 | #include 10 | 11 | G_BEGIN_DECLS 12 | 13 | /* 14 | * Type macros 15 | */ 16 | 17 | /* define GOBJECT macros */ 18 | #define FCITX_G_TYPE_WATCHER (fcitx_g_watcher_get_type()) 19 | 20 | G_DECLARE_FINAL_TYPE(FcitxGWatcher, fcitx_g_watcher, FCITX_G, WATCHER, GObject) 21 | 22 | FcitxGWatcher *fcitx_g_watcher_new(); 23 | 24 | void fcitx_g_watcher_watch(FcitxGWatcher *self); 25 | void fcitx_g_watcher_unwatch(FcitxGWatcher *self); 26 | 27 | void fcitx_g_watcher_set_watch_portal(FcitxGWatcher *self, gboolean watch); 28 | gboolean fcitx_g_watcher_is_service_available(FcitxGWatcher *self); 29 | const gchar *fcitx_g_watcher_get_service_name(FcitxGWatcher *self); 30 | GDBusConnection *fcitx_g_watcher_get_connection(FcitxGWatcher *self); 31 | 32 | G_END_DECLS 33 | 34 | #endif // _FCITX_GCLIENT_FCITXWATCHER_H_ 35 | -------------------------------------------------------------------------------- /gtk-common/marshall.list: -------------------------------------------------------------------------------- 1 | VOID:UINT,UINT,INT 2 | VOID:STRING,STRING,STRING 3 | VOID:STRING,INT 4 | VOID:BOXED,INT 5 | VOID:INT,UINT 6 | VOID:BOXED,INT,BOXED,BOXED,BOXED,INT,INT,BOOLEAN,BOOLEAN 7 | -------------------------------------------------------------------------------- /gtk2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(FCITX_GTK2_IM_MODULE_SOURCES 2 | fcitxim.c 3 | fcitximcontext.cpp 4 | ) 5 | 6 | if (NOT DEFINED GTK2_IM_MODULEDIR) 7 | set(GTK2_IM_MODULEDIR "${CMAKE_INSTALL_LIBDIR}/gtk-2.0/${GTK2_BINARY_VERSION}/immodules" CACHE PATH "Gtk2 im module directory") 8 | endif() 9 | 10 | add_library(im-fcitx5 MODULE ${FCITX_GTK2_IM_MODULE_SOURCES}) 11 | set_target_properties( im-fcitx5 PROPERTIES PREFIX "" 12 | COMPILE_FLAGS "-fno-exceptions") 13 | target_link_libraries(im-fcitx5 Fcitx5::GClient XKBCommon::XKBCommon PkgConfig::Gtk2 PkgConfig::Gdk2 PkgConfig::Gdk2X11 X11Import) 14 | install(TARGETS im-fcitx5 DESTINATION "${GTK2_IM_MODULEDIR}") 15 | 16 | 17 | if (NOT BUILD_ONLY_PLUGIN) 18 | add_executable(fcitx5-gtk2-immodule-probing immodule-probing.cpp) 19 | target_link_libraries(fcitx5-gtk2-immodule-probing PkgConfig::Gtk2) 20 | install(TARGETS fcitx5-gtk2-immodule-probing DESTINATION "${CMAKE_INSTALL_BINDIR}") 21 | endif() 22 | -------------------------------------------------------------------------------- /gtk2/fcitxflags.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | #ifndef _COMMON_FCITXFLAGS_H_ 7 | #define _COMMON_FCITXFLAGS_H_ 8 | 9 | #include 10 | 11 | namespace fcitx { 12 | 13 | // This need to keep sync with fcitx5. 14 | enum FcitxCapabilityFlag : uint64_t { 15 | FcitxCapabilityFlag_Preedit = (1 << 1), 16 | FcitxCapabilityFlag_Password = (1 << 3), 17 | FcitxCapabilityFlag_FormattedPreedit = (1 << 4), 18 | FcitxCapabilityFlag_ClientUnfocusCommit = (1 << 5), 19 | FcitxCapabilityFlag_SurroundingText = (1 << 6), 20 | FcitxCapabilityFlag_Email = (1 << 7), 21 | FcitxCapabilityFlag_Digit = (1 << 8), 22 | FcitxCapabilityFlag_Uppercase = (1 << 9), 23 | FcitxCapabilityFlag_Lowercase = (1 << 10), 24 | FcitxCapabilityFlag_NoAutoUpperCase = (1 << 11), 25 | FcitxCapabilityFlag_Url = (1 << 12), 26 | FcitxCapabilityFlag_Dialable = (1 << 13), 27 | FcitxCapabilityFlag_Number = (1 << 14), 28 | FcitxCapabilityFlag_NoOnScreenKeyboard = (1 << 15), 29 | FcitxCapabilityFlag_SpellCheck = (1 << 16), 30 | FcitxCapabilityFlag_NoSpellCheck = (1 << 17), 31 | FcitxCapabilityFlag_WordCompletion = (1 << 18), 32 | FcitxCapabilityFlag_UppercaseWords = (1 << 19), 33 | FcitxCapabilityFlag_UppwercaseSentences = (1 << 20), 34 | FcitxCapabilityFlag_Alpha = (1 << 21), 35 | FcitxCapabilityFlag_Name = (1 << 22), 36 | FcitxCapabilityFlag_GetIMInfoOnFocus = (1 << 23), 37 | FcitxCapabilityFlag_RelativeRect = (1 << 24), 38 | 39 | FcitxCapabilityFlag_Multiline = (1ull << 35), 40 | FcitxCapabilityFlag_Sensitive = (1ull << 36), 41 | FcitxCapabilityFlag_KeyEventOrderFix = (1ull << 37), 42 | FcitxCapabilityFlag_ReportKeyRepeat = (1ull << 38), 43 | FcitxCapabilityFlag_ClientSideInputPanel = (1ull << 39), 44 | }; 45 | 46 | enum FcitxTextFormatFlag : int { 47 | FcitxTextFormatFlag_Underline = (1 << 3), /**< underline is a flag */ 48 | FcitxTextFormatFlag_HighLight = (1 << 4), /**< highlight the preedit */ 49 | FcitxTextFormatFlag_DontCommit = (1 << 5), 50 | FcitxTextFormatFlag_Bold = (1 << 6), 51 | FcitxTextFormatFlag_Strike = (1 << 7), 52 | FcitxTextFormatFlag_Italic = (1 << 8), 53 | FcitxTextFormatFlag_None = 0, 54 | }; 55 | 56 | enum class FcitxCandidateLayoutHint { NotSet, Vertical, Horizontal }; 57 | 58 | } // namespace fcitx 59 | 60 | #endif // _COMMON_FCITXFLAGS_H_ 61 | -------------------------------------------------------------------------------- /gtk2/fcitxim.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | #include "fcitximcontext.h" 9 | #include 10 | #include 11 | 12 | static const GtkIMContextInfo fcitx5_im_info = { 13 | "fcitx5", "Fcitx5 (Flexible Input Method Framework5)", "fcitx5", LOCALEDIR, 14 | "ja:ko:zh:*"}; 15 | 16 | static const GtkIMContextInfo fcitx_im_info = { 17 | "fcitx", "Fcitx5 (Flexible Input Method Framework5)", "fcitx5", LOCALEDIR, 18 | "ja:ko:zh:*"}; 19 | 20 | static const GtkIMContextInfo *info_list[] = {&fcitx_im_info, &fcitx5_im_info}; 21 | 22 | G_MODULE_EXPORT const gchar * 23 | g_module_check_init(G_GNUC_UNUSED GModule *module) { 24 | return glib_check_version(GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, 0); 25 | } 26 | 27 | G_MODULE_EXPORT void im_module_init(GTypeModule *type_module) { 28 | /* make module resident */ 29 | g_type_module_use(type_module); 30 | fcitx_im_context_register_type(type_module); 31 | } 32 | 33 | G_MODULE_EXPORT void im_module_exit(void) {} 34 | 35 | G_MODULE_EXPORT GtkIMContext *im_module_create(const gchar *context_id) { 36 | if (context_id != NULL && (g_strcmp0(context_id, "fcitx5") == 0 || 37 | g_strcmp0(context_id, "fcitx") == 0)) { 38 | FcitxIMContext *context; 39 | context = fcitx_im_context_new(); 40 | return (GtkIMContext *)context; 41 | } 42 | return NULL; 43 | } 44 | 45 | G_MODULE_EXPORT void im_module_list(const GtkIMContextInfo ***contexts, 46 | gint *n_contexts) { 47 | *contexts = info_list; 48 | *n_contexts = G_N_ELEMENTS(info_list); 49 | } 50 | 51 | // kate: indent-mode cstyle; space-indent on; indent-width 0; 52 | -------------------------------------------------------------------------------- /gtk2/fcitximcontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef __FCITX_IM_CONTEXT_H_ 8 | #define __FCITX_IM_CONTEXT_H_ 9 | 10 | #include 11 | 12 | /* 13 | * Type macros. 14 | */ 15 | #define FCITX_TYPE_IM_CONTEXT (fcitx_im_context_get_type()) 16 | #define FCITX_IM_CONTEXT(obj) \ 17 | (G_TYPE_CHECK_INSTANCE_CAST((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContext)) 18 | #define FCITX_IM_CONTEXT_CLASS(klass) \ 19 | (G_TYPE_CHECK_CLASS_CAST((klass), FCITX_TYPE_IM_CONTEXT, \ 20 | FcitxIMContextClass)) 21 | #define FCITX_IS_IM_CONTEXT(obj) \ 22 | (G_TYPE_CHECK_INSTANCE_TYPE((obj), FCITX_TYPE_IM_CONTEXT)) 23 | #define FCITX_IS_IM_CONTEXT_CLASS(klass) \ 24 | (G_TYPE_CHECK_CLASS_TYPE((klass), FCITX_TYPE_IM_CONTEXT)) 25 | #define FCITX_IM_CONTEXT_GET_CLASS(obj) \ 26 | (G_TYPE_CHECK_GET_CLASS((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContextClass)) 27 | 28 | G_BEGIN_DECLS 29 | 30 | typedef struct _FcitxIMContext FcitxIMContext; 31 | typedef struct _FcitxIMContextClass FcitxIMContextClass; 32 | 33 | GType fcitx_im_context_get_type(void); 34 | FcitxIMContext *fcitx_im_context_new(void); 35 | void fcitx_im_context_register_type(GTypeModule *type_module); 36 | 37 | G_END_DECLS 38 | #endif 39 | // kate: indent-mode cstyle; space-indent on; indent-width 0; 40 | -------------------------------------------------------------------------------- /gtk2/immodule-probing.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023~2023 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include 8 | #include 9 | #include 10 | 11 | int main(int argc, char *argv[]) { 12 | GtkIMContext *context; 13 | char *preedit_string = NULL; 14 | PangoAttrList *preedit_attrs = NULL; 15 | const char *context_id; 16 | 17 | #if GTK_CHECK_VERSION(4, 0, 0) 18 | (void)argc; 19 | (void)argv; 20 | gtk_init(); 21 | #else 22 | gtk_init(&argc, &argv); 23 | #endif 24 | context = gtk_im_multicontext_new(); 25 | gtk_im_context_get_preedit_string(context, &preedit_string, &preedit_attrs, 26 | 0); 27 | context_id = 28 | gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(context)); 29 | std::cout << "GTK_IM_MODULE=" << context_id << std::endl; 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /gtk2/utils.cpp: -------------------------------------------------------------------------------- 1 | ../gtk3/utils.cpp -------------------------------------------------------------------------------- /gtk2/utils.h: -------------------------------------------------------------------------------- 1 | ../gtk3/utils.h -------------------------------------------------------------------------------- /gtk3/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(FCITX_GTK3_IM_MODULE_SOURCES 2 | fcitxim.c 3 | fcitximcontext.cpp 4 | fcitxtheme.cpp 5 | utils.cpp 6 | inputwindow.cpp 7 | gtk3inputwindow.cpp 8 | ) 9 | 10 | if (NOT DEFINED GTK3_IM_MODULEDIR) 11 | set(GTK3_IM_MODULEDIR "${CMAKE_INSTALL_LIBDIR}/gtk-3.0/${GTK3_BINARY_VERSION}/immodules" CACHE PATH "Gtk3 im module directory") 12 | endif() 13 | 14 | add_library(im-fcitx5-gtk3 MODULE ${FCITX_GTK3_IM_MODULE_SOURCES}) 15 | set_target_properties(im-fcitx5-gtk3 PROPERTIES PREFIX "" OUTPUT_NAME "im-fcitx5" 16 | COMPILE_FLAGS "-fno-exceptions") 17 | 18 | target_link_libraries(im-fcitx5-gtk3 Fcitx5::GClient XKBCommon::XKBCommon PkgConfig::Gtk3 PkgConfig::GioUnix2) 19 | if (TARGET PkgConfig::Gdk3X11) 20 | target_link_libraries(im-fcitx5-gtk3 PkgConfig::Gdk3X11 X11Import) 21 | endif() 22 | 23 | install(TARGETS im-fcitx5-gtk3 DESTINATION "${GTK3_IM_MODULEDIR}") 24 | 25 | 26 | if (NOT BUILD_ONLY_PLUGIN) 27 | add_executable(fcitx5-gtk3-immodule-probing immodule-probing.cpp) 28 | target_link_libraries(fcitx5-gtk3-immodule-probing PkgConfig::Gtk3) 29 | install(TARGETS fcitx5-gtk3-immodule-probing DESTINATION "${CMAKE_INSTALL_BINDIR}") 30 | endif() 31 | -------------------------------------------------------------------------------- /gtk3/fcitxflags.h: -------------------------------------------------------------------------------- 1 | ../gtk2/fcitxflags.h -------------------------------------------------------------------------------- /gtk3/fcitxim.c: -------------------------------------------------------------------------------- 1 | ../gtk2/fcitxim.c -------------------------------------------------------------------------------- /gtk3/fcitximcontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef __FCITX_IM_CONTEXT_H_ 8 | #define __FCITX_IM_CONTEXT_H_ 9 | 10 | #include 11 | 12 | /* 13 | * Type macros. 14 | */ 15 | #define FCITX_TYPE_IM_CONTEXT (fcitx_im_context_get_type()) 16 | #define FCITX_IM_CONTEXT(obj) \ 17 | (G_TYPE_CHECK_INSTANCE_CAST((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContext)) 18 | #define FCITX_IM_CONTEXT_CLASS(klass) \ 19 | (G_TYPE_CHECK_CLASS_CAST((klass), FCITX_TYPE_IM_CONTEXT, \ 20 | FcitxIMContextClass)) 21 | #define FCITX_IS_IM_CONTEXT(obj) \ 22 | (G_TYPE_CHECK_INSTANCE_TYPE((obj), FCITX_TYPE_IM_CONTEXT)) 23 | #define FCITX_IS_IM_CONTEXT_CLASS(klass) \ 24 | (G_TYPE_CHECK_CLASS_TYPE((klass), FCITX_TYPE_IM_CONTEXT)) 25 | #define FCITX_IM_CONTEXT_GET_CLASS(obj) \ 26 | (G_TYPE_CHECK_GET_CLASS((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContextClass)) 27 | 28 | G_BEGIN_DECLS 29 | 30 | typedef struct _FcitxIMContext FcitxIMContext; 31 | typedef struct _FcitxIMContextClass FcitxIMContextClass; 32 | 33 | GType fcitx_im_context_get_type(void); 34 | FcitxIMContext *fcitx_im_context_new(void); 35 | void fcitx_im_context_register_type(GTypeModule *type_module); 36 | 37 | G_END_DECLS 38 | #endif 39 | // kate: indent-mode cstyle; space-indent on; indent-width 0; 40 | -------------------------------------------------------------------------------- /gtk3/fcitxtheme.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include "fcitxtheme.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace fcitx::gtk { 17 | 18 | namespace { 19 | 20 | // Read string value 21 | std::string getValue(GKeyFile *configFile, const char *group, const char *key, 22 | const char *defaultValue) { 23 | UniqueCPtr value( 24 | g_key_file_get_value(configFile, group, key, nullptr)); 25 | if (!value) { 26 | return defaultValue; 27 | } 28 | std::string valueStr = value.get(); 29 | if (!unescape(valueStr)) { 30 | return defaultValue; 31 | } 32 | return valueStr; 33 | } 34 | 35 | // Read bool value 36 | bool getValue(GKeyFile *configFile, const char *group, const char *key, 37 | bool defaultValue) { 38 | return getValue(configFile, group, key, defaultValue ? "True" : "False") == 39 | "True"; 40 | } 41 | 42 | // Read gravity enum value 43 | Gravity getValue(GKeyFile *configFile, const char *group, const char *key, 44 | Gravity defaultValue) { 45 | std::string value = getValue(configFile, group, key, ""); 46 | if (value == "Top Left") { 47 | return Gravity::TopLeft; 48 | } else if (value == "Top Center") { 49 | return Gravity::TopCenter; 50 | } else if (value == "Top Right") { 51 | return Gravity::TopRight; 52 | } else if (value == "Center Left") { 53 | return Gravity::CenterLeft; 54 | } else if (value == "Center") { 55 | return Gravity::Center; 56 | } else if (value == "Center Right") { 57 | return Gravity::CenterRight; 58 | } else if (value == "Bottom Left") { 59 | return Gravity::BottomLeft; 60 | } else if (value == "Bottom Center") { 61 | return Gravity::BottomCenter; 62 | } else if (value == "Bottom Right") { 63 | return Gravity::BottomRight; 64 | } 65 | return defaultValue; 66 | } 67 | 68 | // Read PageButtonAlignment enum value 69 | PageButtonAlignment getValue(GKeyFile *configFile, const char *group, 70 | const char *key, 71 | PageButtonAlignment defaultValue) { 72 | std::string value = getValue(configFile, group, key, ""); 73 | if (value == "Top") { 74 | return PageButtonAlignment::Top; 75 | } else if (value == "First Candidate") { 76 | return PageButtonAlignment::FirstCandidate; 77 | } else if (value == "Center") { 78 | return PageButtonAlignment::Center; 79 | } else if (value == "Last Candidate") { 80 | return PageButtonAlignment::LastCandidate; 81 | } else if (value == "Bottom") { 82 | return PageButtonAlignment::Bottom; 83 | } 84 | return defaultValue; 85 | } 86 | 87 | // Read int value 88 | int getValue(GKeyFile *configFile, const char *group, const char *key, 89 | int defaultValue) { 90 | std::string value = getValue(configFile, group, key, ""); 91 | char *eof_int; 92 | int result = strtol(value.data(), &eof_int, 10); 93 | if (value.empty() || (*eof_int != '\0' && !g_ascii_isspace(*eof_int))) { 94 | return defaultValue; 95 | } 96 | return result; 97 | } 98 | 99 | unsigned short roundColor(unsigned short c) { return c <= 255 ? c : 255; } 100 | 101 | unsigned short extendColor(unsigned short c) { 102 | c = roundColor(c); 103 | return c << 8 | c; 104 | } 105 | 106 | inline unsigned short toHexDigit(char hi, char lo) { 107 | hi = g_ascii_tolower(hi); 108 | lo = g_ascii_tolower(lo); 109 | int dhi = 0, dlo = 0; 110 | if (hi >= '0' && hi <= '9') { 111 | dhi = hi - '0'; 112 | } else { 113 | dhi = hi - 'a' + 10; 114 | } 115 | if (lo >= '0' && lo <= '9') { 116 | dlo = lo - '0'; 117 | } else { 118 | dlo = lo - 'a' + 10; 119 | } 120 | 121 | return dhi * 16 + dlo; 122 | } 123 | 124 | GdkRGBA makeGdkRGBA(unsigned short r, unsigned short g, unsigned short b, 125 | unsigned short a) { 126 | GdkRGBA result; 127 | result.red = 128 | extendColor(r) / double(std::numeric_limits::max()); 129 | result.green = 130 | extendColor(g) / double(std::numeric_limits::max()); 131 | result.blue = 132 | extendColor(b) / double(std::numeric_limits::max()); 133 | result.alpha = 134 | extendColor(a) / double(std::numeric_limits::max()); 135 | return result; 136 | } 137 | 138 | GdkRGBA getValue(GKeyFile *configFile, const char *group, const char *key, 139 | GdkRGBA defaultValue) { 140 | std::string value = getValue(configFile, group, key, ""); 141 | 142 | do { 143 | size_t idx = 0; 144 | 145 | // skip space 146 | while (value[idx] && g_ascii_isspace(value[idx])) { 147 | idx++; 148 | } 149 | 150 | if (value[idx] == '#') { 151 | // count the digit length 152 | size_t len = 0; 153 | const char *digits = &value[idx + 1]; 154 | while (digits[len] && 155 | (g_ascii_isdigit(digits[len]) || 156 | ('A' <= digits[len] && digits[len] <= 'F') | 157 | ('a' <= digits[len] && digits[len] <= 'f'))) { 158 | len++; 159 | } 160 | if (len != 8 && len != 6) { 161 | break; 162 | } 163 | 164 | unsigned short r, g, b, a; 165 | r = toHexDigit(digits[0], digits[1]); 166 | digits += 2; 167 | g = toHexDigit(digits[0], digits[1]); 168 | digits += 2; 169 | b = toHexDigit(digits[0], digits[1]); 170 | if (len == 8) { 171 | digits += 2; 172 | a = toHexDigit(digits[0], digits[1]); 173 | } else { 174 | a = 255; 175 | } 176 | 177 | return makeGdkRGBA(r, g, b, a); 178 | } else { 179 | unsigned short r, g, b; 180 | if (sscanf(value.data(), "%hu %hu %hu", &r, &g, &b) != 3) { 181 | break; 182 | } 183 | 184 | return makeGdkRGBA(r, g, b, 255); 185 | } 186 | } while (0); 187 | return defaultValue; 188 | } 189 | 190 | cairo_rectangle_int_t intersect(cairo_rectangle_int_t rect1, 191 | cairo_rectangle_int_t rect2) { 192 | cairo_rectangle_int_t tmp; 193 | tmp.x = std::max(rect1.x, rect2.x); 194 | tmp.y = std::max(rect1.y, rect2.y); 195 | auto x2 = std::min(rect1.x + rect1.width, rect2.x + rect2.width); 196 | auto y2 = std::min(rect1.y + rect1.height, rect2.y + rect2.height); 197 | 198 | if (tmp.x < x2 && tmp.y < y2) { 199 | tmp.width = x2 - tmp.x; 200 | tmp.height = y2 - tmp.y; 201 | } else { 202 | tmp.x = 0; 203 | tmp.y = 0; 204 | tmp.height = 0; 205 | tmp.width = 0; 206 | } 207 | return tmp; 208 | } 209 | 210 | UniqueCPtr 211 | locateXdgFile(const char *user, const char *const *dirs, const char *file) { 212 | if (!file) { 213 | return nullptr; 214 | } 215 | 216 | if (file[0] == '/') { 217 | return UniqueCPtr{g_strdup(file)}; 218 | } 219 | UniqueCPtr filename(g_build_filename(user, file, nullptr)); 220 | if (filename && g_file_test(filename.get(), G_FILE_TEST_IS_REGULAR)) { 221 | return filename; 222 | } 223 | 224 | for (int i = 0; dirs[i]; i++) { 225 | filename.reset(g_build_filename(dirs[i], file, nullptr)); 226 | if (filename && g_file_test(filename.get(), G_FILE_TEST_IS_REGULAR)) { 227 | return filename; 228 | } 229 | } 230 | return nullptr; 231 | } 232 | 233 | auto locateXdgConfigFile(const char *file) { 234 | return locateXdgFile(g_get_user_config_dir(), g_get_system_config_dirs(), 235 | file); 236 | } 237 | 238 | auto locateXdgDataFile(const char *file) { 239 | return locateXdgFile(g_get_user_data_dir(), g_get_system_data_dirs(), file); 240 | } 241 | 242 | template 243 | decltype(&std::declval().begin()->second) findValue(M &&m, K &&key) { 244 | auto iter = m.find(key); 245 | if (iter != m.end()) { 246 | return &iter->second; 247 | } 248 | return nullptr; 249 | } 250 | 251 | cairo_surface_t *pixBufToCairoSurface(GdkPixbuf *image) { 252 | cairo_format_t format; 253 | cairo_surface_t *surface; 254 | 255 | if (gdk_pixbuf_get_n_channels(image) == 3) { 256 | format = CAIRO_FORMAT_RGB24; 257 | } else { 258 | format = CAIRO_FORMAT_ARGB32; 259 | } 260 | 261 | surface = cairo_image_surface_create(format, gdk_pixbuf_get_width(image), 262 | gdk_pixbuf_get_height(image)); 263 | 264 | gint width, height; 265 | guchar *gdk_pixels, *cairo_pixels; 266 | int gdk_rowstride, cairo_stride; 267 | int n_channels; 268 | int j; 269 | 270 | if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { 271 | cairo_surface_destroy(surface); 272 | return nullptr; 273 | } 274 | 275 | cairo_surface_flush(surface); 276 | 277 | width = gdk_pixbuf_get_width(image); 278 | height = gdk_pixbuf_get_height(image); 279 | gdk_pixels = gdk_pixbuf_get_pixels(image); 280 | gdk_rowstride = gdk_pixbuf_get_rowstride(image); 281 | n_channels = gdk_pixbuf_get_n_channels(image); 282 | cairo_stride = cairo_image_surface_get_stride(surface); 283 | cairo_pixels = cairo_image_surface_get_data(surface); 284 | 285 | for (j = height; j; j--) { 286 | guchar *p = gdk_pixels; 287 | guchar *q = cairo_pixels; 288 | 289 | if (n_channels == 3) { 290 | guchar *end = p + 3 * width; 291 | 292 | while (p < end) { 293 | #if G_BYTE_ORDER == G_LITTLE_ENDIAN 294 | q[0] = p[2]; 295 | q[1] = p[1]; 296 | q[2] = p[0]; 297 | q[3] = 0xFF; 298 | #else 299 | q[0] = 0xFF; 300 | q[1] = p[0]; 301 | q[2] = p[1]; 302 | q[3] = p[2]; 303 | #endif 304 | p += 3; 305 | q += 4; 306 | } 307 | } else { 308 | guchar *end = p + 4 * width; 309 | guint t1, t2, t3; 310 | 311 | #define MULT(d, c, a, t) \ 312 | G_STMT_START { \ 313 | t = c * a + 0x80; \ 314 | d = ((t >> 8) + t) >> 8; \ 315 | } \ 316 | G_STMT_END 317 | 318 | while (p < end) { 319 | #if G_BYTE_ORDER == G_LITTLE_ENDIAN 320 | MULT(q[0], p[2], p[3], t1); 321 | MULT(q[1], p[1], p[3], t2); 322 | MULT(q[2], p[0], p[3], t3); 323 | q[3] = p[3]; 324 | #else 325 | q[0] = p[3]; 326 | MULT(q[1], p[0], p[3], t1); 327 | MULT(q[2], p[1], p[3], t2); 328 | MULT(q[3], p[2], p[3], t3); 329 | #endif 330 | 331 | p += 4; 332 | q += 4; 333 | } 334 | 335 | #undef MULT 336 | } 337 | 338 | gdk_pixels += gdk_rowstride; 339 | cairo_pixels += cairo_stride; 340 | } 341 | 342 | cairo_surface_mark_dirty(surface); 343 | return surface; 344 | } 345 | 346 | cairo_surface_t *loadImage(const char *filename) { 347 | if (!filename) { 348 | return nullptr; 349 | } 350 | 351 | if (g_str_has_suffix(filename, ".png")) { 352 | auto *surface = cairo_image_surface_create_from_png(filename); 353 | if (!surface) { 354 | return nullptr; 355 | } 356 | if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { 357 | g_clear_pointer(&surface, cairo_surface_destroy); 358 | return nullptr; 359 | } 360 | return surface; 361 | } 362 | 363 | auto *image = gdk_pixbuf_new_from_file(filename, nullptr); 364 | if (!image) { 365 | return nullptr; 366 | } 367 | 368 | auto *surface = pixBufToCairoSurface(image); 369 | 370 | g_object_unref(image); 371 | 372 | return surface; 373 | } 374 | 375 | } // namespace 376 | 377 | ThemeImage::ThemeImage(const std::string &name, 378 | const BackgroundImageConfig &cfg) { 379 | if (!cfg.image.empty()) { 380 | UniqueCPtr filename(g_build_filename( 381 | "fcitx5/themes", name.data(), cfg.image.data(), nullptr)); 382 | auto imageFile = locateXdgDataFile(filename.get()); 383 | image_.reset(loadImage(imageFile.get())); 384 | if (image_ && 385 | cairo_surface_status(image_.get()) != CAIRO_STATUS_SUCCESS) { 386 | image_.reset(); 387 | } 388 | valid_ = image_ != nullptr; 389 | } 390 | 391 | if (!cfg.overlay.empty()) { 392 | UniqueCPtr filename(g_build_filename( 393 | "fcitx5/themes", name.data(), cfg.overlay.data(), nullptr)); 394 | auto imageFile = locateXdgDataFile(filename.get()); 395 | overlay_.reset(loadImage(imageFile.get())); 396 | if (overlay_ && 397 | cairo_surface_status(overlay_.get()) != CAIRO_STATUS_SUCCESS) { 398 | overlay_.reset(); 399 | } 400 | } 401 | 402 | if (!image_) { 403 | auto width = cfg.margin.marginLeft + cfg.margin.marginRight + 1; 404 | auto height = cfg.margin.marginTop + cfg.margin.marginBottom + 1; 405 | 406 | auto borderWidth = std::min( 407 | {cfg.borderWidth, cfg.margin.marginLeft, cfg.margin.marginRight, 408 | cfg.margin.marginTop, cfg.margin.marginBottom}); 409 | borderWidth = std::max(0, borderWidth); 410 | 411 | image_.reset( 412 | cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height)); 413 | auto *cr = cairo_create(image_.get()); 414 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 415 | if (borderWidth) { 416 | cairoSetSourceColor(cr, cfg.borderColor); 417 | cairo_paint(cr); 418 | } 419 | 420 | cairo_rectangle(cr, borderWidth, borderWidth, width - borderWidth * 2, 421 | height - borderWidth * 2); 422 | cairo_clip(cr); 423 | cairoSetSourceColor(cr, cfg.color); 424 | cairo_paint(cr); 425 | cairo_destroy(cr); 426 | } 427 | } 428 | 429 | ThemeImage::ThemeImage(const std::string &name, const ActionImageConfig &cfg) { 430 | if (!cfg.image.empty()) { 431 | UniqueCPtr filename(g_build_filename( 432 | "fcitx5/themes", name.data(), cfg.image.data(), nullptr)); 433 | auto imageFile = locateXdgDataFile(filename.get()); 434 | image_.reset(loadImage(imageFile.get())); 435 | if (image_ && 436 | cairo_surface_status(image_.get()) != CAIRO_STATUS_SUCCESS) { 437 | image_.reset(); 438 | } 439 | valid_ = image_ != nullptr; 440 | } 441 | } 442 | 443 | Theme::Theme() {} 444 | 445 | Theme::~Theme() {} 446 | 447 | const ThemeImage &Theme::loadBackground(const BackgroundImageConfig &cfg) { 448 | if (auto *image = findValue(backgroundImageTable_, &cfg)) { 449 | return *image; 450 | } 451 | 452 | auto result = backgroundImageTable_.emplace( 453 | std::piecewise_construct, std::forward_as_tuple(&cfg), 454 | std::forward_as_tuple(name_, cfg)); 455 | assert(result.second); 456 | return result.first->second; 457 | } 458 | 459 | const ThemeImage &Theme::loadAction(const ActionImageConfig &cfg) { 460 | if (auto *image = findValue(actionImageTable_, &cfg)) { 461 | return *image; 462 | } 463 | 464 | auto result = actionImageTable_.emplace(std::piecewise_construct, 465 | std::forward_as_tuple(&cfg), 466 | std::forward_as_tuple(name_, cfg)); 467 | assert(result.second); 468 | return result.first->second; 469 | } 470 | 471 | void Theme::paint(cairo_t *c, const BackgroundImageConfig &cfg, int width, 472 | int height, double alpha) { 473 | const ThemeImage &image = loadBackground(cfg); 474 | auto marginTop = cfg.margin.marginTop; 475 | auto marginBottom = cfg.margin.marginBottom; 476 | auto marginLeft = cfg.margin.marginLeft; 477 | auto marginRight = cfg.margin.marginRight; 478 | int resizeHeight = 479 | cairo_image_surface_get_height(image) - marginTop - marginBottom; 480 | int resizeWidth = 481 | cairo_image_surface_get_width(image) - marginLeft - marginRight; 482 | 483 | if (resizeHeight <= 0) { 484 | resizeHeight = 1; 485 | } 486 | 487 | if (resizeWidth <= 0) { 488 | resizeWidth = 1; 489 | } 490 | 491 | if (height < 0) { 492 | height = resizeHeight; 493 | } 494 | 495 | if (width < 0) { 496 | width = resizeWidth; 497 | } 498 | 499 | const auto targetResizeWidth = width - marginLeft - marginRight; 500 | const auto targetResizeHeight = height - marginTop - marginBottom; 501 | const double scaleX = static_cast(targetResizeWidth) / resizeWidth; 502 | const double scaleY = 503 | static_cast(targetResizeHeight) / resizeHeight; 504 | 505 | cairo_save(c); 506 | 507 | /* 508 | * 7 8 9 509 | * 4 5 6 510 | * 1 2 3 511 | */ 512 | 513 | if (marginLeft && marginBottom) { 514 | /* part 1 */ 515 | cairo_save(c); 516 | cairo_translate(c, 0, height - marginBottom); 517 | cairo_set_source_surface(c, image, 0, -marginTop - resizeHeight); 518 | cairo_rectangle(c, 0, 0, marginLeft, marginBottom); 519 | cairo_clip(c); 520 | cairo_paint_with_alpha(c, alpha); 521 | cairo_restore(c); 522 | } 523 | 524 | if (marginRight && marginBottom) { 525 | /* part 3 */ 526 | cairo_save(c); 527 | cairo_translate(c, width - marginRight, height - marginBottom); 528 | cairo_set_source_surface(c, image, -marginLeft - resizeWidth, 529 | -marginTop - resizeHeight); 530 | cairo_rectangle(c, 0, 0, marginRight, marginBottom); 531 | cairo_clip(c); 532 | cairo_paint_with_alpha(c, alpha); 533 | cairo_restore(c); 534 | } 535 | 536 | if (marginLeft && marginTop) { 537 | /* part 7 */ 538 | cairo_save(c); 539 | cairo_set_source_surface(c, image, 0, 0); 540 | cairo_rectangle(c, 0, 0, marginLeft, marginTop); 541 | cairo_clip(c); 542 | cairo_paint_with_alpha(c, alpha); 543 | cairo_restore(c); 544 | } 545 | 546 | if (marginRight && marginTop) { 547 | /* part 9 */ 548 | cairo_save(c); 549 | cairo_translate(c, width - marginRight, 0); 550 | cairo_set_source_surface(c, image, -marginLeft - resizeWidth, 0); 551 | cairo_rectangle(c, 0, 0, marginRight, marginTop); 552 | cairo_clip(c); 553 | cairo_paint_with_alpha(c, alpha); 554 | cairo_restore(c); 555 | } 556 | 557 | /* part 2 & 8 */ 558 | if (marginTop && targetResizeWidth > 0) { 559 | cairo_save(c); 560 | cairo_translate(c, marginLeft, 0); 561 | cairo_scale(c, scaleX, 1); 562 | cairo_set_source_surface(c, image, -marginLeft, 0); 563 | cairo_rectangle(c, 0, 0, resizeWidth, marginTop); 564 | cairo_clip(c); 565 | cairo_paint_with_alpha(c, alpha); 566 | cairo_restore(c); 567 | } 568 | 569 | if (marginBottom && targetResizeWidth > 0) { 570 | cairo_save(c); 571 | cairo_translate(c, marginLeft, height - marginBottom); 572 | cairo_scale(c, scaleX, 1); 573 | cairo_set_source_surface(c, image, -marginLeft, 574 | -marginTop - resizeHeight); 575 | cairo_rectangle(c, 0, 0, resizeWidth, marginBottom); 576 | cairo_clip(c); 577 | cairo_paint_with_alpha(c, alpha); 578 | cairo_restore(c); 579 | } 580 | 581 | /* part 4 & 6 */ 582 | if (marginLeft && targetResizeHeight > 0) { 583 | cairo_save(c); 584 | cairo_translate(c, 0, marginTop); 585 | cairo_scale(c, 1, scaleY); 586 | cairo_set_source_surface(c, image, 0, -marginTop); 587 | cairo_rectangle(c, 0, 0, marginLeft, resizeHeight); 588 | cairo_clip(c); 589 | cairo_paint_with_alpha(c, alpha); 590 | cairo_restore(c); 591 | } 592 | 593 | if (marginRight && targetResizeHeight > 0) { 594 | cairo_save(c); 595 | cairo_translate(c, width - marginRight, marginTop); 596 | cairo_scale(c, 1, scaleY); 597 | cairo_set_source_surface(c, image, -marginLeft - resizeWidth, 598 | -marginTop); 599 | cairo_rectangle(c, 0, 0, marginRight, resizeHeight); 600 | cairo_clip(c); 601 | cairo_paint_with_alpha(c, alpha); 602 | cairo_restore(c); 603 | } 604 | 605 | /* part 5 */ 606 | if (targetResizeHeight > 0 && targetResizeWidth > 0) { 607 | cairo_save(c); 608 | cairo_translate(c, marginLeft, marginTop); 609 | cairo_scale(c, scaleX, scaleY); 610 | cairo_set_source_surface(c, image, -marginLeft, -marginTop); 611 | cairo_pattern_set_filter(cairo_get_source(c), CAIRO_FILTER_NEAREST); 612 | int w = resizeWidth, h = resizeHeight; 613 | 614 | cairo_rectangle(c, 0, 0, w, h); 615 | cairo_clip(c); 616 | cairo_paint_with_alpha(c, alpha); 617 | cairo_restore(c); 618 | } 619 | cairo_restore(c); 620 | 621 | if (!image.overlay()) { 622 | return; 623 | } 624 | 625 | auto clipWidth = width - cfg.overlayClipMargin.marginLeft - 626 | cfg.overlayClipMargin.marginRight; 627 | auto clipHeight = height - cfg.overlayClipMargin.marginTop - 628 | cfg.overlayClipMargin.marginBottom; 629 | if (clipWidth <= 0 || clipHeight <= 0) { 630 | return; 631 | } 632 | cairo_rectangle_int_t clipRect; 633 | clipRect.x = cfg.overlayClipMargin.marginLeft; 634 | clipRect.y = cfg.overlayClipMargin.marginTop; 635 | clipRect.width = clipWidth; 636 | clipRect.height = clipHeight; 637 | 638 | int x = 0, y = 0; 639 | switch (cfg.gravity) { 640 | case Gravity::TopLeft: 641 | case Gravity::CenterLeft: 642 | case Gravity::BottomLeft: 643 | x = cfg.overlayOffsetX; 644 | break; 645 | case Gravity::TopCenter: 646 | case Gravity::Center: 647 | case Gravity::BottomCenter: 648 | x = (width - image.overlayWidth()) / 2 + cfg.overlayOffsetX; 649 | break; 650 | case Gravity::TopRight: 651 | case Gravity::CenterRight: 652 | case Gravity::BottomRight: 653 | x = width - image.overlayWidth() - cfg.overlayOffsetX; 654 | break; 655 | } 656 | switch (cfg.gravity) { 657 | case Gravity::TopLeft: 658 | case Gravity::TopCenter: 659 | case Gravity::TopRight: 660 | y = cfg.overlayOffsetY; 661 | break; 662 | case Gravity::CenterLeft: 663 | case Gravity::Center: 664 | case Gravity::CenterRight: 665 | y = (height - image.overlayHeight()) / 2 + cfg.overlayOffsetY; 666 | break; 667 | case Gravity::BottomLeft: 668 | case Gravity::BottomCenter: 669 | case Gravity::BottomRight: 670 | y = height - image.overlayHeight() - cfg.overlayOffsetY; 671 | break; 672 | } 673 | 674 | cairo_rectangle_int_t rect; 675 | rect.x = x; 676 | rect.y = y; 677 | rect.width = image.overlayWidth(); 678 | rect.height = image.overlayHeight(); 679 | auto finalRect = intersect(rect, clipRect); 680 | if (finalRect.width == 0 || finalRect.height == 0) { 681 | return; 682 | } 683 | 684 | if (cfg.hideOverlayIfOversize && !rectContains(clipRect, rect)) { 685 | return; 686 | } 687 | 688 | cairo_save(c); 689 | cairo_set_operator(c, CAIRO_OPERATOR_OVER); 690 | cairo_translate(c, finalRect.x, finalRect.y); 691 | cairo_set_source_surface(c, image.overlay(), x - finalRect.x, 692 | y - finalRect.y); 693 | cairo_rectangle(c, 0, 0, finalRect.width, finalRect.height); 694 | cairo_clip(c); 695 | cairo_paint_with_alpha(c, alpha); 696 | cairo_restore(c); 697 | } 698 | 699 | void Theme::paint(cairo_t *c, const ActionImageConfig &cfg, double alpha) { 700 | const ThemeImage &image = loadAction(cfg); 701 | int height = cairo_image_surface_get_height(image); 702 | int width = cairo_image_surface_get_width(image); 703 | 704 | cairo_save(c); 705 | cairo_set_source_surface(c, image, 0, 0); 706 | cairo_rectangle(c, 0, 0, width, height); 707 | cairo_clip(c); 708 | cairo_paint_with_alpha(c, alpha); 709 | cairo_restore(c); 710 | } 711 | 712 | void Theme::load(const std::string &name) { 713 | backgroundImageTable_.clear(); 714 | actionImageTable_.clear(); 715 | name_ = name; 716 | 717 | UniqueCPtr configFile{g_key_file_new()}; 718 | UniqueCPtr filename( 719 | g_build_filename("fcitx5/themes", name.data(), "theme.conf", nullptr)); 720 | bool result = g_key_file_load_from_data_dirs( 721 | configFile.get(), filename.get(), nullptr, G_KEY_FILE_NONE, nullptr); 722 | if (!result) { 723 | result = g_key_file_load_from_data_dirs( 724 | configFile.get(), "fcitx5/themes/default/theme.conf", nullptr, 725 | G_KEY_FILE_NONE, nullptr); 726 | name_ = "default"; 727 | } 728 | 729 | InputPanelThemeConfig::load(configFile.get()); 730 | if (!result) { 731 | // Build a default theme like setup, so we have some default value for 732 | // flatpak. 733 | contentMargin = MarginConfig{2, 2, 2, 2}; 734 | textMargin = MarginConfig{5, 5, 5, 5}; 735 | highlight.color = highlightBackgroundColor; 736 | highlight.borderColor = highlightBackgroundColor; 737 | highlight.margin = textMargin; 738 | background.borderColor = highlightBackgroundColor; 739 | background.margin = contentMargin; 740 | background.borderWidth = 2; 741 | } 742 | } 743 | 744 | void InputPanelThemeConfig::load(GKeyFile *file) { 745 | normalColor = 746 | getValue(file, "InputPanel", "NormalColor", makeGdkRGBA(0, 0, 0, 255)); 747 | highlightCandidateColor = 748 | getValue(file, "InputPanel", "HighlightCandidateColor", 749 | makeGdkRGBA(255, 255, 255, 255)); 750 | enableBlur = getValue(file, "InputPanel", "EnableBlur", false); 751 | fullWidthHighlight = 752 | getValue(file, "InputPanel", "FullWidthHighlight", false); 753 | highlightColor = getValue(file, "InputPanel", "HighlightColor", 754 | makeGdkRGBA(255, 255, 255, 255)); 755 | highlightBackgroundColor = 756 | getValue(file, "InputPanel", "HighlightBackgroundColor", 757 | makeGdkRGBA(0xa5, 0xa5, 0xa5, 255)); 758 | buttonAlignment = getValue(file, "InputPanel", "PageButtonAlignment", 759 | PageButtonAlignment::Bottom); 760 | background.load(file, "InputPanel/Background"); 761 | highlight.load(file, "InputPanel/Highlight"); 762 | contentMargin.load(file, "InputPanel/ContentMargin"); 763 | textMargin.load(file, "InputPanel/TextMargin"); 764 | prev.load(file, "InputPanel/PrevPage"); 765 | next.load(file, "InputPanel/NextPage"); 766 | blurMargin.load(file, "InputPanel/BlurMargin"); 767 | shadowMargin.load(file, "InputPanel/ShadowMargin"); 768 | } 769 | 770 | void MarginConfig::load(GKeyFile *file, const char *group) { 771 | marginLeft = getValue(file, group, "Left", 0); 772 | marginRight = getValue(file, group, "Right", 0); 773 | marginTop = getValue(file, group, "Top", 0); 774 | marginBottom = getValue(file, group, "Bottom", 0); 775 | } 776 | 777 | void HighlightBackgroundImageConfig::load(GKeyFile *file, const char *group) { 778 | BackgroundImageConfig::load(file, group); 779 | std::string path = group; 780 | path.append("/HighlightClickMargin"); 781 | clickMargin.load(file, path.data()); 782 | } 783 | 784 | void ActionImageConfig::load(GKeyFile *file, const char *group) { 785 | std::string path = group; 786 | path.append("/ClickMargin"); 787 | image = getValue(file, group, "Image", ""); 788 | clickMargin.load(file, path.data()); 789 | } 790 | 791 | void BackgroundImageConfig::load(GKeyFile *file, const char *group) { 792 | image = getValue(file, group, "Image", ""); 793 | overlay = getValue(file, group, "Overlay", ""); 794 | color = getValue(file, group, "Color", {1, 1, 1, 1}); 795 | borderColor = getValue(file, group, "BorderColor", {1, 1, 1, 0}); 796 | borderWidth = getValue(file, group, "BorderWidth", 0); 797 | gravity = getValue(file, group, "Gravity", Gravity::TopLeft); 798 | overlayOffsetX = getValue(file, group, "OverlayOffsetX", 0); 799 | overlayOffsetY = getValue(file, group, "OverlayOffsetY", 0); 800 | hideOverlayIfOversize = 801 | getValue(file, group, "HideOverlayIfOversize", false); 802 | margin.load(file, (std::string(group) + "/Margin").data()); 803 | overlayClipMargin.load(file, 804 | (std::string(group) + "/OverlayClipMargin").data()); 805 | } 806 | 807 | ClassicUIConfig::ClassicUIConfig() { 808 | UniqueCPtr filename(g_build_filename( 809 | g_get_user_config_dir(), "fcitx5/conf/classicui.conf", nullptr)); 810 | GObjectUniquePtr file(g_file_new_for_path(filename.get())); 811 | monitor_.reset( 812 | g_file_monitor_file(file.get(), G_FILE_MONITOR_NONE, nullptr, nullptr)); 813 | 814 | g_signal_connect(monitor_.get(), "changed", 815 | G_CALLBACK(&ClassicUIConfig::configChangedCallback), this); 816 | 817 | load(); 818 | } 819 | 820 | ClassicUIConfig::~ClassicUIConfig() { 821 | resetThemeFileMonitor(); 822 | if (monitor_) { 823 | g_signal_handlers_disconnect_by_func( 824 | monitor_.get(), 825 | reinterpret_cast( 826 | G_CALLBACK(&ClassicUIConfig::configChangedCallback)), 827 | this); 828 | } 829 | } 830 | 831 | void ClassicUIConfig::load() { 832 | UniqueCPtr configFile{g_key_file_new()}; 833 | auto filename = locateXdgConfigFile("fcitx5/conf/classicui.conf"); 834 | gchar *content = nullptr; 835 | if (filename && 836 | g_file_get_contents(filename.get(), &content, nullptr, nullptr)) { 837 | UniqueCPtr ini(g_strdup_printf("[Group]\n%s", content)); 838 | g_free(content); 839 | g_key_file_load_from_data(configFile.get(), ini.get(), -1, 840 | G_KEY_FILE_NONE, nullptr); 841 | } 842 | 843 | font_ = getValue(configFile.get(), "Group", "Font", "Sans 10"); 844 | vertical_ = getValue(configFile.get(), "Group", "Vertical Candidate List", 845 | "False") == "True"; 846 | wheelForPaging_ = 847 | getValue(configFile.get(), "Group", "WheelForPaging", "True") == "True"; 848 | themeName_ = getValue(configFile.get(), "Group", "Theme", "default"); 849 | useInputMethodLanguageToDisplayText_ = getValue( 850 | configFile.get(), "Group", "UseInputMethodLangaugeToDisplayText", true); 851 | 852 | theme_.load(themeName_); 853 | 854 | resetThemeFileMonitor(); 855 | if (!theme_.name().empty()) { 856 | UniqueCPtr filename( 857 | g_build_filename(g_get_user_data_dir(), "fcitx5/themes", 858 | theme_.name().data(), "theme.conf", nullptr)); 859 | GObjectUniquePtr file(g_file_new_for_path(filename.get())); 860 | themeFileMonitor_.reset(g_file_monitor_file( 861 | file.get(), G_FILE_MONITOR_NONE, nullptr, nullptr)); 862 | 863 | g_signal_connect(themeFileMonitor_.get(), "changed", 864 | G_CALLBACK(&ClassicUIConfig::configChangedCallback), 865 | this); 866 | } 867 | } 868 | 869 | void ClassicUIConfig::resetThemeFileMonitor() { 870 | if (!themeFileMonitor_) { 871 | return; 872 | } 873 | g_signal_handlers_disconnect_by_func( 874 | themeFileMonitor_.get(), 875 | reinterpret_cast( 876 | G_CALLBACK(&ClassicUIConfig::configChangedCallback)), 877 | this); 878 | themeFileMonitor_.reset(); 879 | } 880 | 881 | } // namespace fcitx::gtk 882 | -------------------------------------------------------------------------------- /gtk3/fcitxtheme.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK3_FCITXTHEME_H_ 8 | #define _GTK3_FCITXTHEME_H_ 9 | 10 | #include "utils.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace fcitx::gtk { 18 | 19 | enum class Gravity { 20 | TopLeft, 21 | TopCenter, 22 | TopRight, 23 | CenterLeft, 24 | Center, 25 | CenterRight, 26 | BottomLeft, 27 | BottomCenter, 28 | BottomRight 29 | }; 30 | 31 | enum class PageButtonAlignment { 32 | Top, 33 | FirstCandidate, 34 | Center, 35 | LastCandidate, 36 | Bottom 37 | }; 38 | 39 | struct MarginConfig { 40 | void load(GKeyFile *file, const char *group); 41 | 42 | int marginLeft, marginRight, marginTop, marginBottom; 43 | }; 44 | 45 | struct BackgroundImageConfig { 46 | void load(GKeyFile *file, const char *group); 47 | 48 | std::string image; 49 | GdkRGBA color; 50 | GdkRGBA borderColor; 51 | int borderWidth = 0; 52 | std::string overlay; 53 | Gravity gravity; 54 | int overlayOffsetX = 0; 55 | int overlayOffsetY = 0; 56 | bool hideOverlayIfOversize = false; 57 | MarginConfig margin; 58 | MarginConfig overlayClipMargin; 59 | }; 60 | 61 | struct HighlightBackgroundImageConfig : public BackgroundImageConfig { 62 | void load(GKeyFile *file, const char *group); 63 | 64 | MarginConfig clickMargin; 65 | }; 66 | 67 | struct ActionImageConfig { 68 | void load(GKeyFile *file, const char *group); 69 | 70 | std::string image; 71 | MarginConfig clickMargin; 72 | }; 73 | 74 | struct InputPanelThemeConfig { 75 | void load(GKeyFile *file); 76 | 77 | GdkRGBA normalColor; 78 | GdkRGBA highlightCandidateColor; 79 | bool enableBlur = false; 80 | bool fullWidthHighlight = true; 81 | GdkRGBA highlightColor; 82 | GdkRGBA highlightBackgroundColor; 83 | PageButtonAlignment buttonAlignment; 84 | BackgroundImageConfig background; 85 | HighlightBackgroundImageConfig highlight; 86 | MarginConfig contentMargin; 87 | MarginConfig textMargin; 88 | ActionImageConfig prev; 89 | ActionImageConfig next; 90 | MarginConfig blurMargin; 91 | MarginConfig shadowMargin; 92 | }; 93 | 94 | class ThemeImage { 95 | public: 96 | ThemeImage(const std::string &name, const BackgroundImageConfig &cfg); 97 | ThemeImage(const std::string &name, const ActionImageConfig &cfg); 98 | 99 | operator cairo_surface_t *() const { return image_.get(); } 100 | auto height() const { 101 | int height = 1; 102 | if (image_) { 103 | height = cairo_image_surface_get_height(image_.get()); 104 | } 105 | return height <= 0 ? 1 : height; 106 | } 107 | auto width() const { 108 | int width = 1; 109 | if (image_) { 110 | width = cairo_image_surface_get_width(image_.get()); 111 | } 112 | return width <= 0 ? 1 : width; 113 | } 114 | 115 | auto size() const { return size_; } 116 | 117 | bool valid() const { return valid_; } 118 | cairo_surface_t *overlay() const { return overlay_.get(); } 119 | auto overlayWidth() const { 120 | int width = 1; 121 | if (overlay_) { 122 | width = cairo_image_surface_get_width(overlay_.get()); 123 | } 124 | return width <= 0 ? 1 : width; 125 | } 126 | auto overlayHeight() const { 127 | int height = 1; 128 | if (overlay_) { 129 | height = cairo_image_surface_get_height(overlay_.get()); 130 | } 131 | return height <= 0 ? 1 : height; 132 | } 133 | 134 | private: 135 | bool valid_ = false; 136 | std::string currentText_; 137 | uint32_t size_ = 0; 138 | UniqueCPtr image_; 139 | UniqueCPtr overlay_; 140 | }; 141 | 142 | class Theme : public InputPanelThemeConfig { 143 | public: 144 | Theme(); 145 | ~Theme(); 146 | 147 | void load(const std::string &name); 148 | const ThemeImage &loadBackground(const BackgroundImageConfig &cfg); 149 | const ThemeImage &loadAction(const ActionImageConfig &cfg); 150 | 151 | void paint(cairo_t *c, const BackgroundImageConfig &cfg, int width, 152 | int height, double alpha = 1.0); 153 | 154 | void paint(cairo_t *c, const ActionImageConfig &cfg, double alpha = 1.0); 155 | 156 | const auto &name() const { return name_; } 157 | 158 | private: 159 | std::unordered_map 160 | backgroundImageTable_; 161 | std::unordered_map actionImageTable_; 162 | std::string name_; 163 | }; 164 | 165 | class ClassicUIConfig { 166 | public: 167 | ClassicUIConfig(); 168 | ~ClassicUIConfig(); 169 | 170 | void load(); 171 | 172 | std::string font_; 173 | bool vertical_ = false; 174 | bool wheelForPaging_ = true; 175 | std::string themeName_ = "default"; 176 | bool useInputMethodLanguageToDisplayText_ = true; 177 | Theme theme_; 178 | 179 | private: 180 | void resetThemeFileMonitor(); 181 | 182 | static void configChangedCallback(GFileMonitor *, GFile *, GFile *, 183 | GFileMonitorEvent event_type, 184 | gpointer user_data) { 185 | if (event_type != G_FILE_MONITOR_EVENT_CHANGED && 186 | event_type != G_FILE_MONITOR_EVENT_CREATED) { 187 | return; 188 | } 189 | static_cast(user_data)->load(); 190 | } 191 | 192 | GObjectUniquePtr monitor_; 193 | GObjectUniquePtr themeFileMonitor_; 194 | }; 195 | 196 | inline void cairoSetSourceColor(cairo_t *cr, const GdkRGBA &color) { 197 | cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha); 198 | } 199 | 200 | inline void shrink(cairo_rectangle_int_t &rect, const MarginConfig &margin) { 201 | int newWidth = rect.width - margin.marginLeft - margin.marginRight; 202 | int newHeight = rect.height - margin.marginTop - margin.marginBottom; 203 | if (newWidth < 0) { 204 | newWidth = 0; 205 | } 206 | if (newHeight < 0) { 207 | newHeight = 0; 208 | } 209 | rect.x = rect.x + margin.marginLeft; 210 | rect.y = rect.y + margin.marginTop; 211 | rect.width = newWidth; 212 | rect.height = newHeight; 213 | } 214 | 215 | } // namespace fcitx::gtk 216 | 217 | #endif // _GTK3_FCITXTHEME_H_ 218 | -------------------------------------------------------------------------------- /gtk3/gtk3inputwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include "gtk3inputwindow.h" 8 | #include "fcitxtheme.h" 9 | #include 10 | 11 | namespace fcitx::gtk { 12 | 13 | Gtk3InputWindow::Gtk3InputWindow(ClassicUIConfig *config, FcitxGClient *client, 14 | bool isWayland) 15 | : InputWindow(config, client), isWayland_(isWayland) { 16 | rect_.x = rect_.y = rect_.height = rect_.width = 0; 17 | } 18 | 19 | Gtk3InputWindow::~Gtk3InputWindow() { 20 | if (window_) { 21 | g_signal_handlers_disconnect_by_data(window_.get(), this); 22 | window_.reset(); 23 | } 24 | // Clean up weak pointer reference. 25 | setParent(nullptr); 26 | } 27 | 28 | void Gtk3InputWindow::draw(cairo_t *cr) { paint(cr, width_, height_); } 29 | 30 | void Gtk3InputWindow::screenChanged() { 31 | GdkScreen *screen = gtk_widget_get_screen(window_.get()); 32 | GdkVisual *visual = gdk_screen_get_rgba_visual(screen); 33 | 34 | if (!visual) { 35 | visual = gdk_screen_get_system_visual(screen); 36 | supportAlpha = false; 37 | } else { 38 | supportAlpha = true; 39 | } 40 | 41 | gtk_widget_set_visual(window_.get(), visual); 42 | } 43 | 44 | void Gtk3InputWindow::setParent(GdkWindow *parent) { 45 | if (parent_ == parent) { 46 | return; 47 | } 48 | if (parent_) { 49 | g_object_remove_weak_pointer(G_OBJECT(parent_), 50 | reinterpret_cast(&parent_)); 51 | } 52 | if (parent) { 53 | g_object_add_weak_pointer(G_OBJECT(parent), 54 | reinterpret_cast(&parent_)); 55 | if (window_) { 56 | gtk_window_set_screen(GTK_WINDOW(window_.get()), 57 | gdk_window_get_screen(parent)); 58 | gtk_widget_realize(window_.get()); 59 | auto window = gtk_widget_get_window(window_.get()); 60 | if (window) { 61 | gdk_window_set_transient_for(window, parent); 62 | } 63 | } 64 | } 65 | parent_ = parent; 66 | } 67 | 68 | void Gtk3InputWindow::setCursorRect(GdkRectangle rect) { 69 | if (!parent_) { 70 | return; 71 | } 72 | if (rect.height <= 1) { 73 | rect.y = rect.y - 20 + rect.height; 74 | rect.height = 20; 75 | } 76 | 77 | if (rect_.x != rect.x || rect_.y != rect.y || rect_.height != rect.height || 78 | rect_.width != rect.width) { 79 | rect_ = rect; 80 | if (window_) { 81 | reposition(); 82 | } 83 | } 84 | } 85 | 86 | void Gtk3InputWindow::update() { 87 | if (visible() && parent_) { 88 | init(); 89 | pango_cairo_context_set_font_options( 90 | context_.get(), 91 | gdk_screen_get_font_options(gtk_widget_get_screen(window_.get()))); 92 | dpi_ = gdk_screen_get_resolution(gtk_widget_get_screen(window_.get())); 93 | pango_cairo_context_set_resolution(context_.get(), dpi_); 94 | std::tie(width_, height_) = sizeHint(); 95 | if (width_ <= 0 || height_ <= 0) { 96 | gtk_widget_hide(window_.get()); 97 | return; 98 | } 99 | 100 | if (auto gdkWindow = gtk_widget_get_window(window_.get())) { 101 | gdk_window_set_shadow_width( 102 | gdkWindow, config_->theme_.shadowMargin.marginLeft, 103 | config_->theme_.shadowMargin.marginRight, 104 | config_->theme_.shadowMargin.marginTop, 105 | config_->theme_.shadowMargin.marginBottom); 106 | } 107 | 108 | gtk_widget_realize(window_.get()); 109 | gtk_window_resize(GTK_WINDOW(window_.get()), width_, height_); 110 | gtk_widget_queue_draw(window_.get()); 111 | reposition(); 112 | gtk_widget_show_all(window_.get()); 113 | } else if (window_) { 114 | gtk_widget_hide(window_.get()); 115 | } 116 | } 117 | 118 | void Gtk3InputWindow::init() { 119 | if (window_) { 120 | return; 121 | } 122 | if (!parent_) { 123 | return; 124 | } 125 | window_.reset(gtk_window_new(GTK_WINDOW_POPUP)); 126 | auto window = window_.get(); 127 | gtk_window_set_screen(GTK_WINDOW(window), gdk_window_get_screen(parent_)); 128 | gtk_container_set_border_width(GTK_CONTAINER(window), 0); 129 | gtk_window_set_decorated(GTK_WINDOW(window), false); 130 | 131 | gtk_window_set_type_hint(GTK_WINDOW(window), 132 | GDK_WINDOW_TYPE_HINT_POPUP_MENU); 133 | gtk_widget_set_app_paintable(window, TRUE); 134 | gtk_widget_set_events(window, GDK_LEAVE_NOTIFY_MASK | GDK_SCROLL_MASK | 135 | GDK_BUTTON_RELEASE_MASK | 136 | GDK_POINTER_MOTION_MASK); 137 | 138 | auto draw = [](GtkWidget *, cairo_t *cr, gpointer userdata) { 139 | static_cast(userdata)->draw(cr); 140 | }; 141 | 142 | auto screen_changed = [](GtkWidget *, GdkScreen *, gpointer userdata) { 143 | static_cast(userdata)->screenChanged(); 144 | }; 145 | 146 | auto leave = [](GtkWidget *, GdkEvent *, gpointer userdata) -> gboolean { 147 | static_cast(userdata)->leave(); 148 | return TRUE; 149 | }; 150 | auto motion = [](GtkWidget *, GdkEvent *event, 151 | gpointer userdata) -> gboolean { 152 | static_cast(userdata)->motion(event); 153 | return TRUE; 154 | }; 155 | auto scroll = [](GtkWidget *, GdkEvent *event, 156 | gpointer userdata) -> gboolean { 157 | static_cast(userdata)->scroll(event); 158 | return TRUE; 159 | }; 160 | auto release = [](GtkWidget *, GdkEvent *event, 161 | gpointer userdata) -> gboolean { 162 | static_cast(userdata)->release(event); 163 | return TRUE; 164 | }; 165 | g_signal_connect(G_OBJECT(window), "draw", G_CALLBACK(+draw), this); 166 | g_signal_connect(G_OBJECT(window), "screen-changed", 167 | G_CALLBACK(+screen_changed), this); 168 | g_signal_connect(G_OBJECT(window), "motion-notify-event", 169 | G_CALLBACK(+motion), this); 170 | g_signal_connect(G_OBJECT(window), "leave-notify-event", G_CALLBACK(+leave), 171 | this); 172 | g_signal_connect(G_OBJECT(window), "scroll-event", G_CALLBACK(+scroll), 173 | this); 174 | g_signal_connect(G_OBJECT(window), "button-release-event", 175 | G_CALLBACK(+release), this); 176 | gtk_widget_realize(window_.get()); 177 | if (auto gdkWindow = gtk_widget_get_window(window_.get())) { 178 | gdk_window_set_transient_for(gdkWindow, parent_); 179 | } 180 | 181 | screenChanged(); 182 | } 183 | 184 | void Gtk3InputWindow::reposition() { 185 | if (!parent_ || !visible()) { 186 | return; 187 | } 188 | 189 | if (auto gdkWindow = gtk_widget_get_window(window_.get())) { 190 | if (!isWayland_) { 191 | gdk_window_move_to_rect(gdkWindow, &rect_, GDK_GRAVITY_SOUTH_WEST, 192 | GDK_GRAVITY_NORTH_WEST, 193 | static_cast( 194 | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_FLIP_Y | 195 | GDK_ANCHOR_SLIDE_Y), 196 | 0, 0); 197 | return; 198 | } 199 | 200 | GdkWindow *parent; 201 | GdkWindow *window = parent_; 202 | int posX = rect_.x, posY = rect_.y; 203 | 204 | // Find the top level window. 205 | while ((parent = gdk_window_get_effective_parent(window)) != NULL) { 206 | double dx, dy; 207 | gdk_window_coords_to_parent(window, posX, posY, &dx, &dy); 208 | posX = dx; 209 | posY = dy; 210 | if (gdk_window_get_window_type(parent) == GDK_WINDOW_ROOT) { 211 | break; 212 | } 213 | window = parent; 214 | } 215 | int x, y, w, h; 216 | gdk_window_get_geometry(window, &x, &y, &w, &h); 217 | posY += rect_.height; 218 | 219 | if (posX + width_ > x + w || posY + height_ > y + h || 220 | lastRect_.height != rect_.height || 221 | lastRect_.width != rect_.width || lastRect_.x != rect_.x || 222 | lastRect_.y != rect_.y) { 223 | gtk_widget_hide(window_.get()); 224 | lastRect_ = rect_; 225 | gdk_window_move_to_rect( 226 | gdkWindow, &rect_, GDK_GRAVITY_SOUTH_WEST, 227 | GDK_GRAVITY_NORTH_WEST, 228 | (GdkAnchorHints)(GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_FLIP_Y), 0, 0); 229 | gtk_widget_show_all(window_.get()); 230 | } 231 | } 232 | } 233 | 234 | void Gtk3InputWindow::leave() { 235 | auto oldHighlight = highlight(); 236 | hoverIndex_ = -1; 237 | if (highlight() != oldHighlight) { 238 | gtk_widget_queue_draw(window_.get()); 239 | } 240 | } 241 | 242 | void Gtk3InputWindow::release(GdkEvent *event) { 243 | guint button; 244 | gdk_event_get_button(event, &button); 245 | if (button == 1) { 246 | double x = 0, y = 0; 247 | gdk_event_get_coords(event, &x, &y); 248 | click(x, y); 249 | } 250 | } 251 | 252 | void Gtk3InputWindow::scroll(GdkEvent *event) { 253 | double vscroll_factor = 0.0; 254 | double x_scroll, y_scroll; 255 | // Handle discrete scrolling with a known constant delta; 256 | const double delta = 1.0; 257 | 258 | // In Gtk 3, axis and axis discrete will be emitted at the same time. 259 | if (gdk_event_get_scroll_deltas(event, &x_scroll, &y_scroll)) { 260 | // Handle smooth scrolling directly 261 | vscroll_factor = y_scroll; 262 | if (vscroll_factor != 0) { 263 | scrollDelta_ += vscroll_factor; 264 | while (scrollDelta_ >= delta) { 265 | scrollDelta_ -= delta; 266 | next(); 267 | } 268 | while (scrollDelta_ <= -delta) { 269 | scrollDelta_ += delta; 270 | prev(); 271 | } 272 | } 273 | } 274 | } 275 | 276 | void Gtk3InputWindow::motion(GdkEvent *event) { 277 | double x = 0, y = 0; 278 | gdk_event_get_coords(event, &x, &y); 279 | if (hover(x, y)) { 280 | gtk_widget_queue_draw(window_.get()); 281 | } 282 | } 283 | 284 | } // namespace fcitx::gtk 285 | -------------------------------------------------------------------------------- /gtk3/gtk3inputwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK3_GTK3INPUTWINDOW_H_ 8 | #define _GTK3_GTK3INPUTWINDOW_H_ 9 | 10 | #include "inputwindow.h" 11 | #include 12 | 13 | namespace fcitx::gtk { 14 | 15 | class Gtk3InputWindow : public InputWindow { 16 | public: 17 | Gtk3InputWindow(ClassicUIConfig *config, FcitxGClient *client, 18 | bool isWayland); 19 | 20 | ~Gtk3InputWindow(); 21 | 22 | void setParent(GdkWindow *parent); 23 | void update() override; 24 | void setCursorRect(GdkRectangle rect); 25 | 26 | private: 27 | void init(); 28 | void draw(cairo_t *cr); 29 | void screenChanged(); 30 | void reposition(); 31 | void scroll(GdkEvent *event); 32 | void motion(GdkEvent *event); 33 | void release(GdkEvent *event); 34 | void leave(); 35 | 36 | bool supportAlpha = false; 37 | UniqueCPtr window_; 38 | GdkWindow *parent_ = nullptr; 39 | int width_ = 1; 40 | int height_ = 1; 41 | GdkRectangle rect_; 42 | double scrollDelta_ = 0; 43 | const bool isWayland_ = false; 44 | GdkRectangle lastRect_ = {0, 0, 0, 0}; 45 | }; 46 | 47 | } // namespace fcitx::gtk 48 | 49 | #endif // _GTK3_GTK3INPUTWINDOW_H_ 50 | -------------------------------------------------------------------------------- /gtk3/immodule-probing.cpp: -------------------------------------------------------------------------------- 1 | ../gtk2/immodule-probing.cpp -------------------------------------------------------------------------------- /gtk3/inputwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017-2017 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include "inputwindow.h" 8 | #include "fcitxtheme.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace fcitx::gtk { 17 | 18 | size_t textLength(GPtrArray *array) { 19 | size_t length = 0; 20 | for (unsigned int i = 0; i < array->len; i++) { 21 | auto *preedit = 22 | static_cast(g_ptr_array_index(array, i)); 23 | length += strlen(preedit->string); 24 | } 25 | return length; 26 | } 27 | 28 | auto newPangoLayout(PangoContext *context) { 29 | GObjectUniquePtr ptr(pango_layout_new(context)); 30 | pango_layout_set_single_paragraph_mode(ptr.get(), false); 31 | return ptr; 32 | } 33 | 34 | static void prepareLayout(cairo_t *cr, PangoLayout *layout) { 35 | const PangoMatrix *matrix; 36 | 37 | matrix = pango_context_get_matrix(pango_layout_get_context(layout)); 38 | 39 | if (matrix) { 40 | cairo_matrix_t cairo_matrix; 41 | 42 | cairo_matrix_init(&cairo_matrix, matrix->xx, matrix->yx, matrix->xy, 43 | matrix->yy, matrix->x0, matrix->y0); 44 | 45 | cairo_transform(cr, &cairo_matrix); 46 | } 47 | } 48 | 49 | static void renderLayout(cairo_t *cr, PangoLayout *layout, int x, int y) { 50 | auto context = pango_layout_get_context(layout); 51 | auto *metrics = pango_context_get_metrics( 52 | context, pango_context_get_font_description(context), 53 | pango_context_get_language(context)); 54 | auto ascent = pango_font_metrics_get_ascent(metrics); 55 | pango_font_metrics_unref(metrics); 56 | auto baseline = pango_layout_get_baseline(layout); 57 | auto yOffset = PANGO_PIXELS(ascent - baseline); 58 | cairo_save(cr); 59 | 60 | cairo_move_to(cr, x, y + yOffset); 61 | prepareLayout(cr, layout); 62 | pango_cairo_show_layout(cr, layout); 63 | 64 | cairo_restore(cr); 65 | } 66 | 67 | int MultilineLayout::width() const { 68 | int width = 0; 69 | for (const auto &layout : lines_) { 70 | int w, h; 71 | pango_layout_get_pixel_size(layout.get(), &w, &h); 72 | width = std::max(width, w); 73 | } 74 | return width; 75 | } 76 | 77 | void MultilineLayout::render(cairo_t *cr, int x, int y, int lineHeight, 78 | bool highlight) { 79 | for (size_t i = 0; i < lines_.size(); i++) { 80 | if (highlight) { 81 | pango_layout_set_attributes(lines_[i].get(), 82 | highlightAttrLists_[i].get()); 83 | } else { 84 | pango_layout_set_attributes(lines_[i].get(), attrLists_[i].get()); 85 | } 86 | renderLayout(cr, lines_[i].get(), x, y); 87 | y += lineHeight; 88 | } 89 | } 90 | 91 | InputWindow::InputWindow(ClassicUIConfig *config, FcitxGClient *client) 92 | : config_(config), client_(FCITX_G_CLIENT(g_object_ref(client))) { 93 | context_.reset( 94 | pango_font_map_create_context(pango_cairo_font_map_get_default())); 95 | upperLayout_ = newPangoLayout(context_.get()); 96 | lowerLayout_ = newPangoLayout(context_.get()); 97 | 98 | auto update_ui_callback = 99 | [](FcitxGClient *, GPtrArray *preedit, int cursor_pos, GPtrArray *auxUp, 100 | GPtrArray *auxDown, GPtrArray *candidates, int highlight, 101 | int layoutHint, gboolean hasPrev, gboolean hasNext, 102 | void *user_data) { 103 | auto that = static_cast(user_data); 104 | that->updateUI(preedit, cursor_pos, auxUp, auxDown, candidates, 105 | highlight, layoutHint, hasPrev, hasNext); 106 | }; 107 | 108 | auto update_im_callback = [](FcitxGClient *, gchar *, gchar *, 109 | gchar *langCode, void *user_data) { 110 | auto that = static_cast(user_data); 111 | that->updateLanguage(langCode); 112 | }; 113 | 114 | g_signal_connect(client_.get(), "update-client-side-ui", 115 | G_CALLBACK(+update_ui_callback), this); 116 | 117 | g_signal_connect(client_.get(), "current-im", 118 | G_CALLBACK(+update_im_callback), this); 119 | } 120 | 121 | InputWindow::~InputWindow() { 122 | g_signal_handlers_disconnect_by_data(client_.get(), this); 123 | } 124 | 125 | void InputWindow::insertAttr(PangoAttrList *attrList, 126 | FcitxTextFormatFlag format, int start, int end, 127 | bool highlight) const { 128 | if (format & FcitxTextFormatFlag_Underline) { 129 | auto *attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); 130 | attr->start_index = start; 131 | attr->end_index = end; 132 | pango_attr_list_insert(attrList, attr); 133 | } 134 | if (format & FcitxTextFormatFlag_Italic) { 135 | auto *attr = pango_attr_style_new(PANGO_STYLE_ITALIC); 136 | attr->start_index = start; 137 | attr->end_index = end; 138 | pango_attr_list_insert(attrList, attr); 139 | } 140 | if (format & FcitxTextFormatFlag_Strike) { 141 | auto *attr = pango_attr_strikethrough_new(true); 142 | attr->start_index = start; 143 | attr->end_index = end; 144 | pango_attr_list_insert(attrList, attr); 145 | } 146 | if (format & FcitxTextFormatFlag_Bold) { 147 | auto *attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD); 148 | attr->start_index = start; 149 | attr->end_index = end; 150 | pango_attr_list_insert(attrList, attr); 151 | } 152 | GdkRGBA color = (format & FcitxTextFormatFlag_HighLight) 153 | ? config_->theme_.highlightColor 154 | : (highlight ? config_->theme_.highlightCandidateColor 155 | : config_->theme_.normalColor); 156 | const auto scale = std::numeric_limits::max(); 157 | auto *attr = pango_attr_foreground_new( 158 | color.red * scale, color.green * scale, color.blue * scale); 159 | attr->start_index = start; 160 | attr->end_index = end; 161 | pango_attr_list_insert(attrList, attr); 162 | 163 | if (color.alpha != 1.0) { 164 | auto *alphaAttr = pango_attr_foreground_alpha_new(color.alpha * scale); 165 | alphaAttr->start_index = start; 166 | alphaAttr->end_index = end; 167 | pango_attr_list_insert(attrList, alphaAttr); 168 | } 169 | 170 | const auto &background = config_->theme_.highlightBackgroundColor; 171 | if ((format & FcitxTextFormatFlag_HighLight) && background.alpha > 0) { 172 | attr = pango_attr_background_new(background.red * scale, 173 | background.green * scale, 174 | background.blue * scale); 175 | attr->start_index = start; 176 | attr->end_index = end; 177 | pango_attr_list_insert(attrList, attr); 178 | 179 | if (background.alpha != 1.0) { 180 | auto *alphaAttr = 181 | pango_attr_background_alpha_new(background.alpha * scale); 182 | alphaAttr->start_index = start; 183 | alphaAttr->end_index = end; 184 | pango_attr_list_insert(attrList, alphaAttr); 185 | } 186 | } 187 | } 188 | 189 | void InputWindow::appendText(std::string &s, PangoAttrList *attrList, 190 | PangoAttrList *highlightAttrList, 191 | const GPtrArray *text) { 192 | for (size_t i = 0, e = text->len; i < e; i++) { 193 | auto *item = 194 | static_cast(g_ptr_array_index(text, i)); 195 | appendText(s, attrList, highlightAttrList, item->string, item->type); 196 | } 197 | } 198 | 199 | void InputWindow::appendText(std::string &s, PangoAttrList *attrList, 200 | PangoAttrList *highlightAttrList, 201 | const gchar *text, int format) { 202 | auto start = s.size(); 203 | s.append(text); 204 | auto end = s.size(); 205 | if (start == end) { 206 | return; 207 | } 208 | const auto formatFlags = static_cast(format); 209 | insertAttr(attrList, formatFlags, start, end, false); 210 | if (highlightAttrList) { 211 | insertAttr(highlightAttrList, formatFlags, start, end, true); 212 | } 213 | } 214 | 215 | void InputWindow::resizeCandidates(size_t n) { 216 | while (labelLayouts_.size() < n) { 217 | labelLayouts_.emplace_back(); 218 | } 219 | while (candidateLayouts_.size() < n) { 220 | candidateLayouts_.emplace_back(); 221 | } 222 | 223 | nCandidates_ = n; 224 | } 225 | void InputWindow::setLanguageAttr(size_t size, PangoAttrList *attrList, 226 | PangoAttrList *highlightAttrList) { 227 | do { 228 | if (!config_->useInputMethodLanguageToDisplayText_ || 229 | language_.empty()) { 230 | break; 231 | } 232 | auto language = pango_language_from_string(language_.c_str()); 233 | if (!language) { 234 | break; 235 | } 236 | if (attrList) { 237 | auto attr = pango_attr_language_new(language); 238 | attr->start_index = 0; 239 | attr->end_index = size; 240 | pango_attr_list_insert(attrList, attr); 241 | } 242 | if (highlightAttrList) { 243 | auto attr = pango_attr_language_new(language); 244 | attr->start_index = 0; 245 | attr->end_index = size; 246 | pango_attr_list_insert(highlightAttrList, attr); 247 | } 248 | return; 249 | } while (0); 250 | } 251 | 252 | void InputWindow::setTextToMultilineLayout(MultilineLayout &layout, 253 | const gchar *text) { 254 | gchar **lines = g_strsplit(text, "\n", -1); 255 | layout.lines_.clear(); 256 | layout.attrLists_.clear(); 257 | layout.highlightAttrLists_.clear(); 258 | 259 | for (int i = 0; lines && lines[i]; i++) { 260 | layout.lines_.emplace_back(pango_layout_new(context_.get())); 261 | layout.attrLists_.emplace_back(); 262 | layout.highlightAttrLists_.emplace_back(); 263 | setTextToLayout(layout.lines_.back().get(), &layout.attrLists_.back(), 264 | &layout.highlightAttrLists_.back(), lines[i]); 265 | } 266 | 267 | g_strfreev(lines); 268 | } 269 | 270 | void InputWindow::setTextToLayout( 271 | PangoLayout *layout, PangoAttrListUniquePtr *attrList, 272 | PangoAttrListUniquePtr *highlightAttrList, 273 | std::initializer_list texts) { 274 | auto *newAttrList = pango_attr_list_new(); 275 | if (attrList) { 276 | // PangoAttrList does not have "clear()". So when we set new text, 277 | // we need to create a new one and get rid of old one. 278 | // We keep a ref to the attrList. 279 | attrList->reset(pango_attr_list_ref(newAttrList)); 280 | } 281 | PangoAttrList *newHighlightAttrList = nullptr; 282 | if (highlightAttrList) { 283 | newHighlightAttrList = pango_attr_list_new(); 284 | highlightAttrList->reset(newHighlightAttrList); 285 | } 286 | std::string line; 287 | for (const auto &text : texts) { 288 | appendText(line, newAttrList, newHighlightAttrList, text); 289 | } 290 | 291 | setLanguageAttr(line.size(), newAttrList, newHighlightAttrList); 292 | 293 | pango_layout_set_text(layout, line.c_str(), line.size()); 294 | pango_layout_set_attributes(layout, newAttrList); 295 | pango_attr_list_unref(newAttrList); 296 | } 297 | 298 | void InputWindow::setTextToLayout(PangoLayout *layout, 299 | PangoAttrListUniquePtr *attrList, 300 | PangoAttrListUniquePtr *highlightAttrList, 301 | const gchar *text) { 302 | auto *newAttrList = pango_attr_list_new(); 303 | if (attrList) { 304 | // PangoAttrList does not have "clear()". So when we set new text, 305 | // we need to create a new one and get rid of old one. 306 | // We keep a ref to the attrList. 307 | attrList->reset(pango_attr_list_ref(newAttrList)); 308 | } 309 | PangoAttrList *newHighlightAttrList = nullptr; 310 | if (highlightAttrList) { 311 | newHighlightAttrList = pango_attr_list_new(); 312 | highlightAttrList->reset(newHighlightAttrList); 313 | } 314 | std::string line; 315 | appendText(line, newAttrList, newHighlightAttrList, text); 316 | 317 | pango_layout_set_text(layout, line.c_str(), line.size()); 318 | pango_layout_set_attributes(layout, newAttrList); 319 | pango_attr_list_unref(newAttrList); 320 | } 321 | 322 | void InputWindow::updateUI(GPtrArray *preedit, int cursor_pos, GPtrArray *auxUp, 323 | GPtrArray *auxDown, GPtrArray *candidates, 324 | int highlight, int layoutHint, bool hasPrev, 325 | bool hasNext) { 326 | // | aux up | preedit 327 | // | aux down 328 | // | 1 candidate | 2 ... 329 | // or 330 | // | aux up | preedit 331 | // | aux down 332 | // | candidate 1 333 | // | candidate 2 334 | // | candidate 3 335 | 336 | cursor_ = -1; 337 | pango_layout_set_single_paragraph_mode(upperLayout_.get(), true); 338 | setTextToLayout(upperLayout_.get(), nullptr, nullptr, {auxUp, preedit}); 339 | if (cursor_pos >= 0 && 340 | static_cast(cursor_pos) <= textLength(preedit)) { 341 | 342 | cursor_ = cursor_pos + textLength(auxUp); 343 | } 344 | 345 | setTextToLayout(lowerLayout_.get(), nullptr, nullptr, {auxDown}); 346 | 347 | // Count non-placeholder candidates. 348 | resizeCandidates(candidates->len); 349 | 350 | candidateIndex_ = highlight; 351 | for (int i = 0, e = candidates->len; i < e; i++) { 352 | auto *candidate = static_cast( 353 | g_ptr_array_index(candidates, i)); 354 | setTextToMultilineLayout(labelLayouts_[i], candidate->label); 355 | setTextToMultilineLayout(candidateLayouts_[i], candidate->candidate); 356 | } 357 | 358 | layoutHint_ = static_cast(layoutHint); 359 | hasPrev_ = hasPrev; 360 | hasNext_ = hasNext; 361 | 362 | visible_ = nCandidates_ || 363 | pango_layout_get_character_count(upperLayout_.get()) || 364 | pango_layout_get_character_count(lowerLayout_.get()); 365 | 366 | update(); 367 | } 368 | 369 | void InputWindow::updateLanguage(const char *language) { 370 | language_ = language; 371 | do { 372 | if (!config_->useInputMethodLanguageToDisplayText_ || 373 | language_.empty()) { 374 | break; 375 | } 376 | auto language = pango_language_from_string(language_.c_str()); 377 | if (!language) { 378 | break; 379 | } 380 | pango_context_set_language(context_.get(), language); 381 | return; 382 | } while (0); 383 | 384 | pango_context_set_language(context_.get(), pango_language_get_default()); 385 | } 386 | 387 | std::pair InputWindow::sizeHint() { 388 | auto *fontDesc = pango_font_description_from_string(config_->font_.data()); 389 | pango_context_set_font_description(context_.get(), fontDesc); 390 | pango_font_description_free(fontDesc); 391 | pango_layout_context_changed(upperLayout_.get()); 392 | pango_layout_context_changed(lowerLayout_.get()); 393 | for (size_t i = 0; i < nCandidates_; i++) { 394 | labelLayouts_[i].contextChanged(); 395 | candidateLayouts_[i].contextChanged(); 396 | } 397 | auto *metrics = pango_context_get_metrics( 398 | context_.get(), pango_context_get_font_description(context_.get()), 399 | pango_context_get_language(context_.get())); 400 | auto fontHeight = pango_font_metrics_get_ascent(metrics) + 401 | pango_font_metrics_get_descent(metrics); 402 | pango_font_metrics_unref(metrics); 403 | fontHeight = PANGO_PIXELS(fontHeight); 404 | 405 | size_t width = 0; 406 | size_t height = 0; 407 | auto updateIfLarger = [](size_t &m, size_t n) { 408 | if (n > m) { 409 | m = n; 410 | } 411 | }; 412 | int w, h; 413 | 414 | const auto &textMargin = config_->theme_.textMargin; 415 | auto extraW = textMargin.marginLeft + textMargin.marginRight; 416 | auto extraH = textMargin.marginTop + textMargin.marginBottom; 417 | if (pango_layout_get_character_count(upperLayout_.get())) { 418 | pango_layout_get_pixel_size(upperLayout_.get(), &w, &h); 419 | height += fontHeight + extraH; 420 | updateIfLarger(width, w + extraW); 421 | } 422 | if (pango_layout_get_character_count(lowerLayout_.get())) { 423 | pango_layout_get_pixel_size(lowerLayout_.get(), &w, &h); 424 | height += fontHeight + extraH; 425 | updateIfLarger(width, w + extraW); 426 | } 427 | 428 | bool vertical = config_->vertical_; 429 | if (layoutHint_ == FcitxCandidateLayoutHint::Vertical) { 430 | vertical = true; 431 | } else if (layoutHint_ == FcitxCandidateLayoutHint::Horizontal) { 432 | vertical = false; 433 | } 434 | 435 | size_t wholeH = 0, wholeW = 0; 436 | for (size_t i = 0; i < nCandidates_; i++) { 437 | size_t candidateW = 0, candidateH = 0; 438 | if (labelLayouts_[i].characterCount()) { 439 | candidateW += labelLayouts_[i].width(); 440 | updateIfLarger(candidateH, 441 | std::max(1, labelLayouts_[i].size()) * fontHeight + 442 | extraH); 443 | } 444 | if (candidateLayouts_[i].characterCount()) { 445 | candidateW += candidateLayouts_[i].width(); 446 | updateIfLarger( 447 | candidateH, 448 | std::max(1, candidateLayouts_[i].size()) * fontHeight + extraH); 449 | } 450 | candidateW += extraW; 451 | 452 | if (vertical) { 453 | wholeH += candidateH; 454 | updateIfLarger(wholeW, candidateW); 455 | } else { 456 | wholeW += candidateW; 457 | updateIfLarger(wholeH, candidateH); 458 | } 459 | } 460 | updateIfLarger(width, wholeW); 461 | candidatesHeight_ = wholeH; 462 | height += wholeH; 463 | const auto &margin = config_->theme_.contentMargin; 464 | width += margin.marginLeft + margin.marginRight; 465 | height += margin.marginTop + margin.marginBottom; 466 | 467 | if (nCandidates_ && (hasPrev_ || hasNext_)) { 468 | const auto &prev = config_->theme_.loadAction(config_->theme_.prev); 469 | const auto &next = config_->theme_.loadAction(config_->theme_.next); 470 | if (prev.valid() && next.valid()) { 471 | width += prev.width() + next.width(); 472 | } 473 | } 474 | 475 | return {width, height}; 476 | } 477 | 478 | void InputWindow::paint(cairo_t *cr, unsigned int width, unsigned int height) { 479 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 480 | config_->theme_.paint(cr, config_->theme_.background, width, height); 481 | const auto &margin = config_->theme_.contentMargin; 482 | const auto &textMargin = config_->theme_.textMargin; 483 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 484 | cairo_save(cr); 485 | // Move position to the right place. 486 | cairo_translate(cr, margin.marginLeft, margin.marginTop); 487 | cairoSetSourceColor(cr, config_->theme_.normalColor); 488 | // CLASSICUI_DEBUG() << theme.inputPanel->normalColor->toString(); 489 | auto *metrics = pango_context_get_metrics( 490 | context_.get(), pango_context_get_font_description(context_.get()), 491 | pango_context_get_language(context_.get())); 492 | auto fontHeight = pango_font_metrics_get_ascent(metrics) + 493 | pango_font_metrics_get_descent(metrics); 494 | pango_font_metrics_unref(metrics); 495 | fontHeight = PANGO_PIXELS(fontHeight); 496 | 497 | size_t currentHeight = 0; 498 | int w, h; 499 | auto extraW = textMargin.marginLeft + textMargin.marginRight; 500 | auto extraH = textMargin.marginTop + textMargin.marginBottom; 501 | if (pango_layout_get_character_count(upperLayout_.get())) { 502 | renderLayout(cr, upperLayout_.get(), textMargin.marginLeft, 503 | textMargin.marginTop); 504 | pango_layout_get_pixel_size(upperLayout_.get(), &w, &h); 505 | PangoRectangle pos; 506 | if (cursor_ >= 0) { 507 | pango_layout_get_cursor_pos(upperLayout_.get(), cursor_, &pos, 508 | nullptr); 509 | 510 | cairo_save(cr); 511 | cairo_set_line_width(cr, 2); 512 | auto offsetX = pango_units_to_double(pos.x); 513 | cairo_move_to(cr, textMargin.marginLeft + offsetX + 1, 514 | textMargin.marginTop); 515 | cairo_line_to(cr, textMargin.marginLeft + offsetX + 1, 516 | textMargin.marginTop + fontHeight); 517 | cairo_stroke(cr); 518 | cairo_restore(cr); 519 | } 520 | currentHeight += fontHeight + extraH; 521 | } 522 | if (pango_layout_get_character_count(lowerLayout_.get())) { 523 | renderLayout(cr, lowerLayout_.get(), textMargin.marginLeft, 524 | textMargin.marginTop + currentHeight); 525 | pango_layout_get_pixel_size(lowerLayout_.get(), &w, nullptr); 526 | currentHeight += fontHeight + extraH; 527 | } 528 | 529 | bool vertical = config_->vertical_; 530 | if (layoutHint_ == FcitxCandidateLayoutHint::Vertical) { 531 | vertical = true; 532 | } else if (layoutHint_ == FcitxCandidateLayoutHint::Horizontal) { 533 | vertical = false; 534 | } 535 | 536 | candidateRegions_.clear(); 537 | candidateRegions_.reserve(nCandidates_); 538 | size_t wholeW = 0, wholeH = 0; 539 | 540 | // size of text = textMargin + actual text size. 541 | // HighLight = HighLight margin + TEXT. 542 | // Click region = HighLight - click 543 | 544 | for (size_t i = 0; i < nCandidates_; i++) { 545 | int x, y; 546 | if (vertical) { 547 | x = 0; 548 | y = currentHeight + wholeH; 549 | } else { 550 | x = wholeW; 551 | y = currentHeight; 552 | } 553 | x += textMargin.marginLeft; 554 | y += textMargin.marginTop; 555 | int labelW = 0, labelH = 0, candidateW = 0, candidateH = 0; 556 | if (labelLayouts_[i].characterCount()) { 557 | labelW = labelLayouts_[i].width(); 558 | labelH = fontHeight * labelLayouts_[i].size(); 559 | } 560 | if (candidateLayouts_[i].characterCount()) { 561 | candidateW = candidateLayouts_[i].width(); 562 | candidateH = fontHeight * candidateLayouts_[i].size(); 563 | } 564 | int vheight; 565 | if (vertical) { 566 | vheight = std::max({fontHeight, labelH, candidateH}); 567 | wholeH += vheight + extraH; 568 | } else { 569 | vheight = candidatesHeight_ - extraH; 570 | wholeW += candidateW + labelW + extraW; 571 | } 572 | const auto &highlightMargin = config_->theme_.highlight.margin; 573 | const auto &clickMargin = config_->theme_.highlight.clickMargin; 574 | auto highlightWidth = labelW + candidateW; 575 | if (config_->theme_.fullWidthHighlight && vertical) { 576 | // Last candidate, fill. 577 | highlightWidth = width - margin.marginLeft - margin.marginRight - 578 | textMargin.marginRight - textMargin.marginLeft; 579 | } 580 | const int highlightIndex = highlight(); 581 | bool highlight = false; 582 | if (highlightIndex >= 0 && i == static_cast(highlightIndex)) { 583 | cairo_save(cr); 584 | cairo_translate(cr, x - highlightMargin.marginLeft, 585 | y - highlightMargin.marginTop); 586 | config_->theme_.paint(cr, config_->theme_.highlight, 587 | highlightWidth + highlightMargin.marginLeft + 588 | highlightMargin.marginRight, 589 | vheight + highlightMargin.marginTop + 590 | highlightMargin.marginBottom); 591 | cairo_restore(cr); 592 | highlight = true; 593 | } 594 | cairo_rectangle_int_t candidateRegion; 595 | candidateRegion.x = margin.marginLeft + x - highlightMargin.marginLeft + 596 | clickMargin.marginLeft; 597 | candidateRegion.y = margin.marginTop + y - highlightMargin.marginTop + 598 | clickMargin.marginTop; 599 | candidateRegion.width = highlightWidth + highlightMargin.marginLeft + 600 | highlightMargin.marginRight - 601 | clickMargin.marginLeft - 602 | clickMargin.marginRight; 603 | candidateRegion.height = 604 | vheight + highlightMargin.marginTop + highlightMargin.marginBottom - 605 | clickMargin.marginTop - clickMargin.marginBottom; 606 | candidateRegions_.push_back(candidateRegion); 607 | if (labelLayouts_[i].characterCount()) { 608 | labelLayouts_[i].render(cr, x, y, fontHeight, highlight); 609 | } 610 | if (candidateLayouts_[i].characterCount()) { 611 | candidateLayouts_[i].render(cr, x + labelW, y, fontHeight, 612 | highlight); 613 | } 614 | } 615 | cairo_restore(cr); 616 | 617 | prevRegion_ = cairo_rectangle_int_t{0, 0, 0, 0}; 618 | nextRegion_ = cairo_rectangle_int_t{0, 0, 0, 0}; 619 | if (nCandidates_ && (hasPrev_ || hasNext_)) { 620 | const auto &prev = config_->theme_.loadAction(config_->theme_.prev); 621 | const auto &next = config_->theme_.loadAction(config_->theme_.next); 622 | if (prev.valid() && next.valid()) { 623 | cairo_save(cr); 624 | int prevY = 0, nextY = 0; 625 | switch (config_->theme_.buttonAlignment) { 626 | case PageButtonAlignment::Top: 627 | prevY = margin.marginTop; 628 | nextY = margin.marginTop; 629 | break; 630 | case PageButtonAlignment::FirstCandidate: 631 | prevY = 632 | candidateRegions_.front().y + 633 | (candidateRegions_.front().height - prev.height()) / 2.0; 634 | nextY = 635 | candidateRegions_.front().y + 636 | (candidateRegions_.front().height - prev.height()) / 2.0; 637 | break; 638 | case PageButtonAlignment::Center: 639 | prevY = 640 | margin.marginTop + (height - margin.marginTop - 641 | margin.marginBottom - prev.height()) / 642 | 2.0; 643 | nextY = 644 | margin.marginTop + (height - margin.marginTop - 645 | margin.marginBottom - next.height()) / 646 | 2.0; 647 | break; 648 | case PageButtonAlignment::LastCandidate: 649 | prevY = candidateRegions_.back().y + 650 | (candidateRegions_.back().height - prev.height()) / 2.0; 651 | nextY = candidateRegions_.back().y + 652 | (candidateRegions_.back().height - next.height()) / 2.0; 653 | break; 654 | case PageButtonAlignment::Bottom: 655 | default: 656 | prevY = height - margin.marginBottom - prev.height(); 657 | nextY = height - margin.marginBottom - next.height(); 658 | break; 659 | } 660 | nextRegion_.x = width - margin.marginRight - next.width(); 661 | nextRegion_.y = nextY; 662 | nextRegion_.width = next.width(); 663 | nextRegion_.height = next.height(); 664 | cairo_translate(cr, nextRegion_.x, nextRegion_.y); 665 | shrink(nextRegion_, config_->theme_.next.clickMargin); 666 | double alpha = 1.0; 667 | if (!hasNext_) { 668 | alpha = 0.3; 669 | } else if (nextHovered_) { 670 | alpha = 0.7; 671 | } 672 | config_->theme_.paint(cr, config_->theme_.next, alpha); 673 | cairo_restore(cr); 674 | cairo_save(cr); 675 | prevRegion_.x = 676 | width - margin.marginRight - next.width() - prev.width(); 677 | prevRegion_.y = prevY; 678 | prevRegion_.width = prev.width(); 679 | prevRegion_.height = prev.height(); 680 | cairo_translate(cr, prevRegion_.x, prevRegion_.y); 681 | shrink(prevRegion_, config_->theme_.prev.clickMargin); 682 | alpha = 1.0; 683 | if (!hasPrev_) { 684 | alpha = 0.3; 685 | } else if (prevHovered_) { 686 | alpha = 0.7; 687 | } 688 | config_->theme_.paint(cr, config_->theme_.prev, alpha); 689 | cairo_restore(cr); 690 | } 691 | } 692 | } 693 | 694 | void InputWindow::click(int x, int y) { 695 | if (hasPrev_ && rectContains(prevRegion_, x, y)) { 696 | prev(); 697 | return; 698 | } 699 | if (hasNext_ && rectContains(nextRegion_, x, y)) { 700 | next(); 701 | return; 702 | } 703 | for (size_t idx = 0, e = candidateRegions_.size(); idx < e; idx++) { 704 | if (rectContains(candidateRegions_[idx], x, y)) { 705 | selectCandidate(idx); 706 | return; 707 | } 708 | } 709 | } 710 | 711 | void InputWindow::wheel(bool up) { 712 | if (!config_->wheelForPaging_) { 713 | return; 714 | } 715 | if (nCandidates_ == 0) { 716 | return; 717 | } 718 | if (up) { 719 | if (hasPrev_) { 720 | prev(); 721 | } 722 | } else { 723 | if (hasNext_) { 724 | next(); 725 | } 726 | } 727 | } 728 | 729 | int InputWindow::highlight() const { 730 | int highlightIndex = (hoverIndex_ >= 0) ? hoverIndex_ : candidateIndex_; 731 | return highlightIndex; 732 | } 733 | 734 | bool InputWindow::hover(int x, int y) { 735 | bool needRepaint = false; 736 | 737 | bool prevHovered = false; 738 | bool nextHovered = false; 739 | auto oldHighlight = highlight(); 740 | hoverIndex_ = -1; 741 | 742 | prevHovered = rectContains(prevRegion_, x, y); 743 | if (!prevHovered) { 744 | nextHovered = rectContains(nextRegion_, x, y); 745 | if (!nextHovered) { 746 | for (int idx = 0, e = candidateRegions_.size(); idx < e; idx++) { 747 | if (rectContains(candidateRegions_[idx], x, y)) { 748 | hoverIndex_ = idx; 749 | break; 750 | } 751 | } 752 | } 753 | } 754 | 755 | needRepaint = needRepaint || prevHovered_ != prevHovered; 756 | prevHovered_ = prevHovered; 757 | 758 | needRepaint = needRepaint || nextHovered_ != nextHovered; 759 | nextHovered_ = nextHovered; 760 | 761 | needRepaint = needRepaint || oldHighlight != highlight(); 762 | return needRepaint; 763 | } 764 | 765 | void InputWindow::prev() { 766 | if (hasPrev_) { 767 | fcitx_g_client_prev_page(client_.get()); 768 | } 769 | } 770 | 771 | void InputWindow::next() { 772 | if (hasNext_) { 773 | fcitx_g_client_next_page(client_.get()); 774 | } 775 | } 776 | 777 | void InputWindow::selectCandidate(int i) { 778 | fcitx_g_client_select_candidate(client_.get(), i); 779 | } 780 | 781 | } // namespace fcitx::gtk 782 | -------------------------------------------------------------------------------- /gtk3/inputwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK3_INPUTWINDOW_H_ 8 | #define _GTK3_INPUTWINDOW_H_ 9 | 10 | #include "fcitx-gclient/fcitxgclient.h" 11 | #include "fcitxflags.h" 12 | #include "utils.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace fcitx::gtk { 20 | 21 | class ClassicUIConfig; 22 | 23 | using PangoAttrListUniquePtr = UniqueCPtr; 24 | 25 | class MultilineLayout { 26 | public: 27 | MultilineLayout() = default; 28 | MultilineLayout(MultilineLayout &&) = default; 29 | 30 | void contextChanged() { 31 | for (const auto &layout : lines_) { 32 | pango_layout_context_changed(layout.get()); 33 | } 34 | } 35 | int characterCount() const { 36 | int count = 0; 37 | for (const auto &layout : lines_) { 38 | count += pango_layout_get_character_count(layout.get()); 39 | } 40 | return count; 41 | } 42 | 43 | int width() const; 44 | 45 | int size() { return lines_.size(); } 46 | void render(cairo_t *cr, int x, int y, int lineHeight, bool highlight); 47 | 48 | std::vector> lines_; 49 | std::vector attrLists_; 50 | std::vector highlightAttrLists_; 51 | }; 52 | 53 | class InputWindow { 54 | public: 55 | InputWindow(ClassicUIConfig *config, FcitxGClient *client); 56 | virtual ~InputWindow(); 57 | std::pair sizeHint(); 58 | void paint(cairo_t *cr, unsigned int width, unsigned int height); 59 | void hide(); 60 | bool visible() const { return visible_; } 61 | bool hover(int x, int y); 62 | void click(int x, int y); 63 | void wheel(bool up); 64 | 65 | virtual void update() = 0; 66 | 67 | protected: 68 | void resizeCandidates(size_t n); 69 | void appendText(std::string &s, PangoAttrList *attrList, 70 | PangoAttrList *highlightAttrList, const GPtrArray *text); 71 | void appendText(std::string &s, PangoAttrList *attrList, 72 | PangoAttrList *highlightAttrList, const gchar *text, 73 | int format = 0); 74 | void insertAttr(PangoAttrList *attrList, FcitxTextFormatFlag format, 75 | int start, int end, bool highlight) const; 76 | void setTextToLayout(PangoLayout *layout, PangoAttrListUniquePtr *attrList, 77 | PangoAttrListUniquePtr *highlightAttrList, 78 | std::initializer_list texts); 79 | void setTextToLayout(PangoLayout *layout, PangoAttrListUniquePtr *attrList, 80 | PangoAttrListUniquePtr *highlightAttrList, 81 | const gchar *text); 82 | void setTextToMultilineLayout(MultilineLayout &layout, const gchar *text); 83 | 84 | int highlight() const; 85 | 86 | void prev(); 87 | void next(); 88 | void selectCandidate(int i); 89 | void updateUI(GPtrArray *preedit, int cursor_pos, GPtrArray *auxUp, 90 | GPtrArray *auxDown, GPtrArray *candidates, int highlight, 91 | int layoutHint, bool hasPrev, bool hasNext); 92 | void updateLanguage(const char *language); 93 | 94 | void setLanguageAttr(size_t size, PangoAttrList *attrList, 95 | PangoAttrList *highlightAttrList); 96 | 97 | ClassicUIConfig *config_; 98 | GObjectUniquePtr client_; 99 | GObjectUniquePtr context_; 100 | GObjectUniquePtr upperLayout_; 101 | GObjectUniquePtr lowerLayout_; 102 | std::vector labelLayouts_; 103 | std::vector candidateLayouts_; 104 | std::vector candidateRegions_; 105 | std::string language_; 106 | bool visible_ = false; 107 | int cursor_ = 0; 108 | int dpi_ = -1; 109 | size_t nCandidates_ = 0; 110 | bool hasPrev_ = false; 111 | bool hasNext_ = false; 112 | cairo_rectangle_int_t prevRegion_; 113 | cairo_rectangle_int_t nextRegion_; 114 | bool prevHovered_ = false; 115 | bool nextHovered_ = false; 116 | int candidateIndex_ = -1; 117 | FcitxCandidateLayoutHint layoutHint_ = FcitxCandidateLayoutHint::NotSet; 118 | size_t candidatesHeight_ = 0; 119 | int hoverIndex_ = -1; 120 | }; 121 | } // namespace fcitx::gtk 122 | 123 | #endif // _GTK3_INPUTWINDOW_H_ 124 | -------------------------------------------------------------------------------- /gtk3/utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include 8 | 9 | namespace fcitx::gtk { 10 | 11 | enum class UnescapeState { NORMAL, ESCAPE }; 12 | 13 | bool unescape(std::string &str) { 14 | if (str.empty()) { 15 | return true; 16 | } 17 | 18 | bool unescapeQuote = false; 19 | // having quote at beginning and end, escape 20 | if (str.size() >= 2 && str.front() == '"' && str.back() == '"') { 21 | unescapeQuote = true; 22 | str.pop_back(); 23 | str.erase(0, 1); 24 | } 25 | 26 | size_t i = 0; 27 | size_t j = 0; 28 | UnescapeState state = UnescapeState::NORMAL; 29 | do { 30 | switch (state) { 31 | case UnescapeState::NORMAL: 32 | if (str[i] == '\\') { 33 | state = UnescapeState::ESCAPE; 34 | } else { 35 | str[j] = str[i]; 36 | j++; 37 | } 38 | break; 39 | case UnescapeState::ESCAPE: 40 | if (str[i] == '\\') { 41 | str[j] = '\\'; 42 | j++; 43 | } else if (str[i] == 'n') { 44 | str[j] = '\n'; 45 | j++; 46 | } else if (str[i] == '\"' && unescapeQuote) { 47 | str[j] = '\"'; 48 | j++; 49 | } else { 50 | return false; 51 | } 52 | state = UnescapeState::NORMAL; 53 | break; 54 | } 55 | } while (str[i++]); 56 | str.resize(j - 1); 57 | return true; 58 | } 59 | 60 | } // namespace fcitx::gtk 61 | -------------------------------------------------------------------------------- /gtk3/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK3_UTILS_H_ 8 | #define _GTK3_UTILS_H_ 9 | 10 | #include "fcitxflags.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace fcitx::gtk { 17 | 18 | bool unescape(std::string &str); 19 | 20 | template 21 | struct FunctionDeleter { 22 | template 23 | void operator()(T *p) const { 24 | if (p) { 25 | FreeFunction(const_cast *>(p)); 26 | } 27 | } 28 | }; 29 | template 30 | using UniqueCPtr = std::unique_ptr>; 31 | 32 | template 33 | using GObjectUniquePtr = UniqueCPtr; 34 | 35 | static inline bool rectContains(cairo_rectangle_int_t rect1, 36 | cairo_rectangle_int_t rect2) { 37 | return (rect1.x <= rect2.x && rect1.y <= rect2.y && 38 | rect1.x + rect1.width >= rect2.x + rect2.width && 39 | rect1.y + rect1.height >= rect2.y + rect2.height); 40 | } 41 | 42 | static inline bool rectContains(cairo_rectangle_int_t rect, int x, int y) { 43 | return x >= rect.x && y >= rect.y && x <= rect.x + rect.width && 44 | y <= rect.y + rect.height; 45 | } 46 | 47 | static inline gboolean check_app_name(const gchar *pattern) { 48 | bool result = FALSE; 49 | const gchar *prgname = g_get_prgname(); 50 | if (!prgname) { 51 | return FALSE; 52 | } 53 | gchar **p; 54 | gchar **apps = g_strsplit(pattern, ",", 0); 55 | for (p = apps; *p != NULL; p++) { 56 | if (g_regex_match_simple(*p, prgname, (GRegexCompileFlags)0, 57 | (GRegexMatchFlags)0)) { 58 | result = TRUE; 59 | break; 60 | } 61 | } 62 | g_strfreev(apps); 63 | return result; 64 | } 65 | 66 | static inline bool get_boolean_env(const char *name, bool defval) { 67 | const char *value = getenv(name); 68 | 69 | if (value == nullptr) { 70 | return defval; 71 | } 72 | 73 | if (g_strcmp0(value, "") == 0 || g_strcmp0(value, "0") == 0 || 74 | g_strcmp0(value, "false") == 0 || g_strcmp0(value, "False") == 0 || 75 | g_strcmp0(value, "FALSE") == 0) { 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | 82 | constexpr int MAX_CACHED_HANDLED_EVENT = 40; 83 | 84 | constexpr uint64_t purpose_related_capability = 85 | fcitx::FcitxCapabilityFlag_Alpha | fcitx::FcitxCapabilityFlag_Digit | 86 | fcitx::FcitxCapabilityFlag_Number | fcitx::FcitxCapabilityFlag_Dialable | 87 | fcitx::FcitxCapabilityFlag_Url | fcitx::FcitxCapabilityFlag_Email | 88 | fcitx::FcitxCapabilityFlag_Password; 89 | 90 | constexpr uint64_t hints_related_capability = 91 | fcitx::FcitxCapabilityFlag_SpellCheck | 92 | fcitx::FcitxCapabilityFlag_NoSpellCheck | 93 | fcitx::FcitxCapabilityFlag_WordCompletion | 94 | fcitx::FcitxCapabilityFlag_Lowercase | 95 | fcitx::FcitxCapabilityFlag_Uppercase | 96 | fcitx::FcitxCapabilityFlag_UppercaseWords | 97 | fcitx::FcitxCapabilityFlag_UppwercaseSentences | 98 | fcitx::FcitxCapabilityFlag_NoOnScreenKeyboard; 99 | 100 | constexpr uint32_t HandledMask = (1 << 24); 101 | constexpr uint32_t IgnoredMask = (1 << 25); 102 | constexpr unsigned int MAX_CACHED_EVENTS = 30; 103 | 104 | } // namespace fcitx::gtk 105 | 106 | #endif // _GTK3_UTILS_H_ 107 | -------------------------------------------------------------------------------- /gtk4/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(FCITX_GTK4_IM_MODULE_SOURCES 2 | fcitxim.c 3 | fcitximcontext.cpp 4 | fcitximcontext5.cpp 5 | inputwindow.cpp 6 | gtk4inputwindow.cpp 7 | fcitxtheme.cpp 8 | utils.cpp 9 | ) 10 | 11 | if (NOT DEFINED GTK4_IM_MODULEDIR) 12 | set(GTK4_IM_MODULEDIR "${CMAKE_INSTALL_LIBDIR}/gtk-4.0/${GTK4_BINARY_VERSION}/immodules" CACHE PATH "Gtk4 im module directory") 13 | endif() 14 | 15 | add_library(im-fcitx5-gtk4 MODULE ${FCITX_GTK4_IM_MODULE_SOURCES}) 16 | set_target_properties(im-fcitx5-gtk4 PROPERTIES OUTPUT_NAME "im-fcitx5" 17 | COMPILE_FLAGS "-fno-exceptions") 18 | target_link_libraries(im-fcitx5-gtk4 Fcitx5::GClient XKBCommon::XKBCommon PkgConfig::Gtk4 PkgConfig::GioUnix2) 19 | if (TARGET PkgConfig::Gtk4X11) 20 | target_link_libraries(im-fcitx5-gtk4 PkgConfig::Gtk4X11 X11Import) 21 | endif() 22 | install(TARGETS im-fcitx5-gtk4 DESTINATION "${GTK4_IM_MODULEDIR}") 23 | 24 | if (NOT BUILD_ONLY_PLUGIN) 25 | add_executable(fcitx5-gtk4-immodule-probing immodule-probing.cpp) 26 | target_link_libraries(fcitx5-gtk4-immodule-probing PkgConfig::Gtk4) 27 | install(TARGETS fcitx5-gtk4-immodule-probing DESTINATION "${CMAKE_INSTALL_BINDIR}") 28 | endif() 29 | -------------------------------------------------------------------------------- /gtk4/fcitx5imcontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef __FCITX_IM_CONTEXT_H_ 8 | #define __FCITX_IM_CONTEXT_H_ 9 | 10 | #include 11 | 12 | G_BEGIN_DECLS 13 | 14 | GType fcitx5_im_context_get_type(void); 15 | void fcitx5_im_context_register_type(GTypeModule *type_module); 16 | 17 | G_END_DECLS 18 | #endif 19 | // kate: indent-mode cstyle; space-indent on; indent-width 0; 20 | -------------------------------------------------------------------------------- /gtk4/fcitxflags.h: -------------------------------------------------------------------------------- 1 | ../gtk2/fcitxflags.h -------------------------------------------------------------------------------- /gtk4/fcitxim.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #include "config.h" 8 | #include "fcitximcontext.h" 9 | #include "fcitximcontext5.h" 10 | #include 11 | #include 12 | 13 | G_MODULE_EXPORT void g_io_im_fcitx5_load(GIOModule *module) { 14 | g_type_module_use(G_TYPE_MODULE(module)); 15 | fcitx_im_context_register_type(G_TYPE_MODULE(module)); 16 | fcitx_im_context5_register_type(G_TYPE_MODULE(module)); 17 | 18 | g_io_extension_point_implement(GTK_IM_MODULE_EXTENSION_POINT_NAME, 19 | FCITX_TYPE_IM_CONTEXT, "fcitx", 10); 20 | g_io_extension_point_implement(GTK_IM_MODULE_EXTENSION_POINT_NAME, 21 | FCITX_TYPE_IM_CONTEXT5, "fcitx5", 10); 22 | } 23 | 24 | G_MODULE_EXPORT void g_io_im_fcitx5_unload(GIOModule *module) { 25 | g_type_module_unuse(G_TYPE_MODULE(module)); 26 | } 27 | -------------------------------------------------------------------------------- /gtk4/fcitximcontext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010~2020 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #ifndef __FCITX_IM_CONTEXT_H_ 8 | #define __FCITX_IM_CONTEXT_H_ 9 | 10 | #include 11 | 12 | /* 13 | * Type macros. 14 | */ 15 | #define FCITX_TYPE_IM_CONTEXT (fcitx_im_context_get_type()) 16 | #define FCITX_IM_CONTEXT(obj) \ 17 | (G_TYPE_CHECK_INSTANCE_CAST((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContext)) 18 | #define FCITX_IM_CONTEXT_CLASS(klass) \ 19 | (G_TYPE_CHECK_CLASS_CAST((klass), FCITX_TYPE_IM_CONTEXT, \ 20 | FcitxIMContextClass)) 21 | #define FCITX_IS_IM_CONTEXT(obj) \ 22 | (G_TYPE_CHECK_INSTANCE_TYPE((obj), FCITX_TYPE_IM_CONTEXT)) 23 | #define FCITX_IS_IM_CONTEXT_CLASS(klass) \ 24 | (G_TYPE_CHECK_CLASS_TYPE((klass), FCITX_TYPE_IM_CONTEXT)) 25 | #define FCITX_IM_CONTEXT_GET_CLASS(obj) \ 26 | (G_TYPE_CHECK_GET_CLASS((obj), FCITX_TYPE_IM_CONTEXT, FcitxIMContextClass)) 27 | 28 | G_BEGIN_DECLS 29 | 30 | typedef struct _FcitxIMContext FcitxIMContext; 31 | typedef struct _FcitxIMContextClass FcitxIMContextClass; 32 | 33 | GType fcitx_im_context_get_type(void); 34 | void fcitx_im_context_register_type(GTypeModule *type_module); 35 | 36 | G_END_DECLS 37 | #endif 38 | // kate: indent-mode cstyle; space-indent on; indent-width 0; 39 | -------------------------------------------------------------------------------- /gtk4/fcitximcontext5.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include "fcitximcontext5.h" 8 | #include "fcitximcontextprivate.h" 9 | 10 | static GType _fcitx_type_im_context5 = 0; 11 | 12 | struct _FcitxIMContext5Class { 13 | FcitxIMContextClass parent; 14 | /* klass members */ 15 | }; 16 | 17 | struct _FcitxIMContext5 { 18 | FcitxIMContext parent; 19 | }; 20 | 21 | void fcitx_im_context5_register_type(GTypeModule *type_module) { 22 | static const GTypeInfo fcitx_im_context5_info = { 23 | sizeof(FcitxIMContext5Class), (GBaseInitFunc)NULL, 24 | (GBaseFinalizeFunc)NULL, (GClassInitFunc)NULL, 25 | (GClassFinalizeFunc)NULL, NULL, /* klass data */ 26 | sizeof(FcitxIMContext5), 0, 27 | (GInstanceInitFunc)NULL, 0}; 28 | 29 | if (_fcitx_type_im_context5) { 30 | return; 31 | } 32 | if (type_module) { 33 | _fcitx_type_im_context5 = g_type_module_register_type( 34 | type_module, FCITX_TYPE_IM_CONTEXT, "FcitxIMContext5", 35 | &fcitx_im_context5_info, (GTypeFlags)0); 36 | } else { 37 | _fcitx_type_im_context5 = 38 | g_type_register_static(FCITX_TYPE_IM_CONTEXT, "FcitxIMContext5", 39 | &fcitx_im_context5_info, (GTypeFlags)0); 40 | } 41 | } 42 | 43 | GType fcitx_im_context5_get_type(void) { 44 | if (_fcitx_type_im_context5 == 0) { 45 | fcitx_im_context5_register_type(NULL); 46 | } 47 | 48 | g_assert(_fcitx_type_im_context5 != 0); 49 | return _fcitx_type_im_context5; 50 | } 51 | -------------------------------------------------------------------------------- /gtk4/fcitximcontext5.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK4_FCITXIMCONTEXT5_H_ 8 | #define _GTK4_FCITXIMCONTEXT5_H_ 9 | 10 | #include 11 | 12 | /* 13 | * Create a alias type for GTK_IM_MODULE=fcitx5 14 | */ 15 | #define FCITX_TYPE_IM_CONTEXT5 (fcitx_im_context5_get_type()) 16 | #define FCITX_IM_CONTEXT5(obj) \ 17 | (G_TYPE_CHECK_INSTANCE_CAST((obj), FCITX_TYPE_IM_CONTEXT5, FcitxIMContext5)) 18 | #define FCITX_IM_CONTEXT5_CLASS(klass) \ 19 | (G_TYPE_CHECK_CLASS_CAST((klass), FCITX_TYPE_IM_CONTEXT5, \ 20 | FcitxIMContextClass)) 21 | #define FCITX_IS_IM_CONTEXT5(obj) \ 22 | (G_TYPE_CHECK_INSTANCE_TYPE((obj), FCITX_TYPE_IM_CONTEXT5)) 23 | #define FCITX_IS_IM_CONTEXT5_CLASS(klass) \ 24 | (G_TYPE_CHECK_CLASS_TYPE((klass), FCITX_TYPE_IM_CONTEXT5)) 25 | #define FCITX_IM_CONTEXT5_GET_CLASS(obj) \ 26 | (G_TYPE_CHECK_GET_CLASS((obj), FCITX_TYPE_IM_CONTEXT5, FcitxIMContextClass)) 27 | 28 | G_BEGIN_DECLS 29 | 30 | typedef struct _FcitxIMContext5 FcitxIMContext5; 31 | typedef struct _FcitxIMContext5Class FcitxIMContext5Class; 32 | 33 | GType fcitx_im_context5_get_type(void); 34 | void fcitx_im_context5_register_type(GTypeModule *type_module); 35 | 36 | G_END_DECLS 37 | 38 | #endif // _GTK4_FCITXIMCONTEXT5_H_ 39 | -------------------------------------------------------------------------------- /gtk4/fcitximcontextprivate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK4_FCITXIMCONTEXTPRIVATE_H_ 8 | #define _GTK4_FCITXIMCONTEXTPRIVATE_H_ 9 | 10 | #include "fcitximcontext.h" 11 | #include "gtk4inputwindow.h" 12 | 13 | struct _FcitxIMContext { 14 | GtkIMContext parent; 15 | 16 | GtkWidget *client_widget; 17 | bool has_rect; 18 | GdkRectangle area; 19 | FcitxGClient *client; 20 | GtkIMContext *slave; 21 | int has_focus; 22 | guint32 time; 23 | guint32 last_key_code; 24 | bool last_is_release; 25 | gboolean use_preedit; 26 | gboolean support_surrounding_text; 27 | gboolean is_inpreedit; 28 | gboolean is_wayland; 29 | char *preedit_string; 30 | char *commit_preedit_string; 31 | char *surrounding_text; 32 | int cursor_pos; 33 | guint64 capability_from_toolkit; 34 | guint64 last_updated_capability; 35 | PangoAttrList *attrlist; 36 | int last_cursor_pos; 37 | int last_anchor_pos; 38 | struct xkb_compose_state *xkbComposeState; 39 | 40 | GHashTable *pending_events; 41 | GHashTable *handled_events; 42 | GQueue *handled_events_list; 43 | 44 | gboolean ignore_reset; 45 | 46 | fcitx::gtk::Gtk4InputWindow *candidate_window; 47 | }; 48 | 49 | struct _FcitxIMContextClass { 50 | GtkIMContextClass parent; 51 | /* klass members */ 52 | }; 53 | 54 | #endif // _GTK4_FCITXIMCONTEXTPRIVATE_H_ 55 | -------------------------------------------------------------------------------- /gtk4/fcitxtheme.cpp: -------------------------------------------------------------------------------- 1 | ../gtk3/fcitxtheme.cpp -------------------------------------------------------------------------------- /gtk4/fcitxtheme.h: -------------------------------------------------------------------------------- 1 | ../gtk3/fcitxtheme.h -------------------------------------------------------------------------------- /gtk4/gtk4inputwindow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #include "gtk4inputwindow.h" 8 | #include 9 | #include 10 | 11 | namespace fcitx::gtk { 12 | 13 | Gtk4InputWindow::Gtk4InputWindow(ClassicUIConfig *config, FcitxGClient *client) 14 | : InputWindow(config, client) { 15 | rect_.x = rect_.y = rect_.height = rect_.width = 0; 16 | dummyWidget_.reset(GTK_WINDOW(gtk_window_new())); 17 | } 18 | 19 | Gtk4InputWindow::~Gtk4InputWindow() { 20 | if (window_) { 21 | g_signal_handlers_disconnect_by_data(window_.get(), this); 22 | gdk_surface_destroy(window_.release()); 23 | } 24 | // Clean up weak pointer. 25 | setParent(nullptr); 26 | } 27 | 28 | void Gtk4InputWindow::draw(cairo_t *cr) { paint(cr, width_, height_); } 29 | 30 | void Gtk4InputWindow::setParent(GtkWidget *parent) { 31 | if (parent_ == parent) { 32 | return; 33 | } 34 | if (parent_) { 35 | g_object_remove_weak_pointer(G_OBJECT(parent_), 36 | reinterpret_cast(&parent_)); 37 | } 38 | if (parent) { 39 | g_object_add_weak_pointer(G_OBJECT(parent), 40 | reinterpret_cast(&parent_)); 41 | resetWindow(); 42 | } 43 | parent_ = parent; 44 | } 45 | 46 | void Gtk4InputWindow::setCursorRect(GdkRectangle rect) { 47 | if (!parent_) { 48 | return; 49 | } 50 | auto *root = gtk_widget_get_native(parent_); 51 | if (!root) { 52 | return; 53 | } 54 | 55 | double px, py; 56 | gtk_widget_translate_coordinates(parent_, GTK_WIDGET(root), rect.x, rect.y, 57 | &px, &py); 58 | double offsetX = 0, offsetY = 0; 59 | 60 | if (auto native = gtk_widget_get_native(GTK_WIDGET(root))) { 61 | gtk_native_get_surface_transform(native, &offsetX, &offsetY); 62 | } 63 | rect.x = px + offsetX; 64 | rect.y = py + offsetY; 65 | 66 | // Sanitize the rect value to workaround a mutter bug: 67 | // https://gitlab.gnome.org/GNOME/mutter/-/issues/2525 68 | // The procedure make sure the rect is with in the parent window region. 69 | const int rootWidth = gtk_widget_get_width(GTK_WIDGET(root)); 70 | const int rootHeight = gtk_widget_get_height(GTK_WIDGET(root)); 71 | if (rootWidth <= 0 || rootHeight <= 0) { 72 | return; 73 | } 74 | rect.x = CLAMP(rect.x, 0, rootWidth - 1); 75 | rect.y = CLAMP(rect.y, 0, rootHeight - 1); 76 | rect.width = CLAMP(rect.width, 1, rootWidth - rect.x); 77 | rect.height = CLAMP(rect.height, 1, rootHeight - rect.y); 78 | 79 | rect_ = rect; 80 | 81 | if (window_) { 82 | reposition(); 83 | } 84 | } 85 | 86 | void Gtk4InputWindow::update() { 87 | if (!visible() || !parent_) { 88 | resetWindow(); 89 | return; 90 | } 91 | 92 | syncFontOptions(); 93 | std::tie(width_, height_) = sizeHint(); 94 | if (width_ <= 0 || height_ <= 0) { 95 | resetWindow(); 96 | return; 97 | } 98 | auto native = gtk_widget_get_native(parent_); 99 | if (!native) { 100 | return; 101 | } 102 | 103 | auto surface = gtk_native_get_surface(native); 104 | if (!surface) { 105 | return; 106 | } 107 | 108 | if (window_) { 109 | if (surface == gdk_popup_get_parent(GDK_POPUP(window_.get()))) { 110 | gdk_surface_queue_render(window_.get()); 111 | reposition(); 112 | return; 113 | } 114 | } 115 | 116 | resetWindow(); 117 | window_.reset(gdk_surface_new_popup(surface, false)); 118 | cairoCcontext_.reset(gdk_surface_create_cairo_context(window_.get())); 119 | 120 | auto mapped = [](GdkSurface *surface, GParamSpec *, gpointer user_data) { 121 | Gtk4InputWindow *that = static_cast(user_data); 122 | that->surfaceNotifyMapped(surface); 123 | }; 124 | g_signal_connect(surface, "notify::mapped", G_CALLBACK(+mapped), this); 125 | 126 | auto surface_render = [](GdkSurface *surface, cairo_region_t *, 127 | gpointer user_data) { 128 | Gtk4InputWindow *that = static_cast(user_data); 129 | cairo_rectangle_int_t r; 130 | r.x = 0; 131 | r.y = 0; 132 | r.width = gdk_surface_get_width(surface); 133 | r.height = gdk_surface_get_height(surface); 134 | auto region = cairo_region_create_rectangle(&r); 135 | gdk_draw_context_begin_frame( 136 | GDK_DRAW_CONTEXT(that->cairoCcontext_.get()), region); 137 | cairo_region_destroy(region); 138 | auto cr = gdk_cairo_context_cairo_create(that->cairoCcontext_.get()); 139 | 140 | static_cast(user_data)->draw(cr); 141 | 142 | cairo_destroy(cr); 143 | 144 | gdk_draw_context_end_frame( 145 | GDK_DRAW_CONTEXT(that->cairoCcontext_.get())); 146 | return TRUE; 147 | }; 148 | auto event = [](GdkSurface *, gpointer event, gpointer user_data) { 149 | return static_cast(user_data)->event( 150 | static_cast(event)); 151 | }; 152 | g_signal_connect(window_.get(), "render", G_CALLBACK(+surface_render), 153 | this); 154 | g_signal_connect(window_.get(), "event", G_CALLBACK(+event), this); 155 | 156 | surfaceNotifyMapped(surface); 157 | } 158 | 159 | void Gtk4InputWindow::reposition() { 160 | if (!window_) { 161 | return; 162 | } 163 | 164 | auto popupLayout = gdk_popup_layout_new(&rect_, GDK_GRAVITY_SOUTH_WEST, 165 | GDK_GRAVITY_NORTH_WEST); 166 | gdk_popup_layout_set_anchor_hints( 167 | popupLayout, 168 | static_cast(GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_FLIP_Y | 169 | GDK_ANCHOR_SLIDE_Y)); 170 | 171 | gdk_popup_layout_set_shadow_width( 172 | popupLayout, config_->theme_.shadowMargin.marginLeft, 173 | config_->theme_.shadowMargin.marginRight, 174 | config_->theme_.shadowMargin.marginTop, 175 | config_->theme_.shadowMargin.marginBottom); 176 | gdk_popup_present(GDK_POPUP(window_.get()), width_, height_, popupLayout); 177 | gdk_popup_layout_unref(popupLayout); 178 | } 179 | 180 | void Gtk4InputWindow::surfaceNotifyMapped(GdkSurface *surface) { 181 | if (surface != gdk_popup_get_parent(GDK_POPUP(window_.get()))) { 182 | return; 183 | } 184 | if (!window_) { 185 | return; 186 | } 187 | if (!gdk_surface_get_mapped(surface)) { 188 | resetWindow(); 189 | return; 190 | } else if (visible()) { 191 | reposition(); 192 | } 193 | } 194 | 195 | void Gtk4InputWindow::resetWindow() { 196 | if (!window_) { 197 | return; 198 | } 199 | if (auto parent = gdk_popup_get_parent(GDK_POPUP(window_.get()))) { 200 | g_signal_handlers_disconnect_by_data(parent, this); 201 | g_signal_handlers_disconnect_by_data(window_.get(), this); 202 | cairoCcontext_.reset(); 203 | window_.reset(); 204 | } 205 | } 206 | 207 | void Gtk4InputWindow::syncFontOptions() { 208 | auto context = gtk_widget_get_pango_context(GTK_WIDGET(dummyWidget_.get())); 209 | pango_cairo_context_set_font_options( 210 | context_.get(), pango_cairo_context_get_font_options(context)); 211 | dpi_ = pango_cairo_context_get_resolution(context); 212 | pango_cairo_context_set_resolution(context_.get(), dpi_); 213 | } 214 | 215 | gboolean Gtk4InputWindow::event(GdkEvent *event) { 216 | auto eventType = gdk_event_get_event_type(event); 217 | if (eventType == GDK_MOTION_NOTIFY) { 218 | double x = 0, y = 0; 219 | gdk_event_get_position(event, &x, &y); 220 | if (hover(x, y)) { 221 | gdk_surface_queue_render(window_.get()); 222 | } 223 | } else if (eventType == GDK_LEAVE_NOTIFY) { 224 | auto oldHighlight = highlight(); 225 | hoverIndex_ = -1; 226 | if (highlight() != oldHighlight) { 227 | gdk_surface_queue_render(window_.get()); 228 | } 229 | return true; 230 | } else if (eventType == GDK_SCROLL) { 231 | double vscroll_factor = 0.0; 232 | double x_scroll, y_scroll; 233 | // Handle discrete scrolling with a known constant delta; 234 | const double delta = 1.0; 235 | 236 | // In Gtk 4, there will be either discrete or axis event. 237 | auto direction = gdk_scroll_event_get_direction(event); 238 | switch (direction) { 239 | case GDK_SCROLL_UP: 240 | vscroll_factor = -delta; 241 | break; 242 | case GDK_SCROLL_DOWN: 243 | vscroll_factor = delta; 244 | break; 245 | case GDK_SCROLL_SMOOTH: 246 | gdk_scroll_event_get_deltas(event, &x_scroll, &y_scroll); 247 | // Handle smooth scrolling directly 248 | vscroll_factor = y_scroll; 249 | break; 250 | default: 251 | // no scrolling 252 | break; 253 | } 254 | if (vscroll_factor != 0) { 255 | scrollDelta_ += vscroll_factor; 256 | while (scrollDelta_ >= delta) { 257 | scrollDelta_ -= delta; 258 | next(); 259 | } 260 | while (scrollDelta_ <= -delta) { 261 | scrollDelta_ += delta; 262 | prev(); 263 | } 264 | } 265 | return true; 266 | } else if (eventType == GDK_BUTTON_RELEASE) { 267 | guint button = gdk_button_event_get_button(event); 268 | if (button == 1) { 269 | double x = 0, y = 0; 270 | gdk_event_get_position(event, &x, &y); 271 | click(x, y); 272 | } 273 | } 274 | return false; 275 | } 276 | 277 | } // namespace fcitx::gtk 278 | -------------------------------------------------------------------------------- /gtk4/gtk4inputwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021~2021 CSSlayer 3 | * 4 | * SPDX-License-Identifier: LGPL-2.1-or-later 5 | * 6 | */ 7 | #ifndef _GTK3_GTK3INPUTWINDOW_H_ 8 | #define _GTK3_GTK3INPUTWINDOW_H_ 9 | 10 | #include "inputwindow.h" 11 | #include 12 | 13 | namespace fcitx::gtk { 14 | 15 | class Gtk4InputWindow : public InputWindow { 16 | public: 17 | Gtk4InputWindow(ClassicUIConfig *config, FcitxGClient *client); 18 | 19 | ~Gtk4InputWindow(); 20 | 21 | void setParent(GtkWidget *parent); 22 | void update() override; 23 | void setCursorRect(GdkRectangle rect); 24 | 25 | private: 26 | void draw(cairo_t *cr); 27 | gboolean event(GdkEvent *event); 28 | void reposition(); 29 | void surfaceNotifyMapped(GdkSurface *surface); 30 | void resetWindow(); 31 | void syncFontOptions(); 32 | 33 | bool supportAlpha = false; 34 | // Dummy widget to track font options. 35 | UniqueCPtr dummyWidget_; 36 | UniqueCPtr window_; 37 | UniqueCPtr cairoCcontext_; 38 | GtkWidget *parent_ = nullptr; 39 | size_t width_ = 1; 40 | size_t height_ = 1; 41 | GdkRectangle rect_; 42 | double scrollDelta_ = 0; 43 | }; 44 | 45 | } // namespace fcitx::gtk 46 | 47 | #endif // _GTK3_GTK3INPUTWINDOW_H_ 48 | -------------------------------------------------------------------------------- /gtk4/immodule-probing.cpp: -------------------------------------------------------------------------------- 1 | ../gtk2/immodule-probing.cpp -------------------------------------------------------------------------------- /gtk4/inputwindow.cpp: -------------------------------------------------------------------------------- 1 | ../gtk3/inputwindow.cpp -------------------------------------------------------------------------------- /gtk4/inputwindow.h: -------------------------------------------------------------------------------- 1 | ../gtk3/inputwindow.h -------------------------------------------------------------------------------- /gtk4/utils.cpp: -------------------------------------------------------------------------------- 1 | ../gtk3/utils.cpp -------------------------------------------------------------------------------- /gtk4/utils.h: -------------------------------------------------------------------------------- 1 | ../gtk3/utils.h --------------------------------------------------------------------------------