├── .gitignore ├── 80-dvorak.rules ├── dvorak@.service ├── Makefile ├── README.md ├── LICENSE └── dvorak.c /.gitignore: -------------------------------------------------------------------------------- 1 | dvorak 2 | dvorak.o 3 | .project 4 | .idea 5 | .vscode 6 | .vscode/settings.json 7 | .zed -------------------------------------------------------------------------------- /80-dvorak.rules: -------------------------------------------------------------------------------- 1 | SUBSYSTEM=="input", KERNEL=="event[0-9]*", ACTION=="add", RUN+="/bin/systemctl start dvorak@%k.service" 2 | -------------------------------------------------------------------------------- /dvorak@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Dvorak Virtual Keyboard 3 | 4 | [Service] 5 | ExecStart=/usr/local/bin/dvorak -d /dev/input/%i 6 | StandardOutput=null 7 | StandardError=journal -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #pkg-config from: https://www.geany.org/manual/gtk/glib/glib-compiling.html 2 | #https://github.com/joprietoe/gdbus/blob/master/Makefile 3 | #https://stackoverflow.com/questions/51269129/minimal-gdbus-client 4 | TARGET = dvorak 5 | CC = gcc 6 | CFLAGS = -Wall -O3 7 | 8 | .PHONY: default all clean install uninstall 9 | 10 | default: all 11 | 12 | all: dvorak.c 13 | $(CC) $(CFLAGS) -o $(TARGET) dvorak.c 14 | 15 | clean: 16 | -rm -f *.o 17 | -rm -f $(TARGET) 18 | 19 | install: 20 | cp dvorak /usr/local/bin/ 21 | cp 80-dvorak.rules /etc/udev/rules.d/ 22 | cp dvorak@.service /etc/systemd/system/ 23 | udevadm control --reload 24 | systemctl restart systemd-udevd.service 25 | systemctl daemon-reload 26 | 27 | uninstall: 28 | systemctl stop 'dvorak@*.service' 29 | rm /usr/local/bin/dvorak 30 | rm /etc/udev/rules.d/80-dvorak.rules 31 | rm /etc/systemd/system/dvorak@.service 32 | udevadm control --reload 33 | systemctl restart systemd-udevd.service 34 | systemctl daemon-reload 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dvorak <> Qwerty - Keyboard remapping for Linux when pressing L-CTRL, R-CTRL, L-ALT, L-WIN, CAPSLOCK 2 | 3 | Since I type with the "Dvorak" keyboard layout, the shortcuts such as ctrl-c, ctrl-x, or ctrl-v are not comfortable anymore for using the left hand only. 4 | 5 | Furthermore, many applications have their default shortcuts, which I'm used to. So for these shortcuts I prefer "Qwerty". Since there is no way to configure this, this program intercept these keys and remap them from "Dvorak" to "Qwerty" when pressing L-CTRL, R-CTRL, L-ALT, L-WIN, CAPSLOCK, or any of those combinations. CAPSLOCK is also used as a modifier, but can be disabled with the "-c" flag. 6 | 7 | With X11 I was relying on the [xdq](https://github.com/kentonv/dvorak-qwerty) from Kenton Varda. However, this does not work reliably with Wayland. 8 | 9 | ## Keyboard remapping with dvorak that works reliably with Wayland - make ctrl-c ctrl-c again (and not ctrl-i) 10 | 11 | X11's XGrabKey() works partially with some application but not with others (e.g., gedit is not working). Since XGrabKey() is an X11 function with some support in Wayland, I was looking for a more stable solution. After a quick look to this [repo](https://github.com/kentonv/dvorak-qwerty), I saw that Kenton added a systemtap script to implement the mapping. It scared me a bit to follow the systemtap path, so I implemented an other solution based on /dev/uinput. The idea is to read /dev/input, grab keys with EVIOCGRAB, create a virtual device that can emit the keys and pass the keys from /dev/input to /dev/uinput. If L-CTRL, R-CTRL, L-ALT, L-WIN, CAPSLOCK is pressed it will map the keys back to "Qwerty". 12 | 13 | This program is tested with Arch and Ubuntu, and Kenton Varda reported that it also works with Chrome OS. 14 | 15 | ## Installation 16 | 17 | * create binary with ```make``` 18 | * install it with ```sudo make install``` 19 | 20 | This will copy 3 files: dvorak, 80-dvorak.rules, and dvorak@.service 21 | 22 | The file is triggered on the udev rule and call dvorak systemd service with the device that was attached. The rule contains 23 | the search term "keyb k360 k750", that will match case insensitive the device name. Only a device with name that contains the substring 24 | "keyb k360 k750" will be considered. To prevent an endless loop, the newly created virtual device is excluded from mapping itself. If your keyboard name does not match these keywords, then you have to add a keyword matching your keyboard. 25 | 26 | That way, the program ```dvorak``` will be called whenever an input device is attached. 27 | 28 | If you have more mappings, e.g., a Dvorak mapping a non-Dvorak mapping, you can disable the mapping, as the shortcuts would be mappend as well, and for ctrl-c you need to press ctrl-i. To disable this mapping hit **3 times L-ALT** to disable the Dvorak <> Qwerty keyboard remapping. 29 | 30 | ## Run 31 | 32 | Most likely, you will need to use sudo as it needs access to those input device. The following parameters can be used: 33 | 34 | ``` 35 | usage: dvorak [OPTION] 36 | -u Enable Umlaut mapping. 37 | -d /dev/input/by-id/… Specifies which device should be captured. 38 | -m STRING Match only the STRING with the USB device name. 39 | STRING can contain multiple words, separated by space. 40 | -t Disable layout toggle feature (press Left-Alt 3 times to switch layout). 41 | -c Disable caps lock as a modifier. 42 | 43 | example: dvorak -u -d /dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-kbd -m "k750 k350" 44 | ``` 45 | Once installed via ```make install```, the mapping will be applied whenever a keyboard is attached, as it listends to the udev event when a device is attached. 46 | 47 | The flag ```-u``` was added to remap some keys when using the ```Dvorak intl., with dead keys``` keyboard layout. Since this layout is handy for special characters it deviates too much from the original US-based Dvorak layout. So this -u flag maps some characters back. Only use this if you are using ```Dvorak intl., with dead keys```. 48 | 49 | ## Not a matching device: [xyz] 50 | 51 | If you see the above message in syslog or journalctl, it means that your keyboard device name does not have the string "keyb" (case insensitive) in it. For example, ```Not a matching device: [Logitech K360]```. In order to make it work with your device, in dvorak@.service, you can call the executable with 52 | 53 | ``` 54 | ExecStart=/usr/bin/dvorak /dev/input/%i keyb k360 55 | ``` 56 | 57 | ## Unistallation 58 | 59 | To uninstall with make use: 60 | 61 | ``` 62 | sudo make uninstall 63 | ``` 64 | 65 | To uninstall manually, you can type (if you are not root, use sudo): 66 | 67 | ``` 68 | systemctl stop 'dvorak@*.service' 69 | rm /usr/local/bin/dvorak 70 | rm /etc/udev/rules.d/80-dvorak.rules 71 | rm /etc/systemd/system/dvorak@.service 72 | udevadm control --reload 73 | systemctl restart systemd-udevd.service 74 | systemctl daemon-reload 75 | ``` 76 | 77 | ### Resolving a Boot Delay 78 | 79 | If you experience an x-minute boot delay after installing the script, it's likely due to the `systemd-udev-settle.service`. The code seems to call this service, causing the computer to wait for device initialization, which significantly slows down the boot process. 80 | 81 | This service is deprecated, and you can restore normal boot times by masking it with the following command: 82 | ```bash 83 | systemctl mask systemd-udev-settle.service 84 | ``` 85 | 86 | --- 87 | 88 | ## Related Links 89 | I used the following sites for inspiration: 90 | 91 | * https://www.kernel.org/doc/html/v4.12/input/uinput.html 92 | * https://www.linuxquestions.org/questions/programming-9/uinput-any-complete-example-4175524044/ 93 | * https://stackoverflow.com/questions/20943322/accessing-keys-from-linux-input-device 94 | * https://gist.github.com/toinsson/7e9fdd3c908b3c3d3cd635321d19d44d 95 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /dvorak.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Thomas Bocek 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | /* 19 | * Why is this tool useful? 20 | * ======================== 21 | * 22 | * Since I type with the "Dvorak" keyboard layout, the shortcuts such 23 | * as ctrl-c, ctrl-x, or ctrl-v are not comfortable anymore and one of them 24 | * require two hands to press. 25 | * 26 | * Furthermore, applications such as Intellij and Eclipse have their 27 | * shortcuts, which I'm used to. So for these shortcuts I prefer "Querty". 28 | * Since there is no way to configure this, I had to intercept the 29 | * keys and remap the keys from "Dvorak" to "Querty" once CTRL, ALT, 30 | * WIN or any of those combinations are pressed. 31 | * 32 | * With X.org I was relying on the wonderful tool from Kenton Varda, 33 | * which I modified a bit, to make it work when Numlock is active. Other 34 | * than that, it worked as expected. 35 | * 36 | * And then came Wayland. XGrabKey() works partially with some application 37 | * but not with others (e.g., gedit is not working). Since XGrabKey() is 38 | * an X.org function with some support in Wayland, I was looking for a more 39 | * stable solution. After a quick look to the repo https://github.com/kentonv/dvorak-qwerty 40 | * I saw that Kenton added a systemtap script to implement the mapping. This 41 | * scared me a bit to follow that path, so I implemented an other solution 42 | * based on /dev/uinput. The idea is to read /dev/input, grab keys with 43 | * EVIOCGRAB, create a virtual device that can emit the keys and pass 44 | * the keys from /dev/input to /dev/uinput. If CTRL/ALT/WIN is 45 | * pressed it will map the keys back to "Qwerty". 46 | * 47 | * Installation 48 | * =========== 49 | * 50 | * make dvorak 51 | * //make sure your user belongs to the group "input" -> ls -la /dev/input 52 | * //this also applies for /dev/uinput -> https://github.com/tuomasjjrasanen/python-uinput/blob/master/udev-rules/40-uinput.rules 53 | * //start it in startup applications 54 | * 55 | * Related Links 56 | * ============= 57 | * I used the following sites for inspiration: 58 | * https://www.kernel.org/doc/html/v4.12/input/uinput.html 59 | * https://www.linuxquestions.org/questions/programming-9/uinput-any-complete-example-4175524044/ 60 | * https://stackoverflow.com/questions/20943322/accessing-keys-from-linux-input-device 61 | * https://gist.github.com/toinsson/7e9fdd3c908b3c3d3cd635321d19d44d 62 | * 63 | */ 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | 74 | //a key combination has a maximum amount of 8 characters. That should be enough. 75 | #define MAX_LENGTH 8 76 | 77 | static int fdi; 78 | static volatile sig_atomic_t keep_running = 1; 79 | static void sig_handler(int sig) { 80 | keep_running = 0; 81 | close(fdi); 82 | } 83 | 84 | //from: https://github.com/kentonv/dvorak-qwerty/tree/master/unix 85 | static int modifier_bit(int key) { 86 | switch (key) { 87 | case KEY_LEFTCTRL: 88 | return 1; 89 | case KEY_RIGHTCTRL: 90 | return 2; 91 | case KEY_LEFTALT: 92 | return 4; 93 | case KEY_LEFTMETA: 94 | return 8; 95 | case KEY_CAPSLOCK: 96 | return 16; 97 | default: 98 | return 0; 99 | } 100 | } 101 | 102 | //from: https://github.com/kentonv/dvorak-qwerty/tree/master/unix 103 | static int qwerty2dvorak(int key) { 104 | switch (key) { 105 | case KEY_MINUS: 106 | return KEY_APOSTROPHE; 107 | case KEY_EQUAL: 108 | return KEY_RIGHTBRACE; 109 | case KEY_Q: 110 | return KEY_X; 111 | case KEY_W: 112 | return KEY_COMMA; 113 | case KEY_E: 114 | return KEY_D; 115 | case KEY_R: 116 | return KEY_O; 117 | case KEY_T: 118 | return KEY_K; 119 | case KEY_Y: 120 | return KEY_T; 121 | case KEY_U: 122 | return KEY_F; 123 | case KEY_I: 124 | return KEY_G; 125 | case KEY_O: 126 | return KEY_S; 127 | case KEY_P: 128 | return KEY_R; 129 | case KEY_LEFTBRACE: 130 | return KEY_MINUS; 131 | case KEY_RIGHTBRACE: 132 | return KEY_EQUAL; 133 | case KEY_A: 134 | return KEY_A; 135 | case KEY_S: 136 | return KEY_SEMICOLON; 137 | case KEY_D: 138 | return KEY_H; 139 | case KEY_F: 140 | return KEY_Y; 141 | case KEY_G: 142 | return KEY_U; 143 | case KEY_H: 144 | return KEY_J; 145 | case KEY_J: 146 | return KEY_C; 147 | case KEY_K: 148 | return KEY_V; 149 | case KEY_L: 150 | return KEY_P; 151 | case KEY_SEMICOLON: 152 | return KEY_Z; 153 | case KEY_APOSTROPHE: 154 | return KEY_Q; 155 | case KEY_Z: 156 | return KEY_SLASH; 157 | case KEY_X: 158 | return KEY_B; 159 | case KEY_C: 160 | return KEY_I; 161 | case KEY_V: 162 | return KEY_DOT; 163 | case KEY_B: 164 | return KEY_N; 165 | case KEY_N: 166 | return KEY_L; 167 | case KEY_M: 168 | return KEY_M; 169 | case KEY_COMMA: 170 | return KEY_W; 171 | case KEY_DOT: 172 | return KEY_E; 173 | case KEY_SLASH: 174 | return KEY_LEFTBRACE; 175 | default: 176 | return key; 177 | } 178 | } 179 | 180 | static ssize_t emit(int fd, int type, int code, int value, struct timeval time) { 181 | struct input_event ev = {0}; 182 | ev.type = type; 183 | ev.code = code; 184 | ev.value = value; 185 | ev.time = time; 186 | //fprintf(stdout, "Emit event type=%d code=%d value=%d\n",ev.type, ev.code, ev.value); 187 | return write(fd, &ev, sizeof(ev)); 188 | } 189 | 190 | static bool has_event_type(const unsigned int array_bit_ev[], int event_type) { 191 | return (array_bit_ev[event_type/32] & (1U << (event_type % 32))) != 0; 192 | } 193 | 194 | static bool setup_event_type(int fdo, unsigned long event_type, int max_val, const unsigned int array_bit[]) { 195 | struct uinput_abs_setup abs_setup = {}; 196 | bool abs_init_once = false; 197 | 198 | for (int i = 0; i < max_val; i++) { 199 | if (!(array_bit[i / 32] & (1U << (i % 32)))) { 200 | continue; 201 | } 202 | 203 | //fprintf(stderr, "Setting capability %d for event type %lu\n", i, event_type); 204 | switch(event_type) { 205 | case UI_SET_EVBIT: 206 | if (ioctl(fdo, UI_SET_EVBIT, i) < 0) { 207 | fprintf(stderr, "Cannot set EV bit %d: %s\n", i, strerror(errno)); 208 | return false; 209 | } 210 | break; 211 | case UI_SET_KEYBIT: 212 | if (ioctl(fdo, UI_SET_KEYBIT, i) < 0) { 213 | fprintf(stderr, "Cannot set KEY bit %d: %s\n", i, strerror(errno)); 214 | return false; 215 | } 216 | break; 217 | case UI_SET_RELBIT: 218 | if (ioctl(fdo, UI_SET_RELBIT, i) < 0) { 219 | fprintf(stderr, "Cannot set REL bit %d: %s\n", i, strerror(errno)); 220 | return false; 221 | } 222 | break; 223 | case UI_SET_ABSBIT: 224 | if (!abs_init_once) { 225 | abs_setup.code = i; 226 | if (ioctl(fdi, EVIOCGABS(i), &abs_setup.absinfo) < 0) { 227 | fprintf(stderr, "Failed to get ABS info for axis %d: %s\n", i, strerror(errno)); 228 | continue; 229 | } 230 | if (ioctl(fdo, UI_ABS_SETUP, &abs_setup) < 0) { 231 | fprintf(stderr, "Failed to setup ABS axis %d: %s\n", i, strerror(errno)); 232 | continue; 233 | } 234 | abs_init_once = true; 235 | } 236 | 237 | if (ioctl(fdo, UI_SET_ABSBIT, i) < 0) { 238 | fprintf(stderr, "Cannot set ABS bit %d: %s\n", i, strerror(errno)); 239 | return false; 240 | } 241 | break; 242 | case UI_SET_MSCBIT: 243 | if (ioctl(fdo, UI_SET_MSCBIT, i) < 0) { 244 | fprintf(stderr, "Cannot set MSC bit %d: %s\n", i, strerror(errno)); 245 | return false; 246 | } 247 | break; 248 | } 249 | } 250 | return true; 251 | } 252 | 253 | static void usage(const char *path) { 254 | /* take only the last portion of the path */ 255 | const char *basename = strrchr(path, '/'); 256 | basename = basename ? basename + 1 : path; 257 | 258 | fprintf(stderr, "usage: %s [OPTION]\n", basename); 259 | fprintf(stderr, " -d /dev/input/by-id/…\t" 260 | "Specifies which device should be captured.\n"); 261 | fprintf(stderr, " -m STRING\t\t" 262 | "Match only the STRING with the USB device name. \n" 263 | "\t\t\tSTRING can contain multiple words, separated by space.\n"); 264 | fprintf(stderr, " -t\t\t\t" 265 | "Disable layout toggle feature (press Left-Alt 3 times to switch layout).\n"); 266 | fprintf(stderr, " -c\t\t\t" 267 | "Disable caps lock as a modifier.\n\n"); 268 | fprintf(stderr, "example: %s -u -d /dev/input/by-id/usb-Logitech_USB_Receiver-if02-event-kbd -m \"k750 k350\"\n", basename); 269 | } 270 | 271 | int main(int argc, char *argv[]) { 272 | signal(SIGTERM, sig_handler); 273 | 274 | int opt; 275 | char *device = NULL, 276 | *match = NULL; 277 | bool noToggle = false, 278 | noCapsLockAsModifier = false; 279 | while ((opt = getopt(argc, argv, "d:m:tc")) != -1) { 280 | switch (opt) { 281 | case 'd': 282 | device = optarg; 283 | break; 284 | case 'm': 285 | match = optarg; 286 | break; 287 | case 't': 288 | noToggle = true; 289 | break; 290 | case 'c': 291 | noCapsLockAsModifier = true; 292 | break; 293 | default: 294 | usage(argv[0]); 295 | return EXIT_FAILURE; 296 | } 297 | } 298 | 299 | if (device == NULL) { 300 | usage(argv[0]); 301 | fprintf(stderr, "Error: Input device not specified.\n"); 302 | fprintf(stderr, "Hint: Provide a valid input device, typically found under /dev/input/by-id/...\n"); 303 | return EXIT_FAILURE; 304 | } 305 | 306 | //Start the fdi setup 307 | fdi = open(device, O_RDONLY); 308 | if (fdi < 0) { 309 | fprintf(stderr, "Error: Failed to open device [%s]: %s.\n", device, strerror(errno)); 310 | fprintf(stderr, "Hint: Check if the device path is correct and you have the necessary permissions.\n"); 311 | return EXIT_FAILURE; 312 | } 313 | 314 | char keyboard_name[UINPUT_MAX_NAME_SIZE] = "Unknown"; 315 | int ret_val = ioctl(fdi, EVIOCGNAME(sizeof(keyboard_name) - 1), keyboard_name); 316 | if (ret_val < 0) { 317 | fprintf(stderr, "Error: Unable to retrieve device name for [%s]: %s.\n", device, strerror(errno)); 318 | fprintf(stderr, "Hint: Verify if the device is functional and properly configured.\n"); 319 | close(fdi); 320 | return EXIT_FAILURE; 321 | } 322 | 323 | struct uinput_setup usetup = 324 | { .id = 325 | { .bustype = BUS_USB, .vendor = 0x1111, .product = 0x2222 }, 326 | .name = "Virtual Dvorak Keyboard" }; 327 | if (strcmp(keyboard_name, usetup.name) == 0) { 328 | fprintf(stdout, "Info: Skipping mapping for the device we just created: %s.\n", keyboard_name); 329 | close(fdi); 330 | return EXIT_SUCCESS; 331 | } 332 | 333 | ret_val = -1; 334 | if (match != NULL) { 335 | char *token = strtok(match, " "); 336 | while (token != NULL) { 337 | if (strcasestr(keyboard_name, token) != NULL) { 338 | printf("Info: Found matching input: [%s] for device [%s].\n", keyboard_name, device); 339 | ret_val = 0; 340 | break; 341 | } 342 | token = strtok(NULL, " "); 343 | } 344 | if (ret_val < 0) { 345 | fprintf(stderr, "Error: Device [%s] does not match any of the specified keywords: [%s].\n", keyboard_name, match); 346 | close(fdi); 347 | return EXIT_FAILURE; 348 | } 349 | } 350 | 351 | // Read capabilities 352 | unsigned int 353 | array_bit_ev[EV_MAX/32 + 1]= {0}, 354 | array_bit_key[KEY_MAX/32 + 1]= {0}, 355 | array_bit_rel[REL_MAX/32 + 1]= {0}, 356 | array_bit_abs[ABS_MAX/32 + 1]= {0}, 357 | array_bit_msc[MSC_MAX/32 + 1]= {0}; 358 | 359 | ret_val = ioctl(fdi, EVIOCGBIT(0, sizeof(array_bit_ev)), &array_bit_ev); 360 | if (ret_val < 0) { 361 | fprintf(stderr, "Error: Failed to retrieve event capabilities for device [%s]: %s.\n", device, strerror(errno)); 362 | close(fdi); 363 | return EXIT_FAILURE; 364 | } 365 | 366 | if (has_event_type(array_bit_ev, EV_KEY)) { 367 | ret_val = ioctl(fdi, EVIOCGBIT(EV_KEY, sizeof(array_bit_key)), &array_bit_key); 368 | if (ret_val < 0) { 369 | fprintf(stderr, "Error: Failed to retrieve EV_KEY capabilities for device [%s]: %s.\n", device, strerror(errno)); 370 | close(fdi); 371 | return EXIT_FAILURE; 372 | } 373 | } 374 | 375 | if (has_event_type(array_bit_ev, EV_REL)) { 376 | ret_val = ioctl(fdi, EVIOCGBIT(EV_REL, sizeof(array_bit_rel)), &array_bit_rel); 377 | if (ret_val < 0) { 378 | fprintf(stderr, "Error: Failed to retrieve EV_REL capabilities for device [%s]: %s.\n", device, strerror(errno)); 379 | close(fdi); 380 | return EXIT_FAILURE; 381 | } 382 | } 383 | 384 | if (has_event_type(array_bit_ev, EV_ABS)) { 385 | ret_val = ioctl(fdi, EVIOCGBIT(EV_ABS, sizeof(array_bit_abs)), &array_bit_abs); 386 | if (ret_val < 0) { 387 | fprintf(stderr, "Error: Failed to retrieve EV_ABS capabilities for device [%s]: %s.\n", device, strerror(errno)); 388 | close(fdi); 389 | return EXIT_FAILURE; 390 | } 391 | } 392 | 393 | if (has_event_type(array_bit_ev, EV_MSC)) { 394 | ret_val = ioctl(fdi, EVIOCGBIT(EV_MSC, sizeof(array_bit_msc)), &array_bit_msc); 395 | if (ret_val < 0) { 396 | fprintf(stderr, "Error: Failed to retrieve EV_MSC capabilities for device [%s]: %s.\n", device, strerror(errno)); 397 | close(fdi); 398 | return EXIT_FAILURE; 399 | } 400 | } 401 | 402 | //Check we are a keyboard 403 | if (!(array_bit_key[KEY_X / 32] & (1 << (KEY_X % 32))) || 404 | !(array_bit_key[KEY_C / 32] & (1 << (KEY_C % 32))) || 405 | !(array_bit_key[KEY_V / 32] & (1 << (KEY_V % 32)))) { 406 | fprintf(stdout, "Info: Device [%s] is not recognized as a keyboard (missing essential keys).\n", device); 407 | close(fdi); 408 | return EXIT_SUCCESS; 409 | } 410 | 411 | // Start the uinput setup 412 | int fdo = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 413 | if (fdo < 0) { 414 | fprintf(stderr, "Error: Failed to open /dev/uinput for device [%s]: %s.\n", device, strerror(errno)); 415 | close(fdi); 416 | return EXIT_FAILURE; 417 | } 418 | 419 | // Configure the virtual device 420 | if (ioctl(fdo, UI_DEV_SETUP, &usetup) < 0) { 421 | fprintf(stderr, "Error: Failed to configure the virtual device for [%s]: %s.\n", device, strerror(errno)); 422 | close(fdo); 423 | close(fdi); 424 | return EXIT_FAILURE; 425 | } 426 | 427 | if(!setup_event_type(fdo, UI_SET_EVBIT, EV_SW, array_bit_ev)) { 428 | fprintf(stderr, "Cannot setup_event_type for UI_SET_EVBIT/device [%s]: %s.\n", device, strerror(errno)); 429 | close(fdo); 430 | close(fdi); 431 | return EXIT_FAILURE; 432 | } 433 | 434 | if(!setup_event_type(fdo, UI_SET_KEYBIT, KEY_MAX, array_bit_key)) { 435 | fprintf(stderr, "Cannot setup_event_type for EV_KEY/device [%s]: %s.\n", device, strerror(errno)); 436 | close(fdo); 437 | close(fdi); 438 | return EXIT_FAILURE; 439 | } 440 | 441 | if(!setup_event_type(fdo, UI_SET_RELBIT, REL_MAX, array_bit_rel)) { 442 | fprintf(stderr, "Cannot setup_event_type for EV_REL/device [%s]: %s.\n", device, strerror(errno)); 443 | close(fdo); 444 | close(fdi); 445 | return EXIT_FAILURE; 446 | } 447 | 448 | if(!setup_event_type(fdo, UI_SET_ABSBIT, ABS_MAX, array_bit_abs)) { 449 | fprintf(stderr, "Cannot setup_event_type for EV_ABS/device [%s]: %s.\n", device, strerror(errno)); 450 | close(fdo); 451 | close(fdi); 452 | return EXIT_FAILURE; 453 | } 454 | 455 | if(!setup_event_type(fdo, UI_SET_MSCBIT, MSC_MAX, array_bit_msc)) { 456 | fprintf(stderr, "Cannot setup_event_type for MSC_MAX/device [%s]: %s.\n", device, strerror(errno)); 457 | close(fdo); 458 | close(fdi); 459 | return EXIT_FAILURE; 460 | } 461 | 462 | if (ioctl(fdo, UI_DEV_CREATE) < 0) { 463 | fprintf(stderr, "Cannot create device: %s.\n", strerror(errno)); 464 | close(fdo); 465 | close(fdi); 466 | return EXIT_FAILURE; 467 | } 468 | 469 | // Wait for device to be ready 470 | usleep(200000); 471 | 472 | if (ioctl(fdi, EVIOCGRAB, 1) < 0) { 473 | fprintf(stderr, "Cannot grab key: %s.\n", strerror(errno)); 474 | close(fdo); 475 | close(fdi); 476 | return EXIT_FAILURE; 477 | } 478 | 479 | struct input_event ev = {0}; 480 | int l_alt =0, 481 | mod_state = 0, 482 | array_qwerty_counter = 0; 483 | bool disable_mapping = false; 484 | unsigned int array_qwerty[MAX_LENGTH] = {0}; 485 | 486 | fprintf(stderr, "Staring event loop with keyboard: [%s] for device [%s].\n", keyboard_name, device); 487 | 488 | while (keep_running) { 489 | ssize_t n = read(fdi, &ev, sizeof ev); 490 | if (n == (ssize_t) -1) { 491 | if (errno == EINTR) 492 | continue; 493 | break; 494 | } else if (n != sizeof ev) { 495 | break; 496 | } 497 | 498 | if (!noToggle && ev.code == KEY_LEFTALT) { 499 | if (ev.value == 1 && ++l_alt >= 3) { 500 | disable_mapping = !disable_mapping; 501 | l_alt = 0; 502 | fprintf(stdout, "mapping is set to [%s]\n", !disable_mapping ? "true" : "false"); 503 | } 504 | } else if (ev.type == EV_KEY) { 505 | l_alt = 0; 506 | } 507 | 508 | if(!disable_mapping && ev.type == EV_KEY) { 509 | int mod_current = modifier_bit(ev.code); 510 | 511 | if(noCapsLockAsModifier && mod_current == modifier_bit(KEY_CAPSLOCK)) { 512 | mod_current = 0; 513 | } 514 | 515 | if (mod_current > 0) { 516 | if (ev.value != 0) { 517 | //set mod state when either 1 (key press), or 2 (repeat) 518 | mod_state |= mod_current; 519 | } else { 520 | //remove mod state when 0 (released) 521 | mod_state &= ~mod_current; 522 | } 523 | } 524 | 525 | int qwerty_code = qwerty2dvorak(ev.code); 526 | if (ev.code != qwerty_code) { 527 | //pressed key 528 | if (ev.value == 1) { 529 | //modifier pressed 530 | if(mod_state > 0) { 531 | if (array_qwerty_counter == MAX_LENGTH) { 532 | printf("warning, too many keys pressed: %d. %s 0x%04x (%d), arr:%d\n", 533 | MAX_LENGTH, ev.value == 1 ? "pressed" : "released", (int) ev.code, (int) ev.code, 534 | array_qwerty_counter); 535 | } else { 536 | array_qwerty[array_qwerty_counter++] = qwerty_code; 537 | //remap to qwerty - press key 538 | emit(fdo, ev.type, qwerty_code, ev.value, ev.time); 539 | } 540 | } else { 541 | //no modifier 542 | emit(fdo, ev.type, ev.code, ev.value, ev.time); 543 | } 544 | } else if(ev.value == 2) { 545 | //repeating button 546 | bool is_in_array = false; 547 | for (int i = 0; i < array_qwerty_counter; i++) { 548 | if (array_qwerty[i] == qwerty_code) { 549 | is_in_array = true; 550 | break; 551 | } 552 | } 553 | if(is_in_array) { 554 | //this is a repeating qwerty 555 | emit(fdo, ev.type, qwerty_code, ev.value, ev.time); 556 | } else { 557 | //not in the array, regular key 558 | emit(fdo, ev.type, ev.code, ev.value, ev.time); 559 | } 560 | } else if(ev.value == 0) { 561 | //release the key 562 | bool need_emit = false; 563 | for (int i = 0; i < array_qwerty_counter; i++) { 564 | if (array_qwerty[i] == qwerty_code) { 565 | array_qwerty[i] = 0; 566 | need_emit = true; 567 | break; 568 | } 569 | } 570 | if(need_emit) { 571 | int last_nonzero = -1; 572 | for (int i = 0; i < array_qwerty_counter; i++) { 573 | if (array_qwerty[i] != 0) { 574 | last_nonzero = i; 575 | } 576 | } 577 | array_qwerty_counter = last_nonzero + 1; 578 | //remap to qwerty - release key 579 | emit(fdo, ev.type, qwerty_code, ev.value, ev.time); 580 | } else { 581 | //regular dvorak key 582 | emit(fdo, ev.type, ev.code, ev.value, ev.time); 583 | } 584 | } else { 585 | //this should not happen 586 | emit(fdo, ev.type, ev.code, ev.value, ev.time); 587 | } 588 | } else { 589 | //regular dvorak key 590 | emit(fdo, ev.type, ev.code, ev.value, ev.time); 591 | } 592 | } else { 593 | //non regular key 594 | emit(fdo, ev.type, ev.code, ev.value, ev.time); 595 | } 596 | } 597 | close(fdi); 598 | close(fdo); 599 | return EXIT_SUCCESS; 600 | } --------------------------------------------------------------------------------