├── .clang-format ├── .clang_complete ├── .gitignore ├── LICENSE ├── README.md ├── common └── config.h.in ├── dists ├── icons │ ├── 128x128 │ │ └── one.alynx.showmethekey.png │ ├── 64x64 │ │ └── one.alynx.showmethekey.png │ └── scalable │ │ └── one.alynx.showmethekey.svg ├── one.alynx.showmethekey.desktop.in ├── one.alynx.showmethekey.gschema.xml ├── one.alynx.showmethekey.metainfo.xml ├── one.alynx.showmethekey.policy.in └── one.alynx.showmethekey.rules ├── docs ├── .nojekyll ├── CNAME ├── css │ ├── index.css │ └── normalize.css ├── images │ ├── screenshot.gif │ ├── screenshot.mp4 │ └── screenshot.png └── index.html ├── meson.build ├── meson_options.txt ├── meson_post_install.py ├── showmethekey-cli ├── main.c └── meson.build └── showmethekey-gtk ├── main.c ├── meson.build ├── one.alynx.showmethekey.gresource.xml ├── po ├── LINGUAS ├── POTFILES.in ├── es_ES.po ├── meson.build ├── showmethekey.pot └── zh_CN.po ├── smtk-app-win-menu.ui ├── smtk-app-win.c ├── smtk-app-win.h ├── smtk-app-win.ui ├── smtk-app.c ├── smtk-app.h ├── smtk-event.c ├── smtk-event.h ├── smtk-keymap-list.c ├── smtk-keymap-list.h ├── smtk-keys-area.c ├── smtk-keys-area.h ├── smtk-keys-emitter.c ├── smtk-keys-emitter.h ├── smtk-keys-mapper.c ├── smtk-keys-mapper.h ├── smtk-keys-win.c ├── smtk-keys-win.h ├── smtk.h └── style.css /.clang-format: -------------------------------------------------------------------------------- 1 | # Clang Format config taken from Linux Kernel source tree. 2 | # Edited by AlynxZhou. 3 | # Licensed with GPL-2.0. 4 | # Need clang-format v7.0.0 or higher. 5 | --- 6 | AccessModifierOffset: -4 7 | AlignAfterOpenBracket: Align 8 | AlignConsecutiveAssignments: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlines: Left 11 | AlignOperands: true 12 | AlignTrailingComments: false 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: None 17 | AllowShortIfStatementsOnASingleLine: false 18 | AllowShortLoopsOnASingleLine: false 19 | AlwaysBreakAfterDefinitionReturnType: None 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AlwaysBreakTemplateDeclarations: false 23 | BinPackArguments: true 24 | BinPackParameters: true 25 | BraceWrapping: 26 | AfterClass: false 27 | AfterControlStatement: false 28 | AfterEnum: false 29 | AfterFunction: true 30 | AfterNamespace: true 31 | AfterObjCDeclaration: false 32 | AfterStruct: false 33 | AfterUnion: false 34 | AfterExternBlock: false 35 | BeforeCatch: false 36 | BeforeElse: false 37 | IndentBraces: false 38 | SplitEmptyFunction: true 39 | SplitEmptyRecord: true 40 | SplitEmptyNamespace: true 41 | BreakBeforeBinaryOperators: None 42 | BreakBeforeBraces: Custom 43 | BreakBeforeInheritanceComma: false 44 | BreakBeforeTernaryOperators: false 45 | BreakConstructorInitializersBeforeComma: false 46 | BreakConstructorInitializers: BeforeComma 47 | BreakAfterJavaFieldAnnotations: false 48 | BreakStringLiterals: false 49 | ColumnLimit: 80 50 | CommentPragmas: '^ IWYU pragma:' 51 | CompactNamespaces: false 52 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 53 | ConstructorInitializerIndentWidth: 8 54 | ContinuationIndentWidth: 8 55 | Cpp11BracedListStyle: false 56 | DerivePointerAlignment: false 57 | DisableFormat: false 58 | ExperimentalAutoDetectBinPacking: false 59 | FixNamespaceComments: false 60 | 61 | ForEachMacros: 62 | 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '.*' 66 | Priority: 1 67 | IncludeIsMainRegex: '(Test)?$' 68 | IndentCaseLabels: false 69 | IndentPPDirectives: AfterHash 70 | IndentWidth: 8 71 | IndentWrappedFunctionNames: false 72 | JavaScriptQuotes: Leave 73 | JavaScriptWrapImports: true 74 | KeepEmptyLinesAtTheStartOfBlocks: false 75 | MacroBlockBegin: '' 76 | MacroBlockEnd: '' 77 | MaxEmptyLinesToKeep: 1 78 | NamespaceIndentation: Inner 79 | ObjCBinPackProtocolList: Auto 80 | ObjCBlockIndentWidth: 8 81 | ObjCSpaceAfterProperty: true 82 | ObjCSpaceBeforeProtocolList: true 83 | 84 | # Taken from git's rules 85 | PenaltyBreakAssignment: 10 86 | PenaltyBreakBeforeFirstCallParameter: 30 87 | PenaltyBreakComment: 10 88 | PenaltyBreakFirstLessLess: 0 89 | PenaltyBreakString: 10 90 | PenaltyExcessCharacter: 100 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | 93 | PointerAlignment: Right 94 | ReflowComments: false 95 | SortIncludes: false 96 | SortUsingDeclarations: false 97 | SpaceAfterCStyleCast: false 98 | SpaceAfterTemplateKeyword: true 99 | SpaceBeforeAssignmentOperators: true 100 | SpaceBeforeCtorInitializerColon: true 101 | SpaceBeforeInheritanceColon: true 102 | SpaceBeforeParens: ControlStatements 103 | SpaceBeforeRangeBasedForLoopColon: true 104 | SpaceInEmptyParentheses: false 105 | SpacesBeforeTrailingComments: 1 106 | SpacesInAngles: false 107 | SpacesInContainerLiterals: false 108 | SpacesInCStyleCastParentheses: false 109 | SpacesInParentheses: false 110 | SpacesInSquareBrackets: false 111 | Standard: Cpp03 112 | TabWidth: 8 113 | UseTab: Always 114 | ... 115 | -------------------------------------------------------------------------------- /.clang_complete: -------------------------------------------------------------------------------- 1 | -I/usr/include/libevdev-1.0 2 | -I/usr/include/gtk-3.0 3 | -I/usr/include/gtk-4.0 4 | -I/usr/include/pango-1.0 5 | -I/usr/include/glib-2.0 6 | -I/usr/lib/glib-2.0/include 7 | -I/usr/include/harfbuzz 8 | -I/usr/include/freetype2 9 | -I/usr/include/libpng16 10 | -I/usr/include/libmount 11 | -I/usr/include/blkid 12 | -I/usr/include/fribidi 13 | -I/usr/include/cairo 14 | -I/usr/include/lzo 15 | -I/usr/include/pixman-1 16 | -I/usr/include/gdk-pixbuf-2.0 17 | -I/usr/include/graphene-1.0 18 | -I/usr/lib/graphene-1.0/include 19 | -I/usr/include/gio-unix-2.0 20 | -I/usr/include/glib-2.0 21 | -I/usr/lib/glib-2.0/include 22 | -I/usr/include/libmount 23 | -I/usr/include/blkid 24 | -I/usr/include/json-glib-1.0 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | .cache/ 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Show Me The Key 2 | =============== 3 | 4 | Show keys you typed on screen. 5 | ------------------------------ 6 | 7 | [Project Website](https://showmethekey.alynx.one/) 8 | 9 | A SUSE Hack Week 20 Project: [Show Me The Key: A screenkey alternative that works under Wayland via libinput](https://hackweek.suse.com/20/projects/a-screenkey-alternative-that-works-under-wayland-via-reading-evdev-directly). 10 | 11 | # Install 12 | 13 | ## Distribution Package (Recommended) 14 | 15 | ### AOSC OS 16 | 17 | Just run following command to install from official repository: 18 | 19 | ``` 20 | # apt install showmethekey 21 | ``` 22 | 23 | ### Arch Linux 24 | 25 | #### Install From AUR 26 | 27 | ``` 28 | $ paru showmethekey 29 | ``` 30 | 31 | Or use other AUR helpers. 32 | 33 | #### Install From `archlinuxcn` 34 | 35 | First [add archlinuxcn repo to your system](ttps://www.archlinuxcn.org/archlinux-cn-repo-and-mirror/). 36 | 37 | ``` 38 | # pacman -S showmethekey 39 | ``` 40 | 41 | ### openSUSE 42 | 43 | #### Install from OBS 44 | 45 | Packages can be found in [my OBS project](https://build.opensuse.org/package/show/home:AZhou/showmethekey). 46 | 47 | ``` 48 | # zypper ar https://download.opensuse.org/repositories/home:/AZhou/openSUSE_Tumbleweed/home:AZhou.repo 49 | # zypper in showmethekey showmethekey-lang 50 | ``` 51 | 52 | Leap users please replace URL for Tumbleweed with URL for your Leap version. 53 | 54 | ### Fedora 55 | 56 | #### Install from COPR 57 | 58 | To install the package on Fedora Workstation, run the following commands: 59 | 60 | ```bash 61 | sudo dnf copr enable pesader/showmethekey 62 | sudo dnf install showmethekey 63 | ``` 64 | 65 | If you are running an Atomic Desktop (Fedora Silverblue, Fedora Kinoite, Fedora Sericea, etc), run: 66 | 67 | ```bash 68 | export RELEASE=40 # or whichever release of Fedora you are running 69 | sudo curl -o /etc/yum.repos.d/showmethekey.repo https://copr.fedorainfracloud.org/coprs/pesader/showmethekey/repo/fedora-$RELEASE/pesader-showmethekey-fedora-$RELEASE.repo 70 | rpm-ostree install showmethekey 71 | ``` 72 | 73 | ### Other Distributions 74 | 75 | Please help package showmethekey to your distribution! 76 | 77 | ## Build From Source 78 | 79 | ### Dependencies 80 | 81 | - libevdev 82 | - udev (or systemd) 83 | - libinput 84 | - glib2 85 | - gtk4 86 | - libadwaita 87 | - json-glib 88 | - cairo 89 | - pango 90 | - libxkbcommon 91 | - polkit 92 | - meson 93 | - ninja 94 | - gcc 95 | 96 | ### Build 97 | 98 | ``` 99 | $ git clone https://github.com/AlynxZhou/showmethekey.git 100 | $ cd showmethekey 101 | $ mkdir build && cd build && meson setup --prefix=/usr . .. && meson compile && meson install 102 | $ showmethekey-gtk 103 | ``` 104 | 105 | # Usage 106 | 107 | For detailed usage please run usage dialog from app menu! 108 | 109 | You need to toggle the switch to start it manually and need to input admin password to polkit authentication agent's dialog, because we need superuser permission to read keyboard events (this program does not handle your password so it is safe). Wayland does not allow a client to set its position, so this program does not set its position in preference, and you can click the "Clickable Area" in titlebar and drag the floating window to anywhere you want. 110 | 111 | Users in `wheel` group can skip password authentication. 112 | 113 | ## Special Notice for Wayland Session Users 114 | 115 | There is no official Wayland protocol allowing toplevel clients to set their own position and layer, only users can change those things. But don't worry, users are always allowed to do those things by themselves if their compositors support it. 116 | 117 | For example if you are using GNOME Shell (Wayland), you can right click the "Clickable Area" on title bar to show a window manager menu and check "Always on Top" and "Always on Visible Workspace" in it. 118 | 119 | If you are using KDE Plasma (Wayland), you can right click "Floating Window - Show Me The Key" on task bar, check "Move to Desktop" -> "All Desktops" and "More Actions" -> "Keep Above Others". 120 | 121 | For Sway users, you can add following configurations into `~/.config/sway/config` to enable floating and sticky (thanks to [haxibami's blog post](https://zenn.dev/haxibami/articles/wayland-sway-install#%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E8%A8%AD%E5%AE%9A): 122 | 123 | ``` 124 | for_window [app_id="one.alynx.showmethekey" title="Floating Window - Show Me The Key"] { 125 | floating enable 126 | sticky enable 127 | } 128 | ``` 129 | 130 | # Feature 131 | 132 | [screenkey](https://gitlab.com/screenkey/screenkey) is a popular project for streamers or tutorial recorders because it can make your typing visual on screen, but it only works under X11, not Wayland because it uses X11 functions to get keyboard event. 133 | 134 | This program, instead, reads key events via libinput directly, and then put it on screen, so it will not depend on X11 or special Wayland Compositors and will work across them. 135 | 136 | # Project Structure 137 | 138 | ## CLI 139 | 140 | This part exists because of Wayland's security policy, which means you cannot run a GUI program with `sudo` (see ). It's suggested to split your program into a GUI frontend and a CLI backend that do privileged operations, and this is the backend, a custom re-write of , based on [libinput](https://www.freedesktop.org/wiki/Software/libinput/), [libudev](https://www.freedesktop.org/software/systemd/man/libudev.html) and [libevdev](https://www.freedesktop.org/wiki/Software/libevdev/). 141 | 142 | It generates JSON in lines like `{"event_name": "KEYBOARD_KEY", "event_type": 300, "time_stamp": 39869802, "key_name": "KEY_C", "key_code": 46, "state_name": "PRESSED", "state_code": 1}`. 143 | 144 | ## GTK 145 | 146 | A GUI frontend based on GTK, will run CLI backend as root via `pkexec`, and show a transparent floating window to display events. 147 | 148 | # FAQ 149 | 150 | ## Why your program needs root permission? screenkey never asks for it! 151 | 152 | If you debug with libinput, you'll find it needs root permission, too. Because this program support both Wayland and X11, it does not get input events via display protocol, actually it's reading directly from evdev interface under `/dev`. **And if you want to interact with files under `/dev`, you need root permission.** screenkey does not needs root permission because it's heavily X11-based, **it gets input events from X server** instead of `/dev`, which already done it. And because of this it will never support Wayland. 153 | 154 | ## I am using Sway/Wayfire/[not DEs], and I always get `AUTHENTICATION FAILED` in terminal! 155 | 156 | This is a pkexec bug that it's tty authentication does not work, see . Most DEs have their own authentication agents, but if you are not using them, pkexec will try to make itself an agent, and you get this bug. 157 | 158 | A possible workaround is , actually you can use any agents, not only the gnome one. 159 | 160 | ## I have remapped Caps Lock to Control, but this program shows Caps_Lock U instead of ^u! 161 | 162 | Keys which are remapped via the Desktop Environment/X11 tools (for example, `setxkb`, GNOME Tweaks, KDE settings, or other high-level tools) are not visible to Show Me The Key. A workaround is to remap keys using **udev** rules. This has the added benefit of working identically in the Linux console (TTY), Wayland, and X11 alike. See . 163 | 164 | # Translate 165 | 166 | If you changed translatable strings, don't forget to run `meson compile showmethekey-update-po` in build directory and then edit po files, and please check if there are `fuzzy` tag in comment, you should remove them and make translation exact, otherwise it will not work. 167 | 168 | If you added new source files with translatable strings, don't forget to add it to `showmethekey-gtk/po/POTFILES.in` before running `meson compile showmethekey-update-po`. File paths in `POTFILES.in` should be relative to project directory. 169 | 170 | If you want to add languages, first add a country code in `showmethekey-gtk/po/LINGUAS`, then run `meson compile showmethekey-update-po`, you will get a new `.po` file with your added country code. If this language needs UTF-8 encoding, don't use words like `zh_CN.UTF-8` in `showmethekey-gtk/po/LINGUAS` or file name, because RPM's find\_lang script may ignore them sometimes, and you should change to `charset=UTF-8` manually in the header. 171 | 172 | # Name 173 | 174 | As I want some clear name that hints its usage, but `screenkey` is already taken and I think `visualkey` sounds like `Visual Studio` and it's horrible. My friend [@LGiki](https://github.com/LGiki) suggests `Show Me The Key` which sounds like "Show me the code" from Linus Torvalds. At first I think it's a little bit long, but now it is acceptable so it's called `showmethekey` or `Show Me The Key`. 175 | 176 | The Chinese translate of this program name should be `让我看键`, and it's only used for app window title, debug output, package name, desktop entry name and floating window title should not be translated. (**The floating window title is important because some compositors relies on it to write window rules so you should never translate it!!!**) 177 | 178 | # Icon 179 | 180 | Program icon made by Freepik from www.flaticon.com. 181 | -------------------------------------------------------------------------------- /common/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | #define PROJECT_VERSION "@project_version@" 5 | #define GETTEXT_PACKAGE "@gettext_package@" 6 | #define PACKAGE_BINDIR "@package_bindir@" 7 | #define PACKAGE_LOCALEDIR "@package_localedir@" 8 | #define PKEXEC_PATH "@pkexec_path@" 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /dists/icons/128x128/one.alynx.showmethekey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlynxZhou/showmethekey/5d832d4f10d383c45e8b08f31965e9dc9dea5d1e/dists/icons/128x128/one.alynx.showmethekey.png -------------------------------------------------------------------------------- /dists/icons/64x64/one.alynx.showmethekey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlynxZhou/showmethekey/5d832d4f10d383c45e8b08f31965e9dc9dea5d1e/dists/icons/64x64/one.alynx.showmethekey.png -------------------------------------------------------------------------------- /dists/icons/scalable/one.alynx.showmethekey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /dists/one.alynx.showmethekey.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Show Me The Key 4 | GenericName=Show Me The Key 5 | Comment=Show keys you typed on screen. 6 | Icon=one.alynx.showmethekey 7 | Exec=@package_bindir@/showmethekey-gtk 8 | TryExec=@package_bindir@/showmethekey-gtk 9 | StartupNotify=false 10 | Terminal=false 11 | Categories=Utility;Accessibility; 12 | -------------------------------------------------------------------------------- /dists/one.alynx.showmethekey.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | First Time Launch or Not 6 | The program is running for the first time. 7 | true 8 | 9 | 10 | Show Shift or Not 11 | Show shift separately or only when not consumed. 12 | true 13 | 14 | 15 | Show Keyboard Key or Not 16 | Show keyboard key. 17 | true 18 | 19 | 20 | Show Mouse Button or Not 21 | Show mouse button. 22 | true 23 | 24 | 25 | Draw Border around Keys or Not 26 | Draw border around keys or only show keys. 27 | true 28 | 29 | 30 | Hide Visible Keys or Not 31 | Hide visible keys or show all keys. 32 | false 33 | 34 | 35 | Keys Display Mode 36 | Display keys in composed, raw or compact mode. 37 | 0 38 | 39 | 40 | Keys Display Alignment 41 | Align keys to end or center. 42 | 0 43 | 44 | 45 | 46 | Floating Window Width 47 | The width of the floating window, in pixels. 48 | 1500 49 | 50 | 51 | 52 | Floating Window Height 53 | The height of the floating window, in pixels. 54 | 200 55 | 56 | 57 | 58 | Text Timeout 59 | The inactive timeout after which text disappears, in milliseconds. 60 | 0 61 | 62 | 63 | 64 | Keymap 65 | Keyboard layout and variant. 66 | "us" 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /dists/one.alynx.showmethekey.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | one.alynx.showmethekey 5 | 6 | Show Me The Key 7 | Show keys you typed on screen. 8 | 9 | CC-BY-SA-3.0 10 | Apache-2.0 11 | 12 | 13 | 14 | one.alynx.showmethekey.desktop 15 | 16 | 17 | pointing 18 | keyboard 19 | touch 20 | tablet 21 | 22 | 23 | 24 |

25 | Show keys you typed on screen, so your audiences can see what you do clearly while you are streaming or recording. This is a screenkey alternative, and works not only on X11 but also Wayland. 26 |

27 |
28 | https://showmethekey.alynx.one/ 29 | 30 | 31 | https://showmethekey.alynx.one/images/screenshot.png 32 | 33 | 34 | 35 | 36 | 37 | 38 |

Updated UI.

39 |
40 |
41 | 42 | 43 |

Updated debug output.

44 |

Updated to only use GTK basic theme for SmtkKeysWin.

45 |

Updated app settings handling to allow changing while working.

46 |
47 |
48 | 49 | 50 |

Updated to fix some build warnings.

51 |
52 |
53 | 54 | 55 |

Updated to GTK4.

56 |

Updated to use custom drawing area instead of label for keys.

57 |
58 |
59 | 60 | 61 |

Updated to remove unused code.

62 |
63 |
64 | 65 | 66 |

Updated to stop translating floating window's title.

67 |

Fixed some code.

68 |
69 |
70 | 71 | 72 |

Updated to update icon cache on install.

73 |
74 |
75 | 76 | 77 |

Updated to use Meson's bindir option instead of hard-code bin directory.

78 |

Added support for custom pkexec path.

79 |

Updated polkit policy to stop asking password for wheel group.

80 |

Updated to follow Freedesktop specifications.

81 |
82 |
83 | 84 | 85 |

Added text timeout feature.

86 |
87 |
88 | 89 | 90 |

Updated desktop entry.

91 |
92 |
93 | 94 | 95 |

Updated desktop entry.

96 |
97 |
98 |
99 |
100 | -------------------------------------------------------------------------------- /dists/one.alynx.showmethekey.policy.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Run CLI backend as root to read input devices. 6 | pkexec @package_bindir@/showmethekey-cli 7 | 8 | 9 | auth_admin 10 | auth_admin 11 | auth_admin_keep 12 | 13 | @package_bindir@/showmethekey-cli 14 | 15 | false 16 | 17 | 18 | -------------------------------------------------------------------------------- /dists/one.alynx.showmethekey.rules: -------------------------------------------------------------------------------- 1 | // Allow any user in the 'wheel' group to run CLI backend 2 | // without entering a password. 3 | polkit.addRule(function (action, subject) { 4 | if (action.id === "one.alynx.showmethekey.cli" && 5 | subject.isInGroup("wheel")) { 6 | return polkit.Result.YES; 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlynxZhou/showmethekey/5d832d4f10d383c45e8b08f31965e9dc9dea5d1e/docs/.nojekyll -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | showmethekey.alynx.one 2 | -------------------------------------------------------------------------------- /docs/css/index.css: -------------------------------------------------------------------------------- 1 | div.container { 2 | padding: 0; 3 | margin: 0 auto; 4 | width: 75%; 5 | height: 100%; 6 | /* You have a brand screen! You must be so rich but we take care your neck. */ 7 | max-width: 120em; 8 | } 9 | pre.code { 10 | /* Keep Chinese and English the same height. */ 11 | line-height: 1.5; 12 | font-feature-settings: "liga" 0; 13 | white-space: pre-line; 14 | } 15 | div.title { 16 | font: bold 2rem Roboto, sans-serif; 17 | } 18 | div.title a { 19 | color: #000; 20 | text-decoration: none; 21 | border-bottom: none; 22 | } 23 | div.subtitle { 24 | font: italic 1.5rem Roboto, sans-serif; 25 | } 26 | div.screenshots { 27 | text-align: center; 28 | } 29 | .screenshot { 30 | width: 100%; 31 | } 32 | -------------------------------------------------------------------------------- /docs/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } 350 | -------------------------------------------------------------------------------- /docs/images/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlynxZhou/showmethekey/5d832d4f10d383c45e8b08f31965e9dc9dea5d1e/docs/images/screenshot.gif -------------------------------------------------------------------------------- /docs/images/screenshot.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlynxZhou/showmethekey/5d832d4f10d383c45e8b08f31965e9dc9dea5d1e/docs/images/screenshot.mp4 -------------------------------------------------------------------------------- /docs/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlynxZhou/showmethekey/5d832d4f10d383c45e8b08f31965e9dc9dea5d1e/docs/images/screenshot.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Show Me The Key 13 | 14 | 15 |
16 |
17 | 18 |
Show keys you typed on screen.
19 |
20 |
21 |
22 |
23 |

Description

24 |

25 | Show keys you typed on screen, so your audiences can see what you do clearly while you are streaming or recording. 26 |

27 |

28 | This is a screenkey alternative, and works not only on X11 but also Wayland. 29 |

30 |

31 | Your desktop must support composition and you may need to set "always on top" and "show on all workspaces" manually so be sure your desktop supports them. 32 |

33 |

Feature

34 |
    35 |
  • Key events reading via libinput.
  • 36 |
  • Configurable floating window size.
  • 37 |
  • Click-through but draggable floating window.
  • 38 |
  • Pausing support: Press both alt keys anywhere to temporary hide input like password.
  • 39 |
  • Keymap handling via xkbcommon.
  • 40 |
  • Mouse button support.
  • 41 |
42 |

Release

43 |

44 | Please go to GitHub Release Page. 45 |

46 |

Installation

47 |
48 |

49 | This section does not always get updated, please read Install section of README first. 50 |

51 |
52 |
53 |

Distribution Package (Recommended)

54 |

Arch Linux

55 |

Install From AUR

56 |
 57 |             $ paru showmethekey
 58 |           
59 |

Or use other AUR helpers.

60 |

Install From archlinuxcn

61 |

First add archlinuxcn repo to your system.

62 |
 63 |             # pacman -S showmethekey
 64 |           
65 |

openSUSE

66 |

Install from OBS

67 |

Packages can be found in my OBS project.

68 |
 69 |             # zypper ar https://download.opensuse.org/repositories/home:/AZhou/openSUSE_Tumbleweed/home:AZhou.repo
 70 |             # zypper in showmethekey showmethekey-lang
 71 |           
72 |

Leap users please replace URL for Tumbleweed with URL for your Leap version.

73 |

Other Distributions

74 |

75 | Please help package showmethekey to your distribution! 76 |

77 |

Build From Source

78 |

Dependencies

79 |
    80 |
  • libevdev
  • 81 |
  • udev (or systemd)
  • 82 |
  • libinput
  • 83 |
  • glib2
  • 84 |
  • gtk4
  • 85 |
  • libadwaita
  • 86 |
  • json-glib
  • 87 |
  • cairo
  • 88 |
  • pango
  • 89 |
  • libxkbcommon
  • 90 |
  • polkit
  • 91 |
  • meson
  • 92 |
  • ninja
  • 93 |
  • gcc
  • 94 |
95 |

Build

96 |
 97 |             $ git clone https://github.com/AlynxZhou/showmethekey.git
 98 |             $ cd showmethekey
 99 |             $ mkdir build && cd build && meson setup --prefix=/usr . .. && meson compile && meson install
100 |             $ showmethekey-gtk
101 |           
102 |
103 |

Usage

104 |

105 | For detailed usage please run usage dialog from app menu! 106 |

107 |

108 | You need to toggle the switch to start it manually and need to input admin password to pkexec's dialog, because we need superuser permission to read keyboard events (this program does not handle your password so it is safe). Wayland does not allow a client to set its position, so this program does not set its position in preference, and you can click the "Clickable Area" in titlebar and drag the floating window to anywhere you want. 109 |

110 |

111 | Users in `wheel` group can skip password authentication. 112 |

113 |

FAQ

114 |

Why your program needs root permission? screenkey never asks for it!

115 |

116 | If you debug with libinput, you'll find it needs root permission, too. Because this program support both Wayland and X11, it does not get input events via display protocol, actually it's reading directly from evdev interface under /dev. And if you want to interact with files under /dev, you need root permission. screenkey does not needs root permission because it's heavily X11-based, it gets input events from X server instead of /dev, which already done it. And because of this it will never support Wayland. 117 |

118 |

I am using Sway/Wayfire/[not DEs], and I always get AUTHENTICATION FAILED in terminal!

119 |

120 | This is a pkexec bug that it's tty authentication does not work, see https://gitlab.freedesktop.org/polkit/polkit/-/issues/17. Most DEs have their own authentication agents, but if you are not using them, pkexec will try to make itself an agent, and you get this bug. 121 |

122 |

123 | A possible workaround is https://github.com/AlynxZhou/showmethekey/issues/2#issuecomment-1019439959, actually you can use any agents, not only the gnome one. 124 |

125 |

I have remapped Caps Lock to Control, but this program shows Caps_Lock U instead of ^u!

126 |

127 | Keys which are remapped via the Desktop Environment/X11 tools (for example, setxkb, GNOME Tweaks, KDE settings, or other high-level tools) are not visible to Show Me The Key. A workaround is to remap keys using udev rules. This has the added benefit of working identically in the Linux console (TTY), Wayland, and X11 alike. See here. 128 |

129 |

Screenshots

130 |
131 | screenshot.gif 132 | 136 | screenshot.png 137 |
138 |
139 |
140 |
141 |
142 |

This page is currently working in progress, for more info please visit project repo.

143 | Copyright Alynx Zhou 2021 144 |
145 |
146 | 147 | 148 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'showmethekey', 3 | 'c', 4 | version: '1.18.3', 5 | license: 'Apache-2.0', 6 | default_options: ['c_std=c11'] 7 | ) 8 | 9 | cc = meson.get_compiler('c') 10 | 11 | c_args = [] 12 | if get_option('debug') == true 13 | warning_level = 3 14 | c_args += ['-D__DEBUG__'] 15 | endif 16 | 17 | conf_data = configuration_data() 18 | conf_data.set('project_version', meson.project_version()) 19 | conf_data.set('gettext_package', meson.project_name()) 20 | conf_data.set('package_bindir', get_option('prefix') / get_option('bindir')) 21 | conf_data.set('package_localedir', get_option('prefix') / get_option('localedir')) 22 | conf_data.set('pkexec_path', get_option('pkexec_path')) 23 | 24 | configure_file( 25 | input: 'common' / 'config.h.in', 26 | output: 'config.h', 27 | configuration: conf_data 28 | ) 29 | 30 | subdir('showmethekey-cli') 31 | 32 | subdir('showmethekey-gtk') 33 | 34 | install_data( 35 | 'LICENSE', 36 | install_dir: get_option('datadir') / 'licenses' / meson.project_name() 37 | ) 38 | install_data( 39 | 'dists' / 'icons' / '128x128' / 'one.alynx.showmethekey.png', 40 | install_dir: get_option('datadir') / 'icons' / 'hicolor' / '128x128' / 'apps' 41 | ) 42 | install_data( 43 | 'dists' / 'icons' / '64x64' / 'one.alynx.showmethekey.png', 44 | install_dir: get_option('datadir') / 'icons' / 'hicolor' / '64x64' / 'apps' 45 | ) 46 | install_data( 47 | 'dists' / 'icons' / 'scalable' / 'one.alynx.showmethekey.svg', 48 | install_dir: get_option('datadir') / 'icons' / 'hicolor' / 'scalable' / 'apps' 49 | ) 50 | install_data( 51 | 'dists' / 'one.alynx.showmethekey.gschema.xml', 52 | install_dir: get_option('datadir') / 'glib-2.0' / 'schemas' 53 | ) 54 | install_data( 55 | 'dists' / 'one.alynx.showmethekey.metainfo.xml', 56 | install_dir: get_option('datadir') / 'metainfo' 57 | ) 58 | install_data( 59 | 'dists' / 'one.alynx.showmethekey.rules', 60 | install_dir: get_option('datadir') / 'polkit-1' / 'rules.d' 61 | ) 62 | configure_file( 63 | input: 'dists' / 'one.alynx.showmethekey.policy.in', 64 | output: 'one.alynx.showmethekey.policy', 65 | configuration: conf_data, 66 | install: true, 67 | install_dir: get_option('datadir') / 'polkit-1' / 'actions' 68 | ) 69 | configure_file( 70 | input: 'dists/one.alynx.showmethekey.desktop.in', 71 | output: 'one.alynx.showmethekey.desktop', 72 | configuration: conf_data, 73 | install: true, 74 | install_dir: get_option('datadir') / 'applications' 75 | ) 76 | 77 | # We should not compile schemas by ourselves when packaging for distributions. 78 | # A simple way to detect packaging is the `DISTDIR` env, but before Meson 0.57.0 79 | # does not support this and it also does not allow us to get env, 80 | # so typically we use a Python script here as fallback. 81 | if meson.version().version_compare('>= 0.57.0') 82 | meson.add_install_script( 83 | 'glib-compile-schemas', 84 | get_option('prefix') / get_option('datadir') / 'glib-2.0' / 'schemas', 85 | skip_if_destdir: true 86 | ) 87 | meson.add_install_script( 88 | 'gtk4-update-icon-cache', 89 | '-f', 90 | '-t', 91 | get_option('prefix') / get_option('datadir') / 'icons' / 'hicolor', 92 | skip_if_destdir: true 93 | ) 94 | else 95 | meson.add_install_script( 96 | 'meson_post_install.py', 97 | get_option('prefix') / get_option('datadir') 98 | ) 99 | endif 100 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('pkexec_path', type: 'string', value: 'pkexec', 2 | description: 'Path of the pkexec program (or an alternative)') 3 | -------------------------------------------------------------------------------- /meson_post_install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import subprocess 6 | 7 | schema_dir = os.path.join(sys.argv[1], 'glib-2.0', 'schemas') 8 | icon_dir = os.path.join(sys.argv[1], 'icons', 'hicolor') 9 | 10 | # Package manager will set DESTDIR on packaging and run those on installing. 11 | # We do nothing if packaging. 12 | if not os.environ.get("DESTDIR"): 13 | argv = ["glib-compile-schemas", schema_dir] 14 | print(" ".join(argv)) 15 | subprocess.call(argv) 16 | 17 | argv = ["gtk4-update-icon-cache", "-f", "-t", icon_dir] 18 | print(" ".join(argv)) 19 | subprocess.call(argv) 20 | -------------------------------------------------------------------------------- /showmethekey-cli/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "config.h" 19 | 20 | #define MAX_BUFFER_LENGTH 512 21 | 22 | enum error_code { 23 | NO_ERROR, 24 | UDEV_FAILED, 25 | LIBINPUT_FAILED, 26 | SEAT_FAILED, 27 | PERMISSION_FAILED 28 | }; 29 | 30 | struct input_handler_data { 31 | struct udev *udev; 32 | struct libinput *libinput; 33 | }; 34 | 35 | static void *handle_input(void *user_data) 36 | { 37 | struct input_handler_data *input_handler_data = user_data; 38 | 39 | char line[MAX_BUFFER_LENGTH]; 40 | while (fgets(line, MAX_BUFFER_LENGTH, stdin) != NULL) { 41 | if (strcmp(line, "stop\n") == 0) { 42 | libinput_unref(input_handler_data->libinput); 43 | udev_unref(input_handler_data->udev); 44 | exit(EXIT_SUCCESS); 45 | } 46 | } 47 | 48 | return NULL; 49 | } 50 | 51 | static int open_restricted(const char *path, int flags, void *user_data) 52 | { 53 | int fd = open(path, flags); 54 | if (fd < 0) 55 | fprintf(stderr, "Failed to open %s because of %s.\n", path, 56 | strerror(errno)); 57 | return fd < 0 ? -errno : fd; 58 | } 59 | 60 | static void close_restricted(int fd, void *user_data) 61 | { 62 | close(fd); 63 | } 64 | 65 | static const struct libinput_interface interface = { 66 | .open_restricted = open_restricted, 67 | .close_restricted = close_restricted, 68 | }; 69 | 70 | static int print_key_event(struct libinput_event *event) 71 | { 72 | struct libinput_event_keyboard *keyboard = 73 | libinput_event_get_keyboard_event(event); 74 | 75 | enum libinput_event_type event_type = libinput_event_get_type(event); 76 | uint32_t time_stamp = libinput_event_keyboard_get_time(keyboard); 77 | uint32_t key_code = libinput_event_keyboard_get_key(keyboard); 78 | const char *key_name = libevdev_event_code_get_name(EV_KEY, key_code); 79 | key_name = key_name ? key_name : "null"; 80 | enum libinput_key_state state_code = 81 | libinput_event_keyboard_get_key_state(keyboard); 82 | const char *state_name = state_code == LIBINPUT_KEY_STATE_PRESSED ? 83 | "PRESSED" : 84 | "RELEASED"; 85 | 86 | return printf("{" 87 | "\"event_name\": \"KEYBOARD_KEY\", " 88 | "\"event_type\": %d, " 89 | "\"time_stamp\": %d, " 90 | "\"key_name\": \"%s\", " 91 | "\"key_code\": %d, " 92 | "\"state_name\": \"%s\", " 93 | "\"state_code\": %d" 94 | "}\n", 95 | event_type, time_stamp, key_name, key_code, state_name, 96 | state_code); 97 | } 98 | 99 | static int print_button_event(struct libinput_event *event) 100 | { 101 | struct libinput_event_pointer *pointer = 102 | libinput_event_get_pointer_event(event); 103 | 104 | enum libinput_event_type event_type = libinput_event_get_type(event); 105 | uint32_t time_stamp = libinput_event_pointer_get_time(pointer); 106 | uint32_t button_code = libinput_event_pointer_get_button(pointer); 107 | const char *button_name = 108 | libevdev_event_code_get_name(EV_KEY, button_code); 109 | enum libinput_button_state state_code = 110 | libinput_event_pointer_get_button_state(pointer); 111 | const char *state_name = state_code == LIBINPUT_BUTTON_STATE_PRESSED ? 112 | "PRESSED" : 113 | "RELEASED"; 114 | return printf("{" 115 | "\"event_name\": \"POINTER_BUTTON\", " 116 | "\"event_type\": %d, " 117 | "\"time_stamp\": %d, " 118 | "\"key_name\": \"%s\", " 119 | "\"key_code\": %d, " 120 | "\"state_name\": \"%s\", " 121 | "\"state_code\": %d" 122 | "}\n", 123 | event_type, time_stamp, button_name, button_code, 124 | state_name, state_code); 125 | } 126 | 127 | static int handle_events(struct libinput *libinput) 128 | { 129 | int result = -1; 130 | struct libinput_event *event; 131 | 132 | if (libinput_dispatch(libinput) < 0) 133 | return result; 134 | 135 | // Please keep printing a line per json. 136 | while ((event = libinput_get_event(libinput)) != NULL) { 137 | switch (libinput_event_get_type(event)) { 138 | // This program only handle key event. 139 | case LIBINPUT_EVENT_KEYBOARD_KEY: 140 | print_key_event(event); 141 | break; 142 | // Sorry, mouse button is also a key. 143 | case LIBINPUT_EVENT_POINTER_BUTTON: 144 | print_button_event(event); 145 | break; 146 | default: 147 | break; 148 | } 149 | // Do a `fflush(stdout)` here, so when we write to pipes, 150 | // the other one can always get a latest result. 151 | // If we don't have `fflush(stdout)` here, pipe will save 152 | // some lines in buffer and pass them together. 153 | fflush(stdout); 154 | libinput_event_destroy(event); 155 | result = 0; 156 | } 157 | 158 | return result; 159 | } 160 | 161 | static int run_mainloop(struct libinput *libinput) 162 | { 163 | struct pollfd fd; 164 | fd.fd = libinput_get_fd(libinput); 165 | fd.events = POLLIN; 166 | fd.revents = 0; 167 | 168 | if (handle_events(libinput) != 0) { 169 | fprintf(stderr, 170 | "Expected device added events on startup but " 171 | "got none. Maybe you don't have the right permissions?" 172 | "\n"); 173 | return -1; 174 | } 175 | while (poll(&fd, 1, -1) > -1) 176 | handle_events(libinput); 177 | return 0; 178 | } 179 | 180 | void print_help(char *program_name) 181 | { 182 | printf("The backend of Show Me The Key.\n"); 183 | printf("Version " PROJECT_VERSION ".\n"); 184 | printf("Usage: %s [OPTION…]\n", program_name); 185 | printf("Options:\n"); 186 | printf("\t-h, --help\tDisplay help then exit.\n"); 187 | printf("\t-v, --version\tDisplay version then exit.\n"); 188 | printf("Warning: This is the backend and is not designed to run " 189 | "by users. You should run the frontend of Show Me The Key, " 190 | "and the frontend will run this.\n"); 191 | } 192 | 193 | int main(int argc, char *argv[]) 194 | { 195 | const struct option long_options[] = { { "version", no_argument, 0, 196 | 'v' }, 197 | { "help", no_argument, 0, 'h' }, 198 | { NULL, 0, NULL, 0 } }; 199 | 200 | int option_index = 0; 201 | int opt = 0; 202 | while ((opt = getopt_long(argc, argv, "vh", long_options, 203 | &option_index)) != -1) { 204 | switch (opt) { 205 | case 0: 206 | // We don't use this. 207 | break; 208 | case 'v': 209 | printf(PROJECT_VERSION "\n"); 210 | return 0; 211 | case 'h': 212 | print_help(argv[0]); 213 | return 0; 214 | case '?': 215 | // getopt_long already printed an error message. 216 | break; 217 | default: 218 | fprintf(stderr, "%s: Invalid option `-%c`.\n", argv[0], 219 | opt); 220 | break; 221 | } 222 | } 223 | 224 | struct udev *udev = udev_new(); 225 | if (udev == NULL) { 226 | fprintf(stderr, "Failed to initialize udev.\n"); 227 | return UDEV_FAILED; 228 | } 229 | 230 | struct libinput *libinput = 231 | libinput_udev_create_context(&interface, NULL, udev); 232 | if (!libinput) { 233 | fprintf(stderr, "Failed to initialize libinput from udev.\n"); 234 | return LIBINPUT_FAILED; 235 | } 236 | 237 | // TODO: Support custom seat. 238 | if (libinput_udev_assign_seat(libinput, "seat0") != 0) { 239 | fprintf(stderr, "Failed to set seat.\n"); 240 | libinput_unref(libinput); 241 | udev_unref(udev); 242 | return SEAT_FAILED; 243 | } 244 | 245 | // Typically this will be run with pkexec as a subprocess, 246 | // and the parent cannot kill it because it is privileged, 247 | // so we use another thread to see if it gets "stop\n" from stdin, 248 | // it will exit by itself. 249 | pthread_t input_handler; 250 | struct input_handler_data input_handler_data = { udev, libinput }; 251 | pthread_create(&input_handler, NULL, handle_input, &input_handler_data); 252 | 253 | if (run_mainloop(libinput) < 0) 254 | return PERMISSION_FAILED; 255 | 256 | libinput_unref(libinput); 257 | udev_unref(udev); 258 | 259 | return NO_ERROR; 260 | } 261 | -------------------------------------------------------------------------------- /showmethekey-cli/meson.build: -------------------------------------------------------------------------------- 1 | cli_sources = files( 2 | 'main.c' 3 | ) 4 | 5 | cli_dependencies = [] 6 | cli_include_directories = [] 7 | libevdev = dependency('libevdev', required: true) 8 | libudev = dependency('libudev', required: true) 9 | libinput = dependency('libinput', required: true) 10 | threads = dependency('threads', required: true) 11 | cli_dependencies += [libevdev, libudev, libinput, threads] 12 | # For config.h. 13 | cli_include_directories += include_directories('../') 14 | 15 | executable( 16 | 'showmethekey-cli', 17 | sources: cli_sources, 18 | c_args: c_args, 19 | dependencies: cli_dependencies, 20 | include_directories: cli_include_directories, 21 | install: true 22 | # By default, meson install binary to 23 | # `get_option('prefix') / get_option('bindir')`, 24 | # so we can omit `install_dir` here. 25 | ) 26 | -------------------------------------------------------------------------------- /showmethekey-gtk/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "smtk.h" 6 | #include "smtk-app.h" 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | setlocale(LC_ALL, ""); 11 | // Typically "/usr" "/" "share/locale". 12 | bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALEDIR); 13 | bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); 14 | textdomain(GETTEXT_PACKAGE); 15 | 16 | SmtkApp *app = smtk_app_new(); 17 | int result = g_application_run(G_APPLICATION(app), argc, argv); 18 | g_object_unref(app); 19 | 20 | return result; 21 | } 22 | -------------------------------------------------------------------------------- /showmethekey-gtk/meson.build: -------------------------------------------------------------------------------- 1 | gnome = import('gnome') 2 | 3 | gtk_sources = files( 4 | 'main.c', 5 | 'smtk-app.c', 6 | 'smtk-app-win.c', 7 | 'smtk-keys-win.c', 8 | 'smtk-keys-area.c', 9 | 'smtk-keys-emitter.c', 10 | 'smtk-keys-mapper.c', 11 | 'smtk-keymap-list.c', 12 | 'smtk-event.c' 13 | ) 14 | 15 | gtk_headers = files( 16 | 'smtk.h', 17 | 'smtk-app.h', 18 | 'smtk-app-win.h', 19 | 'smtk-keys-win.h', 20 | 'smtk-keys-area.h', 21 | 'smtk-keys-emitter.h', 22 | 'smtk-keys-mapper.h', 23 | 'smtk-keymap-list.h', 24 | 'smtk-event.h' 25 | ) 26 | 27 | gtk_dependencies = [] 28 | gtk_include_directories = [] 29 | gtk = dependency('gtk4', required: true) 30 | libadw = dependency('libadwaita-1', required: true) 31 | x11 = dependency('x11') 32 | glib = dependency('glib-2.0', required: true) 33 | json_glib = dependency('json-glib-1.0', required: true) 34 | gio = dependency('gio-2.0', required: true) 35 | cairo = dependency('cairo', required: true) 36 | pango = dependency('pango', required: true) 37 | xkbcommon = dependency('xkbcommon', required: true) 38 | xkbregistry = dependency('xkbregistry', required: true) 39 | gtk_dependencies += [ 40 | gtk, libadw, x11, glib, json_glib, gio, cairo, pango, xkbcommon, xkbregistry 41 | ] 42 | # For config.h. 43 | gtk_include_directories += include_directories('../') 44 | 45 | resources = gnome.compile_resources( 46 | 'smtk-resources', 47 | 'one.alynx.showmethekey.gresource.xml', 48 | source_dir: '.', 49 | c_name: 'smtk' 50 | ) 51 | 52 | enum_types = gnome.mkenums_simple('smtk-enum-types', sources: gtk_headers) 53 | 54 | executable( 55 | 'showmethekey-gtk', 56 | sources: gtk_sources + resources + enum_types, 57 | c_args: c_args, 58 | dependencies: gtk_dependencies, 59 | include_directories: gtk_include_directories, 60 | install: true 61 | # By default, meson install binary to 62 | # `get_option('prefix') / get_option('bindir')`, 63 | # so we can omit `install_dir` here. 64 | ) 65 | 66 | subdir('po') 67 | -------------------------------------------------------------------------------- /showmethekey-gtk/one.alynx.showmethekey.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | smtk-app-win.ui 5 | smtk-app-win-menu.ui 6 | style.css 7 | 8 | 9 | -------------------------------------------------------------------------------- /showmethekey-gtk/po/LINGUAS: -------------------------------------------------------------------------------- 1 | zh_CN 2 | es_ES 3 | -------------------------------------------------------------------------------- /showmethekey-gtk/po/POTFILES.in: -------------------------------------------------------------------------------- 1 | showmethekey-gtk/main.c 2 | showmethekey-gtk/smtk-app.c 3 | showmethekey-gtk/smtk-app-win.c 4 | showmethekey-gtk/smtk-app-win.ui 5 | showmethekey-gtk/smtk-app-win-menu.ui 6 | showmethekey-gtk/smtk-event.c 7 | showmethekey-gtk/smtk-keys-emitter.c 8 | showmethekey-gtk/smtk-keys-mapper.c 9 | showmethekey-gtk/smtk-keys-win.c 10 | -------------------------------------------------------------------------------- /showmethekey-gtk/po/es_ES.po: -------------------------------------------------------------------------------- 1 | # Spanish translations for showmethekey package. 2 | # Copyright (C) 2024 THE showmethekey'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the showmethekey package. 4 | # Automatically generated, 2024. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: showmethekey\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2025-01-10 22:15+0800\n" 11 | "PO-Revision-Date: 2024-08-02 10:16-0600\n" 12 | "Last-Translator: ItzSelenux\n" 13 | "Language-Team: none\n" 14 | "Language: es_ES\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: showmethekey-gtk/smtk-app.c:110 showmethekey-gtk/smtk-app-win.c:735 20 | #: showmethekey-gtk/smtk-app-win.ui:9 showmethekey-gtk/smtk-app-win.ui:17 21 | msgid "Show Me The Key" 22 | msgstr "Muéstrame La Tecla" 23 | 24 | #: showmethekey-gtk/smtk-app.c:115 25 | msgid "Display version then exit." 26 | msgstr "Mostrar versión y salir." 27 | 28 | #: showmethekey-gtk/smtk-app.c:118 29 | msgid "Show keys window on start up (deprecated by `-k, --show-keys-win`)." 30 | msgstr "" 31 | 32 | #: showmethekey-gtk/smtk-app.c:121 33 | #, fuzzy 34 | msgid "Show keys window on start up." 35 | msgstr "Muestra las teclas que escribiste en pantalla." 36 | 37 | #: showmethekey-gtk/smtk-app.c:123 38 | msgid "Hide app window and show keys window on start up." 39 | msgstr "" 40 | 41 | #: showmethekey-gtk/smtk-app.c:126 42 | msgid "Make keys window unclickable on start up." 43 | msgstr "" 44 | 45 | #: showmethekey-gtk/smtk-app-win.c:632 showmethekey-gtk/smtk-app-win.c:635 46 | msgid "Usage" 47 | msgstr "Uso" 48 | 49 | #: showmethekey-gtk/smtk-app-win.c:637 50 | msgid "" 51 | "1. Please input admin password after toggling the switch, because it needs " 52 | "superuser permission to read input events, and Wayland does not allow " 53 | "running graphics program with superuser permission, so it uses polkit to run " 54 | "a backend with superuser permission. This program does not handle or store " 55 | "your password. Users in `wheel` group can skip password authentication.\n" 56 | "\n" 57 | "2. After you toggle the switch to show the floating window, you need to drag " 58 | "it manually to anywhere you want, because Wayland does not allow window to " 59 | "set its position. The \"Clickable\" label on titlebar can be dragged as a " 60 | "handle.\n" 61 | "\n" 62 | "3. Because Wayland does not allow a window to set \"Always on Top\" and " 63 | "\"Always on Visible Workspace\" by itself, you should set it manually if you " 64 | "are in a Wayland session and your window manager support it.\n" 65 | "For example if you are using GNOME Shell (Wayland), you can right click the " 66 | "\"Clickable\" on title bar to show a window manager menu and check \"Always " 67 | "on Top\" and \"Always on Visible Workspace\" in it.\n" 68 | "If you are using KDE Plasma (Wayland), you can right click \"Floating Window " 69 | "- Show Me The Key\" on task bar, check \"Move to Desktop\" -> \"All " 70 | "Desktops\" and \"More Actions\" -> \"Keep Above Others\".\n" 71 | "You can check this project's README to see if " 73 | "there are configurations for your compositor.\n" 74 | "\n" 75 | "4. To allow user move or resize the keys window, it is not click through by " 76 | "default, after moving it to the location you want, turn off \"Clickable\" " 77 | "switch so it won't block your other operations.\n" 78 | "\n" 79 | "5. If you want to pause it (for example you need to insert password), you " 80 | "can use the \"Pause\" switch, it will not record your keys when paused.\n" 81 | "\n" 82 | "6. Set Timeout to 0 if you want to keep all keys.\n" 83 | "\n" 84 | "You can open this dialog again via menu icon on title bar -> \"Usage\"." 85 | msgstr "" 86 | "1. Por favor, ingrese la contraseña de administrador después de activar el " 87 | "interruptor. Los permisos elevados son necesarios para leer eventos de " 88 | "entrada, y Wayland no permite ejecutar programas gráficos con permisos " 89 | "elevados, por lo que se utiliza polkit para ejecutar un backend con " 90 | "permisos elevados. Este programa no maneja ni almacena su contraseña. Los " 91 | "usuarios en el grupo `wheel` pueden omitir la autenticación de contraseña.\n" 92 | "\n" 93 | "2. Después de activar el interruptor para mostrar la ventana flotante, debe " 94 | "arrastrarla manualmente a cualquier lugar que desee, porque Wayland no " 95 | "permite que la ventana establezca su posición. La etiqueta \"Clickable\" en " 96 | "la barra de título se puede arrastrar con el cursor.\n" 97 | "\n" 98 | "3. Debido a que Wayland no permite que una ventana establezca \"Siempre " 99 | "Encima\" y \"Siempre en el Espacio de Trabajo Visible\" por sí misma, debe " 100 | "configurarlo manualmente si está en una sesión de Wayland y su gestor de " 101 | "ventanas lo admite.\n" 102 | "Por ejemplo, si está utilizando GNOME Shell (Wayland), puede hacer clic " 103 | "derecho en \"Clickable\" en la barra de título para mostrar un menú del " 104 | "gestor de ventanas y marcar \"Siempre encima\" y \"Siempre en el Espacio de " 105 | "Trabajo Visible\" en él.\n" 106 | "Si está utilizando KDE Plasma (Wayland), puede hacer clic derecho en " 107 | "\"Floating Window - Show Me The Key\" en la barra de tareas, marcar \"Mover " 108 | "al Escritorio\" -> \"Todos los Escritorios\" y \"Más Acciones\" -> " 109 | "\"Mantener Encima\".\n" 110 | "Puede consultar README para ver si hay " 112 | "configuraciones para su compositor.\n" 113 | "\n" 114 | "4. Para permitir al usuario mover o cambiar el tamaño de la ventana de " 115 | "teclas, no es clickeable por defecto, después de moverlo a la ubicación " 116 | "deseada, apague el interruptor \"Clickeable\" para que no bloquee sus otras " 117 | "operaciones.\n" 118 | "\n" 119 | "5. Si desea pausarlo (por ejemplo, necesita ingresar una contraseña), puede " 120 | "utilizar el interruptor \"Pausa\", no registrará sus teclas cuando esté en " 121 | "pausa.\n" 122 | "\n" 123 | "6. Establezca el tiempo de espera en 0 si desea mantener todas las teclas.\n" 124 | "\n" 125 | "Puede abrir este diálogo nuevamente a través del icono de menú en la barra " 126 | "de título -> \"Uso\"." 127 | 128 | #: showmethekey-gtk/smtk-app-win.c:678 showmethekey-gtk/smtk-app-win.c:687 129 | msgid "Close" 130 | msgstr "Cerrar" 131 | 132 | #: showmethekey-gtk/smtk-app-win.c:733 133 | msgid "translator-credits" 134 | msgstr "ItzSelenux" 135 | 136 | #: showmethekey-gtk/smtk-app-win.c:734 137 | msgid "About Show Me The Key" 138 | msgstr "Acerca de Muéstrame la Tecla" 139 | 140 | #: showmethekey-gtk/smtk-app-win.c:736 141 | msgid "Show keys you typed on screen." 142 | msgstr "Muestra las teclas que escribiste en pantalla." 143 | 144 | #: showmethekey-gtk/smtk-app-win.ui:35 145 | msgid "Menu" 146 | msgstr "Menú" 147 | 148 | #: showmethekey-gtk/smtk-app-win.ui:38 149 | msgid "Primary menu" 150 | msgstr "Menú principal" 151 | 152 | #: showmethekey-gtk/smtk-app-win.ui:56 153 | msgid "General" 154 | msgstr "General" 155 | 156 | #: showmethekey-gtk/smtk-app-win.ui:60 157 | msgid "_Clickable" 158 | msgstr "_Clickeable" 159 | 160 | #: showmethekey-gtk/smtk-app-win.ui:61 161 | msgid "" 162 | "Turning off will pass input event to other apps under the floating window." 163 | msgstr "" 164 | "Desactivar permitirá que los eventos de entrada pasen a otras aplicaciones " 165 | "bajo la ventana flotante." 166 | 167 | #: showmethekey-gtk/smtk-app-win.ui:79 168 | msgid "_Pause" 169 | msgstr "_Pausa" 170 | 171 | #: showmethekey-gtk/smtk-app-win.ui:80 172 | #, fuzzy 173 | msgid "" 174 | "Temporary hide input like password. Press both alt anywhere to toggle this." 175 | msgstr "Ocultar temporalmente entradas como una contraseña." 176 | 177 | #: showmethekey-gtk/smtk-app-win.ui:99 178 | msgid "Show _Shift Separately" 179 | msgstr "Mostrar _Shift Separadamente" 180 | 181 | #: showmethekey-gtk/smtk-app-win.ui:100 182 | msgid "Show Ctrl+Shift+A or Ctrl+A." 183 | msgstr "Mostrar Ctrl+Shift+A o Ctrl+A." 184 | 185 | #: showmethekey-gtk/smtk-app-win.ui:118 186 | #, fuzzy 187 | msgid "Show _Keyboard Key" 188 | msgstr "Teclado" 189 | 190 | #: showmethekey-gtk/smtk-app-win.ui:119 191 | msgid "Maybe some users don't want keyboard keys." 192 | msgstr "" 193 | 194 | #: showmethekey-gtk/smtk-app-win.ui:137 195 | msgid "Show _Mouse Button" 196 | msgstr "Mostrar _Botón del Ratón" 197 | 198 | #: showmethekey-gtk/smtk-app-win.ui:138 199 | msgid "Mouse buttons can be very annoying." 200 | msgstr "Los botones del ratón pueden ser muy molestos." 201 | 202 | #: showmethekey-gtk/smtk-app-win.ui:156 203 | msgid "Draw Keys _Border" 204 | msgstr "Dibujar _Borde de Teclas" 205 | 206 | #: showmethekey-gtk/smtk-app-win.ui:157 207 | msgid "In case keys and key combos are hard to distinguish." 208 | msgstr "" 209 | "En caso de que las teclas y combinaciones de teclas sean difíciles de " 210 | "distinguir." 211 | 212 | #: showmethekey-gtk/smtk-app-win.ui:175 213 | msgid "Hide _Visible Keys" 214 | msgstr "" 215 | 216 | #: showmethekey-gtk/smtk-app-win.ui:176 217 | msgid "Only show keys with modifiers." 218 | msgstr "" 219 | 220 | #: showmethekey-gtk/smtk-app-win.ui:193 221 | msgid "Display Mode" 222 | msgstr "Modo de Pantalla" 223 | 224 | #: showmethekey-gtk/smtk-app-win.ui:194 225 | msgid "Raw is Linux kernel's scancode and maybe not so useful." 226 | msgstr "" 227 | "\"Bruto\" es el código de escaneo del núcleo de Linux y tal vez no sea tan " 228 | "útil." 229 | 230 | #: showmethekey-gtk/smtk-app-win.ui:200 231 | msgid "Composed" 232 | msgstr "Compuesto" 233 | 234 | #: showmethekey-gtk/smtk-app-win.ui:201 235 | msgid "Raw" 236 | msgstr "Bruto" 237 | 238 | #: showmethekey-gtk/smtk-app-win.ui:202 239 | msgid "Compact" 240 | msgstr "Compacto" 241 | 242 | #: showmethekey-gtk/smtk-app-win.ui:215 243 | msgid "Alignment Mode" 244 | msgstr "" 245 | 246 | #: showmethekey-gtk/smtk-app-win.ui:216 247 | msgid "Some users prefer to display keys in center." 248 | msgstr "" 249 | 250 | #: showmethekey-gtk/smtk-app-win.ui:222 251 | msgid "End" 252 | msgstr "" 253 | 254 | #: showmethekey-gtk/smtk-app-win.ui:223 255 | msgid "Center" 256 | msgstr "" 257 | 258 | #: showmethekey-gtk/smtk-app-win.ui:237 259 | msgid "Timeout (ms)" 260 | msgstr "Tiempo de Espera (ms)" 261 | 262 | #: showmethekey-gtk/smtk-app-win.ui:238 263 | msgid "" 264 | "Clear keys if no new key after how many miliscconds. Set to 0 to keep all " 265 | "keys." 266 | msgstr "" 267 | "Borrar teclas si no hay nuevas teclas después de cuántos milisegundos. " 268 | "Establecer en 0 para mantener todas las teclas." 269 | 270 | #: showmethekey-gtk/smtk-app-win.ui:258 271 | msgid "Width (px)" 272 | msgstr "Anchura (px)" 273 | 274 | #: showmethekey-gtk/smtk-app-win.ui:259 275 | msgid "Width of keys window." 276 | msgstr "Anchura de la ventana de teclas." 277 | 278 | #: showmethekey-gtk/smtk-app-win.ui:274 279 | msgid "Height (px)" 280 | msgstr "Altura (px)" 281 | 282 | #: showmethekey-gtk/smtk-app-win.ui:275 283 | msgid "Height of keys window." 284 | msgstr "Altura de la ventana de teclas." 285 | 286 | #: showmethekey-gtk/smtk-app-win.ui:291 287 | msgid "Keyboard" 288 | msgstr "Teclado" 289 | 290 | #: showmethekey-gtk/smtk-app-win.ui:295 291 | msgid "Keymap" 292 | msgstr "Distribución de Teclas" 293 | 294 | #: showmethekey-gtk/smtk-app-win.ui:296 295 | msgid "Choose your keyboard layout and variant." 296 | msgstr "Elija su distribución y variante del teclado." 297 | 298 | #: showmethekey-gtk/smtk-app-win-menu.ui:7 299 | msgid "_Usage" 300 | msgstr "_Uso" 301 | 302 | #: showmethekey-gtk/smtk-app-win-menu.ui:11 303 | msgid "_About Show Me The Key" 304 | msgstr "_Acerca de Muéstrame La Tecla" 305 | 306 | #: showmethekey-gtk/smtk-app-win-menu.ui:17 307 | msgid "_Quit" 308 | msgstr "_Salir" 309 | 310 | #: showmethekey-gtk/smtk-keys-win.c:353 311 | msgid "Clickable" 312 | msgstr "Clickeable" 313 | -------------------------------------------------------------------------------- /showmethekey-gtk/po/meson.build: -------------------------------------------------------------------------------- 1 | i18n = import('i18n') 2 | # gettext does not support custom find path??? 3 | # It read POTFILES and LIGUAS from the path of meson.build, 4 | # but files in POTFILES are relative to source root, why??? 5 | i18n.gettext(meson.project_name(), preset: 'glib') 6 | -------------------------------------------------------------------------------- /showmethekey-gtk/po/showmethekey.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the showmethekey package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: showmethekey\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2025-01-10 22:15+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: showmethekey-gtk/smtk-app.c:110 showmethekey-gtk/smtk-app-win.c:735 21 | #: showmethekey-gtk/smtk-app-win.ui:9 showmethekey-gtk/smtk-app-win.ui:17 22 | msgid "Show Me The Key" 23 | msgstr "" 24 | 25 | #: showmethekey-gtk/smtk-app.c:115 26 | msgid "Display version then exit." 27 | msgstr "" 28 | 29 | #: showmethekey-gtk/smtk-app.c:118 30 | msgid "Show keys window on start up (deprecated by `-k, --show-keys-win`)." 31 | msgstr "" 32 | 33 | #: showmethekey-gtk/smtk-app.c:121 34 | msgid "Show keys window on start up." 35 | msgstr "" 36 | 37 | #: showmethekey-gtk/smtk-app.c:123 38 | msgid "Hide app window and show keys window on start up." 39 | msgstr "" 40 | 41 | #: showmethekey-gtk/smtk-app.c:126 42 | msgid "Make keys window unclickable on start up." 43 | msgstr "" 44 | 45 | #: showmethekey-gtk/smtk-app-win.c:632 showmethekey-gtk/smtk-app-win.c:635 46 | msgid "Usage" 47 | msgstr "" 48 | 49 | #: showmethekey-gtk/smtk-app-win.c:637 50 | msgid "" 51 | "1. Please input admin password after toggling the switch, because it needs " 52 | "superuser permission to read input events, and Wayland does not allow " 53 | "running graphics program with superuser permission, so it uses polkit to run " 54 | "a backend with superuser permission. This program does not handle or store " 55 | "your password. Users in `wheel` group can skip password authentication.\n" 56 | "\n" 57 | "2. After you toggle the switch to show the floating window, you need to drag " 58 | "it manually to anywhere you want, because Wayland does not allow window to " 59 | "set its position. The \"Clickable\" label on titlebar can be dragged as a " 60 | "handle.\n" 61 | "\n" 62 | "3. Because Wayland does not allow a window to set \"Always on Top\" and " 63 | "\"Always on Visible Workspace\" by itself, you should set it manually if you " 64 | "are in a Wayland session and your window manager support it.\n" 65 | "For example if you are using GNOME Shell (Wayland), you can right click the " 66 | "\"Clickable\" on title bar to show a window manager menu and check \"Always " 67 | "on Top\" and \"Always on Visible Workspace\" in it.\n" 68 | "If you are using KDE Plasma (Wayland), you can right click \"Floating Window " 69 | "- Show Me The Key\" on task bar, check \"Move to Desktop\" -> \"All " 70 | "Desktops\" and \"More Actions\" -> \"Keep Above Others\".\n" 71 | "You can check this project's README to see if " 73 | "there are configurations for your compositor.\n" 74 | "\n" 75 | "4. To allow user move or resize the keys window, it is not click through by " 76 | "default, after moving it to the location you want, turn off \"Clickable\" " 77 | "switch so it won't block your other operations.\n" 78 | "\n" 79 | "5. If you want to pause it (for example you need to insert password), you " 80 | "can use the \"Pause\" switch, it will not record your keys when paused.\n" 81 | "\n" 82 | "6. Set Timeout to 0 if you want to keep all keys.\n" 83 | "\n" 84 | "You can open this dialog again via menu icon on title bar -> \"Usage\"." 85 | msgstr "" 86 | 87 | #: showmethekey-gtk/smtk-app-win.c:678 showmethekey-gtk/smtk-app-win.c:687 88 | msgid "Close" 89 | msgstr "" 90 | 91 | #: showmethekey-gtk/smtk-app-win.c:733 92 | msgid "translator-credits" 93 | msgstr "" 94 | 95 | #: showmethekey-gtk/smtk-app-win.c:734 96 | msgid "About Show Me The Key" 97 | msgstr "" 98 | 99 | #: showmethekey-gtk/smtk-app-win.c:736 100 | msgid "Show keys you typed on screen." 101 | msgstr "" 102 | 103 | #: showmethekey-gtk/smtk-app-win.ui:35 104 | msgid "Menu" 105 | msgstr "" 106 | 107 | #: showmethekey-gtk/smtk-app-win.ui:38 108 | msgid "Primary menu" 109 | msgstr "" 110 | 111 | #: showmethekey-gtk/smtk-app-win.ui:56 112 | msgid "General" 113 | msgstr "" 114 | 115 | #: showmethekey-gtk/smtk-app-win.ui:60 116 | msgid "_Clickable" 117 | msgstr "" 118 | 119 | #: showmethekey-gtk/smtk-app-win.ui:61 120 | msgid "" 121 | "Turning off will pass input event to other apps under the floating window." 122 | msgstr "" 123 | 124 | #: showmethekey-gtk/smtk-app-win.ui:79 125 | msgid "_Pause" 126 | msgstr "" 127 | 128 | #: showmethekey-gtk/smtk-app-win.ui:80 129 | msgid "" 130 | "Temporary hide input like password. Press both alt anywhere to toggle this." 131 | msgstr "" 132 | 133 | #: showmethekey-gtk/smtk-app-win.ui:99 134 | msgid "Show _Shift Separately" 135 | msgstr "" 136 | 137 | #: showmethekey-gtk/smtk-app-win.ui:100 138 | msgid "Show Ctrl+Shift+A or Ctrl+A." 139 | msgstr "" 140 | 141 | #: showmethekey-gtk/smtk-app-win.ui:118 142 | msgid "Show _Keyboard Key" 143 | msgstr "" 144 | 145 | #: showmethekey-gtk/smtk-app-win.ui:119 146 | msgid "Maybe some users don't want keyboard keys." 147 | msgstr "" 148 | 149 | #: showmethekey-gtk/smtk-app-win.ui:137 150 | msgid "Show _Mouse Button" 151 | msgstr "" 152 | 153 | #: showmethekey-gtk/smtk-app-win.ui:138 154 | msgid "Mouse buttons can be very annoying." 155 | msgstr "" 156 | 157 | #: showmethekey-gtk/smtk-app-win.ui:156 158 | msgid "Draw Keys _Border" 159 | msgstr "" 160 | 161 | #: showmethekey-gtk/smtk-app-win.ui:157 162 | msgid "In case keys and key combos are hard to distinguish." 163 | msgstr "" 164 | 165 | #: showmethekey-gtk/smtk-app-win.ui:175 166 | msgid "Hide _Visible Keys" 167 | msgstr "" 168 | 169 | #: showmethekey-gtk/smtk-app-win.ui:176 170 | msgid "Only show keys with modifiers." 171 | msgstr "" 172 | 173 | #: showmethekey-gtk/smtk-app-win.ui:193 174 | msgid "Display Mode" 175 | msgstr "" 176 | 177 | #: showmethekey-gtk/smtk-app-win.ui:194 178 | msgid "Raw is Linux kernel's scancode and maybe not so useful." 179 | msgstr "" 180 | 181 | #: showmethekey-gtk/smtk-app-win.ui:200 182 | msgid "Composed" 183 | msgstr "" 184 | 185 | #: showmethekey-gtk/smtk-app-win.ui:201 186 | msgid "Raw" 187 | msgstr "" 188 | 189 | #: showmethekey-gtk/smtk-app-win.ui:202 190 | msgid "Compact" 191 | msgstr "" 192 | 193 | #: showmethekey-gtk/smtk-app-win.ui:215 194 | msgid "Alignment Mode" 195 | msgstr "" 196 | 197 | #: showmethekey-gtk/smtk-app-win.ui:216 198 | msgid "Some users prefer to display keys in center." 199 | msgstr "" 200 | 201 | #: showmethekey-gtk/smtk-app-win.ui:222 202 | msgid "End" 203 | msgstr "" 204 | 205 | #: showmethekey-gtk/smtk-app-win.ui:223 206 | msgid "Center" 207 | msgstr "" 208 | 209 | #: showmethekey-gtk/smtk-app-win.ui:237 210 | msgid "Timeout (ms)" 211 | msgstr "" 212 | 213 | #: showmethekey-gtk/smtk-app-win.ui:238 214 | msgid "" 215 | "Clear keys if no new key after how many miliscconds. Set to 0 to keep all " 216 | "keys." 217 | msgstr "" 218 | 219 | #: showmethekey-gtk/smtk-app-win.ui:258 220 | msgid "Width (px)" 221 | msgstr "" 222 | 223 | #: showmethekey-gtk/smtk-app-win.ui:259 224 | msgid "Width of keys window." 225 | msgstr "" 226 | 227 | #: showmethekey-gtk/smtk-app-win.ui:274 228 | msgid "Height (px)" 229 | msgstr "" 230 | 231 | #: showmethekey-gtk/smtk-app-win.ui:275 232 | msgid "Height of keys window." 233 | msgstr "" 234 | 235 | #: showmethekey-gtk/smtk-app-win.ui:291 236 | msgid "Keyboard" 237 | msgstr "" 238 | 239 | #: showmethekey-gtk/smtk-app-win.ui:295 240 | msgid "Keymap" 241 | msgstr "" 242 | 243 | #: showmethekey-gtk/smtk-app-win.ui:296 244 | msgid "Choose your keyboard layout and variant." 245 | msgstr "" 246 | 247 | #: showmethekey-gtk/smtk-app-win-menu.ui:7 248 | msgid "_Usage" 249 | msgstr "" 250 | 251 | #: showmethekey-gtk/smtk-app-win-menu.ui:11 252 | msgid "_About Show Me The Key" 253 | msgstr "" 254 | 255 | #: showmethekey-gtk/smtk-app-win-menu.ui:17 256 | msgid "_Quit" 257 | msgstr "" 258 | 259 | #: showmethekey-gtk/smtk-keys-win.c:353 260 | msgid "Clickable" 261 | msgstr "" 262 | -------------------------------------------------------------------------------- /showmethekey-gtk/po/zh_CN.po: -------------------------------------------------------------------------------- 1 | # Chinese translations for showmethekey package. 2 | # Copyright (C) 2021 THE showmethekey'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the showmethekey package. 4 | # Automatically generated, 2021. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: showmethekey\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2025-01-10 22:15+0800\n" 11 | "PO-Revision-Date: 2021-04-21 15:16+0800\n" 12 | "Last-Translator: Automatically generated\n" 13 | "Language-Team: none\n" 14 | "Language: zh_CN\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: showmethekey-gtk/smtk-app.c:110 showmethekey-gtk/smtk-app-win.c:735 20 | #: showmethekey-gtk/smtk-app-win.ui:9 showmethekey-gtk/smtk-app-win.ui:17 21 | msgid "Show Me The Key" 22 | msgstr "让我看键" 23 | 24 | #: showmethekey-gtk/smtk-app.c:115 25 | msgid "Display version then exit." 26 | msgstr "显示版本号然后退出。" 27 | 28 | #: showmethekey-gtk/smtk-app.c:118 29 | msgid "Show keys window on start up (deprecated by `-k, --show-keys-win`)." 30 | msgstr "在启动时显示按键窗口(已废弃,请使用 `-k, --show-keys-win`)。" 31 | 32 | #: showmethekey-gtk/smtk-app.c:121 33 | msgid "Show keys window on start up." 34 | msgstr "在启动时显示按键窗口。" 35 | 36 | #: showmethekey-gtk/smtk-app.c:123 37 | msgid "Hide app window and show keys window on start up." 38 | msgstr "在启动时隐藏配置窗口并显示按键窗口。" 39 | 40 | #: showmethekey-gtk/smtk-app.c:126 41 | msgid "Make keys window unclickable on start up." 42 | msgstr "在启动时令按键窗口不接收输入事件。" 43 | 44 | #: showmethekey-gtk/smtk-app-win.c:632 showmethekey-gtk/smtk-app-win.c:635 45 | msgid "Usage" 46 | msgstr "使用说明(_U)" 47 | 48 | #: showmethekey-gtk/smtk-app-win.c:637 49 | msgid "" 50 | "1. Please input admin password after toggling the switch, because it needs " 51 | "superuser permission to read input events, and Wayland does not allow " 52 | "running graphics program with superuser permission, so it uses polkit to run " 53 | "a backend with superuser permission. This program does not handle or store " 54 | "your password. Users in `wheel` group can skip password authentication.\n" 55 | "\n" 56 | "2. After you toggle the switch to show the floating window, you need to drag " 57 | "it manually to anywhere you want, because Wayland does not allow window to " 58 | "set its position. The \"Clickable\" label on titlebar can be dragged as a " 59 | "handle.\n" 60 | "\n" 61 | "3. Because Wayland does not allow a window to set \"Always on Top\" and " 62 | "\"Always on Visible Workspace\" by itself, you should set it manually if you " 63 | "are in a Wayland session and your window manager support it.\n" 64 | "For example if you are using GNOME Shell (Wayland), you can right click the " 65 | "\"Clickable\" on title bar to show a window manager menu and check \"Always " 66 | "on Top\" and \"Always on Visible Workspace\" in it.\n" 67 | "If you are using KDE Plasma (Wayland), you can right click \"Floating Window " 68 | "- Show Me The Key\" on task bar, check \"Move to Desktop\" -> \"All " 69 | "Desktops\" and \"More Actions\" -> \"Keep Above Others\".\n" 70 | "You can check this project's README to see if " 72 | "there are configurations for your compositor.\n" 73 | "\n" 74 | "4. To allow user move or resize the keys window, it is not click through by " 75 | "default, after moving it to the location you want, turn off \"Clickable\" " 76 | "switch so it won't block your other operations.\n" 77 | "\n" 78 | "5. If you want to pause it (for example you need to insert password), you " 79 | "can use the \"Pause\" switch, it will not record your keys when paused.\n" 80 | "\n" 81 | "6. Set Timeout to 0 if you want to keep all keys.\n" 82 | "\n" 83 | "You can open this dialog again via menu icon on title bar -> \"Usage\"." 84 | msgstr "" 85 | "1. 请在打开开关之后输入管理员密码,因为读取输入事件需要超级用户权限,并且 " 86 | "Wayland 不允许图形程序以超级用户权限运行,所以这个程序使用 polkit 来运行一个" 87 | "具有超级用户权限的后端。这个程序并不处理或储存您的密码。wheel 组的用户可以跳" 88 | "过密码验证。\n" 89 | "\n" 90 | "2. 在打开开关显示悬浮窗口之后,您需要手动拖拽它到您想要的位置,因为 Wayland " 91 | "不允许窗口自行设定位置。标题栏上的“可点击”标签可以作为把手来拖拽。\n" 92 | "\n" 93 | "3. 因为 Wayland 不允许窗口自行设置“置顶”和“总在可见工作区”,所以如果您在使用 " 94 | "Wayland 会话,您需要手动设置这两种功能,如果您的窗口管理器支持的话。\n" 95 | "例如如果您在使用 GNOME Shell (Wayland),您可以右键点击标题栏上的“可点击区" 96 | "域”来显示窗口管理器菜单并勾选“置顶”和“总在可见工作区”。\n" 97 | "如果您在使用 KDE Plasma (Wayland),您可以右键点击任务栏上的“Floating Window " 98 | "- Show Me The Key”,勾选“移动到桌面”->“全部桌面”和“更多动作”->“常居顶端”。\n" 99 | "您可以访问项目的 README 文件来查看是否有针对您的混成器" 101 | "的配置方法。\n" 102 | "\n" 103 | "4. 为了允许用户移动窗口或者调整窗口大小,窗口默认是接收输入事件的,当把窗口移" 104 | "动到您需要的位置后,可以关闭“接收输入事件(_C)”开关,这样它就不会影响您的其" 105 | "它操作。\n" 106 | "\n" 107 | "5. 如果您想要暂停该程序(比如你需要输入密码),您可以使用“暂停”开关,在暂停状" 108 | "态下它不会记录您的按键。\n" 109 | "\n" 110 | "6. 如果想保留所有的按键记录,请将超时设为 0。\n" 111 | "\n" 112 | "您可以通过标题栏上的菜单按钮 ->“使用说明”来再次访问此对话框。" 113 | 114 | #: showmethekey-gtk/smtk-app-win.c:678 showmethekey-gtk/smtk-app-win.c:687 115 | msgid "Close" 116 | msgstr "关闭" 117 | 118 | #: showmethekey-gtk/smtk-app-win.c:733 119 | msgid "translator-credits" 120 | msgstr "Alynx Zhou" 121 | 122 | #: showmethekey-gtk/smtk-app-win.c:734 123 | msgid "About Show Me The Key" 124 | msgstr "关于让我看键" 125 | 126 | #: showmethekey-gtk/smtk-app-win.c:736 127 | msgid "Show keys you typed on screen." 128 | msgstr "在屏幕上显示你按的键。" 129 | 130 | #: showmethekey-gtk/smtk-app-win.ui:35 131 | msgid "Menu" 132 | msgstr "菜单" 133 | 134 | #: showmethekey-gtk/smtk-app-win.ui:38 135 | msgid "Primary menu" 136 | msgstr "主菜单" 137 | 138 | #: showmethekey-gtk/smtk-app-win.ui:56 139 | msgid "General" 140 | msgstr "常规" 141 | 142 | #: showmethekey-gtk/smtk-app-win.ui:60 143 | msgid "_Clickable" 144 | msgstr "接收输入事件(_C)" 145 | 146 | #: showmethekey-gtk/smtk-app-win.ui:61 147 | msgid "" 148 | "Turning off will pass input event to other apps under the floating window." 149 | msgstr "关闭该项后,输入事件将会被传递给悬浮窗口下面的其它应用。" 150 | 151 | #: showmethekey-gtk/smtk-app-win.ui:79 152 | msgid "_Pause" 153 | msgstr "暂停(_P)" 154 | 155 | #: showmethekey-gtk/smtk-app-win.ui:80 156 | msgid "" 157 | "Temporary hide input like password. Press both alt anywhere to toggle this." 158 | msgstr "临时隐藏输入,比如密码。可以随时通过同时按下两个 alt 键切换该选项。" 159 | 160 | #: showmethekey-gtk/smtk-app-win.ui:99 161 | msgid "Show _Shift Separately" 162 | msgstr "单独显示 Shift(_S)" 163 | 164 | #: showmethekey-gtk/smtk-app-win.ui:100 165 | msgid "Show Ctrl+Shift+A or Ctrl+A." 166 | msgstr "显示 Ctrl+Shift+A 还是 Ctrl+A。" 167 | 168 | #: showmethekey-gtk/smtk-app-win.ui:118 169 | msgid "Show _Keyboard Key" 170 | msgstr "显示键盘按键(_K)" 171 | 172 | #: showmethekey-gtk/smtk-app-win.ui:119 173 | msgid "Maybe some users don't want keyboard keys." 174 | msgstr "也许一些用户不想看见键盘按键。" 175 | 176 | #: showmethekey-gtk/smtk-app-win.ui:137 177 | msgid "Show _Mouse Button" 178 | msgstr "显示鼠标按钮(_M)" 179 | 180 | #: showmethekey-gtk/smtk-app-win.ui:138 181 | msgid "Mouse buttons can be very annoying." 182 | msgstr "鼠标按钮有时候会太占地方。" 183 | 184 | #: showmethekey-gtk/smtk-app-win.ui:156 185 | msgid "Draw Keys _Border" 186 | msgstr "绘制按键边框(_B)" 187 | 188 | #: showmethekey-gtk/smtk-app-win.ui:157 189 | msgid "In case keys and key combos are hard to distinguish." 190 | msgstr "以防按键和组合键难以区分。" 191 | 192 | #: showmethekey-gtk/smtk-app-win.ui:175 193 | msgid "Hide _Visible Keys" 194 | msgstr "隐藏可见按键(_V)" 195 | 196 | #: showmethekey-gtk/smtk-app-win.ui:176 197 | msgid "Only show keys with modifiers." 198 | msgstr "只显示包含修饰键的组合键。" 199 | 200 | #: showmethekey-gtk/smtk-app-win.ui:193 201 | msgid "Display Mode" 202 | msgstr "显示模式" 203 | 204 | #: showmethekey-gtk/smtk-app-win.ui:194 205 | msgid "Raw is Linux kernel's scancode and maybe not so useful." 206 | msgstr "按键名是 Linux 内核的扫描码,可能不会特别有用。" 207 | 208 | #: showmethekey-gtk/smtk-app-win.ui:200 209 | msgid "Composed" 210 | msgstr "组合键" 211 | 212 | #: showmethekey-gtk/smtk-app-win.ui:201 213 | msgid "Raw" 214 | msgstr "按键名" 215 | 216 | #: showmethekey-gtk/smtk-app-win.ui:202 217 | msgid "Compact" 218 | msgstr "紧凑" 219 | 220 | #: showmethekey-gtk/smtk-app-win.ui:215 221 | msgid "Alignment Mode" 222 | msgstr "对齐方向" 223 | 224 | #: showmethekey-gtk/smtk-app-win.ui:216 225 | msgid "Some users prefer to display keys in center." 226 | msgstr "有些用户希望居中显示按键。" 227 | 228 | #: showmethekey-gtk/smtk-app-win.ui:222 229 | msgid "End" 230 | msgstr "末尾" 231 | 232 | #: showmethekey-gtk/smtk-app-win.ui:223 233 | msgid "Center" 234 | msgstr "居中" 235 | 236 | #: showmethekey-gtk/smtk-app-win.ui:237 237 | msgid "Timeout (ms)" 238 | msgstr "超时(毫秒)" 239 | 240 | #: showmethekey-gtk/smtk-app-win.ui:238 241 | msgid "" 242 | "Clear keys if no new key after how many miliscconds. Set to 0 to keep all " 243 | "keys." 244 | msgstr "多少毫秒没有新按键之后清除已有的按键。设置为 0 保留所有按键。" 245 | 246 | #: showmethekey-gtk/smtk-app-win.ui:258 247 | msgid "Width (px)" 248 | msgstr "宽度(像素)" 249 | 250 | #: showmethekey-gtk/smtk-app-win.ui:259 251 | msgid "Width of keys window." 252 | msgstr "按键窗口的宽度。" 253 | 254 | #: showmethekey-gtk/smtk-app-win.ui:274 255 | msgid "Height (px)" 256 | msgstr "高度(像素)" 257 | 258 | #: showmethekey-gtk/smtk-app-win.ui:275 259 | msgid "Height of keys window." 260 | msgstr "按键窗口的高度。" 261 | 262 | #: showmethekey-gtk/smtk-app-win.ui:291 263 | msgid "Keyboard" 264 | msgstr "键盘" 265 | 266 | #: showmethekey-gtk/smtk-app-win.ui:295 267 | msgid "Keymap" 268 | msgstr "键位" 269 | 270 | #: showmethekey-gtk/smtk-app-win.ui:296 271 | msgid "Choose your keyboard layout and variant." 272 | msgstr "选择您的键盘布局和变体。" 273 | 274 | #: showmethekey-gtk/smtk-app-win-menu.ui:7 275 | msgid "_Usage" 276 | msgstr "使用说明(_U)" 277 | 278 | #: showmethekey-gtk/smtk-app-win-menu.ui:11 279 | msgid "_About Show Me The Key" 280 | msgstr "关于让我看键(_A)" 281 | 282 | #: showmethekey-gtk/smtk-app-win-menu.ui:17 283 | msgid "_Quit" 284 | msgstr "退出(_Q)" 285 | 286 | #: showmethekey-gtk/smtk-keys-win.c:353 287 | msgid "Clickable" 288 | msgstr "可点击" 289 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-app-win-menu.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | _Usage 8 | app.usage 9 | 10 | 11 | _About Show Me The Key 12 | app.about 13 | 14 |
15 |
16 | 17 | _Quit 18 | app.quit 19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-app-win.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_APP_WIN_H__ 2 | #define __SMTK_APP_WIN_H__ 3 | 4 | #include 5 | #include 6 | 7 | #include "smtk-app.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define SMTK_TYPE_APP_WIN smtk_app_win_get_type() 12 | G_DECLARE_FINAL_TYPE(SmtkAppWin, smtk_app_win, SMTK, APP_WIN, 13 | AdwApplicationWindow) 14 | 15 | GtkWidget *smtk_app_win_new(SmtkApp *app); 16 | void smtk_app_win_activate(SmtkAppWin *win); 17 | void smtk_app_win_toggle_clickable_switch(SmtkAppWin *win); 18 | void smtk_app_win_toggle_pause_switch(SmtkAppWin *win); 19 | void smtk_app_win_toggle_shift_switch(SmtkAppWin *win); 20 | void smtk_app_win_toggle_mouse_switch(SmtkAppWin *win); 21 | void smtk_app_win_toggle_border_switch(SmtkAppWin *win); 22 | void smtk_app_win_toggle_hide_visible_switch(SmtkAppWin *win); 23 | void smtk_app_win_set_size(SmtkAppWin *win, int width, int height); 24 | void smtk_app_win_show_usage_dialog(SmtkAppWin *win); 25 | void smtk_app_win_show_about_dialog(SmtkAppWin *win); 26 | 27 | G_END_DECLS 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-app-win.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | open-menu-symbolic 6 | 7 | 315 | 316 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-app.c: -------------------------------------------------------------------------------- 1 | #include "glib-object.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "smtk.h" 7 | #include "smtk-app.h" 8 | #include "smtk-app-win.h" 9 | 10 | struct _SmtkApp { 11 | AdwApplication parent_instance; 12 | GtkWidget *win; 13 | bool keys_win_opt; 14 | bool app_win_opt; 15 | bool clickable_opt; 16 | }; 17 | G_DEFINE_TYPE(SmtkApp, smtk_app, ADW_TYPE_APPLICATION) 18 | 19 | static void clickable_action(GSimpleAction *action, GVariant *parameter, 20 | gpointer user_data) 21 | { 22 | SmtkApp *app = SMTK_APP(user_data); 23 | 24 | if (app->win != NULL) 25 | smtk_app_win_toggle_clickable_switch(SMTK_APP_WIN(app->win)); 26 | } 27 | 28 | static void pause_action(GSimpleAction *action, GVariant *parameter, 29 | gpointer user_data) 30 | { 31 | SmtkApp *app = SMTK_APP(user_data); 32 | 33 | if (app->win != NULL) 34 | smtk_app_win_toggle_pause_switch(SMTK_APP_WIN(app->win)); 35 | } 36 | 37 | static void shift_action(GSimpleAction *action, GVariant *parameter, 38 | gpointer user_data) 39 | { 40 | SmtkApp *app = SMTK_APP(user_data); 41 | 42 | if (app->win != NULL) 43 | smtk_app_win_toggle_shift_switch(SMTK_APP_WIN(app->win)); 44 | } 45 | 46 | static void mouse_action(GSimpleAction *action, GVariant *parameter, 47 | gpointer user_data) 48 | { 49 | SmtkApp *app = SMTK_APP(user_data); 50 | 51 | if (app->win != NULL) 52 | smtk_app_win_toggle_mouse_switch(SMTK_APP_WIN(app->win)); 53 | } 54 | 55 | static void border_action(GSimpleAction *action, GVariant *parameter, 56 | gpointer user_data) 57 | { 58 | SmtkApp *app = SMTK_APP(user_data); 59 | 60 | if (app->win != NULL) 61 | smtk_app_win_toggle_border_switch(SMTK_APP_WIN(app->win)); 62 | } 63 | 64 | static void hide_visible_action(GSimpleAction *action, GVariant *parameter, 65 | gpointer user_data) 66 | { 67 | SmtkApp *app = SMTK_APP(user_data); 68 | 69 | if (app->win != NULL) 70 | smtk_app_win_toggle_hide_visible_switch(SMTK_APP_WIN(app->win)); 71 | } 72 | 73 | static void usage_action(GSimpleAction *action, GVariant *parameter, 74 | gpointer user_data) 75 | { 76 | SmtkApp *app = SMTK_APP(user_data); 77 | 78 | if (app->win != NULL) 79 | smtk_app_win_show_usage_dialog(SMTK_APP_WIN(app->win)); 80 | } 81 | 82 | static void about_action(GSimpleAction *action, GVariant *parameter, 83 | gpointer user_data) 84 | { 85 | SmtkApp *app = SMTK_APP(user_data); 86 | 87 | if (app->win != NULL) 88 | smtk_app_win_show_about_dialog(SMTK_APP_WIN(app->win)); 89 | } 90 | 91 | static void quit_action(GSimpleAction *action, GVariant *parameter, 92 | gpointer user_data) 93 | { 94 | SmtkApp *app = SMTK_APP(user_data); 95 | 96 | smtk_app_quit(app); 97 | } 98 | 99 | static void smtk_app_init(SmtkApp *app) 100 | { 101 | app->win = NULL; 102 | app->keys_win_opt = false; 103 | app->app_win_opt = true; 104 | app->clickable_opt = true; 105 | 106 | g_set_application_name(_("Show Me The Key")); 107 | gtk_window_set_default_icon_name("one.alynx.showmethekey"); 108 | 109 | const GOptionEntry options[] = { 110 | { "version", 'v', 0, G_OPTION_ARG_NONE, NULL, 111 | N_("Display version then exit."), NULL }, 112 | // Keep this option because I don't want to break CLI. 113 | { "active", 'a', 0, G_OPTION_ARG_NONE, NULL, 114 | N_("Show keys window on start up (deprecated by `-k, --show-keys-win`)."), 115 | NULL }, 116 | { "keys-win", 'k', 0, G_OPTION_ARG_NONE, NULL, 117 | N_("Show keys window on start up."), NULL }, 118 | { "no-app-win", 'A', 0, G_OPTION_ARG_NONE, NULL, 119 | N_("Hide app window and show keys window on start up."), 120 | NULL }, 121 | { "no-clickable", 'C', 0, G_OPTION_ARG_NONE, NULL, 122 | N_("Make keys window unclickable on start up."), NULL }, 123 | { NULL, 0, 0, 0, NULL, NULL, NULL } 124 | }; 125 | 126 | g_application_add_main_option_entries(G_APPLICATION(app), options); 127 | } 128 | 129 | static void smtk_app_activate(GApplication *g_app) 130 | { 131 | // Application is already single instance. 132 | // We use this to prevent mutliply windows. 133 | SmtkApp *app = SMTK_APP(g_app); 134 | 135 | if (app->win == NULL) { 136 | app->win = smtk_app_win_new(SMTK_APP(app)); 137 | g_debug("Keys win: %s.", app->keys_win_opt ? "true" : "false"); 138 | g_debug("App win: %s.", app->app_win_opt ? "true" : "false"); 139 | g_debug("Clickable: %s.", 140 | app->clickable_opt ? "true" : "false"); 141 | // By default keys win is clickable. 142 | if (!app->clickable_opt) 143 | smtk_app_win_toggle_clickable_switch( 144 | SMTK_APP_WIN(app->win)); 145 | // Quit if no visible window left. 146 | if (app->app_win_opt) 147 | gtk_window_present(GTK_WINDOW(app->win)); 148 | else 149 | g_signal_connect_swapped(app->win, "keys-win-destroy", 150 | G_CALLBACK(smtk_app_quit), 151 | app); 152 | if (app->keys_win_opt) 153 | smtk_app_win_activate(SMTK_APP_WIN(app->win)); 154 | } 155 | } 156 | 157 | static void smtk_app_startup(GApplication *g_app) 158 | { 159 | SmtkApp *app = SMTK_APP(g_app); 160 | 161 | // Because application is not a construct property of GtkWindow, 162 | // we have to setup accels here. 163 | GActionEntry actions[] = { 164 | { "clickable", clickable_action, NULL, NULL, NULL }, 165 | { "pause", pause_action, NULL, NULL, NULL }, 166 | { "shift", shift_action, NULL, NULL, NULL }, 167 | { "mouse", mouse_action, NULL, NULL, NULL }, 168 | { "border", border_action, NULL, NULL, NULL }, 169 | { "hide-visible", hide_visible_action, NULL, NULL, NULL }, 170 | { "usage", usage_action, NULL, NULL, NULL }, 171 | { "about", about_action, NULL, NULL, NULL }, 172 | { "quit", quit_action, NULL, NULL, NULL } 173 | }; 174 | g_action_map_add_action_entries(G_ACTION_MAP(app), actions, 175 | G_N_ELEMENTS(actions), app); 176 | const char *clickable_accels[] = { "C", NULL }; 177 | const char *pause_accels[] = { "P", NULL }; 178 | const char *shift_accels[] = { "S", NULL }; 179 | const char *mouse_accels[] = { "M", NULL }; 180 | const char *border_accels[] = { "B", NULL }; 181 | const char *hide_visible_accels[] = { "V", NULL }; 182 | const char *usage_accels[] = { "U", NULL }; 183 | const char *about_accels[] = { "A", NULL }; 184 | const char *quit_accels[] = { "Q", NULL }; 185 | // See Description of 186 | // 187 | // about "app." here. 188 | gtk_application_set_accels_for_action( 189 | GTK_APPLICATION(app), "app.clickable", clickable_accels); 190 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), "app.pause", 191 | pause_accels); 192 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), "app.shift", 193 | shift_accels); 194 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), "app.mouse", 195 | mouse_accels); 196 | gtk_application_set_accels_for_action( 197 | GTK_APPLICATION(app), "app.hide-visible", hide_visible_accels); 198 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), 199 | "app.border", border_accels); 200 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), "app.usage", 201 | usage_accels); 202 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), "app.about", 203 | about_accels); 204 | gtk_application_set_accels_for_action(GTK_APPLICATION(app), "app.quit", 205 | quit_accels); 206 | 207 | G_APPLICATION_CLASS(smtk_app_parent_class)->startup(g_app); 208 | } 209 | 210 | // See . 211 | static int smtk_app_handle_local_options(GApplication *application, 212 | GVariantDict *options) 213 | { 214 | SmtkApp *app = SMTK_APP(application); 215 | 216 | if (g_variant_dict_contains(options, "version")) { 217 | g_print(PROJECT_VERSION "\n"); 218 | return 0; 219 | } 220 | 221 | if (g_variant_dict_contains(options, "active")) { 222 | g_warning("`-a, --active` is deprecated by `-k, --keys-win`."); 223 | app->keys_win_opt = true; 224 | } 225 | if (g_variant_dict_contains(options, "keys-win")) 226 | app->keys_win_opt = true; 227 | if (g_variant_dict_contains(options, "no-app-win")) { 228 | app->app_win_opt = false; 229 | // No one wants to run it with no window, hiding app window 230 | // means they want keys window directly. 231 | app->keys_win_opt = true; 232 | } 233 | if (g_variant_dict_contains(options, "no-clickable")) 234 | app->clickable_opt = false; 235 | 236 | return -1; 237 | } 238 | 239 | static void smtk_app_class_init(SmtkAppClass *app_class) 240 | { 241 | GApplicationClass *g_app_class = G_APPLICATION_CLASS(app_class); 242 | 243 | g_app_class->activate = smtk_app_activate; 244 | g_app_class->startup = smtk_app_startup; 245 | 246 | // See . 247 | g_app_class->handle_local_options = smtk_app_handle_local_options; 248 | } 249 | 250 | SmtkApp *smtk_app_new(void) 251 | { 252 | return g_object_new(SMTK_TYPE_APP, "application-id", 253 | "one.alynx.showmethekey", NULL); 254 | } 255 | 256 | void smtk_app_quit(SmtkApp *app) 257 | { 258 | g_return_if_fail(app != NULL); 259 | 260 | if (app->win != NULL) { 261 | gtk_window_destroy(GTK_WINDOW(app->win)); 262 | app->win = NULL; 263 | } 264 | 265 | g_application_quit(G_APPLICATION(app)); 266 | } 267 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-app.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_APP_H__ 2 | #define __SMTK_APP_H__ 3 | 4 | #include 5 | #include 6 | 7 | G_BEGIN_DECLS 8 | 9 | #define SMTK_TYPE_APP smtk_app_get_type() 10 | G_DECLARE_FINAL_TYPE(SmtkApp, smtk_app, SMTK, APP, AdwApplication) 11 | 12 | SmtkApp *smtk_app_new(void); 13 | void smtk_app_quit(SmtkApp *app); 14 | 15 | G_END_DECLS 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-event.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "smtk.h" 4 | #include "smtk-event.h" 5 | 6 | struct _SmtkEvent { 7 | GObject parent_instance; 8 | char *source; 9 | SmtkEventType event_type; 10 | SmtkEventState event_state; 11 | char *key_name; 12 | unsigned int key_code; 13 | unsigned int time_stamp; 14 | GError *error; 15 | }; 16 | G_DEFINE_TYPE(SmtkEvent, smtk_event, G_TYPE_OBJECT) 17 | 18 | // Prevent clang-format from adding space between minus. 19 | // clang-format off 20 | G_DEFINE_QUARK(smtk-event-error-quark, smtk_event_error) 21 | // clang-format on 22 | 23 | enum { PROP_0, PROP_SOURCE, N_PROPS }; 24 | 25 | static GParamSpec *obj_props[N_PROPS] = { NULL }; 26 | 27 | static void smtk_event_set_property(GObject *object, unsigned int property_id, 28 | const GValue *value, GParamSpec *pspec) 29 | { 30 | SmtkEvent *event = SMTK_EVENT(object); 31 | 32 | switch (property_id) { 33 | case PROP_SOURCE: 34 | if (event->source != NULL) 35 | g_free(event->source); 36 | event->source = g_value_dup_string(value); 37 | break; 38 | default: 39 | /* We don't have any other property... */ 40 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 41 | break; 42 | } 43 | } 44 | 45 | static void smtk_event_get_property(GObject *object, unsigned int property_id, 46 | GValue *value, GParamSpec *pspec) 47 | { 48 | SmtkEvent *event = SMTK_EVENT(object); 49 | 50 | switch (property_id) { 51 | case PROP_SOURCE: 52 | g_value_set_string(value, event->source); 53 | break; 54 | default: 55 | /* We don't have any other property... */ 56 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 57 | break; 58 | } 59 | } 60 | 61 | static void smtk_event_init(SmtkEvent *event) 62 | { 63 | event->error = NULL; 64 | event->source = NULL; 65 | event->key_name = NULL; 66 | event->event_type = SMTK_EVENT_TYPE_KEYBOARD_KEY; 67 | event->event_state = SMTK_EVENT_STATE_RELEASED; 68 | } 69 | 70 | static void smtk_event_constructed(GObject *object) 71 | { 72 | SmtkEvent *event = SMTK_EVENT(object); 73 | 74 | if (event->source == NULL || strlen(event->source) == 0) { 75 | g_set_error(&event->error, SMTK_EVENT_ERROR, 76 | SMTK_EVENT_ERROR_SOURCE, 77 | "Failed to create event because of empty source."); 78 | return; 79 | } 80 | // See . 81 | // Transfer full, we should free it. 82 | JsonNode *json = json_from_string(event->source, &event->error); 83 | if (json == NULL) 84 | return; 85 | // See . 86 | // Transfer none, so we can't free this. 87 | JsonObject *json_object = json_node_get_object(json); 88 | // See . 89 | // Transfer none, don't free it. 90 | const char *event_name = 91 | json_object_get_string_member(json_object, "event_name"); 92 | if (g_strcmp0(event_name, "POINTER_BUTTON") == 0) 93 | event->event_type = SMTK_EVENT_TYPE_POINTER_BUTTON; 94 | else 95 | event->event_type = SMTK_EVENT_TYPE_KEYBOARD_KEY; 96 | const char *event_state_name = 97 | json_object_get_string_member(json_object, "state_name"); 98 | if (g_strcmp0(event_state_name, "PRESSED") == 0) 99 | event->event_state = SMTK_EVENT_STATE_PRESSED; 100 | else 101 | event->event_state = SMTK_EVENT_STATE_RELEASED; 102 | // See . 103 | // Transfer none, so we need g_strdup(). 104 | event->key_name = g_strdup( 105 | json_object_get_string_member(json_object, "key_name")); 106 | event->key_code = json_object_get_int_member(json_object, "key_code"); 107 | event->time_stamp = 108 | json_object_get_int_member(json_object, "time_stamp"); 109 | json_node_unref(json); 110 | } 111 | 112 | static void smtk_event_finalize(GObject *object) 113 | { 114 | SmtkEvent *event = SMTK_EVENT(object); 115 | if (event->source != NULL) { 116 | g_free(event->source); 117 | event->source = NULL; 118 | } 119 | if (event->key_name != NULL) { 120 | g_free(event->key_name); 121 | event->key_name = NULL; 122 | } 123 | } 124 | 125 | static void smtk_event_class_init(SmtkEventClass *event_class) 126 | { 127 | GObjectClass *object_class = G_OBJECT_CLASS(event_class); 128 | 129 | object_class->set_property = smtk_event_set_property; 130 | object_class->get_property = smtk_event_get_property; 131 | 132 | object_class->constructed = smtk_event_constructed; 133 | 134 | object_class->finalize = smtk_event_finalize; 135 | 136 | obj_props[PROP_SOURCE] = g_param_spec_string( 137 | "source", "Source", "Event Text Source", NULL, 138 | G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE); 139 | 140 | g_object_class_install_properties(object_class, N_PROPS, obj_props); 141 | } 142 | 143 | SmtkEvent *smtk_event_new(char *source, GError **error) 144 | { 145 | SmtkEvent *event = 146 | g_object_new(SMTK_TYPE_EVENT, "source", source, NULL); 147 | 148 | if (event->error != NULL) { 149 | g_propagate_error(error, event->error); 150 | g_object_unref(event); 151 | return NULL; 152 | } 153 | 154 | return event; 155 | } 156 | 157 | SmtkEventType smtk_event_get_event_type(SmtkEvent *event) 158 | { 159 | g_return_val_if_fail(event != NULL, SMTK_EVENT_TYPE_UNKNOWN); 160 | 161 | return event->event_type; 162 | } 163 | 164 | SmtkEventState smtk_event_get_event_state(SmtkEvent *event) 165 | { 166 | g_return_val_if_fail(event != NULL, SMTK_EVENT_STATE_UNKNOWN); 167 | 168 | return event->event_state; 169 | } 170 | 171 | const char *smtk_event_get_key_name(SmtkEvent *event) 172 | { 173 | g_return_val_if_fail(event != NULL, NULL); 174 | 175 | return event->key_name; 176 | } 177 | 178 | unsigned int smtk_event_get_key_code(SmtkEvent *event) 179 | { 180 | // 0 is KEY_RESERVED for evdev. 181 | g_return_val_if_fail(event != NULL, 0); 182 | 183 | return event->key_code; 184 | } 185 | 186 | unsigned int smtk_event_get_time_stamp(SmtkEvent *event) 187 | { 188 | g_return_val_if_fail(event != NULL, 0); 189 | 190 | return event->time_stamp; 191 | } 192 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-event.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_EVENT_H__ 2 | #define __SMTK_EVENT_H__ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | typedef enum { 9 | SMTK_EVENT_TYPE_UNKNOWN, 10 | SMTK_EVENT_TYPE_KEYBOARD_KEY, 11 | SMTK_EVENT_TYPE_POINTER_BUTTON 12 | } SmtkEventType; 13 | 14 | typedef enum { 15 | SMTK_EVENT_STATE_UNKNOWN, 16 | SMTK_EVENT_STATE_RELEASED, 17 | SMTK_EVENT_STATE_PRESSED 18 | } SmtkEventState; 19 | 20 | #define SMTK_EVENT_ERROR smtk_event_error_quark() 21 | typedef enum { 22 | SMTK_EVENT_ERROR_SOURCE, 23 | SMTK_EVENT_ERROR_XKB_UNKNOWN 24 | } SmtkEventError; 25 | 26 | #define SMTK_TYPE_EVENT smtk_event_get_type() 27 | G_DECLARE_FINAL_TYPE(SmtkEvent, smtk_event, SMTK, EVENT, GObject) 28 | 29 | SmtkEvent *smtk_event_new(char *source, GError **error); 30 | SmtkEventType smtk_event_get_event_type(SmtkEvent *event); 31 | SmtkEventState smtk_event_get_event_state(SmtkEvent *event); 32 | const char *smtk_event_get_key_name(SmtkEvent *event); 33 | unsigned int smtk_event_get_key_code(SmtkEvent *event); 34 | unsigned int smtk_event_get_time_stamp(SmtkEvent *event); 35 | 36 | G_END_DECLS 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keymap-list.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "smtk-keymap-list.h" 4 | 5 | bool keymap_is_default(const char *string) 6 | { 7 | return string == NULL || strlen(string) == 0 || 8 | g_strcmp0(string, "(null)") == 0 || 9 | g_strcmp0(string, "default") == 0; 10 | } 11 | 12 | struct _SmtkKeymapItem { 13 | GObject parent_instance; 14 | char *layout; 15 | char *variant; 16 | char *name; 17 | }; 18 | G_DEFINE_TYPE(SmtkKeymapItem, smtk_keymap_item, G_TYPE_OBJECT) 19 | 20 | enum { PROP_0, PROP_LAYOUT, PROP_VARIANT, PROP_NAME, N_PROPS }; 21 | 22 | static GParamSpec *obj_props[N_PROPS] = { NULL }; 23 | 24 | static void smtk_keymap_item_build_name(SmtkKeymapItem *keymap_item) 25 | { 26 | if (keymap_item->name != NULL) 27 | g_free(keymap_item->name); 28 | 29 | if (keymap_is_default(keymap_item->variant)) 30 | keymap_item->name = g_strdup(keymap_item->layout); 31 | else 32 | keymap_item->name = g_strdup_printf( 33 | "%s (%s)", keymap_item->layout, keymap_item->variant); 34 | } 35 | 36 | static void smtk_keymap_item_set_property(GObject *object, 37 | unsigned int property_id, 38 | const GValue *value, 39 | GParamSpec *pspec) 40 | { 41 | SmtkKeymapItem *keymap_item = SMTK_KEYMAP_ITEM(object); 42 | 43 | switch (property_id) { 44 | case PROP_LAYOUT: 45 | smtk_keymap_item_set_layout(keymap_item, 46 | g_value_get_string(value)); 47 | break; 48 | case PROP_VARIANT: 49 | smtk_keymap_item_set_variant(keymap_item, 50 | g_value_get_string(value)); 51 | break; 52 | default: 53 | /* We don't have any other property... */ 54 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 55 | break; 56 | } 57 | } 58 | 59 | static void smtk_keymap_item_get_property(GObject *object, 60 | unsigned int property_id, 61 | GValue *value, GParamSpec *pspec) 62 | { 63 | SmtkKeymapItem *keymap_item = SMTK_KEYMAP_ITEM(object); 64 | 65 | switch (property_id) { 66 | case PROP_LAYOUT: 67 | g_value_set_string(value, keymap_item->layout); 68 | break; 69 | case PROP_VARIANT: 70 | g_value_set_string(value, keymap_item->variant); 71 | break; 72 | case PROP_NAME: 73 | g_value_set_string(value, keymap_item->name); 74 | break; 75 | default: 76 | /* We don't have any other property... */ 77 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 78 | break; 79 | } 80 | } 81 | 82 | static void smtk_keymap_item_finalize(GObject *object) 83 | { 84 | SmtkKeymapItem *keymap_item = SMTK_KEYMAP_ITEM(object); 85 | 86 | g_clear_pointer(&keymap_item->layout, g_free); 87 | g_clear_pointer(&keymap_item->variant, g_free); 88 | g_clear_pointer(&keymap_item->name, g_free); 89 | } 90 | 91 | static void smtk_keymap_item_init(SmtkKeymapItem *keymap_item) 92 | { 93 | } 94 | 95 | static void smtk_keymap_item_class_init(SmtkKeymapItemClass *keymap_item_class) 96 | { 97 | GObjectClass *object_class = G_OBJECT_CLASS(keymap_item_class); 98 | 99 | object_class->set_property = smtk_keymap_item_set_property; 100 | object_class->get_property = smtk_keymap_item_get_property; 101 | 102 | object_class->finalize = smtk_keymap_item_finalize; 103 | 104 | obj_props[PROP_LAYOUT] = 105 | g_param_spec_string("layout", "Layout", "Keymap Layout", NULL, 106 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 107 | obj_props[PROP_VARIANT] = g_param_spec_string( 108 | "variant", "Variant", "Keymap Variant", NULL, 109 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 110 | obj_props[PROP_NAME] = g_param_spec_string( 111 | "name", "Name", "Keymap Display Name", NULL, G_PARAM_READABLE); 112 | 113 | g_object_class_install_properties(object_class, N_PROPS, obj_props); 114 | } 115 | 116 | SmtkKeymapItem *smtk_keymap_item_new(const char *layout, const char *variant) 117 | { 118 | return g_object_new(SMTK_TYPE_KEYMAP_ITEM, "layout", layout, "variant", 119 | variant, NULL); 120 | } 121 | 122 | const char *smtk_keymap_item_get_layout(SmtkKeymapItem *keymap_item) 123 | { 124 | g_return_val_if_fail(keymap_item != NULL, NULL); 125 | 126 | return keymap_item->layout; 127 | } 128 | 129 | void smtk_keymap_item_set_layout(SmtkKeymapItem *keymap_item, 130 | const char *layout) 131 | { 132 | g_return_if_fail(keymap_item != NULL); 133 | 134 | if (keymap_item->layout != NULL) 135 | g_free(keymap_item->layout); 136 | keymap_item->layout = g_strdup(layout); 137 | 138 | smtk_keymap_item_build_name(keymap_item); 139 | } 140 | 141 | const char *smtk_keymap_item_get_variant(SmtkKeymapItem *keymap_item) 142 | { 143 | g_return_val_if_fail(keymap_item != NULL, NULL); 144 | 145 | return keymap_item->variant; 146 | } 147 | 148 | void smtk_keymap_item_set_variant(SmtkKeymapItem *keymap_item, 149 | const char *variant) 150 | { 151 | g_return_if_fail(keymap_item != NULL); 152 | 153 | if (keymap_item->variant != NULL) 154 | g_free(keymap_item->variant); 155 | keymap_item->variant = g_strdup(variant); 156 | 157 | smtk_keymap_item_build_name(keymap_item); 158 | } 159 | 160 | const char *smtk_keymap_item_get_name(SmtkKeymapItem *keymap_item) 161 | { 162 | g_return_val_if_fail(keymap_item != NULL, NULL); 163 | 164 | return keymap_item->name; 165 | } 166 | 167 | struct _SmtkKeymapList { 168 | GObject parent_instance; 169 | GPtrArray *items; 170 | }; 171 | 172 | static GType smtk_keymap_list_get_item_type(GListModel *list) 173 | { 174 | return G_TYPE_OBJECT; 175 | } 176 | 177 | static unsigned int smtk_keymap_list_get_n_items(GListModel *list) 178 | { 179 | SmtkKeymapList *keymap_list = SMTK_KEYMAP_LIST(list); 180 | 181 | return keymap_list->items->len; 182 | } 183 | 184 | static gpointer smtk_keymap_list_get_item(GListModel *list, 185 | unsigned int position) 186 | { 187 | SmtkKeymapList *keymap_list = SMTK_KEYMAP_LIST(list); 188 | 189 | if (position >= keymap_list->items->len) 190 | return NULL; 191 | 192 | return g_object_ref(keymap_list->items->pdata[position]); 193 | } 194 | 195 | static void smtk_keymap_list_model_init(GListModelInterface *iface) 196 | { 197 | iface->get_item_type = smtk_keymap_list_get_item_type; 198 | iface->get_n_items = smtk_keymap_list_get_n_items; 199 | iface->get_item = smtk_keymap_list_get_item; 200 | } 201 | 202 | G_DEFINE_TYPE_WITH_CODE(SmtkKeymapList, smtk_keymap_list, G_TYPE_OBJECT, 203 | G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, 204 | smtk_keymap_list_model_init)) 205 | 206 | static void smtk_keymap_list_finalize(GObject *object) 207 | { 208 | SmtkKeymapList *keymap_list = SMTK_KEYMAP_LIST(object); 209 | 210 | g_clear_pointer(&keymap_list->items, g_ptr_array_unref); 211 | } 212 | 213 | static void smtk_keymap_list_init(SmtkKeymapList *keymap_list) 214 | { 215 | keymap_list->items = g_ptr_array_new_with_free_func(g_object_unref); 216 | } 217 | 218 | static void smtk_keymap_list_class_init(SmtkKeymapListClass *keymap_list_class) 219 | { 220 | GObjectClass *object_class = G_OBJECT_CLASS(keymap_list_class); 221 | 222 | object_class->finalize = smtk_keymap_list_finalize; 223 | } 224 | 225 | SmtkKeymapList *smtk_keymap_list_new(void) 226 | { 227 | return g_object_new(SMTK_TYPE_KEYMAP_LIST, NULL); 228 | } 229 | 230 | void smtk_keymap_list_append(SmtkKeymapList *keymap_list, const char *layout, 231 | const char *variant) 232 | { 233 | g_return_if_fail(keymap_list != NULL); 234 | 235 | g_ptr_array_add(keymap_list->items, 236 | smtk_keymap_item_new(layout, variant)); 237 | 238 | g_list_model_items_changed(G_LIST_MODEL(keymap_list), 239 | keymap_list->items->len - 1, 0, 1); 240 | } 241 | 242 | static int _compare(gconstpointer a, gconstpointer b) 243 | { 244 | #if GLIB_CHECK_VERSION(2, 76, 0) 245 | const char *name1 = 246 | smtk_keymap_item_get_name(SMTK_KEYMAP_ITEM((gpointer)a)); 247 | const char *name2 = 248 | smtk_keymap_item_get_name(SMTK_KEYMAP_ITEM((gpointer)b)); 249 | #else 250 | const char *name1 = 251 | smtk_keymap_item_get_name(SMTK_KEYMAP_ITEM(*(gpointer *)a)); 252 | const char *name2 = 253 | smtk_keymap_item_get_name(SMTK_KEYMAP_ITEM(*(gpointer *)b)); 254 | #endif 255 | // Most people use US (QWERTY) layout, so make it first to be the 256 | // default value. 257 | if (g_strcmp0(name1, "us") == 0) 258 | return -1; 259 | if (g_strcmp0(name2, "us") == 0) 260 | return 1; 261 | return g_strcmp0(name1, name2); 262 | } 263 | 264 | void smtk_keymap_list_sort(SmtkKeymapList *keymap_list) 265 | { 266 | g_return_if_fail(keymap_list != NULL); 267 | #if GLIB_CHECK_VERSION(2, 76, 0) 268 | g_ptr_array_sort_values(keymap_list->items, _compare); 269 | #else 270 | g_ptr_array_sort(keymap_list->items, _compare); 271 | #endif 272 | g_list_model_items_changed(G_LIST_MODEL(keymap_list), 0, 273 | keymap_list->items->len, 274 | keymap_list->items->len); 275 | } 276 | 277 | static int _equal(gconstpointer a, gconstpointer b) 278 | { 279 | const char *name1 = 280 | smtk_keymap_item_get_name(SMTK_KEYMAP_ITEM((gpointer)a)); 281 | const char *name2 = b; 282 | 283 | return g_strcmp0(name1, name2) == 0; 284 | } 285 | 286 | int smtk_keymap_list_find(SmtkKeymapList *keymap_list, const char *name) 287 | { 288 | g_return_val_if_fail(keymap_list != NULL, -1); 289 | 290 | unsigned int position; 291 | 292 | if (g_ptr_array_find_with_equal_func(keymap_list->items, name, _equal, 293 | &position)) 294 | return position; 295 | 296 | return -1; 297 | } 298 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keymap-list.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_KEYMAP_LIST_H__ 2 | #define __SMTK_KEYMAP_LIST_H__ 3 | 4 | #include 5 | #include 6 | 7 | G_BEGIN_DECLS 8 | 9 | bool keymap_is_default(const char *string); 10 | 11 | #define SMTK_TYPE_KEYMAP_ITEM smtk_keymap_item_get_type() 12 | G_DECLARE_FINAL_TYPE(SmtkKeymapItem, smtk_keymap_item, SMTK, KEYMAP_ITEM, 13 | GObject) 14 | 15 | SmtkKeymapItem *smtk_keymap_item_new(const char *layout, const char *variant); 16 | const char *smtk_keymap_item_get_layout(SmtkKeymapItem *keymap_item); 17 | void smtk_keymap_item_set_layout(SmtkKeymapItem *keymap_item, 18 | const char *layout); 19 | const char *smtk_keymap_item_get_variant(SmtkKeymapItem *keymap_item); 20 | void smtk_keymap_item_set_variant(SmtkKeymapItem *keymap_item, 21 | const char *variant); 22 | const char *smtk_keymap_item_get_name(SmtkKeymapItem *keymap_item); 23 | 24 | #define SMTK_TYPE_KEYMAP_LIST smtk_keymap_list_get_type() 25 | G_DECLARE_FINAL_TYPE(SmtkKeymapList, smtk_keymap_list, SMTK, KEYMAP_LIST, 26 | GObject) 27 | 28 | SmtkKeymapList *smtk_keymap_list_new(void); 29 | void smtk_keymap_list_append(SmtkKeymapList *keymap_list, const char *layout, 30 | const char *variant); 31 | void smtk_keymap_list_sort(SmtkKeymapList *keymap_list); 32 | int smtk_keymap_list_find(SmtkKeymapList *keymap_list, const char *name); 33 | 34 | G_END_DECLS 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-area.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "smtk.h" 4 | #include "smtk-keys-area.h" 5 | 6 | struct key_data { 7 | char *string; 8 | int counter; 9 | }; 10 | 11 | struct _SmtkKeysArea { 12 | GtkDrawingArea parent_instance; 13 | 14 | SmtkKeyMode mode; 15 | SmtkKeyAlignment alignment; 16 | GSList *keys; 17 | // Use single list because we only prepend and iter. 18 | GMutex keys_mutex; 19 | 20 | PangoLayout *string_layout; 21 | PangoFontDescription *string_font; 22 | PangoLayout *counter_layout; 23 | PangoFontDescription *counter_font; 24 | int width; 25 | int height; 26 | int string_height; 27 | int counter_height; 28 | int margin; 29 | int padding; 30 | int last_key_x; 31 | bool draw_border; 32 | 33 | GThread *timer_thread; 34 | bool timer_running; 35 | GTimer *timer; 36 | int timeout; 37 | }; 38 | G_DEFINE_TYPE(SmtkKeysArea, smtk_keys_area, GTK_TYPE_DRAWING_AREA) 39 | 40 | enum { 41 | PROP_0, 42 | PROP_MODE, 43 | PROP_ALIGNMENT, 44 | PROP_DRAW_BORDER, 45 | PROP_TIMEOUT, 46 | N_PROPS 47 | }; 48 | 49 | static GParamSpec *obj_props[N_PROPS] = { NULL }; 50 | 51 | static void smtk_keys_area_set_property(GObject *object, 52 | unsigned int property_id, 53 | const GValue *value, GParamSpec *pspec) 54 | { 55 | SmtkKeysArea *area = SMTK_KEYS_AREA(object); 56 | 57 | switch (property_id) { 58 | case PROP_MODE: 59 | smtk_keys_area_set_mode(area, g_value_get_enum(value)); 60 | break; 61 | case PROP_ALIGNMENT: 62 | smtk_keys_area_set_alignment(area, g_value_get_enum(value)); 63 | break; 64 | case PROP_DRAW_BORDER: 65 | smtk_keys_area_set_draw_border(area, 66 | g_value_get_boolean(value)); 67 | break; 68 | case PROP_TIMEOUT: 69 | smtk_keys_area_set_timeout(area, g_value_get_int(value)); 70 | break; 71 | default: 72 | /* We don't have any other property... */ 73 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 74 | break; 75 | } 76 | } 77 | 78 | static void smtk_keys_area_get_property(GObject *object, 79 | unsigned int property_id, GValue *value, 80 | GParamSpec *pspec) 81 | { 82 | SmtkKeysArea *area = SMTK_KEYS_AREA(object); 83 | 84 | switch (property_id) { 85 | case PROP_MODE: 86 | g_value_set_enum(value, area->mode); 87 | break; 88 | case PROP_ALIGNMENT: 89 | g_value_set_enum(value, area->alignment); 90 | break; 91 | case PROP_DRAW_BORDER: 92 | g_value_set_boolean(value, area->draw_border); 93 | break; 94 | case PROP_TIMEOUT: 95 | g_value_set_int(value, area->timeout); 96 | break; 97 | default: 98 | /* We don't have any other property... */ 99 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 100 | break; 101 | } 102 | } 103 | 104 | static int smtk_keys_area_calculate_width(SmtkKeysArea *area) 105 | { 106 | int result = 0; 107 | 108 | g_mutex_lock(&area->keys_mutex); 109 | for (GSList *iter = area->keys; iter; iter = iter->next) { 110 | if (result > area->width) 111 | break; 112 | 113 | const struct key_data *key_data = iter->data; 114 | char *string = g_strdup(key_data->string); 115 | char *counter = g_strdup_printf("×%d", key_data->counter); 116 | 117 | PangoRectangle s_ink; 118 | pango_layout_set_text(area->string_layout, string, -1); 119 | pango_layout_get_pixel_extents(area->string_layout, &s_ink, 120 | NULL); 121 | result += (s_ink.width + area->padding * 2); 122 | 123 | if (key_data->counter > 1) { 124 | PangoRectangle c_ink; 125 | pango_layout_set_text(area->counter_layout, counter, 126 | -1); 127 | pango_layout_get_pixel_extents(area->counter_layout, 128 | &c_ink, NULL); 129 | result += (area->padding + c_ink.width); 130 | } 131 | 132 | result += area->margin; 133 | 134 | g_free(string); 135 | g_free(counter); 136 | } 137 | g_mutex_unlock(&area->keys_mutex); 138 | 139 | return result; 140 | } 141 | 142 | static void smtk_keys_area_draw_key(SmtkKeysArea *area, cairo_t *cr, 143 | const struct key_data *key_data) 144 | { 145 | char *string = g_strdup(key_data->string); 146 | char *counter = g_strdup_printf("×%d", key_data->counter); 147 | 148 | if (key_data->counter > 1) 149 | g_debug("Drawing key: %s%s.", string, counter); 150 | else 151 | g_debug("Drawing key: %s.", string); 152 | 153 | cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); 154 | 155 | // See . 156 | PangoRectangle s_ink; 157 | PangoRectangle s_logical; 158 | pango_layout_set_text(area->string_layout, string, -1); 159 | pango_layout_get_pixel_extents(area->string_layout, &s_ink, &s_logical); 160 | g_debug("Ink: x is %d, y is %d, width is %d, height is %d.", s_ink.x, 161 | s_ink.y, s_ink.width, s_ink.height); 162 | g_debug("Logical: x is %d, y is %d, width is %d, height is %d.", 163 | s_logical.x, s_logical.y, s_logical.width, s_logical.height); 164 | 165 | int border_height = area->string_height; 166 | int border_width = s_ink.width + area->padding * 2; 167 | int border_y = (area->height - area->string_height) / 2; 168 | int border_x = area->last_key_x - (area->margin + border_width); 169 | 170 | PangoRectangle c_ink; 171 | if (key_data->counter > 1) { 172 | pango_layout_set_text(area->counter_layout, counter, -1); 173 | pango_layout_get_pixel_extents(area->counter_layout, &c_ink, 174 | NULL); 175 | 176 | // Don't forget to leave space for counter. 177 | border_x -= (area->padding + c_ink.width); 178 | } 179 | 180 | g_debug("Border: x is %d, y is %d, width is %d, height is %d.", 181 | border_x, border_y, border_width, border_height); 182 | 183 | if (area->draw_border) { 184 | // See . 185 | // 186 | // We align keys by borders, so move texts into borders. To 187 | // archive this, we are treating glyphs as images instead of 188 | // texts, because texts are aligned via baseline, which means 189 | // some glyphs can be drawn outside the given border because the 190 | // given border is treated as baseline. The logical rectangle is 191 | // used to align glyphs as texts on baseline, which is not 192 | // useful to us. The ink rectangle is the bounding box of 193 | // glphys, which is the one we need. 194 | // 195 | // See . 196 | // 197 | // `pango_cairo_show_layout()` draw from the top-left corner of 198 | // the logical rectangle, not the ink rectangle. The x and y 199 | // coordinates of the ink rectangle are the offset to the 200 | // top-left corner of the logical rectangle, so we use them to 201 | // offset the draw point. 202 | // 203 | // For example: 204 | // String font size: 97. 205 | // Drawing key: g. 206 | // Ink: x is -1, y is 50, width is 60, height is 73. 207 | // Logical: x is 0, y is 0, width is 55, height is 129. 208 | // Border: x is 2428, y is 15, width is 84, height is 122. 209 | // 210 | // Finally we will have a center-bottom alignment. 211 | const int x = border_x + area->padding - s_ink.x; 212 | const int y = border_y + border_height - area->padding - 213 | s_ink.height - s_ink.y; 214 | cairo_move_to(cr, x, y); 215 | pango_cairo_show_layout(cr, area->string_layout); 216 | // Draw border. 217 | cairo_set_line_width(cr, area->padding / 5.0); 218 | cairo_rectangle(cr, border_x, border_y, border_width, 219 | border_height); 220 | cairo_stroke(cr); 221 | } else { 222 | // When no border, just let Pango align texts so they will 223 | // sit on the baseline. But some icons are too large so if they 224 | // are aligned by baseline, they will draw outside area, so we 225 | // use ink rectangle for them to align glyphs by bottom. 226 | // 227 | // Finally we get a center-baseline or center-bottom alignment 228 | // so keys won't move horizontally when toggling borders. 229 | const int x = border_x + area->padding - s_ink.x; 230 | int y = border_y - s_logical.y; 231 | if (y + s_logical.height > area->height) { 232 | g_debug("%s will draw outside area if aligned by " 233 | "baseline, will be aligned by bottom.", 234 | string); 235 | y = border_y + border_height - area->padding - 236 | s_ink.height - s_ink.y; 237 | } 238 | cairo_move_to(cr, x, y); 239 | pango_cairo_show_layout(cr, area->string_layout); 240 | } 241 | 242 | if (key_data->counter > 1) { 243 | // We always use the ink rectangles to align counters because 244 | // they are all similiar and this is simple. 245 | const int x = border_x + border_width + area->padding - c_ink.x; 246 | const int y = border_y + border_height - c_ink.height - c_ink.y; 247 | cairo_move_to(cr, x, y); 248 | pango_cairo_show_layout(cr, area->counter_layout); 249 | } 250 | 251 | area->last_key_x = border_x; 252 | 253 | g_free(string); 254 | g_free(counter); 255 | } 256 | 257 | static void smtk_keys_area_draw(GtkDrawingArea *drawing_area, cairo_t *cr, 258 | int width, int height, gpointer user_data) 259 | { 260 | SmtkKeysArea *area = SMTK_KEYS_AREA(drawing_area); 261 | 262 | area->width = width; 263 | area->height = height; 264 | 265 | if (area->string_layout) { 266 | pango_cairo_update_layout(cr, area->string_layout); 267 | } else { 268 | area->string_layout = pango_cairo_create_layout(cr); 269 | pango_layout_set_ellipsize(area->string_layout, 270 | PANGO_ELLIPSIZE_NONE); 271 | } 272 | 273 | area->string_height = area->height * 0.8; 274 | const int string_font_size = area->string_height * 0.8; 275 | g_debug("String font size: %d.", string_font_size); 276 | pango_font_description_set_absolute_size( 277 | area->string_font, string_font_size * PANGO_SCALE); 278 | pango_layout_set_font_description(area->string_layout, 279 | area->string_font); 280 | 281 | if (area->counter_layout) { 282 | pango_cairo_update_layout(cr, area->counter_layout); 283 | } else { 284 | area->counter_layout = pango_cairo_create_layout(cr); 285 | pango_layout_set_ellipsize(area->counter_layout, 286 | PANGO_ELLIPSIZE_NONE); 287 | } 288 | 289 | area->counter_height = area->string_height * 0.5; 290 | const int counter_font_size = area->counter_height * 0.8; 291 | g_debug("Counter font size: %d.", string_font_size); 292 | pango_font_description_set_absolute_size( 293 | area->counter_font, counter_font_size * PANGO_SCALE); 294 | pango_layout_set_font_description(area->counter_layout, 295 | area->counter_font); 296 | 297 | area->margin = area->string_height * 0.4; 298 | area->padding = (area->string_height - string_font_size) / 2; 299 | 300 | // To align to center, we need to first calculate total width. 301 | if (area->alignment == SMTK_KEY_ALIGNMENT_END) { 302 | area->last_key_x = area->width; 303 | } else { 304 | const int keys_width = smtk_keys_area_calculate_width(area); 305 | if (keys_width > area->width) 306 | area->last_key_x = area->width; 307 | else 308 | area->last_key_x = 309 | (area->width - keys_width) / 2 + keys_width; 310 | } 311 | 312 | g_mutex_lock(&area->keys_mutex); 313 | for (GSList *iter = area->keys; iter; iter = iter->next) { 314 | if (area->last_key_x < 0) { 315 | // We cannot handle next one, but it's OK to break 316 | // current one. 317 | GSList *unused = iter->next; 318 | iter->next = NULL; 319 | if (unused != NULL) 320 | g_slist_free_full(g_steal_pointer(&unused), 321 | g_free); 322 | break; 323 | } 324 | smtk_keys_area_draw_key(area, cr, iter->data); 325 | } 326 | g_debug("Keys list length: %d", g_slist_length(area->keys)); 327 | g_mutex_unlock(&area->keys_mutex); 328 | } 329 | 330 | static void idle_destroy_function(gpointer user_data) 331 | { 332 | SmtkKeysArea *area = SMTK_KEYS_AREA(user_data); 333 | g_object_unref(area); 334 | } 335 | 336 | // true and false are C99 _Bool, but GLib expects gboolean, which is C99 int. 337 | static int idle_function(gpointer user_data) 338 | { 339 | SmtkKeysArea *area = SMTK_KEYS_AREA(user_data); 340 | 341 | // Trigger re-draw because content changed. 342 | gtk_widget_queue_draw(GTK_WIDGET(area)); 343 | 344 | return 0; 345 | } 346 | 347 | static gpointer timer_function(gpointer user_data) 348 | { 349 | SmtkKeysArea *area = SMTK_KEYS_AREA(user_data); 350 | 351 | while (area->timer_running) { 352 | g_mutex_lock(&area->keys_mutex); 353 | int elapsed = g_timer_elapsed(area->timer, NULL) * 1000.0; 354 | g_mutex_unlock(&area->keys_mutex); 355 | 356 | if (area->timeout > 0 && elapsed > area->timeout) { 357 | g_debug("Timer triggered, clear keys."); 358 | g_mutex_lock(&area->keys_mutex); 359 | if (area->keys != NULL) 360 | g_slist_free_full(g_steal_pointer(&area->keys), 361 | g_free); 362 | g_timer_start(area->timer); 363 | g_mutex_unlock(&area->keys_mutex); 364 | 365 | // Here is not UI thread so we need to kick an async 366 | // callback into GLib's main loop. 367 | g_timeout_add_full(G_PRIORITY_DEFAULT, 0, idle_function, 368 | g_object_ref(area), 369 | idle_destroy_function); 370 | } 371 | 372 | g_usleep(1000L); 373 | } 374 | 375 | return NULL; 376 | } 377 | 378 | static void smtk_keys_area_init(SmtkKeysArea *area) 379 | { 380 | area->width = 0; 381 | area->height = 0; 382 | 383 | area->string_layout = NULL; 384 | area->string_font = pango_font_description_new(); 385 | pango_font_description_set_family(area->string_font, "monospace"); 386 | area->string_height = 0; 387 | 388 | area->counter_layout = NULL; 389 | area->counter_font = pango_font_description_new(); 390 | pango_font_description_set_family(area->counter_font, "monospace"); 391 | area->counter_height = 0; 392 | 393 | area->keys = NULL; 394 | g_mutex_init(&area->keys_mutex); 395 | 396 | gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), 397 | smtk_keys_area_draw, NULL, NULL); 398 | 399 | area->timer_running = true; 400 | area->timer = g_timer_new(); 401 | 402 | // Just ignore timer thread error because it should work without timer. 403 | area->timer_thread = 404 | g_thread_try_new("timer", timer_function, area, NULL); 405 | } 406 | 407 | static void smtk_keys_area_dispose(GObject *object) 408 | { 409 | SmtkKeysArea *area = SMTK_KEYS_AREA(object); 410 | 411 | g_clear_object(&area->string_layout); 412 | g_clear_pointer(&area->string_font, pango_font_description_free); 413 | 414 | g_clear_object(&area->counter_layout); 415 | g_clear_pointer(&area->counter_font, pango_font_description_free); 416 | 417 | if (area->timer_thread != NULL) { 418 | area->timer_running = false; 419 | g_thread_join(area->timer_thread); 420 | area->timer_thread = NULL; 421 | } 422 | 423 | g_clear_pointer(&area->timer, g_timer_destroy); 424 | 425 | G_OBJECT_CLASS(smtk_keys_area_parent_class)->dispose(object); 426 | } 427 | 428 | static void smtk_keys_area_finalize(GObject *object) 429 | { 430 | SmtkKeysArea *area = SMTK_KEYS_AREA(object); 431 | 432 | g_clear_slist(&area->keys, g_free); 433 | 434 | G_OBJECT_CLASS(smtk_keys_area_parent_class)->finalize(object); 435 | } 436 | 437 | static void smtk_keys_area_class_init(SmtkKeysAreaClass *area_class) 438 | { 439 | GObjectClass *object_class = G_OBJECT_CLASS(area_class); 440 | 441 | object_class->set_property = smtk_keys_area_set_property; 442 | object_class->get_property = smtk_keys_area_get_property; 443 | 444 | object_class->dispose = smtk_keys_area_dispose; 445 | object_class->finalize = smtk_keys_area_finalize; 446 | 447 | obj_props[PROP_MODE] = g_param_spec_enum( 448 | "mode", "Mode", "Key Mode", SMTK_TYPE_KEY_MODE, 449 | SMTK_KEY_MODE_COMPOSED, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 450 | obj_props[PROP_ALIGNMENT] = g_param_spec_enum( 451 | "alignment", "Alignment", "Key Alignment", 452 | SMTK_TYPE_KEY_ALIGNMENT, SMTK_KEY_ALIGNMENT_END, 453 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 454 | obj_props[PROP_TIMEOUT] = g_param_spec_int( 455 | "timeout", "Text Timeout", "Text Timeout", 0, 30000, 1000, 456 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 457 | obj_props[PROP_DRAW_BORDER] = g_param_spec_boolean( 458 | "draw-border", "Draw Border", "Draw Keys Border", true, 459 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 460 | g_object_class_install_properties(object_class, N_PROPS, obj_props); 461 | } 462 | 463 | GtkWidget *smtk_keys_area_new(SmtkKeyMode mode, SmtkKeyAlignment alignment, 464 | bool draw_border, int timeout) 465 | { 466 | SmtkKeysArea *area = 467 | g_object_new(SMTK_TYPE_KEYS_AREA, "mode", mode, "alignment", 468 | alignment, "draw-border", draw_border, "timeout", 469 | timeout, "vexpand", true, "hexpand", true, NULL); 470 | return GTK_WIDGET(area); 471 | } 472 | 473 | void smtk_keys_area_set_mode(SmtkKeysArea *area, SmtkKeyMode mode) 474 | { 475 | g_return_if_fail(area != NULL); 476 | 477 | area->mode = mode; 478 | } 479 | 480 | void smtk_keys_area_set_alignment(SmtkKeysArea *area, 481 | SmtkKeyAlignment alignment) 482 | { 483 | g_return_if_fail(area != NULL); 484 | 485 | area->alignment = alignment; 486 | 487 | // Trigger re-draw because alignment changed. 488 | gtk_widget_queue_draw(GTK_WIDGET(area)); 489 | } 490 | 491 | void smtk_keys_area_set_draw_border(SmtkKeysArea *area, bool draw_border) 492 | { 493 | g_return_if_fail(area != NULL); 494 | 495 | area->draw_border = draw_border; 496 | 497 | // Trigger re-draw because border changed. 498 | gtk_widget_queue_draw(GTK_WIDGET(area)); 499 | } 500 | 501 | void smtk_keys_area_set_timeout(SmtkKeysArea *area, int timeout) 502 | { 503 | g_return_if_fail(area != NULL); 504 | 505 | area->timeout = timeout; 506 | } 507 | 508 | void smtk_keys_area_add_key(SmtkKeysArea *area, char key[]) 509 | { 510 | g_return_if_fail(area != NULL); 511 | 512 | g_debug("Adding key: %s.", key); 513 | g_mutex_lock(&area->keys_mutex); 514 | struct key_data *last = NULL; 515 | if (area->keys != NULL) 516 | last = area->keys->data; 517 | if (area->mode == SMTK_KEY_MODE_COMPACT && last != NULL && 518 | strcmp(last->string, key) == 0) { 519 | ++last->counter; 520 | g_free(key); 521 | } else { 522 | struct key_data *key_data = g_malloc(sizeof(*key_data)); 523 | key_data->string = key; 524 | key_data->counter = 1; 525 | area->keys = g_slist_prepend(area->keys, key_data); 526 | } 527 | g_timer_start(area->timer); 528 | g_mutex_unlock(&area->keys_mutex); 529 | 530 | // Trigger re-draw because content changed. 531 | gtk_widget_queue_draw(GTK_WIDGET(area)); 532 | } 533 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-area.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_KEYS_AREA_H__ 2 | #define __SMTK_KEYS_AREA_H__ 3 | 4 | #include 5 | 6 | #include "smtk-keys-mapper.h" 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define SMTK_TYPE_KEYS_AREA smtk_keys_area_get_type() 11 | G_DECLARE_FINAL_TYPE(SmtkKeysArea, smtk_keys_area, SMTK, KEYS_AREA, 12 | GtkDrawingArea) 13 | 14 | // It looks like glib-mkenums cannot make enums 15 | // if clang-format make this in one line. But why??? 16 | // clang-format off 17 | typedef enum { 18 | SMTK_KEY_ALIGNMENT_END, 19 | SMTK_KEY_ALIGNMENT_CENTER 20 | } SmtkKeyAlignment; 21 | // clang-format on 22 | 23 | GtkWidget *smtk_keys_area_new(SmtkKeyMode mode, SmtkKeyAlignment alignment, 24 | bool draw_border, int timeout); 25 | void smtk_keys_area_set_mode(SmtkKeysArea *area, SmtkKeyMode mode); 26 | void smtk_keys_area_set_alignment(SmtkKeysArea *area, 27 | SmtkKeyAlignment alignment); 28 | void smtk_keys_area_set_draw_border(SmtkKeysArea *area, bool draw_border); 29 | void smtk_keys_area_set_timeout(SmtkKeysArea *area, int timeout); 30 | void smtk_keys_area_add_key(SmtkKeysArea *area, char *key); 31 | 32 | G_END_DECLS 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-emitter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "smtk.h" 6 | #include "smtk-keys-emitter.h" 7 | #include "smtk-keys-mapper.h" 8 | #include "smtk-event.h" 9 | 10 | #define MAX_KEYS 30 11 | 12 | struct _SmtkKeysEmitter { 13 | GObject parent_instance; 14 | SmtkKeysMapper *mapper; 15 | GSubprocess *cli; 16 | GDataInputStream *cli_out; 17 | 18 | GThread *poller; 19 | bool polling; 20 | 21 | SmtkKeyMode mode; 22 | bool show_shift; 23 | bool show_keyboard; 24 | bool show_mouse; 25 | bool hide_visible; 26 | bool alt_pressed; 27 | char *layout; 28 | char *variant; 29 | GError *error; 30 | }; 31 | G_DEFINE_TYPE(SmtkKeysEmitter, smtk_keys_emitter, G_TYPE_OBJECT) 32 | 33 | enum { SIG_KEY, SIG_ERROR_CLI_EXIT, SIG_PAUSE, N_SIGNALS }; 34 | 35 | static unsigned int obj_signals[N_SIGNALS] = { 0 }; 36 | 37 | enum { 38 | PROP_0, 39 | PROP_MODE, 40 | PROP_SHOW_SHIFT, 41 | PROP_SHOW_KEYBOARD, 42 | PROP_SHOW_MOUSE, 43 | PROP_HIDE_VISIBLE, 44 | PROP_LAYOUT, 45 | PROP_VARIANT, 46 | N_PROPS 47 | }; 48 | 49 | static GParamSpec *obj_props[N_PROPS] = { NULL }; 50 | 51 | // Check whether user choose cancel for pkexec. 52 | static void smtk_keys_emitter_cli_on_complete(GObject *source_object, 53 | GAsyncResult *res, 54 | gpointer user_data) 55 | { 56 | // We got a copy of SmtkKeysEmitter's address when setting up this 57 | // callback, and there is a condition, that user closes the switch, 58 | // and emitter is disposed, then this callback is called, and the 59 | // address we hold is invalid, it might point to other objects, and 60 | // still not NULL. To solve this problem we hold a reference to this 61 | // callback to prevent the emitter to be disposed and manually drop it. 62 | g_debug("Calling smtk_keys_emitter_cli_on_complete()."); 63 | 64 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(user_data); 65 | // Cli may already released normally, and this function only cares about 66 | // when cli exited with error, for example user cancelled pkexec. 67 | if (emitter->cli != NULL && 68 | g_subprocess_get_exit_status(emitter->cli) != 0) { 69 | // Better to close thread here to prevent a lot of error. 70 | emitter->polling = false; 71 | if (emitter->poller != NULL) { 72 | g_debug("Stopping poller because cli exitted."); 73 | g_thread_join(emitter->poller); 74 | emitter->poller = NULL; 75 | } 76 | g_signal_emit_by_name(emitter, "error-cli-exit"); 77 | } 78 | g_object_unref(emitter); 79 | } 80 | 81 | static void smtk_keys_emitter_set_property(GObject *object, 82 | unsigned int property_id, 83 | const GValue *value, 84 | GParamSpec *pspec) 85 | { 86 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(object); 87 | 88 | switch (property_id) { 89 | case PROP_MODE: 90 | smtk_keys_emitter_set_mode(emitter, g_value_get_enum(value)); 91 | break; 92 | case PROP_SHOW_SHIFT: 93 | smtk_keys_emitter_set_show_shift(emitter, 94 | g_value_get_boolean(value)); 95 | break; 96 | case PROP_SHOW_KEYBOARD: 97 | smtk_keys_emitter_set_show_keyboard(emitter, 98 | g_value_get_boolean(value)); 99 | break; 100 | case PROP_SHOW_MOUSE: 101 | smtk_keys_emitter_set_show_mouse(emitter, 102 | g_value_get_boolean(value)); 103 | break; 104 | case PROP_HIDE_VISIBLE: 105 | smtk_keys_emitter_set_hide_visible(emitter, 106 | g_value_get_boolean(value)); 107 | break; 108 | case PROP_LAYOUT: 109 | smtk_keys_emitter_set_layout(emitter, 110 | g_value_get_string(value)); 111 | break; 112 | case PROP_VARIANT: 113 | smtk_keys_emitter_set_variant(emitter, 114 | g_value_get_string(value)); 115 | break; 116 | default: 117 | /* We don't have any other property... */ 118 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 119 | break; 120 | } 121 | } 122 | 123 | static void smtk_keys_emitter_get_property(GObject *object, 124 | unsigned int property_id, 125 | GValue *value, GParamSpec *pspec) 126 | { 127 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(object); 128 | 129 | switch (property_id) { 130 | case PROP_MODE: 131 | g_value_set_enum(value, emitter->mode); 132 | break; 133 | case PROP_SHOW_SHIFT: 134 | g_value_set_boolean(value, emitter->show_shift); 135 | break; 136 | case PROP_SHOW_KEYBOARD: 137 | g_value_set_boolean(value, emitter->show_keyboard); 138 | break; 139 | case PROP_SHOW_MOUSE: 140 | g_value_set_boolean(value, emitter->show_mouse); 141 | break; 142 | case PROP_HIDE_VISIBLE: 143 | g_value_set_boolean(value, emitter->hide_visible); 144 | break; 145 | case PROP_LAYOUT: 146 | g_value_set_string(value, emitter->layout); 147 | break; 148 | case PROP_VARIANT: 149 | g_value_set_string(value, emitter->variant); 150 | break; 151 | default: 152 | /* We don't have any other property... */ 153 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 154 | break; 155 | } 156 | } 157 | 158 | struct key_idle_data { 159 | SmtkKeysEmitter *emitter; 160 | char *key; 161 | }; 162 | 163 | static void key_idle_destroy_function(gpointer user_data) 164 | { 165 | struct key_idle_data *key_idle_data = user_data; 166 | SmtkKeysEmitter *emitter = key_idle_data->emitter; 167 | 168 | g_object_unref(emitter); 169 | g_free(key_idle_data); 170 | } 171 | 172 | // true and false are C99 _Bool, but GLib expects gboolean, which is C99 int. 173 | static int key_idle_function(gpointer user_data) 174 | { 175 | // Here we back to UI thread. 176 | struct key_idle_data *key_idle_data = user_data; 177 | SmtkKeysEmitter *emitter = key_idle_data->emitter; 178 | 179 | g_signal_emit_by_name(emitter, "key", key_idle_data->key); 180 | 181 | return 0; 182 | } 183 | 184 | static void trigger_key_idle_function(SmtkKeysEmitter *emitter, 185 | const char key[]) 186 | { 187 | // UI can only be modified in UI thread, and we are not in UI thread 188 | // here. So we need to use `g_timeout_add()` to kick an async callback 189 | // into glib's main loop (the same as GTK UI thread). 190 | // 191 | // Signals are not async! So we cannot emit signal here, because they 192 | // will run in poller thread instead of UI thread. `g_idle_add()` is not 193 | // suitable because we have a high priority. We dup the key and use 194 | // malloc because we will enter another thread, so the poller thread may 195 | // already continue and calls free for key. 196 | struct key_idle_data *key_idle_data = g_malloc(sizeof(*key_idle_data)); 197 | if (!key_idle_data) { 198 | g_warning("Alloc key_idle_data failed.\n"); 199 | return; 200 | } 201 | key_idle_data->emitter = g_object_ref(emitter); 202 | key_idle_data->key = g_strdup(key); 203 | g_timeout_add_full(G_PRIORITY_DEFAULT, 0, key_idle_function, 204 | key_idle_data, key_idle_destroy_function); 205 | } 206 | 207 | static int pause_idle_function(gpointer user_data) 208 | { 209 | SmtkKeysEmitter *emitter = user_data; 210 | 211 | g_signal_emit_by_name(emitter, "pause"); 212 | 213 | return 0; 214 | } 215 | 216 | static void trigger_pause_idle_function(SmtkKeysEmitter *emitter) 217 | { 218 | g_timeout_add_full(G_PRIORITY_DEFAULT, 0, pause_idle_function, 219 | g_object_ref(emitter), g_object_unref); 220 | } 221 | 222 | static gpointer poller_function(gpointer user_data) 223 | { 224 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(user_data); 225 | while (emitter->polling) { 226 | GError *read_line_error = NULL; 227 | char *line = g_data_input_stream_read_line( 228 | emitter->cli_out, NULL, NULL, &read_line_error); 229 | // See . 230 | if (line == NULL) { 231 | if (read_line_error != NULL) { 232 | g_warning("Read line error: %s.", 233 | read_line_error->message); 234 | g_error_free(read_line_error); 235 | } 236 | continue; 237 | } 238 | 239 | GError *event_error = NULL; 240 | SmtkEvent *event = smtk_event_new(line, &event_error); 241 | if (event == NULL) { 242 | g_warning("Create event error: %s.", 243 | event_error->message); 244 | g_free(line); 245 | continue; 246 | } 247 | 248 | // Press both Alt to pause. Xkbcommon treat left and right alt 249 | // as the same one, so we have to do it here. 250 | SmtkEventType type = smtk_event_get_event_type(event); 251 | SmtkEventState state = smtk_event_get_event_state(event); 252 | const char *key_name = smtk_event_get_key_name(event); 253 | if (type == SMTK_EVENT_TYPE_KEYBOARD_KEY && 254 | state == SMTK_EVENT_STATE_PRESSED && 255 | (strcmp(key_name, "KEY_LEFTALT") == 0 || 256 | strcmp(key_name, "KEY_RIGHTALT") == 0)) { 257 | if (emitter->alt_pressed) { 258 | trigger_pause_idle_function(emitter); 259 | emitter->alt_pressed = false; 260 | } else { 261 | emitter->alt_pressed = true; 262 | } 263 | } else { 264 | if (emitter->alt_pressed) 265 | emitter->alt_pressed = false; 266 | } 267 | 268 | char *key = NULL; 269 | // Always get key with SmtkKeysMapper, it will update XKB state 270 | // to keep sync with actual keyboard. 271 | switch (emitter->mode) { 272 | case SMTK_KEY_MODE_COMPOSED: 273 | key = smtk_keys_mapper_get_composed(emitter->mapper, 274 | event); 275 | break; 276 | case SMTK_KEY_MODE_RAW: 277 | key = smtk_keys_mapper_get_raw(emitter->mapper, event); 278 | break; 279 | case SMTK_KEY_MODE_COMPACT: 280 | key = smtk_keys_mapper_get_compact(emitter->mapper, 281 | event); 282 | break; 283 | default: 284 | // Should never be here. 285 | g_warn_if_reached(); 286 | break; 287 | } 288 | if (key != NULL) { 289 | if (state == SMTK_EVENT_STATE_PRESSED && 290 | ((emitter->show_mouse && 291 | type == SMTK_EVENT_TYPE_POINTER_BUTTON) || 292 | (emitter->show_keyboard && 293 | type == SMTK_EVENT_TYPE_KEYBOARD_KEY))) 294 | trigger_key_idle_function(emitter, key); 295 | g_free(key); 296 | } 297 | g_clear_object(&event); 298 | g_free(line); 299 | } 300 | return NULL; 301 | } 302 | 303 | static void smtk_keys_emitter_init(SmtkKeysEmitter *emitter) 304 | { 305 | emitter->mapper = NULL; 306 | emitter->cli = NULL; 307 | emitter->cli_out = NULL; 308 | emitter->poller = NULL; 309 | emitter->layout = NULL; 310 | emitter->variant = NULL; 311 | emitter->error = NULL; 312 | emitter->alt_pressed = false; 313 | } 314 | 315 | static void smtk_keys_emitter_constructed(GObject *object) 316 | { 317 | // Seems we can only get constructor properties here. 318 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(object); 319 | 320 | emitter->mapper = smtk_keys_mapper_new( 321 | emitter->show_shift, emitter->hide_visible, emitter->layout, 322 | emitter->variant, &emitter->error); 323 | // `emitter->error` is already set, just return. 324 | if (emitter->mapper == NULL) 325 | goto out; 326 | 327 | out: 328 | G_OBJECT_CLASS(smtk_keys_emitter_parent_class)->constructed(object); 329 | } 330 | 331 | static void smtk_keys_emitter_dispose(GObject *object) 332 | { 333 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(object); 334 | 335 | g_clear_object(&emitter->mapper); 336 | 337 | G_OBJECT_CLASS(smtk_keys_emitter_parent_class)->dispose(object); 338 | } 339 | 340 | static void smtk_keys_emitter_finalize(GObject *object) 341 | { 342 | SmtkKeysEmitter *emitter = SMTK_KEYS_EMITTER(object); 343 | 344 | g_clear_pointer(&emitter->layout, g_free); 345 | g_clear_pointer(&emitter->variant, g_free); 346 | 347 | G_OBJECT_CLASS(smtk_keys_emitter_parent_class)->finalize(object); 348 | } 349 | 350 | static void smtk_keys_emitter_class_init(SmtkKeysEmitterClass *emitter_class) 351 | { 352 | GObjectClass *object_class = G_OBJECT_CLASS(emitter_class); 353 | 354 | object_class->set_property = smtk_keys_emitter_set_property; 355 | object_class->get_property = smtk_keys_emitter_get_property; 356 | 357 | object_class->constructed = smtk_keys_emitter_constructed; 358 | 359 | object_class->dispose = smtk_keys_emitter_dispose; 360 | object_class->finalize = smtk_keys_emitter_finalize; 361 | 362 | obj_signals[SIG_KEY] = g_signal_new("key", SMTK_TYPE_KEYS_EMITTER, 363 | G_SIGNAL_RUN_LAST, 0, NULL, NULL, 364 | g_cclosure_marshal_VOID__STRING, 365 | G_TYPE_NONE, 1, G_TYPE_STRING); 366 | obj_signals[SIG_ERROR_CLI_EXIT] = g_signal_new( 367 | "error-cli-exit", SMTK_TYPE_KEYS_EMITTER, G_SIGNAL_RUN_LAST, 0, 368 | NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); 369 | obj_signals[SIG_PAUSE] = g_signal_new("pause", SMTK_TYPE_KEYS_EMITTER, 370 | G_SIGNAL_RUN_LAST, 0, NULL, NULL, 371 | g_cclosure_marshal_VOID__VOID, 372 | G_TYPE_NONE, 0); 373 | 374 | obj_props[PROP_MODE] = g_param_spec_enum( 375 | "mode", "Mode", "Key Mode", SMTK_TYPE_KEY_MODE, 376 | SMTK_KEY_MODE_COMPOSED, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 377 | obj_props[PROP_SHOW_SHIFT] = g_param_spec_boolean( 378 | "show-shift", "Show Shift", "Show Shift Separately", true, 379 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 380 | obj_props[PROP_SHOW_KEYBOARD] = g_param_spec_boolean( 381 | "show-keyboard", "Show Keyboard", "Show Keyboard Key", true, 382 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 383 | obj_props[PROP_SHOW_MOUSE] = g_param_spec_boolean( 384 | "show-mouse", "Show Mouse", "Show Mouse Button", true, 385 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 386 | obj_props[PROP_HIDE_VISIBLE] = g_param_spec_boolean( 387 | "hide-visible", "Hide Visible", "Hide Visible Keys", false, 388 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 389 | obj_props[PROP_LAYOUT] = 390 | g_param_spec_string("layout", "Layout", "Keymap Layout", NULL, 391 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 392 | obj_props[PROP_VARIANT] = g_param_spec_string( 393 | "variant", "Variant", "Keymap Variant", NULL, 394 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 395 | 396 | g_object_class_install_properties(object_class, N_PROPS, obj_props); 397 | } 398 | 399 | SmtkKeysEmitter *smtk_keys_emitter_new(bool show_shift, bool show_keyboard, 400 | bool show_mouse, bool hide_visible, 401 | SmtkKeyMode mode, const char *layout, 402 | const char *variant, GError **error) 403 | { 404 | SmtkKeysEmitter *emitter = g_object_new( 405 | SMTK_TYPE_KEYS_EMITTER, "mode", mode, "show-shift", show_shift, 406 | "show-keyboard", show_keyboard, "show-mouse", show_mouse, 407 | "hide-visible", hide_visible, "layout", layout, "variant", 408 | variant, NULL); 409 | 410 | if (emitter->error != NULL) { 411 | g_propagate_error(error, emitter->error); 412 | g_object_unref(emitter); 413 | return NULL; 414 | } 415 | 416 | return emitter; 417 | } 418 | 419 | static bool is_group(const char *group_name) 420 | { 421 | gid_t *groups; 422 | int ngroups; 423 | struct group *grp; 424 | gid_t gid; 425 | 426 | ngroups = getgroups(0, NULL); 427 | groups = g_malloc(ngroups * sizeof(*groups)); 428 | if (getgroups(ngroups, groups) < 0) { 429 | g_free(groups); 430 | return false; 431 | } 432 | 433 | grp = getgrnam(group_name); 434 | if (!grp) { 435 | g_free(groups); 436 | return false; 437 | } 438 | gid = grp->gr_gid; 439 | 440 | for (int i = 0; i < ngroups; i++) { 441 | if (groups[i] == gid) { 442 | g_free(groups); 443 | return true; 444 | } 445 | } 446 | 447 | g_free(groups); 448 | return false; 449 | } 450 | 451 | // Those two functions are splitted from init and dispose functions, 452 | // because we need to pass a reference to the async callback of GTask, 453 | // and don't want a loop reference (e.g. a emitter reference is dropped 454 | // only when callback is called, and callback is called only when cli stopped, 455 | // but cli is stopped only when emitter is disposed, and emitter is disposed 456 | // only when reference is dropped!). So we break it into different functions 457 | // and let the caller stop the cli before dispose. 458 | void smtk_keys_emitter_start_async(SmtkKeysEmitter *emitter, GError **error) 459 | { 460 | g_debug("Calling smtk_keys_emitter_start_async()."); 461 | g_return_if_fail(emitter != NULL); 462 | 463 | if (is_group("input")) 464 | emitter->cli = g_subprocess_new( 465 | G_SUBPROCESS_FLAGS_STDIN_PIPE | 466 | G_SUBPROCESS_FLAGS_STDOUT_PIPE | 467 | G_SUBPROCESS_FLAGS_STDERR_PIPE, 468 | error, PACKAGE_BINDIR "/showmethekey-cli", NULL); 469 | else 470 | emitter->cli = g_subprocess_new( 471 | G_SUBPROCESS_FLAGS_STDIN_PIPE | 472 | G_SUBPROCESS_FLAGS_STDOUT_PIPE | 473 | G_SUBPROCESS_FLAGS_STDERR_PIPE, 474 | error, PKEXEC_PATH, PACKAGE_BINDIR "/showmethekey-cli", 475 | NULL); 476 | // emitter->error is already set, just return. 477 | if (emitter->cli == NULL) 478 | return; 479 | // Actually I don't wait the subprocess to return, they work like 480 | // clients and daemons, why clients want to wait for daemons' exiting? 481 | // This is just spawn subprocess. 482 | // smtk_keys_emitter_cli_on_complete is called when GTask finished, 483 | // and this is async and might the SmtkKeysWin is already destroyed. 484 | // So we have to manually reference to emitter here. 485 | g_subprocess_wait_check_async(emitter->cli, NULL, 486 | smtk_keys_emitter_cli_on_complete, 487 | g_object_ref(emitter)); 488 | emitter->cli_out = g_data_input_stream_new( 489 | g_subprocess_get_stdout_pipe(emitter->cli)); 490 | 491 | emitter->polling = true; 492 | emitter->poller = 493 | g_thread_try_new("poller", poller_function, emitter, error); 494 | // emitter->error is already set, just return. 495 | if (emitter->poller == NULL) 496 | return; 497 | } 498 | 499 | void smtk_keys_emitter_stop_async(SmtkKeysEmitter *emitter) 500 | { 501 | g_debug("Calling smtk_keys_emitter_stop_async()."); 502 | g_return_if_fail(emitter != NULL); 503 | 504 | // Don't know why but I need to stop cli before poller. 505 | if (emitter->cli != NULL) { 506 | // Because we run subprocess with pkexec, 507 | // so we cannot force kill it, 508 | // we use stdin pipe to write a "stop\n", 509 | // and let it exit by itself. 510 | const char stop[] = "stop\n"; 511 | GBytes *input = g_bytes_new(stop, sizeof(stop)); 512 | g_subprocess_communicate(emitter->cli, input, NULL, NULL, NULL, 513 | NULL); 514 | g_bytes_unref(input); 515 | // g_subprocess_force_exit(emitter->cli); 516 | // Just close it, I am not interested in its error. 517 | g_input_stream_close(G_INPUT_STREAM(emitter->cli_out), NULL, 518 | NULL); 519 | emitter->cli_out = NULL; 520 | g_object_unref(emitter->cli); 521 | emitter->cli = NULL; 522 | } 523 | 524 | if (emitter->poller != NULL) { 525 | emitter->polling = false; 526 | // This will wait until thread exited. 527 | // It will call g_thread_unref() internal 528 | // so we don't need to do it. 529 | g_thread_join(emitter->poller); 530 | // g_thread_unref(emitter->poller); 531 | emitter->poller = NULL; 532 | } 533 | } 534 | 535 | void smtk_keys_emitter_set_show_shift(SmtkKeysEmitter *emitter, bool show_shift) 536 | { 537 | g_return_if_fail(emitter != NULL); 538 | 539 | // Pass property to mapper. 540 | if (emitter->mapper != NULL) 541 | smtk_keys_mapper_set_show_shift(emitter->mapper, show_shift); 542 | // Sync self property. 543 | emitter->show_shift = show_shift; 544 | } 545 | 546 | void smtk_keys_emitter_set_show_keyboard(SmtkKeysEmitter *emitter, 547 | bool show_keyboard) 548 | { 549 | g_return_if_fail(emitter != NULL); 550 | 551 | emitter->show_keyboard = show_keyboard; 552 | } 553 | 554 | void smtk_keys_emitter_set_show_mouse(SmtkKeysEmitter *emitter, bool show_mouse) 555 | { 556 | g_return_if_fail(emitter != NULL); 557 | 558 | emitter->show_mouse = show_mouse; 559 | } 560 | 561 | void smtk_keys_emitter_set_hide_visible(SmtkKeysEmitter *emitter, 562 | bool hide_visible) 563 | { 564 | g_return_if_fail(emitter != NULL); 565 | 566 | if (emitter->mapper != NULL) 567 | smtk_keys_mapper_set_hide_visible(emitter->mapper, 568 | hide_visible); 569 | 570 | emitter->hide_visible = hide_visible; 571 | } 572 | 573 | void smtk_keys_emitter_set_mode(SmtkKeysEmitter *emitter, SmtkKeyMode mode) 574 | { 575 | g_return_if_fail(emitter != NULL); 576 | 577 | emitter->mode = mode; 578 | } 579 | 580 | void smtk_keys_emitter_set_layout(SmtkKeysEmitter *emitter, const char *layout) 581 | { 582 | g_return_if_fail(emitter != NULL); 583 | 584 | if (emitter->mapper != NULL) 585 | smtk_keys_mapper_set_layout(emitter->mapper, layout); 586 | 587 | if (emitter->layout != NULL) 588 | g_free(emitter->layout); 589 | emitter->layout = g_strdup(layout); 590 | } 591 | 592 | void smtk_keys_emitter_set_variant(SmtkKeysEmitter *emitter, 593 | const char *variant) 594 | { 595 | g_return_if_fail(emitter != NULL); 596 | 597 | if (emitter->mapper != NULL) 598 | smtk_keys_mapper_set_variant(emitter->mapper, variant); 599 | 600 | if (emitter->variant != NULL) 601 | g_free(emitter->variant); 602 | emitter->variant = g_strdup(variant); 603 | } 604 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-emitter.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_KEYS_EMITTER__ 2 | #define __SMTK_KEYS_EMITTER__ 3 | 4 | #include 5 | #include 6 | 7 | #include "smtk-keys-mapper.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define SMTK_TYPE_KEYS_EMITTER smtk_keys_emitter_get_type() 12 | G_DECLARE_FINAL_TYPE(SmtkKeysEmitter, smtk_keys_emitter, SMTK, KEYS_EMITTER, 13 | GObject) 14 | 15 | SmtkKeysEmitter *smtk_keys_emitter_new(bool show_shift, bool show_keyboard, 16 | bool show_mouse, bool hide_visible, 17 | SmtkKeyMode mode, const char *layout, 18 | const char *variant, GError **error); 19 | void smtk_keys_emitter_start_async(SmtkKeysEmitter *emitter, GError **error); 20 | void smtk_keys_emitter_stop_async(SmtkKeysEmitter *emitter); 21 | void smtk_keys_emitter_set_mode(SmtkKeysEmitter *emitter, SmtkKeyMode mode); 22 | void smtk_keys_emitter_set_show_shift(SmtkKeysEmitter *emitter, 23 | bool show_shift); 24 | void smtk_keys_emitter_set_show_keyboard(SmtkKeysEmitter *emitter, 25 | bool show_keyboard); 26 | void smtk_keys_emitter_set_show_mouse(SmtkKeysEmitter *emitter, 27 | bool show_mouse); 28 | void smtk_keys_emitter_set_hide_visible(SmtkKeysEmitter *emitter, 29 | bool hide_visible); 30 | void smtk_keys_emitter_set_layout(SmtkKeysEmitter *emitter, const char *layout); 31 | void smtk_keys_emitter_set_variant(SmtkKeysEmitter *emitter, 32 | const char *variant); 33 | 34 | G_END_DECLS 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-mapper.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_KEYS_MAPPER_H__ 2 | #define __SMTK_KEYS_MAPPER_H__ 3 | 4 | #include 5 | #include 6 | 7 | #include "smtk-event.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define SMTK_TYPE_KEYS_MAPPER smtk_keys_mapper_get_type() 12 | G_DECLARE_FINAL_TYPE(SmtkKeysMapper, smtk_keys_mapper, SMTK, KEYS_MAPPER, 13 | GObject) 14 | 15 | #define SMTK_KEYS_MAPPER_ERROR smtk_keys_mapper_error_quark() 16 | typedef enum { 17 | SMTK_KEYS_MAPPER_ERROR_XKB_CONTEXT, 18 | SMTK_KEYS_MAPPER_ERROR_XKB_KEYMAP, 19 | SMTK_KEYS_MAPPER_ERROR_XKB_STATE, 20 | SMTK_KEYS_MAPPER_ERROR_UNKNOWN 21 | } SmtkKeysMapperError; 22 | 23 | // It looks like glib-mkenums cannot make enums 24 | // if clang-format make this in one line. But why??? 25 | // clang-format off 26 | typedef enum { 27 | SMTK_KEY_MODE_COMPOSED, 28 | SMTK_KEY_MODE_RAW, 29 | SMTK_KEY_MODE_COMPACT 30 | } SmtkKeyMode; 31 | // clang-format on 32 | 33 | SmtkKeysMapper *smtk_keys_mapper_new(bool show_shift, bool hide_visible, 34 | const char *layout, const char *variant, 35 | GError **error); 36 | char *smtk_keys_mapper_get_raw(SmtkKeysMapper *mapper, SmtkEvent *event); 37 | char *smtk_keys_mapper_get_composed(SmtkKeysMapper *mapper, SmtkEvent *event); 38 | char *smtk_keys_mapper_get_compact(SmtkKeysMapper *mapper, SmtkEvent *event); 39 | void smtk_keys_mapper_set_show_shift(SmtkKeysMapper *mapper, bool show_shift); 40 | void smtk_keys_mapper_set_hide_visible(SmtkKeysMapper *mapper, 41 | bool hide_visible); 42 | void smtk_keys_mapper_set_layout(SmtkKeysMapper *mapper, const char *layout); 43 | void smtk_keys_mapper_set_variant(SmtkKeysMapper *mapper, const char *variant); 44 | 45 | G_END_DECLS 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | # include 6 | #endif 7 | 8 | #include "smtk.h" 9 | #include "smtk-keys-win.h" 10 | 11 | struct _SmtkKeysWin { 12 | AdwWindow parent_instance; 13 | SmtkAppWin *app_win; 14 | GtkWidget *box; 15 | GtkWidget *header_bar; 16 | GtkWidget *handle; 17 | GtkWidget *area; 18 | SmtkKeysEmitter *emitter; 19 | SmtkKeyMode mode; 20 | SmtkKeyAlignment alignment; 21 | bool clickable; 22 | bool paused; 23 | bool show_shift; 24 | bool show_keyboard; 25 | bool show_mouse; 26 | bool draw_border; 27 | bool hide_visible; 28 | int timeout; 29 | char *layout; 30 | char *variant; 31 | GError *error; 32 | }; 33 | G_DEFINE_TYPE(SmtkKeysWin, smtk_keys_win, ADW_TYPE_WINDOW) 34 | 35 | enum { SIG_PAUSE, N_SIGNALS }; 36 | 37 | static unsigned int obj_signals[N_SIGNALS] = { 0 }; 38 | 39 | enum { 40 | PROP_0, 41 | PROP_CLICKABLE, 42 | PROP_SHOW_SHIFT, 43 | PROP_SHOW_KEYBOARD, 44 | PROP_SHOW_MOUSE, 45 | PROP_DRAW_BORDER, 46 | PROP_HIDE_VISIBLE, 47 | PROP_MODE, 48 | PROP_ALIGNMENT, 49 | PROP_TIMEOUT, 50 | PROP_LAYOUT, 51 | PROP_VARIANT, 52 | N_PROPS 53 | }; 54 | 55 | static GParamSpec *obj_props[N_PROPS] = { NULL }; 56 | 57 | static void smtk_keys_win_set_property(GObject *object, 58 | unsigned int property_id, 59 | const GValue *value, GParamSpec *pspec) 60 | { 61 | SmtkKeysWin *win = SMTK_KEYS_WIN(object); 62 | 63 | switch (property_id) { 64 | case PROP_CLICKABLE: 65 | smtk_keys_win_set_clickable(win, g_value_get_boolean(value)); 66 | break; 67 | case PROP_SHOW_SHIFT: 68 | smtk_keys_win_set_show_shift(win, g_value_get_boolean(value)); 69 | break; 70 | case PROP_SHOW_KEYBOARD: 71 | smtk_keys_win_set_show_keyboard(win, 72 | g_value_get_boolean(value)); 73 | break; 74 | case PROP_SHOW_MOUSE: 75 | smtk_keys_win_set_show_mouse(win, g_value_get_boolean(value)); 76 | break; 77 | case PROP_DRAW_BORDER: 78 | smtk_keys_win_set_draw_border(win, g_value_get_boolean(value)); 79 | break; 80 | case PROP_HIDE_VISIBLE: 81 | smtk_keys_win_set_hide_visible(win, g_value_get_boolean(value)); 82 | break; 83 | case PROP_MODE: 84 | smtk_keys_win_set_mode(win, g_value_get_enum(value)); 85 | break; 86 | case PROP_ALIGNMENT: 87 | smtk_keys_win_set_alignment(win, g_value_get_enum(value)); 88 | break; 89 | case PROP_TIMEOUT: 90 | smtk_keys_win_set_timeout(win, g_value_get_int(value)); 91 | break; 92 | case PROP_LAYOUT: 93 | smtk_keys_win_set_layout(win, g_value_get_string(value)); 94 | break; 95 | case PROP_VARIANT: 96 | smtk_keys_win_set_variant(win, g_value_get_string(value)); 97 | break; 98 | default: 99 | /* We don't have any other property... */ 100 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 101 | break; 102 | } 103 | } 104 | 105 | static void smtk_keys_win_get_property(GObject *object, 106 | unsigned int property_id, GValue *value, 107 | GParamSpec *pspec) 108 | { 109 | SmtkKeysWin *win = SMTK_KEYS_WIN(object); 110 | 111 | switch (property_id) { 112 | case PROP_CLICKABLE: 113 | g_value_set_boolean(value, win->clickable); 114 | break; 115 | case PROP_SHOW_SHIFT: 116 | g_value_set_boolean(value, win->show_shift); 117 | break; 118 | case PROP_SHOW_KEYBOARD: 119 | g_value_set_boolean(value, win->show_keyboard); 120 | break; 121 | case PROP_SHOW_MOUSE: 122 | g_value_set_boolean(value, win->show_mouse); 123 | break; 124 | case PROP_DRAW_BORDER: 125 | g_value_set_boolean(value, win->draw_border); 126 | break; 127 | case PROP_HIDE_VISIBLE: 128 | g_value_set_boolean(value, win->hide_visible); 129 | break; 130 | case PROP_MODE: 131 | g_value_set_enum(value, win->mode); 132 | break; 133 | case PROP_ALIGNMENT: 134 | g_value_set_enum(value, win->alignment); 135 | break; 136 | case PROP_TIMEOUT: 137 | g_value_set_int(value, win->timeout); 138 | break; 139 | case PROP_LAYOUT: 140 | g_value_set_string(value, smtk_keys_win_get_layout(win)); 141 | break; 142 | case PROP_VARIANT: 143 | g_value_set_string(value, smtk_keys_win_get_variant(win)); 144 | break; 145 | default: 146 | /* We don't have any other property... */ 147 | G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); 148 | break; 149 | } 150 | } 151 | 152 | static void smtk_keys_win_emitter_on_error_cli_exit(SmtkKeysWin *win, 153 | SmtkKeysEmitter *emitter) 154 | { 155 | gtk_window_destroy(GTK_WINDOW(win)); 156 | } 157 | 158 | static void smtk_keys_win_emitter_on_key(SmtkKeysWin *win, char key[]) 159 | { 160 | if (win->paused) 161 | return; 162 | 163 | // It seems that GObject closure will free string argument. 164 | // See . 165 | // void (*callback)(gpointer instance, const gchar *arg1, gpointer user_data) 166 | smtk_keys_area_add_key(SMTK_KEYS_AREA(win->area), g_strdup(key)); 167 | } 168 | 169 | static void smtk_keys_win_emitter_on_pause(SmtkKeysWin *win) 170 | { 171 | g_signal_emit_by_name(win, "pause"); 172 | } 173 | 174 | #ifdef GDK_WINDOWING_X11 175 | // See . 176 | static void gdk_x11_surface_wmspec_change_state(GdkSurface *surface, bool add, 177 | const char *state) 178 | { 179 | GdkDisplay *display = gdk_surface_get_display(surface); 180 | Display *xdisplay = gdk_x11_display_get_xdisplay(display); 181 | XClientMessageEvent xclient; 182 | 183 | # define _NET_WM_STATE_REMOVE 0 184 | # define _NET_WM_STATE_ADD 1 185 | # define _NET_WM_STATE_TOGGLE 2 186 | 187 | memset(&xclient, 0, sizeof(xclient)); 188 | xclient.type = ClientMessage; 189 | xclient.window = gdk_x11_surface_get_xid(surface); 190 | xclient.display = xdisplay; 191 | xclient.message_type = 192 | gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_STATE"); 193 | xclient.format = 32; 194 | xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; 195 | xclient.data.l[1] = 196 | gdk_x11_get_xatom_by_name_for_display(display, state); 197 | xclient.data.l[2] = None; 198 | // Source indication. 199 | xclient.data.l[3] = 1; 200 | xclient.data.l[4] = 0; 201 | 202 | XSendEvent(xdisplay, gdk_x11_display_get_xrootwindow(display), False, 203 | SubstructureRedirectMask | SubstructureNotifyMask, 204 | (XEvent *)&xclient); 205 | } 206 | 207 | // See . 208 | static void gdk_x11_surface_wmspec_change_desktop(GdkSurface *surface, 209 | long desktop) 210 | { 211 | GdkDisplay *display = gdk_surface_get_display(surface); 212 | Display *xdisplay = gdk_x11_display_get_xdisplay(display); 213 | XClientMessageEvent xclient; 214 | 215 | memset(&xclient, 0, sizeof(xclient)); 216 | xclient.type = ClientMessage; 217 | xclient.window = gdk_x11_surface_get_xid(surface); 218 | xclient.display = xdisplay; 219 | xclient.message_type = gdk_x11_get_xatom_by_name_for_display( 220 | display, "_NET_WM_DESKTOP"); 221 | xclient.format = 32; 222 | xclient.data.l[0] = desktop; 223 | // Source indication. 224 | xclient.data.l[1] = 0; 225 | xclient.data.l[2] = 0; 226 | xclient.data.l[3] = 0; 227 | xclient.data.l[4] = 0; 228 | 229 | XSendEvent(xdisplay, gdk_x11_display_get_xrootwindow(display), False, 230 | SubstructureRedirectMask | SubstructureNotifyMask, 231 | (XEvent *)&xclient); 232 | } 233 | #endif 234 | 235 | static void smtk_keys_win_on_map(SmtkKeysWin *win, gpointer user_data) 236 | { 237 | // GTK4 dropped those API, so we need to implement those by ourselves 238 | // via X11 WMSpec. 239 | // See . 240 | // gtk_window_set_keep_above(GTK_WINDOW(win), true); 241 | // gtk_window_stick(GTK_WINDOW(win)); 242 | #ifdef GDK_WINDOWING_X11 243 | GtkNative *native = gtk_widget_get_native(GTK_WIDGET(win)); 244 | if (native != NULL) { 245 | GdkSurface *surface = gtk_native_get_surface(native); 246 | GdkDisplay *display = gdk_surface_get_display(surface); 247 | if (GDK_IS_X11_DISPLAY(display)) { 248 | // Always on top. 249 | // See . 250 | // Need to remove _NET_WM_STATE_BELOW first. 251 | gdk_x11_surface_wmspec_change_state( 252 | surface, false, "_NET_WM_STATE_BELOW"); 253 | gdk_x11_surface_wmspec_change_state( 254 | surface, true, "_NET_WM_STATE_ABOVE"); 255 | 256 | // Always on visible workspaces. 257 | // See . 258 | // _NET_WM_STATE_STICKY only means WM should keep the 259 | // window's position fixed, even when scrolling virtual 260 | // desktops. 261 | gdk_x11_surface_wmspec_change_state( 262 | surface, true, "_NET_WM_STATE_STICKY"); 263 | // See . 264 | // Setting desktop to 0xFFFFFFFF means shows on all 265 | // desktops. 266 | // See . 267 | gdk_x11_surface_wmspec_change_desktop(surface, 268 | 0xFFFFFFFF); 269 | } 270 | } 271 | #endif 272 | } 273 | 274 | // NOTE: Not sure why but we can only alter input region in this function, 275 | // calling `gdk_surface_set_input_region()` in setter is invalid. 276 | static void smtk_keys_win_size_allocate(GtkWidget *widget, int width, 277 | int height, int baseline) 278 | { 279 | SmtkKeysWin *win = SMTK_KEYS_WIN(widget); 280 | 281 | // We handle our magic after GTK done its internal layout compute. 282 | // We only read but not adjust allocation so this is safe. 283 | GTK_WIDGET_CLASS(smtk_keys_win_parent_class) 284 | ->size_allocate(widget, width, height, baseline); 285 | 286 | g_debug("Allocated size: %d×%d.", width, height); 287 | 288 | smtk_app_win_set_size(win->app_win, width, height); 289 | 290 | GtkNative *native = gtk_widget_get_native(widget); 291 | if (native != NULL) { 292 | GdkSurface *surface = gtk_native_get_surface(native); 293 | if (win->clickable) { 294 | // See . 295 | // The initial value for an input region is infinite. 296 | // That means the whole surface will accept input. A 297 | // NULL wl_region causes the input region to be set to 298 | // infinite. 299 | gdk_surface_set_input_region(surface, NULL); 300 | } else { 301 | cairo_region_t *empty_region = cairo_region_create(); 302 | gdk_surface_set_input_region(surface, empty_region); 303 | cairo_region_destroy(empty_region); 304 | } 305 | } 306 | } 307 | 308 | static void smtk_keys_win_init(SmtkKeysWin *win) 309 | { 310 | // TODO: Are those comments still true for GTK4? 311 | // It seems a widget from `.ui` file is unable to set to transparent. 312 | // So we have to make UI from code. 313 | win->error = NULL; 314 | win->paused = false; 315 | 316 | win->handle = NULL; 317 | win->emitter = NULL; 318 | win->area = NULL; 319 | win->layout = NULL; 320 | win->variant = NULL; 321 | 322 | // AdwApplication will automatically load `style.css` under resource 323 | // base path, so we don't need to load it manually, just add a class so 324 | // we change style of the keys window only. 325 | gtk_widget_add_css_class(GTK_WIDGET(win), "smtk-keys-win"); 326 | 327 | // Since libadwaita v1.6, it starts to set minimal size of window to 328 | // 320x200, however 200 is too large for some users when using this keys 329 | // window, so we unset it. 330 | // 331 | // See . 332 | gtk_widget_set_size_request(GTK_WIDGET(win), -1, -1); 333 | 334 | // Don't know why but realize does not work. 335 | g_signal_connect(GTK_WIDGET(win), "map", 336 | G_CALLBACK(smtk_keys_win_on_map), NULL); 337 | } 338 | 339 | static void smtk_keys_win_constructed(GObject *object) 340 | { 341 | // Seems we can only get constructor properties here. 342 | SmtkKeysWin *win = SMTK_KEYS_WIN(object); 343 | 344 | win->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 345 | adw_window_set_content(ADW_WINDOW(win), win->box); 346 | 347 | // Allow user to choose position by drag this. 348 | win->header_bar = adw_header_bar_new(); 349 | adw_header_bar_set_show_start_title_buttons( 350 | ADW_HEADER_BAR(win->header_bar), false); 351 | adw_header_bar_set_show_end_title_buttons( 352 | ADW_HEADER_BAR(win->header_bar), false); 353 | win->handle = adw_window_title_new(_("Clickable"), NULL); 354 | adw_header_bar_set_title_widget(ADW_HEADER_BAR(win->header_bar), 355 | win->handle); 356 | gtk_box_append(GTK_BOX(win->box), win->header_bar); 357 | gtk_widget_set_visible(win->handle, win->clickable); 358 | 359 | win->emitter = smtk_keys_emitter_new(win->show_shift, 360 | win->show_keyboard, 361 | win->show_mouse, win->hide_visible, 362 | win->mode, win->layout, 363 | win->variant, &win->error); 364 | // `win->error` is set so just return. 365 | if (win->emitter == NULL) 366 | goto out; 367 | g_signal_connect_swapped( 368 | win->emitter, "error-cli-exit", 369 | G_CALLBACK(smtk_keys_win_emitter_on_error_cli_exit), win); 370 | g_signal_connect_swapped(win->emitter, "key", 371 | G_CALLBACK(smtk_keys_win_emitter_on_key), win); 372 | g_signal_connect_swapped(win->emitter, "pause", 373 | G_CALLBACK(smtk_keys_win_emitter_on_pause), 374 | win); 375 | 376 | smtk_keys_emitter_start_async(win->emitter, &win->error); 377 | if (win->error != NULL) 378 | goto out; 379 | 380 | win->area = smtk_keys_area_new(win->mode, win->alignment, 381 | win->draw_border, win->timeout); 382 | gtk_box_append(GTK_BOX(win->box), win->area); 383 | 384 | out: 385 | G_OBJECT_CLASS(smtk_keys_win_parent_class)->constructed(object); 386 | } 387 | 388 | static void smtk_keys_win_dispose(GObject *object) 389 | { 390 | SmtkKeysWin *win = SMTK_KEYS_WIN(object); 391 | 392 | if (win->emitter != NULL) { 393 | smtk_keys_emitter_stop_async(win->emitter); 394 | g_object_unref(win->emitter); 395 | win->emitter = NULL; 396 | } 397 | 398 | G_OBJECT_CLASS(smtk_keys_win_parent_class)->dispose(object); 399 | } 400 | 401 | static void smtk_keys_win_finalize(GObject *object) 402 | { 403 | SmtkKeysWin *win = SMTK_KEYS_WIN(object); 404 | 405 | g_clear_pointer(&win->layout, g_free); 406 | g_clear_pointer(&win->variant, g_free); 407 | } 408 | 409 | static void smtk_keys_win_class_init(SmtkKeysWinClass *win_class) 410 | { 411 | GObjectClass *object_class = G_OBJECT_CLASS(win_class); 412 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(win_class); 413 | 414 | object_class->set_property = smtk_keys_win_set_property; 415 | object_class->get_property = smtk_keys_win_get_property; 416 | 417 | object_class->constructed = smtk_keys_win_constructed; 418 | 419 | object_class->dispose = smtk_keys_win_dispose; 420 | object_class->finalize = smtk_keys_win_finalize; 421 | 422 | // In GTK4 size allocate is not a signal but a virtual method, but I 423 | // really need it. 424 | widget_class->size_allocate = smtk_keys_win_size_allocate; 425 | 426 | obj_signals[SIG_PAUSE] = g_signal_new("pause", SMTK_TYPE_KEYS_WIN, 427 | G_SIGNAL_RUN_LAST, 0, NULL, NULL, 428 | g_cclosure_marshal_VOID__VOID, 429 | G_TYPE_NONE, 0); 430 | 431 | obj_props[PROP_CLICKABLE] = g_param_spec_boolean( 432 | "clickable", "Clickable", "Clickable or Click Through", true, 433 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 434 | obj_props[PROP_MODE] = g_param_spec_enum( 435 | "mode", "Mode", "Key Mode", SMTK_TYPE_KEY_MODE, 436 | SMTK_KEY_MODE_COMPOSED, G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 437 | obj_props[PROP_ALIGNMENT] = g_param_spec_enum( 438 | "alignment", "Alignment", "Key Alignment", 439 | SMTK_TYPE_KEY_ALIGNMENT, SMTK_KEY_ALIGNMENT_END, 440 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 441 | obj_props[PROP_SHOW_SHIFT] = g_param_spec_boolean( 442 | "show-shift", "Show Shift", "Show Shift Separately", true, 443 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 444 | obj_props[PROP_SHOW_KEYBOARD] = g_param_spec_boolean( 445 | "show-keyboard", "Show Keyboard", "Show keyboard key", true, 446 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 447 | obj_props[PROP_SHOW_MOUSE] = g_param_spec_boolean( 448 | "show-mouse", "Show Mouse", "Show Mouse Button", true, 449 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 450 | obj_props[PROP_DRAW_BORDER] = g_param_spec_boolean( 451 | "draw-border", "Draw Border", "Draw Keys Border", true, 452 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 453 | obj_props[PROP_HIDE_VISIBLE] = g_param_spec_boolean( 454 | "hide-visible", "Hide Visible", "Hide Visible Keys", false, 455 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 456 | obj_props[PROP_TIMEOUT] = g_param_spec_int( 457 | "timeout", "Text Timeout", "Text Timeout", 0, 30000, 1000, 458 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 459 | obj_props[PROP_LAYOUT] = 460 | g_param_spec_string("layout", "Layout", "Keymap Layout", NULL, 461 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 462 | obj_props[PROP_VARIANT] = g_param_spec_string( 463 | "variant", "Variant", "Keymap Variant", NULL, 464 | G_PARAM_CONSTRUCT | G_PARAM_READWRITE); 465 | 466 | g_object_class_install_properties(object_class, N_PROPS, obj_props); 467 | } 468 | 469 | GtkWidget *smtk_keys_win_new(SmtkAppWin *app_win, bool clickable, 470 | bool show_shift, bool show_keyboard, 471 | bool show_mouse, bool draw_border, 472 | bool hide_visible, SmtkKeyMode mode, 473 | SmtkKeyAlignment alignment, int width, int height, 474 | int timeout, const char *layout, 475 | const char *variant, GError **error) 476 | { 477 | SmtkKeysWin *win = g_object_new( 478 | // Don't translate floating window's title, maybe users have 479 | // window rules for it. 480 | SMTK_TYPE_KEYS_WIN, "visible", true, "title", 481 | "Floating Window - Show Me The Key", "icon-name", 482 | "one.alynx.showmethekey", "can-focus", false, "focus-on-click", 483 | false, "vexpand", false, "vexpand-set", true, "hexpand", false, 484 | "hexpand-set", true, "focusable", false, "resizable", true, 485 | // Wayland does not support this, it's ok. 486 | // "skip-pager-hint", true, "skip-taskbar-hint", true, 487 | "clickable", clickable, "mode", mode, "alignment", alignment, 488 | "show-shift", show_shift, "show-keyboard", show_keyboard, 489 | "show-mouse", show_mouse, "draw-border", draw_border, 490 | "hide-visible", hide_visible, "timeout", timeout, "layout", 491 | layout, "variant", variant, NULL); 492 | 493 | if (win->error != NULL) { 494 | g_propagate_error(error, win->error); 495 | // GtkWidget is GInitiallyUnowned, 496 | // so we need to sink the floating first. 497 | g_object_ref_sink(win); 498 | gtk_window_destroy(GTK_WINDOW(win)); 499 | return NULL; 500 | } 501 | 502 | win->app_win = app_win; 503 | 504 | gtk_window_set_default_size(GTK_WINDOW(win), width, height); 505 | // Setting transient will block showing on all desktop so don't use it. 506 | // gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(parent)); 507 | 508 | // GTK always return GtkWidget, so do I. 509 | return GTK_WIDGET(win); 510 | } 511 | 512 | void smtk_keys_win_set_clickable(SmtkKeysWin *win, bool clickable) 513 | { 514 | g_return_if_fail(win != NULL); 515 | 516 | // We don't need the handle if click through. But the handle might not 517 | // be there during init. 518 | if (win->handle != NULL) 519 | gtk_widget_set_visible(win->handle, clickable); 520 | 521 | // NOTE: We don't handle input region here, I don't know why we can't. 522 | // We just save property and handle the input region in 523 | // `size_allocate()`. 524 | // Sync self property. 525 | win->clickable = clickable; 526 | } 527 | 528 | void smtk_keys_win_pause(SmtkKeysWin *win) 529 | { 530 | g_return_if_fail(win != NULL); 531 | 532 | win->paused = true; 533 | } 534 | 535 | void smtk_keys_win_resume(SmtkKeysWin *win) 536 | { 537 | g_return_if_fail(win != NULL); 538 | 539 | win->paused = false; 540 | } 541 | 542 | void smtk_keys_win_set_show_shift(SmtkKeysWin *win, bool show_shift) 543 | { 544 | g_return_if_fail(win != NULL); 545 | 546 | // Pass property to emitter. 547 | if (win->emitter != NULL) 548 | smtk_keys_emitter_set_show_shift(win->emitter, show_shift); 549 | // Sync self property. 550 | win->show_shift = show_shift; 551 | } 552 | 553 | void smtk_keys_win_set_show_keyboard(SmtkKeysWin *win, bool show_keyboard) 554 | { 555 | g_return_if_fail(win != NULL); 556 | 557 | // Pass property to emitter. 558 | if (win->emitter != NULL) 559 | smtk_keys_emitter_set_show_keyboard(win->emitter, 560 | show_keyboard); 561 | // Sync self property. 562 | win->show_keyboard = show_keyboard; 563 | } 564 | 565 | void smtk_keys_win_set_show_mouse(SmtkKeysWin *win, bool show_mouse) 566 | { 567 | g_return_if_fail(win != NULL); 568 | 569 | // Pass property to emitter. 570 | if (win->emitter != NULL) 571 | smtk_keys_emitter_set_show_mouse(win->emitter, show_mouse); 572 | // Sync self property. 573 | win->show_mouse = show_mouse; 574 | } 575 | 576 | void smtk_keys_win_set_draw_border(SmtkKeysWin *win, bool draw_border) 577 | { 578 | g_return_if_fail(win != NULL); 579 | 580 | // Pass property to area. 581 | if (win->area != NULL) 582 | smtk_keys_area_set_draw_border(SMTK_KEYS_AREA(win->area), 583 | draw_border); 584 | // Sync self property. 585 | win->draw_border = draw_border; 586 | } 587 | 588 | void smtk_keys_win_set_hide_visible(SmtkKeysWin *win, bool hide_visible) 589 | { 590 | g_return_if_fail(win != NULL); 591 | 592 | // Pass property to emitter. 593 | if (win->emitter != NULL) 594 | smtk_keys_emitter_set_hide_visible(win->emitter, hide_visible); 595 | // Sync self property. 596 | win->hide_visible = hide_visible; 597 | } 598 | 599 | void smtk_keys_win_set_mode(SmtkKeysWin *win, SmtkKeyMode mode) 600 | { 601 | g_return_if_fail(win != NULL); 602 | 603 | // Pass property to emitter. 604 | if (win->emitter != NULL) 605 | smtk_keys_emitter_set_mode(win->emitter, mode); 606 | // Pass property to area. 607 | if (win->area != NULL) 608 | smtk_keys_area_set_mode(SMTK_KEYS_AREA(win->area), mode); 609 | // Sync self property. 610 | win->mode = mode; 611 | } 612 | 613 | void smtk_keys_win_set_alignment(SmtkKeysWin *win, SmtkKeyAlignment alignment) 614 | { 615 | g_return_if_fail(win != NULL); 616 | 617 | // Pass property to area. 618 | if (win->area != NULL) 619 | smtk_keys_area_set_alignment(SMTK_KEYS_AREA(win->area), 620 | alignment); 621 | // Sync self property. 622 | win->alignment = alignment; 623 | } 624 | 625 | void smtk_keys_win_set_timeout(SmtkKeysWin *win, int timeout) 626 | { 627 | g_return_if_fail(win != NULL); 628 | 629 | // Pass property to area. 630 | if (win->area != NULL) 631 | smtk_keys_area_set_timeout(SMTK_KEYS_AREA(win->area), timeout); 632 | // Sync self property. 633 | win->timeout = timeout; 634 | } 635 | 636 | const char *smtk_keys_win_get_layout(SmtkKeysWin *win) 637 | { 638 | g_return_val_if_fail(win != NULL, NULL); 639 | 640 | return win->layout; 641 | } 642 | 643 | void smtk_keys_win_set_layout(SmtkKeysWin *win, const char *layout) 644 | { 645 | g_return_if_fail(win != NULL); 646 | 647 | if (win->emitter != NULL) 648 | smtk_keys_emitter_set_layout(win->emitter, layout); 649 | 650 | if (win->layout != NULL) 651 | g_free(win->layout); 652 | win->layout = g_strdup(layout); 653 | } 654 | 655 | const char *smtk_keys_win_get_variant(SmtkKeysWin *win) 656 | { 657 | g_return_val_if_fail(win != NULL, NULL); 658 | 659 | return win->variant; 660 | } 661 | 662 | void smtk_keys_win_set_variant(SmtkKeysWin *win, const char *variant) 663 | { 664 | g_return_if_fail(win != NULL); 665 | 666 | if (win->emitter != NULL) 667 | smtk_keys_emitter_set_variant(win->emitter, variant); 668 | 669 | if (win->variant != NULL) 670 | g_free(win->variant); 671 | win->variant = g_strdup(variant); 672 | } 673 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk-keys-win.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_KEYS_WIN_H__ 2 | #define __SMTK_KEYS_WIN_H__ 3 | 4 | #include 5 | #include 6 | 7 | #include "smtk-app-win.h" 8 | #include "smtk-keys-area.h" 9 | #include "smtk-keys-emitter.h" 10 | 11 | G_BEGIN_DECLS 12 | 13 | #define SMTK_TYPE_KEYS_WIN smtk_keys_win_get_type() 14 | G_DECLARE_FINAL_TYPE(SmtkKeysWin, smtk_keys_win, SMTK, KEYS_WIN, AdwWindow) 15 | 16 | GtkWidget *smtk_keys_win_new(SmtkAppWin *app_win, bool clickable, 17 | bool show_shift, bool show_keyboard, 18 | bool show_mouse, bool draw_border, 19 | bool hide_visible, SmtkKeyMode mode, 20 | SmtkKeyAlignment alignment, int width, int height, 21 | int timeout, const char *layout, 22 | const char *variant, GError **error); 23 | void smtk_keys_win_set_clickable(SmtkKeysWin *win, bool clickable); 24 | void smtk_keys_win_pause(SmtkKeysWin *win); 25 | void smtk_keys_win_resume(SmtkKeysWin *win); 26 | void smtk_keys_win_set_mode(SmtkKeysWin *win, SmtkKeyMode mode); 27 | void smtk_keys_win_set_alignment(SmtkKeysWin *win, SmtkKeyAlignment alignment); 28 | void smtk_keys_win_set_show_shift(SmtkKeysWin *win, bool show_shift); 29 | void smtk_keys_win_set_show_keyboard(SmtkKeysWin *win, bool show_keyboard); 30 | void smtk_keys_win_set_show_mouse(SmtkKeysWin *win, bool show_mouse); 31 | void smtk_keys_win_set_draw_border(SmtkKeysWin *win, bool draw_border); 32 | void smtk_keys_win_set_hide_visible(SmtkKeysWin *win, bool hide_visible); 33 | void smtk_keys_win_set_timeout(SmtkKeysWin *win, int timeout); 34 | const char *smtk_keys_win_get_layout(SmtkKeysWin *win); 35 | void smtk_keys_win_set_layout(SmtkKeysWin *win, const char *layout); 36 | const char *smtk_keys_win_get_variant(SmtkKeysWin *win); 37 | void smtk_keys_win_set_variant(SmtkKeysWin *win, const char *variant); 38 | 39 | G_END_DECLS 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /showmethekey-gtk/smtk.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMTK_H__ 2 | #define __SMTK_H__ 3 | 4 | #ifdef G_LOG_DOMAIN 5 | # undef G_LOG_DOMAIN 6 | # define G_LOG_DOMAIN "showmethekey-gtk" 7 | #endif 8 | 9 | #include "config.h" 10 | 11 | // Type generated in meson.build. 12 | // See 13 | // and . 14 | #include "smtk-enum-types.h" 15 | #include "smtk-app.h" 16 | #include "smtk-app-win.h" 17 | #include "smtk-keys-win.h" 18 | #include "smtk-keys-area.h" 19 | #include "smtk-keys-emitter.h" 20 | #include "smtk-keys-mapper.h" 21 | #include "smtk-event.h" 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /showmethekey-gtk/style.css: -------------------------------------------------------------------------------- 1 | /* Disable window shadow. */ 2 | window.smtk-keys-win { 3 | margin: 0; 4 | box-shadow: none; 5 | border-radius: 0; 6 | outline: none; 7 | } 8 | /* Half transparent window. */ 9 | window.smtk-keys-win.background { 10 | background: rgba(0, 0, 0, 0.3); 11 | } 12 | 13 | /* Create a transparent header bar. */ 14 | window.smtk-keys-win headerbar { 15 | background-color: rgba(0, 0, 0, 0); 16 | /* In some theme (e.g. Adwaita) it set a background image. */ 17 | background-image: none; 18 | /* Use this to get a compact header bar when subtitle is disabled. */ 19 | min-height: 0px; 20 | box-shadow: none; 21 | border: none; 22 | } 23 | 24 | /* Don't change color when toggle dark mode, we are always dark. */ 25 | window.smtk-keys-win headerbar windowhandle box windowtitle { 26 | color: rgba(255, 255, 255, 0.7); 27 | } 28 | --------------------------------------------------------------------------------