├── .gitignore ├── AUTHORS ├── LICENSE ├── NEWS ├── README.md ├── TODO ├── bash-completion ├── input-emulator └── meson.build ├── examples ├── kbd-mouse-test.sh ├── kbd-test.sh └── mouse-test.sh ├── man ├── input-emulator.1.in └── meson.build ├── meson.build ├── meson_options.txt ├── src ├── event.c ├── event.h ├── filesystem.h ├── keyboard.c ├── keyboard.h ├── main.c ├── meson.build ├── message.c ├── message.h ├── misc.c ├── misc.h ├── mouse.c ├── mouse.h ├── options.c ├── options.h ├── print.c ├── print.h ├── service.c ├── service.h ├── signals.c ├── signals.h ├── touch.c └── touch.h └── test ├── kbd-stress-test.sh ├── mouse-stress-test-2.sh ├── mouse-stress-test-3.sh └── mouse-stress-test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .cache 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Maintainer: 2 | Martin Lund 3 | 4 | Contributors: 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022-2023 Martin Lund 2 | Copyright (C) 2023 DEIF A/S 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | as published by the Free Software Foundation; either version 2 7 | of the License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License version 2 15 | along with tio, in a file named COPYING. If not, see 16 | . 17 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 2 | === input-emulator v0.8 === 3 | 4 | 5 | 6 | Changes since v0.7: 7 | 8 | * Fix mouse scroll feature 9 | 10 | * Add --type-delay to start option for keyboard 11 | 12 | Makes it possible to configure type delay in milliseconds. Default value 13 | is 15 ms. 14 | 15 | * Add mouse test example 16 | 17 | * Add mouse and keyboard stress tests 18 | 19 | * Update examples 20 | 21 | * Fix status print of mouse y-max value 22 | 23 | * Use abstract socket for client/server communication 24 | 25 | Use abstract socket for client/server communication instead of a socket 26 | file. This way we avoid being unable to start the service because of a 27 | stale socket file being left from a potential crash or if someone killed 28 | the service without proper shutdown. 29 | 30 | 31 | 32 | Changes since v0.6: 33 | 34 | * Rename mouse actions 35 | 36 | Rename 'mouse click' to 'mouse button' 37 | Rename 'mouse down' to 'mouse buttondown' 38 | Rename 'mouse up' to 'mouse buttonup' 39 | 40 | README and code updated accordingly. 41 | 42 | The new mouse action naming follows the keyboard action naming for 43 | better consistency. 44 | 45 | 46 | 47 | Changes since v0.5: 48 | 49 | * Add meson option to enable runtime debug output 50 | 51 | Add meson option 'enable-debug' which enables runtime debug output when 52 | set to true. Default value is false. 53 | 54 | * Update README 55 | 56 | * Add mouse and touch config details to status 57 | 58 | * Make communication more robust 59 | 60 | Make communication more robust by reworking the communication transport 61 | mechanism to use a socket instead of messages queues. 62 | 63 | This should solve the issue of the occasianally missing letters when 64 | using the keyboard simulator. 65 | 66 | 67 | 68 | Changes since v0.4: 69 | 70 | * Add kbd and mouse example 71 | 72 | * Rename keyboard-test.sh -> kbd-test.sh 73 | 74 | * Fix server/client communication 75 | 76 | * Update README 77 | 78 | * Update keyboard example 79 | 80 | 81 | 82 | Changes since v0.3: 83 | 84 | * Make sure commands are performed synchronously 85 | 86 | When scripting commands, request messages would pile up and overflow the 87 | message queue resulting in crash or errors. To avoid this we now wait 88 | for each command to finish (wait for ok response) before firing the next 89 | command. 90 | 91 | * Fix stop command crash 92 | 93 | * Wait for service to be fully initialized before exit 94 | 95 | To prevent command-line client from making request before service is 96 | fully initialized. 97 | 98 | * Make sure only one client is talking to server at a time 99 | 100 | * Update kbd example 101 | 102 | * Update README 103 | 104 | * Remove lua script code 105 | 106 | Can be reintroduced if needed. For now everything is perfectly 107 | scriptable via command-line commands. 108 | 109 | * Add 'mouse scroll ' command 110 | 111 | * Add bash completion script 112 | 113 | * Add alias_to_key() and more aliases 114 | 115 | * Move code to keyboard 116 | 117 | 118 | 119 | Changes since v0.2: 120 | 121 | * Add more key aliases 122 | 123 | * Add support for 'stop all' command 124 | 125 | Introduce 'stop all' command which will destroy all input devices which 126 | will make the daemon quit (daemon automatically quits when there are no 127 | more input devices online to maintain). 128 | 129 | 130 | 131 | Changes since v0.1: 132 | 133 | * Add LICENSE file 134 | 135 | 136 | 137 | v0.1: 138 | 139 | First release! 140 | 141 | Sponsored by DEIF A/S I have expanded the keyboard-simulator to also 142 | manage creation of virtual mouse and touch input devices through which 143 | high level mouse and touch actions (movement, clicks, gestures, etc.) 144 | can be performed via command-line or script. This is particular useful 145 | for test automation. 146 | 147 | Similar actions can be performed with existing tools but they are either 148 | bound to X (eg. xdotool) or operates with low level events 149 | (evemu-create, evemu-event, etc.) which make automation not as easy. 150 | 151 | A lot of code and new features have been added - too much to sumarize 152 | here. See the README or man page for more details. 153 | 154 | The new changes have resulted in the script feature being broken for 155 | now but it will eventually be fixed. 156 | 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # input-emulator - A scriptable input emulator 2 | 3 | [![](https://img.shields.io/github/v/release/tio/input-emulator?sort=semver)](https://github.com/tio/input-emulator/releases) 4 | [![](https://img.shields.io/codefactor/grade/github/tio/input-emulator)](https://www.codefactor.io/repository/github/tio/input-emulator) 5 | 6 | ## 1. Introduction 7 | 8 | A scriptable input emulator for Linux which instructs the kernel to create 9 | virtual keyboard, mouse, and touch input devices through which one can perform 10 | various high level actions (typing, movement, gestures, etc.) via command-line. 11 | 12 | Similar actions can be performed with existing tools but they are either 13 | bound to X (eg. xdotool) or operates with low level events (evemu-create, 14 | evemu-event, etc.) which make scripted automation not as easy. 15 | 16 | ### 1.1 Motivation 17 | 18 | To make a simple input emulator for test automation. 19 | 20 | input-emulator was originally named keyboard-simulator and started as part of 21 | the [tio](https://github.com/tio/tio) project to script keyboard input to 22 | automate generation of tio demonstration gifs. 23 | 24 | ## 2. features 25 | 26 | * Emulates the 3 arch type input devices: keyboard, mouse, and touch 27 | * Perform device actions via command-line 28 | * Keyboard actions: type, key, keydown, keyup 29 | * Mouse actions: move, click, down, up, scroll 30 | * Touch actions: tap 31 | * Start/stop individual input device 32 | * Input devices are maintained by background service (default) 33 | * Allows stable input device name 34 | * Status of service can be queried via command-line 35 | * Documented via man page 36 | * Supports various keyboard layouts (TODO, limited to DK for now) 37 | * Shell completion support (bash) 38 | 39 | ## 3. Usage 40 | 41 | ### 3.1 Command-line 42 | 43 | The command-line interface is straightforward as reflected in the output from 'input-emulator --help': 44 | 45 | ``` 46 | Usage: input-emulator [--version] [--help] [] 47 | 48 | -v, --version Display version 49 | -h, --help Display help 50 | 51 | Available commands: 52 | start [] kbd|mouse|touch Create virtual input device 53 | kbd Do keyboard action 54 | mouse Do mouse action 55 | touch Do touch action 56 | status Show status of virtual input devices 57 | stop kbd|mouse|touch|all Destroy virtual input device 58 | 59 | Start options: 60 | -x, --x-max Maximum x-coordinate (only for mouse and touch) 61 | -y, --y-max Maximum y-coordinate (only for mouse and touch) 62 | -s, --slots Maximum number of slots (fingers) recognized (only for touch) 63 | -d, --type-delay Type delay (only for keyboard, default: 15) 64 | -n, --no-daemonize Run in foreground 65 | 66 | Keyboard actions: 67 | type Type string 68 | key Stroke key (press and release) 69 | keydown Press key 70 | keyup Release key 71 | 72 | Mouse actions: 73 | move Move mouse x,y relative 74 | button left|middle|right Click mouse button (press and release) 75 | buttondown left|middle|right Press mouse button 76 | buttonup left|middle|right Release mouse button 77 | scroll Scroll mouse wheel number of ticks 78 | 79 | Touch actions: 80 | tap Tap at x,y coordinate 81 | ``` 82 | 83 | ### 3.2 Examples 84 | 85 | #### 3.2.1 Touch example 86 | ``` 87 | $ input-emulator start touch --x-max 2560 --y-max 1440 --slots 4 88 | $ input-emulator touch tap 1280 720 89 | $ input-emulator stop touch 90 | ``` 91 | #### 3.2.2 Mouse example 92 | ``` 93 | $ input-emulator start mouse --x-max 2560 --y-max 1440 94 | $ input-emulator mouse move 200 -300 95 | $ input-emulator mouse button left 96 | $ input-emulator mouse buttondown right 97 | $ input-emulator mouse buttonup right 98 | $ input-emulator mouse scroll -1 99 | $ input-emulator stop 100 | ``` 101 | #### 3.2.3 Keyboard example 102 | ``` 103 | $ input-emulator start kbd 104 | $ input-emulator kbd type 'hello there' 105 | $ input-emulator kbd keydown ctrl 106 | $ input-emulator kbd key t 107 | $ input-emulator kbd keyup ctrl 108 | $ input-emulator kbd key q 109 | $ input-emulator stop kbd 110 | ``` 111 | #### 3.2.4 Status example 112 | ``` 113 | $ input-emulator status 114 | Online devices: 115 | kbd: /sys/devices/virtual/input/input115 116 | mouse: /sys/devices/virtual/input/input113 (x-max: 1024 y-max: 768) 117 | touch: /sys/devices/virtual/input/input114 (x-max: 1024 y-max: 768 slots: 4) 118 | ``` 119 | 120 | ## 4. Installation 121 | 122 | ### Prerequisite 123 | 124 | For the input-emulator to be able to create emulated input devices the Linux 125 | kernel feature INPUT_UINPUT must be enabled (see drivers/input/misc/Kconfig in 126 | the Linux kernel). 127 | 128 | Most distributions have this feature enabled. 129 | 130 | ### 4.1 Installation from source 131 | Install steps: 132 | 133 | ``` 134 | $ meson build 135 | $ meson compile -C build 136 | $ meson install -C build 137 | ``` 138 | 139 | See meson_options.txt for input-emulator specific build options. 140 | 141 | Note: The meson install steps may differ depending on your specific system and 142 | environment. 143 | 144 | ### 4.2 Set up permissions for /dev/uinput 145 | 146 | To run input-emulator successfully as a normal user it needs access to the dev 147 | uinput /dev/uinput device. This is often permission protected so to gain access 148 | you can do the following: 149 | 150 | Create an uinput group: 151 | ``` 152 | $ sudo groupadd -f uinput 153 | ``` 154 | 155 | Add user to group: 156 | ``` 157 | usermod -a -G uinput 158 | ``` 159 | 160 | Create a udev rule /etc/udev/rules.d/99-input.rules containing: 161 | ``` 162 | KERNEL=="uinput", GROUP="uinput", MODE:="0660" 163 | ``` 164 | 165 | Then reboot computer and your user should have rw access to /dev/uinput and 166 | input-emulator should work as intended. 167 | 168 | 169 | ## 5. Contribute 170 | 171 | Feel free to improve the implementation. It is open source and released under 172 | the GPLv2 license. 173 | 174 | ## 6. Sponsors 175 | 176 | * DEIF A/S 177 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Support various keyboard layouts (right now only DK layout is supported) 2 | 3 | * Add support for more touch actions (doubletap, fingerdown/fingerup , moveto ) 4 | -------------------------------------------------------------------------------- /bash-completion/input-emulator: -------------------------------------------------------------------------------- 1 | # 2 | # Bash completion script for input-emulator 3 | # 4 | 5 | _input-emulator() 6 | { 7 | local cur prev firstword opts start_opts kbd_opts mouse_opts touch_opts 8 | 9 | COMPREPLY=() 10 | cur="${COMP_WORDS[COMP_CWORD]}" 11 | prev="${COMP_WORDS[COMP_CWORD-1]}" 12 | firstword=$(get_firstword) 13 | 14 | # The options we'll complete 15 | opts="-h --help \ 16 | -v --version \ 17 | start \ 18 | kbd \ 19 | mouse \ 20 | touch \ 21 | status \ 22 | stop" 23 | 24 | start_opts="-x --x-max \ 25 | -y --y-max \ 26 | -s --slots \ 27 | -n --no-daemonize 28 | kbd \ 29 | mouse \ 30 | touch" 31 | 32 | kbd_opts="type \ 33 | key \ 34 | keydown \ 35 | keyup" 36 | 37 | mouse_opts="move \ 38 | click \ 39 | down \ 40 | up \ 41 | scroll" 42 | 43 | touch_opts="tap " 44 | 45 | # Complete the options 46 | case "${COMP_CWORD}" in 47 | 1) 48 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 49 | ;; 50 | *) 51 | case ${firstword} in 52 | discover) 53 | COMPREPLY=( $(compgen -W "${start_opts}" -- ${cur}) ) 54 | ;; 55 | kbd) 56 | COMPREPLY=( $(compgen -W "${kbd_opts}" -- ${cur}) ) 57 | ;; 58 | mouse) 59 | COMPREPLY=( $(compgen -W "${mouse_opts}" -- ${cur}) ) 60 | ;; 61 | touch) 62 | COMPREPLY=( $(compgen -W "${touch_opts}" -- ${cur}) ) 63 | ;; 64 | *) 65 | COMPREPLY=() 66 | ;; 67 | esac 68 | ;; 69 | esac 70 | 71 | return 0 72 | } 73 | 74 | get_firstword() { 75 | local firstword i 76 | 77 | firstword= 78 | for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do 79 | if [[ ${COMP_WORDS[i]} != -* ]]; then 80 | firstword=${COMP_WORDS[i]} 81 | break 82 | fi 83 | done 84 | 85 | echo $firstword 86 | } 87 | 88 | # Bind completion to lxi command 89 | complete -o default -F _input-emulator input-emulator 90 | -------------------------------------------------------------------------------- /bash-completion/meson.build: -------------------------------------------------------------------------------- 1 | bashcompletiondir = get_option('bashcompletiondir') 2 | if bashcompletiondir == '' 3 | bash_completion_dep = dependency('bash-completion', required: false) 4 | if bash_completion_dep.found() 5 | bashcompletiondir = join_paths(get_option('datadir'), 'bash-completion', 'completions') 6 | endif 7 | endif 8 | 9 | if (bashcompletiondir != 'no') and (bashcompletiondir != '') 10 | install_data('input-emulator', install_dir: bashcompletiondir) 11 | endif 12 | -------------------------------------------------------------------------------- /examples/kbd-mouse-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Simple keyboard and mouse test 4 | 5 | ie=input-emulator 6 | 7 | ${ie} start mouse --x-max 1920 --y-max 1080 8 | ${ie} start kbd 9 | ${ie} status 10 | ${ie} mouse button left 11 | ${ie} mouse move -10000 -10000 12 | ${ie} mouse move 960 540 13 | ${ie} kbd type "echo 'Hello World'" 14 | ${ie} kbd key enter 15 | ${ie} stop all 16 | -------------------------------------------------------------------------------- /examples/kbd-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Simple keyboard test 4 | 5 | ie=input-emulator 6 | 7 | ${ie} start kbd 8 | 9 | # Press ctrl-l to clear screen 10 | ${ie} kbd keydown ctrl 11 | ${ie} kbd key l 12 | ${ie} kbd keyup ctrl 13 | 14 | # Say hello 15 | ${ie} kbd type "echo 'Hello World!'" 16 | sleep 0.5 17 | ${ie} kbd key enter 18 | 19 | ${ie} stop kbd 20 | -------------------------------------------------------------------------------- /examples/mouse-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Simple mouse test 4 | 5 | ie=input-emulator 6 | 7 | ${ie} start mouse --x-max 1920 --y-max 1080 8 | ${ie} status 9 | ${ie} mouse move -10000 -10000 10 | ${ie} mouse move 960 540 11 | ${ie} mouse button right 12 | ${ie} stop mouse 13 | -------------------------------------------------------------------------------- /man/input-emulator.1.in: -------------------------------------------------------------------------------- 1 | .TH "input-emulator" "1" "@version_date@" "input-emulator @version@" "User Commands" 2 | 3 | .SH "NAME" 4 | input-emulator \- a scriptable input device emulator 5 | 6 | .SH "SYNOPSIS" 7 | .PP 8 | .B input-emulator 9 | .RI [--version] 10 | .RI [--help] 11 | .RI 12 | .RI [] 13 | 14 | .SH "DESCRIPTION" 15 | .PP 16 | \fBinput-emulator\fR is a scriptable input emulator which instructs the Linux 17 | kernel to create virtual keyboard, mouse, and touch input devices through which 18 | one can perform various high level actions (typing, movement, gestures, etc.) 19 | via command-line or script. 20 | 21 | .SH "OPTIONS" 22 | 23 | .TP 24 | .B \-h, \--help 25 | Show help 26 | 27 | .TP 28 | .B \-v, \--version 29 | Show program version 30 | 31 | .SH "COMMANDS" 32 | 33 | .TP 34 | .BR start 35 | .I kbd|mouse|touch 36 | .I [] 37 | 38 | Create virtual input device 39 | 40 | .TP 41 | .BR kbd 42 | .I 43 | .I [] 44 | 45 | Perform keyboard action. 46 | .TP 47 | .BR mouse 48 | .I 49 | .I [] 50 | 51 | Perform mouse action. 52 | .TP 53 | .BR touch 54 | .I 55 | .I [] 56 | 57 | Perform touch action. 58 | .TP 59 | .BR status 60 | 61 | Show status of emulated input devices. 62 | 63 | .TP 64 | .BR run 65 | .I 66 | 67 | Run script file. 68 | 69 | .TP 70 | .BR stop 71 | .I 72 | 73 | Destroy virtual input device 74 | 75 | .SH "START DEVICE OPTIONS" 76 | 77 | .TP 78 | .BR kbd 79 | .B [--type-delay ] 80 | 81 | Create keyboard input device with specified type delay in milliseconds (default: 15) 82 | 83 | .TP 84 | .BR mouse 85 | .B [--x-max ] [--y-max ] 86 | 87 | Create mouse input device with x and y maximum resolution (default: 1920 1080). 88 | 89 | .TP 90 | .BR touch 91 | .B [--x-max ] [--y-max ] [] 92 | 93 | Create multi touch input device with x,y resolution and maximum number of slots 94 | (recognized fingers) (default: 1920 1080 4). 95 | 96 | .SH "KEYBOARD ACTIONS" 97 | 98 | .TP 9n 99 | .BR type 100 | .B 101 | 102 | Type a given string of characters. 103 | 104 | .TP 105 | .BR key 106 | .B 107 | 108 | Type a given key (press and release). 109 | 110 | The following key aliases are recognized: 111 | 112 | alt, altgr, backspace, capslock, compose, ctrl, delete, down, end, enter, esc, f1..f20, help, home, left, meta, playpause, pgdn, pgup, right, shift, space, stopcd, tab, up 113 | 114 | .TP 115 | .BR keydown 116 | .B 117 | 118 | Press and hold a given key. 119 | 120 | The same key aliases mentioned above are recognized. 121 | 122 | .TP 123 | .BR keyup 124 | .B 125 | 126 | Release a given key. 127 | 128 | The same key aliases mentioned above are recognized. 129 | 130 | .SH "MOUSE ACTIONS" 131 | 132 | .TP 12n 133 | .BR move 134 | .B 135 | .B 136 | 137 | Move mouse by x,y points (relative movement). 138 | 139 | .TP 140 | .BR button 141 | .B left|middle|right 142 | 143 | Press and release mouse button (left, middle, right). 144 | 145 | .TP 146 | .BR buttondown 147 | .B left|middle|right 148 | 149 | Press and keep mouse button (left, middle, right) down. 150 | 151 | .TP 152 | .BR buttonup 153 | .B left|middle|right 154 | 155 | Release mouse button (left, middle, right). 156 | 157 | .TP 158 | .BR scroll 159 | .B 160 | 161 | Scroll mouse wheel number of ticks (negative for backwards, positive for forwards). 162 | 163 | .SH "TOUCH ACTIONS" 164 | 165 | .TP 166 | .BR tap 167 | .B 168 | 169 | Tap screen at x,y coordinate. 170 | 171 | .SH "STOP DEVICE OPTIONS" 172 | 173 | .TP 174 | .BR kbd 175 | 176 | Destroy keyboard input device. 177 | 178 | .TP 179 | .BR mouse 180 | 181 | Destroy mouse input device. 182 | 183 | .TP 184 | .BR touch 185 | 186 | Destroy touch input device. 187 | 188 | .TP 189 | .BR all 190 | 191 | Destroy all input devices (quits daemon). 192 | 193 | .SH "EXAMPLES" 194 | 195 | .TP 196 | Touch example: 197 | $ input-emulator start touch --x-max 2560 --y-max 1440 --slots 4 198 | $ input-emulator touch tap 1280 720 199 | $ input-emulator stop touch 200 | 201 | .TP 202 | Mouse example: 203 | $ input-emulator start mouse --x-max 2560 --y-max 1440 204 | $ input-emulator mouse move 200 -300 205 | $ input-emulator mouse click left 206 | $ input-emulator mouse down middle 207 | $ input-emulator mouse up middle 208 | $ input-emulator mouse scroll -1 209 | $ input-emulator stop mouse 210 | 211 | 212 | .TP 213 | Keyboard example: 214 | $ input-emulator start kbd 215 | $ input-emulator kbd type 'hello there' 216 | $ input-emulator kbd keydown ctrl 217 | $ input-emulator kbd key t 218 | $ input-emulator kbd keyup ctrl 219 | $ input-emulator kbd key q 220 | $ input-emulator stop kbd 221 | 222 | .TP 223 | Show status of input devices: 224 | $ input-emulator status 225 | 226 | .TP 227 | Run script: 228 | $ input-emulator run examples/keyboard-test.lua 229 | 230 | .SH "WEBSITE" 231 | .PP 232 | Visit https://github.com/tio/input-emulator 233 | 234 | .SH "AUTHOR" 235 | .PP 236 | Created by Martin Lund . 237 | -------------------------------------------------------------------------------- /man/meson.build: -------------------------------------------------------------------------------- 1 | mandir = join_paths(get_option('prefix'), get_option('mandir')) 2 | man1dir = join_paths(mandir, 'man1') 3 | 4 | conf = configuration_data() 5 | conf.set('version', meson.project_version()) 6 | conf.set('version_date', version_date) 7 | 8 | manpage = configure_file( 9 | input: files('input-emulator.1.in'), 10 | output: 'input-emulator.1', 11 | configuration: conf) 12 | 13 | install_man( 14 | manpage, 15 | install_dir: man1dir) 16 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('input-emulator', 'c', 2 | version : '0.9', 3 | license : [ 'GPL-2.0-or-later'], 4 | meson_version : '>= 0.53.2', 5 | default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu11', 'b_lundef=false' ] 6 | ) 7 | 8 | # The tag date of the project_version(), update when the version bumps. 9 | version_date = '2023-03-09' 10 | 11 | subdir('src') 12 | subdir('man') 13 | subdir('bash-completion') 14 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('bashcompletiondir', 2 | type : 'string', 3 | description : 'Directory for bash completion scripts ["no" disables]') 4 | option('enable-debug', 5 | type : 'boolean', value: false, 6 | description : 'Enable runtime debug output') 7 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022-2023 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "print.h" 27 | 28 | void emit(int fd, int type, int code, int val) 29 | { 30 | struct input_event ie; 31 | int status; 32 | 33 | ie.type = type; 34 | ie.code = code; 35 | ie.value = val; 36 | 37 | /* timestamp values below are ignored */ 38 | ie.time.tv_sec = 0; 39 | ie.time.tv_usec = 0; 40 | 41 | status = write(fd, &ie, sizeof(ie)); 42 | if (status < 0) 43 | { 44 | error_printf("Emit failed (%s)\n", strerror(errno)); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022-2023 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #pragma once 22 | 23 | void emit(int fd, int type, int code, int val); 24 | -------------------------------------------------------------------------------- /src/filesystem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022-2023 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | 25 | #define do_ioctl(fd, request, args...) \ 26 | { \ 27 | int status = ioctl(fd, request, ## args); \ 28 | if (status < 0) \ 29 | { \ 30 | fprintf(stderr, "ioctl error: %s\n", strerror(errno)); \ 31 | exit(EXIT_FAILURE); \ 32 | } \ 33 | } 34 | -------------------------------------------------------------------------------- /src/keyboard.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "event.h" 33 | #include "service.h" 34 | #include "message.h" 35 | #include "print.h" 36 | #include "config.h" 37 | #include "keyboard.h" 38 | #include "misc.h" 39 | 40 | #define SHIFT (1 << 14) 41 | #define ALT_GR (1 << 13) 42 | 43 | static int keyboard_fd = -1; 44 | static char sys_name[SYS_NAME_LENGTH_MAX]; 45 | static uint32_t kbd_type_delay; 46 | 47 | /* Map list of supportd wchar values (incomplete) */ 48 | static struct wchar_to_key_map_t 49 | { 50 | wchar_t wchar; 51 | uint32_t key; 52 | uint32_t modifier; 53 | } 54 | wchar_to_key_map_dk[] = 55 | { 56 | { 32, KEY_SPACE, 0}, // ' ' 57 | { 33, KEY_1, KEY_LEFTSHIFT}, // ! 58 | { 34, KEY_2, KEY_LEFTSHIFT}, // " 59 | { 35, KEY_3, KEY_LEFTSHIFT}, // # 60 | { 36, KEY_4, KEY_LEFTSHIFT}, // $ 61 | { 37, KEY_5, KEY_LEFTSHIFT}, // % 62 | { 38, KEY_6, KEY_LEFTSHIFT}, // & 63 | { 39, KEY_BACKSLASH, 0}, // ' 64 | { 40, KEY_8, KEY_LEFTSHIFT}, // ( 65 | { 41, KEY_9, KEY_LEFTSHIFT}, // ) 66 | { 42, KEY_BACKSLASH, KEY_LEFTSHIFT}, // * 67 | { 43, KEY_MINUS, 0}, // + 68 | { 44, KEY_COMMA, 0}, // , 69 | { 45, KEY_SLASH, 0}, // - 70 | { 46, KEY_DOT, 0}, // . 71 | { 47, KEY_7, KEY_LEFTSHIFT}, // / 72 | { 48, KEY_0, 0}, // 0 73 | { 49, KEY_1, 0}, // 1 74 | { 50, KEY_2, 0}, // 2 75 | { 51, KEY_3, 0}, // 3 76 | { 52, KEY_4, 0}, // 4 77 | { 53, KEY_5, 0}, // 5 78 | { 54, KEY_6, 0}, // 6 79 | { 55, KEY_7, 0}, // 7 80 | { 56, KEY_8, 0}, // 8 81 | { 57, KEY_9, 0}, // 9 82 | { 58, KEY_DOT, KEY_LEFTSHIFT}, // : 83 | { 59, KEY_SEMICOLON, 0}, // ; 84 | { 60, KEY_102ND, 0}, // < 85 | { 61, KEY_0, KEY_LEFTSHIFT}, // = 86 | { 62, KEY_102ND, KEY_LEFTSHIFT}, // > 87 | { 63, KEY_MINUS, KEY_LEFTSHIFT}, // ? 88 | { 64, KEY_2, KEY_LEFTALT}, // @ 89 | { 65, KEY_A, KEY_LEFTSHIFT}, // A 90 | { 66, KEY_B, KEY_LEFTSHIFT}, // B 91 | { 67, KEY_C, KEY_LEFTSHIFT}, // C 92 | { 68, KEY_D, KEY_LEFTSHIFT}, // D 93 | { 69, KEY_E, KEY_LEFTSHIFT}, // E 94 | { 70, KEY_F, KEY_LEFTSHIFT}, // F 95 | { 71, KEY_G, KEY_LEFTSHIFT}, // G 96 | { 72, KEY_H, KEY_LEFTSHIFT}, // H 97 | { 73, KEY_I, KEY_LEFTSHIFT}, // I 98 | { 74, KEY_J, KEY_LEFTSHIFT}, // J 99 | { 75, KEY_K, KEY_LEFTSHIFT}, // K 100 | { 76, KEY_L, KEY_LEFTSHIFT}, // L 101 | { 77, KEY_M, KEY_LEFTSHIFT}, // M 102 | { 78, KEY_N, KEY_LEFTSHIFT}, // N 103 | { 79, KEY_O, KEY_LEFTSHIFT}, // O 104 | { 80, KEY_P, KEY_LEFTSHIFT}, // P 105 | { 81, KEY_Q, KEY_LEFTSHIFT}, // Q 106 | { 82, KEY_R, KEY_LEFTSHIFT}, // R 107 | { 83, KEY_S, KEY_LEFTSHIFT}, // S 108 | { 84, KEY_T, KEY_LEFTSHIFT}, // T 109 | { 85, KEY_U, KEY_LEFTSHIFT}, // U 110 | { 86, KEY_V, KEY_LEFTSHIFT}, // V 111 | { 87, KEY_W, KEY_LEFTSHIFT}, // W 112 | { 88, KEY_X, KEY_LEFTSHIFT}, // X 113 | { 89, KEY_Y, KEY_LEFTSHIFT}, // Y 114 | { 90, KEY_Z, KEY_LEFTSHIFT}, // Z 115 | { 91, KEY_8, KEY_RIGHTALT}, // [ 116 | { 92, KEY_102ND, KEY_LEFTALT}, // '\' 117 | { 93, KEY_9, KEY_LEFTALT}, // ] 118 | { 94, KEY_RIGHTBRACE, KEY_LEFTSHIFT}, // ^ 119 | { 95, KEY_SLASH, KEY_LEFTSHIFT}, // _ 120 | { 96, KEY_EQUAL, KEY_LEFTSHIFT}, // ` 121 | { 97, KEY_A, 0}, // a 122 | { 98, KEY_B, 0}, // b 123 | { 99, KEY_C, 0}, // c 124 | { 100, KEY_D, 0}, // d 125 | { 101, KEY_E, 0}, // e 126 | { 102, KEY_F, 0}, // f 127 | { 103, KEY_G, 0}, // g 128 | { 104, KEY_H, 0}, // h 129 | { 105, KEY_I, 0}, // i 130 | { 106, KEY_J, 0}, // j 131 | { 107, KEY_K, 0}, // k 132 | { 108, KEY_L, 0}, // l 133 | { 109, KEY_M, 0}, // m 134 | { 110, KEY_N, 0}, // n 135 | { 111, KEY_O, 0}, // o 136 | { 112, KEY_P, 0}, // p 137 | { 113, KEY_Q, 0}, // q 138 | { 114, KEY_R, 0}, // r 139 | { 115, KEY_S, 0}, // s 140 | { 116, KEY_T, 0}, // t 141 | { 117, KEY_U, 0}, // u 142 | { 118, KEY_V, 0}, // v 143 | { 119, KEY_W, 0}, // w 144 | { 120, KEY_X, 0}, // x 145 | { 121, KEY_Y, 0}, // y 146 | { 122, KEY_Z, 0}, // z 147 | { 123, KEY_7, KEY_RIGHTALT}, // { 148 | { 124, KEY_EQUAL, KEY_RIGHTALT}, // | 149 | { 125, KEY_7, KEY_RIGHTALT}, // } 150 | { 126, KEY_RIGHTBRACE, KEY_RIGHTALT}, // ~ 151 | 152 | { 180, KEY_EQUAL, 0}, // ` 153 | 154 | { 197, KEY_LEFTBRACE, KEY_LEFTSHIFT}, // Å 155 | { 198, KEY_SEMICOLON, KEY_LEFTSHIFT}, // Æ 156 | { 216, KEY_APOSTROPHE, KEY_LEFTSHIFT}, // Ø 157 | { 229, KEY_LEFTBRACE, 0}, // å 158 | { 230, KEY_SEMICOLON, 0}, // æ 159 | { 248, KEY_APOSTROPHE, 0}, // ø 160 | 161 | { 0, 0, 0}, // End of list 162 | }; 163 | 164 | static struct alias_to_key_map_t 165 | { 166 | wchar_t *alias; 167 | uint32_t key; 168 | uint32_t modifier; 169 | } 170 | alias_to_key_map_dk[] = 171 | { 172 | { L"alt", KEY_LEFTALT, 0}, 173 | { L"altgr", KEY_RIGHTALT, 0}, 174 | { L"backspace", KEY_BACKSPACE, 0}, 175 | { L"capslock", KEY_CAPSLOCK, 0}, 176 | { L"compose", KEY_COMPOSE, 0}, 177 | { L"ctrl", KEY_LEFTCTRL, 0}, 178 | { L"delete", KEY_DELETE, 0}, 179 | { L"down", KEY_DOWN, 0}, 180 | { L"end", KEY_END, 0}, 181 | { L"enter", KEY_ENTER, 0}, 182 | { L"esc", KEY_ESC, 0}, 183 | { L"f1", KEY_F1, 0}, 184 | { L"f2", KEY_F2, 0}, 185 | { L"f3", KEY_F3, 0}, 186 | { L"f4", KEY_F4, 0}, 187 | { L"f5", KEY_F5, 0}, 188 | { L"f6", KEY_F6, 0}, 189 | { L"f7", KEY_F7, 0}, 190 | { L"f8", KEY_F8, 0}, 191 | { L"f9", KEY_F9, 0}, 192 | { L"f10", KEY_F10, 0}, 193 | { L"f11", KEY_F11, 0}, 194 | { L"f12", KEY_F12, 0}, 195 | { L"f13", KEY_F13, 0}, 196 | { L"f14", KEY_F14, 0}, 197 | { L"f15", KEY_F15, 0}, 198 | { L"f16", KEY_F16, 0}, 199 | { L"f17", KEY_F17, 0}, 200 | { L"f18", KEY_F18, 0}, 201 | { L"f19", KEY_F19, 0}, 202 | { L"f20", KEY_F20, 0}, 203 | { L"help", KEY_HELP, 0}, 204 | { L"home", KEY_HOME, 0}, 205 | { L"left", KEY_LEFT, 0}, 206 | { L"meta", KEY_LEFTMETA, 0}, 207 | { L"playpause", KEY_PLAYPAUSE, 0}, 208 | { L"pgdn", KEY_PAGEDOWN, 0}, 209 | { L"pgup", KEY_PAGEUP, 0}, 210 | { L"right", KEY_RIGHT, 0}, 211 | { L"shift", KEY_LEFTSHIFT, 0}, 212 | { L"space", KEY_SPACE, 0}, 213 | { L"stopcd", KEY_STOPCD, 0}, 214 | { L"tab", KEY_TAB, 0}, 215 | { L"up", KEY_UP, 0}, 216 | 217 | { NULL, 0, 0}, // End of list 218 | }; 219 | 220 | /* All input key codes known to man */ 221 | static const int key_list[] = 222 | { 223 | KEY_ESC, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, 224 | KEY_0, KEY_MINUS, KEY_EQUAL, KEY_BACKSPACE, KEY_TAB, KEY_Q, KEY_W, KEY_E, 225 | KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_LEFTBRACE, 226 | KEY_RIGHTBRACE, KEY_ENTER, KEY_LEFTCTRL, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, 227 | KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_GRAVE, 228 | KEY_LEFTSHIFT, KEY_BACKSLASH, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, 229 | KEY_M, KEY_COMMA, KEY_DOT, KEY_SLASH, KEY_RIGHTSHIFT, KEY_KPASTERISK, 230 | KEY_LEFTALT, KEY_SPACE, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, 231 | KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_NUMLOCK, 232 | KEY_SCROLLLOCK, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KPMINUS, KEY_KP4, KEY_KP5, 233 | KEY_KP6, KEY_KPPLUS, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP0, KEY_KPDOT, 234 | KEY_ZENKAKUHANKAKU, KEY_102ND, KEY_F11, KEY_F12, KEY_RO, KEY_KATAKANA, 235 | KEY_HIRAGANA, KEY_HENKAN, KEY_KATAKANAHIRAGANA, KEY_MUHENKAN, KEY_KPJPCOMMA, 236 | KEY_KPENTER, KEY_RIGHTCTRL, KEY_KPSLASH, KEY_SYSRQ, KEY_RIGHTALT, 237 | KEY_LINEFEED, KEY_HOME, KEY_UP, KEY_PAGEUP, KEY_LEFT, KEY_RIGHT, KEY_END, 238 | KEY_DOWN, KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, KEY_MACRO, KEY_MUTE, 239 | KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_POWER, KEY_KPEQUAL, KEY_KPPLUSMINUS, 240 | KEY_PAUSE, KEY_SCALE, KEY_KPCOMMA, KEY_HANGEUL, KEY_HANGUEL, KEY_HANJA, 241 | KEY_YEN, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_COMPOSE, KEY_STOP, KEY_AGAIN, 242 | KEY_PROPS, KEY_UNDO, KEY_FRONT, KEY_COPY, KEY_OPEN, KEY_PASTE, KEY_FIND, 243 | KEY_CUT, KEY_HELP, KEY_MENU, KEY_CALC, KEY_SETUP, KEY_SLEEP, KEY_WAKEUP, 244 | KEY_FILE, KEY_SENDFILE, KEY_DELETEFILE, KEY_XFER, KEY_PROG1, KEY_PROG2, 245 | KEY_WWW, KEY_MSDOS, KEY_COFFEE, KEY_SCREENLOCK, KEY_ROTATE_DISPLAY, 246 | KEY_DIRECTION, KEY_CYCLEWINDOWS, KEY_MAIL, KEY_BOOKMARKS, KEY_COMPUTER, 247 | KEY_BACK, KEY_FORWARD, KEY_CLOSECD, KEY_EJECTCD, KEY_EJECTCLOSECD, 248 | KEY_NEXTSONG, KEY_PLAYPAUSE, KEY_PREVIOUSSONG, KEY_STOPCD, KEY_RECORD, 249 | KEY_REWIND, KEY_PHONE, KEY_ISO, KEY_CONFIG, KEY_HOMEPAGE, KEY_REFRESH, 250 | KEY_EXIT, KEY_MOVE, KEY_EDIT, KEY_SCROLLUP, KEY_SCROLLDOWN, KEY_KPLEFTPAREN, 251 | KEY_KPRIGHTPAREN, KEY_NEW, KEY_REDO, KEY_F13, KEY_F14, KEY_F15, KEY_F16, 252 | KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24, 253 | KEY_PLAYCD, KEY_PAUSECD, KEY_PROG3, KEY_PROG4, KEY_DASHBOARD, KEY_SUSPEND, 254 | KEY_CLOSE, KEY_PLAY, KEY_FASTFORWARD, KEY_BASSBOOST, KEY_PRINT, KEY_HP, 255 | KEY_CAMERA, KEY_SOUND, KEY_QUESTION, KEY_EMAIL, KEY_CHAT, KEY_SEARCH, 256 | KEY_CONNECT, KEY_FINANCE, KEY_SPORT, KEY_SHOP, KEY_ALTERASE, KEY_CANCEL, 257 | KEY_BRIGHTNESSDOWN, KEY_BRIGHTNESSUP, KEY_MEDIA, KEY_SWITCHVIDEOMODE, 258 | KEY_KBDILLUMTOGGLE, KEY_KBDILLUMDOWN, KEY_KBDILLUMUP, KEY_SEND, KEY_REPLY, 259 | KEY_FORWARDMAIL, KEY_SAVE, KEY_DOCUMENTS, KEY_BATTERY, KEY_BLUETOOTH, 260 | KEY_WLAN, KEY_UWB, KEY_UNKNOWN, KEY_VIDEO_NEXT, KEY_VIDEO_PREV, 261 | KEY_BRIGHTNESS_CYCLE, KEY_BRIGHTNESS_AUTO, KEY_BRIGHTNESS_ZERO, 262 | KEY_DISPLAY_OFF, KEY_WWAN, KEY_WIMAX, KEY_RFKILL, KEY_MICMUTE, KEY_OK, 263 | KEY_SELECT, KEY_GOTO, KEY_CLEAR, KEY_POWER2, KEY_OPTION, KEY_INFO, KEY_TIME, 264 | KEY_VENDOR, KEY_ARCHIVE, KEY_PROGRAM, KEY_CHANNEL, KEY_FAVORITES, KEY_EPG, 265 | KEY_PVR, KEY_MHP, KEY_LANGUAGE, KEY_TITLE, KEY_SUBTITLE, KEY_ANGLE, 266 | KEY_ZOOM, KEY_MODE, KEY_KEYBOARD, KEY_SCREEN, KEY_PC, KEY_TV, KEY_TV2, 267 | KEY_VCR, KEY_VCR2, KEY_SAT, KEY_SAT2, KEY_CD, KEY_TAPE, KEY_RADIO, 268 | KEY_TUNER, KEY_PLAYER, KEY_TEXT, KEY_DVD, KEY_AUX, KEY_MP3, KEY_AUDIO, 269 | KEY_VIDEO, KEY_DIRECTORY, KEY_LIST, KEY_MEMO, KEY_CALENDAR, KEY_RED, 270 | KEY_GREEN, KEY_YELLOW, KEY_BLUE, KEY_CHANNELUP, KEY_CHANNELDOWN, KEY_FIRST, 271 | KEY_LAST, KEY_AB, KEY_NEXT, KEY_RESTART, KEY_SLOW, KEY_SHUFFLE, KEY_BREAK, 272 | KEY_PREVIOUS, KEY_DIGITS, KEY_TEEN, KEY_TWEN, KEY_VIDEOPHONE, KEY_GAMES, 273 | KEY_ZOOMIN, KEY_ZOOMOUT, KEY_ZOOMRESET, KEY_WORDPROCESSOR, KEY_EDITOR, 274 | KEY_SPREADSHEET, KEY_GRAPHICSEDITOR, KEY_PRESENTATION, KEY_DATABASE, 275 | KEY_NEWS, KEY_VOICEMAIL, KEY_ADDRESSBOOK, KEY_MESSENGER, KEY_DISPLAYTOGGLE, 276 | KEY_BRIGHTNESS_TOGGLE, KEY_SPELLCHECK, KEY_LOGOFF, KEY_DOLLAR, KEY_EURO, 277 | KEY_FRAMEBACK, KEY_FRAMEFORWARD, KEY_CONTEXT_MENU, KEY_MEDIA_REPEAT, 278 | KEY_10CHANNELSUP, KEY_10CHANNELSDOWN, KEY_IMAGES, KEY_DEL_EOL, KEY_DEL_EOS, 279 | KEY_INS_LINE, KEY_DEL_LINE, KEY_FN, KEY_FN_ESC, KEY_FN_F1, KEY_FN_F2, 280 | KEY_FN_F3, KEY_FN_F4, KEY_FN_F5, KEY_FN_F6, KEY_FN_F7, KEY_FN_F8, KEY_FN_F9, 281 | KEY_FN_F10, KEY_FN_F11, KEY_FN_F12, KEY_FN_1, KEY_FN_2, KEY_FN_D, KEY_FN_E, 282 | KEY_FN_F, KEY_FN_S, KEY_FN_B, KEY_BRL_DOT1, KEY_BRL_DOT2, KEY_BRL_DOT3, 283 | KEY_BRL_DOT4, KEY_BRL_DOT5, KEY_BRL_DOT6, KEY_BRL_DOT7, KEY_BRL_DOT8, 284 | KEY_BRL_DOT9, KEY_BRL_DOT10, KEY_NUMERIC_0, KEY_NUMERIC_1, KEY_NUMERIC_2, 285 | KEY_NUMERIC_3, KEY_NUMERIC_4, KEY_NUMERIC_5, KEY_NUMERIC_6, KEY_NUMERIC_7, 286 | KEY_NUMERIC_8, KEY_NUMERIC_9, KEY_NUMERIC_STAR, KEY_NUMERIC_POUND, 287 | KEY_NUMERIC_A, KEY_NUMERIC_B, KEY_NUMERIC_C, KEY_NUMERIC_D, 288 | KEY_CAMERA_FOCUS, KEY_WPS_BUTTON, KEY_TOUCHPAD_TOGGLE, KEY_TOUCHPAD_ON, 289 | KEY_TOUCHPAD_OFF, KEY_CAMERA_ZOOMIN, KEY_CAMERA_ZOOMOUT, KEY_CAMERA_UP, 290 | KEY_CAMERA_DOWN, KEY_CAMERA_LEFT, KEY_CAMERA_RIGHT, KEY_ATTENDANT_ON, 291 | KEY_ATTENDANT_OFF, KEY_ATTENDANT_TOGGLE, KEY_LIGHTS_TOGGLE, KEY_ALS_TOGGLE, 292 | KEY_BUTTONCONFIG, KEY_TASKMANAGER, KEY_JOURNAL, KEY_CONTROLPANEL, 293 | KEY_APPSELECT, KEY_SCREENSAVER, KEY_VOICECOMMAND, KEY_BRIGHTNESS_MIN, 294 | KEY_BRIGHTNESS_MAX, KEY_KBDINPUTASSIST_PREV, KEY_KBDINPUTASSIST_NEXT, 295 | KEY_KBDINPUTASSIST_PREVGROUP, KEY_KBDINPUTASSIST_NEXTGROUP, 296 | KEY_KBDINPUTASSIST_ACCEPT, KEY_KBDINPUTASSIST_CANCEL, KEY_RIGHT_UP, 297 | KEY_RIGHT_DOWN, KEY_LEFT_UP, KEY_LEFT_DOWN, KEY_ROOT_MENU, 298 | KEY_MEDIA_TOP_MENU, KEY_NUMERIC_11, KEY_NUMERIC_12, KEY_AUDIO_DESC, 299 | KEY_3D_MODE, KEY_NEXT_FAVORITE, KEY_STOP_RECORD, KEY_PAUSE_RECORD, KEY_VOD, 300 | KEY_UNMUTE, KEY_FASTREVERSE, KEY_SLOWREVERSE, KEY_DATA, KEY_MIN_INTERESTING 301 | }; 302 | 303 | 304 | void keyboard_press(uint32_t key) 305 | { 306 | /* Do nothing if no device */ 307 | if (keyboard_fd < 0) 308 | { 309 | return; 310 | } 311 | 312 | debug_printf("Press key %d\n", key); 313 | 314 | emit(keyboard_fd, EV_KEY, key, 1); 315 | emit(keyboard_fd, EV_SYN, SYN_REPORT, 0); 316 | } 317 | 318 | void keyboard_release(uint32_t key) 319 | { 320 | /* Do nothing if no device */ 321 | if (keyboard_fd < 0) 322 | { 323 | return; 324 | } 325 | 326 | debug_printf("Release key %d\n", key); 327 | 328 | emit(keyboard_fd, EV_KEY, key, 0); 329 | emit(keyboard_fd, EV_SYN, SYN_REPORT, 0); 330 | } 331 | 332 | int wchar_to_key(wchar_t wc, uint32_t *key, uint32_t *modifier) 333 | { 334 | int i = 0; 335 | 336 | /* Hardcoded to DK mapping for now */ 337 | while (wchar_to_key_map_dk[i].wchar != 0) 338 | { 339 | if (wchar_to_key_map_dk[i].wchar == wc) 340 | { 341 | // Found key 342 | *key = wchar_to_key_map_dk[i].key; 343 | *modifier = wchar_to_key_map_dk[i].modifier; 344 | return 0; 345 | } 346 | i++; 347 | } 348 | 349 | return -1; 350 | } 351 | 352 | int alias_to_key(wchar_t *wcs, uint32_t *key, uint32_t *modifier) 353 | { 354 | int i = 0; 355 | 356 | /* Hardcoded to DK mapping for now */ 357 | while (alias_to_key_map_dk[i].alias != NULL) 358 | { 359 | if (wcscmp(wcs, alias_to_key_map_dk[i].alias) == 0) 360 | { 361 | // Found key 362 | *key = alias_to_key_map_dk[i].key; 363 | *modifier = alias_to_key_map_dk[i].modifier; 364 | return 0; 365 | } 366 | i++; 367 | } 368 | 369 | return -1; 370 | } 371 | 372 | void wchar_or_alias_to_key(wchar_t *wcs, uint32_t *key) 373 | { 374 | uint32_t modifier; 375 | 376 | if (wcslen(wcs) == 1) 377 | { 378 | wchar_to_key(wcs[0], key, &modifier); 379 | } 380 | else if (alias_to_key(wcs, key, &modifier) == 0) 381 | { 382 | } 383 | else 384 | { 385 | error_printf("Invalid input\n"); 386 | exit(EXIT_FAILURE); 387 | } 388 | } 389 | 390 | int keyboard_create(uint32_t type_delay) 391 | { 392 | struct uinput_setup usetup; 393 | 394 | if (keyboard_fd >= 0) 395 | { 396 | /* Keyboard already started */ 397 | return -1; 398 | } 399 | 400 | kbd_type_delay = type_delay; 401 | 402 | keyboard_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 403 | if (keyboard_fd < 0) 404 | { 405 | error_printf("Could not open /dev/uinput (%s)\n", strerror(errno)); 406 | exit(EXIT_FAILURE); 407 | } 408 | 409 | /* Configure device to pass the following keyboard events */ 410 | do_ioctl(keyboard_fd, UI_SET_EVBIT, EV_KEY); 411 | 412 | for (unsigned long i=0; i<(sizeof(key_list)/sizeof(int)); i++) 413 | { 414 | if (ioctl(keyboard_fd, UI_SET_KEYBIT, key_list[i])) 415 | { 416 | error_printf("UI_SET_KEYBIT %ld failed\n", i); 417 | } 418 | } 419 | 420 | /* Configure device properties */ 421 | memset(&usetup, 0, sizeof(usetup)); 422 | usetup.id.bustype = BUS_USB; 423 | usetup.id.vendor = 0x1234; 424 | usetup.id.product = 0x5678; 425 | usetup.id.version = 1; 426 | strcpy(usetup.name, "Keyboard emulator"); 427 | 428 | /* Create device */ 429 | do_ioctl(keyboard_fd, UI_DEV_SETUP, &usetup); 430 | do_ioctl(keyboard_fd, UI_DEV_CREATE); 431 | 432 | /* Wait for kernel to finish creating device */ 433 | sleep(1); 434 | 435 | device_ref_count++; 436 | 437 | debug_printf("Created keyboard input device\n"); 438 | 439 | /* Save sys name */ 440 | do_ioctl(keyboard_fd, UI_GET_SYSNAME(50), sys_name); 441 | 442 | return 0; 443 | } 444 | 445 | bool keyboard_online(void) 446 | { 447 | if (keyboard_fd >= 0) 448 | { 449 | return true; 450 | } 451 | 452 | return false; 453 | } 454 | 455 | void keyboard_destroy(void) 456 | { 457 | /* 458 | * Give userspace some time to read the events before we destroy the 459 | * device with UI_DEV_DESTROY. 460 | */ 461 | sleep(1); 462 | 463 | if (keyboard_fd < 0) 464 | { 465 | return; 466 | } 467 | debug_printf("Destroying keyboard input device\n"); 468 | 469 | do_ioctl(keyboard_fd, UI_DEV_DESTROY); 470 | close(keyboard_fd); 471 | 472 | keyboard_fd = -1; 473 | 474 | sys_name[0] = 0; 475 | 476 | device_ref_count--; 477 | } 478 | 479 | const char* keyboard_sys_name(void) 480 | { 481 | return sys_name; 482 | } 483 | 484 | uint32_t keyboard_type_delay(void) 485 | { 486 | return kbd_type_delay; 487 | } 488 | 489 | void do_keyboard_start(void *message) 490 | { 491 | message_header_t *header = message; 492 | keyboard_start_data_t *data = message + sizeof(message_header_t); 493 | 494 | if (header->payload_length != sizeof(keyboard_start_data_t)) 495 | { 496 | warning_printf("Warning: Invalid payload length\n"); 497 | return; 498 | } 499 | 500 | keyboard_create(data->type_delay); 501 | 502 | msg_send_rsp_ok(); 503 | } 504 | 505 | void do_keyboard_start_request(uint32_t type_delay) 506 | { 507 | void *message = NULL; 508 | keyboard_start_data_t data; 509 | 510 | debug_printf("Sending keyboard start message!\n"); 511 | 512 | data.type_delay = type_delay; 513 | 514 | msg_create(&message, REQ_KBD_START, &data, sizeof(data)); 515 | msg_send(message); 516 | msg_destroy(message); 517 | 518 | msg_receive_rsp_ok(); 519 | } 520 | 521 | void do_keyboard_keydown(void *message) 522 | { 523 | message_header_t *header = message; 524 | uint32_t *key = message + sizeof(message_header_t); 525 | 526 | if (header->payload_length != sizeof(uint32_t)) 527 | { 528 | warning_printf("Warning: Invalid payload length-\n"); 529 | return; 530 | } 531 | 532 | keyboard_press(*key); 533 | 534 | msg_send_rsp_ok(); 535 | } 536 | 537 | void do_keyboard_keydown_request(uint32_t key) 538 | { 539 | void *message = NULL; 540 | 541 | msg_create(&message, REQ_KBD_KEYDOWN, &key, sizeof(key)); 542 | msg_send(message); 543 | msg_destroy(message); 544 | 545 | msg_receive_rsp_ok(); 546 | } 547 | 548 | void do_keyboard_keyup(void *message) 549 | { 550 | message_header_t *header = message; 551 | uint32_t *key = message + sizeof(message_header_t); 552 | 553 | if (header->payload_length != sizeof(uint32_t)) 554 | { 555 | warning_printf("Warning: Invalid payload length-\n"); 556 | return; 557 | } 558 | 559 | keyboard_release(*key); 560 | 561 | msg_send_rsp_ok(); 562 | } 563 | 564 | void do_keyboard_keyup_request(uint32_t key) 565 | { 566 | void *message = NULL; 567 | 568 | msg_create(&message, REQ_KBD_KEYUP, &key, sizeof(key)); 569 | msg_send(message); 570 | msg_destroy(message); 571 | 572 | msg_receive_rsp_ok(); 573 | } 574 | 575 | void do_keyboard_key(void *message) 576 | { 577 | message_header_t *header = message; 578 | uint32_t *key = message + sizeof(message_header_t); 579 | 580 | if (header->payload_length != sizeof(uint32_t)) 581 | { 582 | warning_printf("Warning: Invalid payload length-\n"); 583 | return; 584 | } 585 | 586 | keyboard_press(*key); 587 | usleep(kbd_type_delay*1000); 588 | keyboard_release(*key); 589 | 590 | msg_send_rsp_ok(); 591 | } 592 | 593 | void do_keyboard_key_request(uint32_t key) 594 | { 595 | void *message = NULL; 596 | 597 | msg_create(&message, REQ_KBD_KEY, &key, sizeof(key)); 598 | msg_send(message); 599 | msg_destroy(message); 600 | 601 | msg_receive_rsp_ok(); 602 | } 603 | 604 | void do_keyboard_type(void *message) 605 | { 606 | const wchar_t *wc_string = message + sizeof(message_header_t); 607 | size_t length = wcslen(wc_string) + 1; 608 | uint32_t modifier; 609 | uint32_t key; 610 | 611 | // Dump data received 612 | message_header_t *header = message; 613 | debug_printf("Dumping received payload:\n"); 614 | debug_print_hex_dump((void *)wc_string, header->payload_length); 615 | 616 | /* Translate each wide character in wc string to uinput key stroke with any 617 | * modifiers (ALT_LEFTSHIFT, ALT_GR, etc) required */ 618 | 619 | for (size_t i = 0; i<(length); i++) 620 | { 621 | debug_printf("wchar: 0x%x\n", wc_string[i]); 622 | if (wchar_to_key(wc_string[i], &key, &modifier) == 0) 623 | { 624 | debug_printf("wchar: %d, key: %d, modifier: %d\n", wc_string[i], key, modifier); 625 | if (modifier) 626 | { 627 | keyboard_press(modifier); 628 | } 629 | 630 | keyboard_press(key); 631 | usleep(kbd_type_delay*1000); 632 | keyboard_release(key); 633 | 634 | if (modifier) 635 | { 636 | keyboard_release(modifier); 637 | } 638 | } 639 | } 640 | 641 | msg_send_rsp_ok(); 642 | } 643 | 644 | void do_keyboard_type_request(const wchar_t *wc_string) 645 | { 646 | void *message = NULL; 647 | uint32_t wc_string_byte_length = sizeof(wchar_t) * (wcslen(wc_string) + 1); 648 | 649 | // Dump data sent 650 | debug_printf("Dumping send payload:\n"); 651 | debug_print_hex_dump((void *)wc_string, wc_string_byte_length); 652 | 653 | msg_create(&message, REQ_KBD_TYPE, (void *) wc_string, wc_string_byte_length); 654 | msg_send(message); 655 | msg_destroy(message); 656 | 657 | msg_receive_rsp_ok(); 658 | } 659 | -------------------------------------------------------------------------------- /src/keyboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | typedef struct 27 | { 28 | uint32_t type_delay; 29 | } keyboard_start_data_t; 30 | 31 | int keyboard_create(uint32_t type_delay); 32 | void keyboard_destroy(void); 33 | bool keyboard_online(void); 34 | const char* keyboard_sys_name(void); 35 | uint32_t keyboard_type_delay(void); 36 | void do_keyboard_keydown(void *message); 37 | void do_keyboard_keydown_request(uint32_t key); 38 | void do_keyboard_keyup(void *message); 39 | void do_keyboard_keyup_request(uint32_t key); 40 | void do_keyboard_key(void *message); 41 | void do_keyboard_key_request(uint32_t key); 42 | void do_keyboard_type(void *message); 43 | void do_keyboard_type_request(const wchar_t *wc_string); 44 | void do_keyboard_start(void *message); 45 | void do_keyboard_start_request(uint32_t type_delay); 46 | int wchar_to_key(wchar_t wc, uint32_t *key, uint32_t *modifier); 47 | void wchar_or_alias_to_key(wchar_t *wcs, uint32_t *key); 48 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * input-emulator - a scriptable input emulator 3 | * 4 | * Copyright (C) 2023 DEIF A/S 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License 8 | * as published by the Free Software Foundation; either version 2 9 | * of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 | * 02110-1301, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "signals.h" 31 | #include "service.h" 32 | #include "message.h" 33 | #include "options.h" 34 | #include "keyboard.h" 35 | #include "touch.h" 36 | #include "mouse.h" 37 | #include "event.h" 38 | #include "print.h" 39 | 40 | void handle_message(void) 41 | { 42 | void *message = NULL; 43 | message_header_t *header; 44 | 45 | /* Receive message (blocking) */ 46 | msg_receive(&message); 47 | header = message; 48 | 49 | /* Handle incoming message request */ 50 | switch (header->type) 51 | { 52 | case REQ_KBD_START: 53 | do_keyboard_start(message); 54 | break; 55 | 56 | case REQ_KBD_KEY: 57 | do_keyboard_key(message); 58 | break; 59 | 60 | case REQ_KBD_KEYDOWN: 61 | do_keyboard_keydown(message); 62 | break; 63 | 64 | case REQ_KBD_KEYUP: 65 | do_keyboard_keyup(message); 66 | break; 67 | 68 | case REQ_KBD_TYPE: 69 | do_keyboard_type(message); 70 | break; 71 | 72 | case REQ_MOUSE_START: 73 | do_mouse_start(message); 74 | break; 75 | 76 | case REQ_MOUSE_MOVE: 77 | do_mouse_move(message); 78 | break; 79 | 80 | case REQ_MOUSE_BUTTON: 81 | do_mouse_click(message); 82 | break; 83 | 84 | case REQ_MOUSE_BUTTONDOWN: 85 | do_mouse_down(message); 86 | break; 87 | 88 | case REQ_MOUSE_BUTTONUP: 89 | do_mouse_up(message); 90 | break; 91 | 92 | case REQ_MOUSE_SCROLL: 93 | do_mouse_scroll(message); 94 | break; 95 | 96 | case REQ_TOUCH_START: 97 | do_touch_start(message); 98 | break; 99 | 100 | case REQ_TOUCH_TAP: 101 | do_touch_tap(message); 102 | break; 103 | 104 | case REQ_STATUS: 105 | debug_printf("Received status message!\n"); 106 | do_service_status(message); 107 | break; 108 | 109 | case REQ_STOP: 110 | do_service_stop(message); 111 | if (!devices_online()) 112 | { 113 | /* No simulated input devices online - stop service */ 114 | printf("No simulated input devices online - stopping input-emulator service!\n"); 115 | exit(EXIT_SUCCESS); 116 | } 117 | break; 118 | 119 | default: 120 | break; 121 | } 122 | 123 | /* Destroy message */ 124 | msg_destroy(message); 125 | } 126 | 127 | int main(int argc, char *argv[]) 128 | { 129 | /* Set default locale */ 130 | setlocale(LC_ALL, ""); 131 | 132 | /* Parse options */ 133 | options_parse(argc, argv); 134 | 135 | /* Check if service is running on non-start commands */ 136 | if (option.command != CMD_START) 137 | { 138 | // Check for running daemon/service 139 | if (!service_running()) 140 | { 141 | printf("Please start service using the 'input-emulator start ' command\n"); 142 | return -1; 143 | } 144 | 145 | /* Open message queue (client) */ 146 | message_client_open(); 147 | atexit(message_client_close); 148 | } 149 | 150 | /* Handle command */ 151 | switch (option.command) 152 | { 153 | case CMD_START: 154 | 155 | if (service_running()) 156 | { 157 | /* Server already running so we will act as client */ 158 | 159 | /* Open message queue (client) */ 160 | message_client_open(); 161 | atexit(message_client_close); 162 | 163 | switch (option.device) 164 | { 165 | case DEV_KEYBOARD: 166 | do_keyboard_start_request(option.type_delay); 167 | break; 168 | 169 | case DEV_MOUSE: 170 | do_mouse_start_request(option.x_max, option.y_max); 171 | break; 172 | 173 | case DEV_TOUCH: 174 | do_touch_start_request(option.x_max, option.y_max, option.slots); 175 | break; 176 | 177 | case DEV_ALL: 178 | case DEV_NONE: 179 | break; 180 | } 181 | return 0; 182 | } 183 | else 184 | { 185 | printf("Starting input-emulator service...\n"); 186 | } 187 | 188 | /* Run in background */ 189 | if (option.daemonize) 190 | { 191 | daemonize(); 192 | } 193 | 194 | /* Install signal handlers */ 195 | signal_handlers_install(); 196 | 197 | switch (option.device) 198 | { 199 | case DEV_KEYBOARD: 200 | /* Initilize keyboard input event device */ 201 | if (keyboard_create(option.type_delay) == 0) 202 | { 203 | atexit(keyboard_destroy); 204 | } 205 | break; 206 | 207 | case DEV_MOUSE: 208 | /* Initilize mouse input event device */ 209 | if (mouse_create(option.x_max, option.y_max) == 0) 210 | { 211 | atexit(mouse_destroy); 212 | } 213 | break; 214 | 215 | case DEV_TOUCH: 216 | /* Initilize touch input event device */ 217 | if (touch_create(option.x_max, option.y_max, option.slots) == 0) 218 | { 219 | atexit(touch_destroy); 220 | } 221 | break; 222 | 223 | case DEV_ALL: 224 | case DEV_NONE: 225 | break; 226 | } 227 | 228 | /* Set up message queue */ 229 | message_server_open(); 230 | atexit(message_server_close); 231 | 232 | /* Enter command handling loop */ 233 | message_server_listen(handle_message); 234 | 235 | break; 236 | 237 | case CMD_KBD: 238 | 239 | switch (option.kbd_action) 240 | { 241 | case KBD_KEY: 242 | do_keyboard_key_request(option.key); 243 | break; 244 | 245 | case KBD_KEYDOWN: 246 | do_keyboard_keydown_request(option.key); 247 | break; 248 | 249 | case KBD_KEYUP: 250 | do_keyboard_keyup_request(option.key); 251 | break; 252 | 253 | case KBD_TYPE: 254 | do_keyboard_type_request(option.wc_string); 255 | break; 256 | 257 | case KBD_NONE: 258 | break; 259 | } 260 | break; 261 | 262 | case CMD_MOUSE: 263 | 264 | switch (option.mouse_action) 265 | { 266 | case MOUSE_MOVE: 267 | do_mouse_move_request(option.x, option.y); 268 | break; 269 | 270 | case MOUSE_BUTTON: 271 | do_mouse_click_request(option.button); 272 | break; 273 | 274 | case MOUSE_BUTTONDOWN: 275 | do_mouse_down_request(option.button); 276 | break; 277 | 278 | case MOUSE_BUTTONUP: 279 | do_mouse_up_request(option.button); 280 | break; 281 | 282 | case MOUSE_SCROLL: 283 | do_mouse_scroll_request(option.ticks); 284 | break; 285 | 286 | case MOUSE_NONE: 287 | break; 288 | } 289 | break; 290 | 291 | case CMD_TOUCH: 292 | 293 | switch (option.touch_action) 294 | { 295 | case TOUCH_TAP: 296 | do_touch_tap_request(option.x, option.y, option.duration); 297 | break; 298 | 299 | case TOUCH_NONE: 300 | break; 301 | } 302 | break; 303 | 304 | case CMD_STATUS: 305 | do_service_status_request(); 306 | break; 307 | 308 | case CMD_STOP: 309 | switch (option.device) 310 | { 311 | case DEV_KEYBOARD: 312 | do_service_stop_request(DEV_KEYBOARD); 313 | break; 314 | 315 | case DEV_MOUSE: 316 | do_service_stop_request(DEV_MOUSE); 317 | break; 318 | 319 | case DEV_TOUCH: 320 | do_service_stop_request(DEV_TOUCH); 321 | break; 322 | 323 | case DEV_ALL: 324 | do_service_stop_request(DEV_ALL); 325 | break; 326 | 327 | 328 | case DEV_NONE: 329 | break; 330 | } 331 | 332 | default: 333 | break; 334 | } 335 | 336 | return 0; 337 | } 338 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | # Generate configuration header 2 | config_h = configuration_data() 3 | config_h.set_quoted('VERSION', meson.project_version()) 4 | configure_file(output: 'config.h', configuration: config_h) 5 | 6 | input_emulator_sources = [ 7 | 'main.c', 8 | 'misc.c', 9 | 'touch.c', 10 | 'mouse.c', 11 | 'event.c', 12 | 'print.c', 13 | 'service.c', 14 | 'message.c', 15 | 'options.c', 16 | 'signals.c', 17 | 'keyboard.c' 18 | ] 19 | 20 | input_emulator_c_args = ['-Wno-unused-result', '-Wno-shadow'] 21 | 22 | enable_debug = get_option('enable-debug') 23 | if enable_debug 24 | input_emulator_c_args += '-DDEBUG' 25 | endif 26 | 27 | # Test for rt library support 28 | compiler = meson.get_compiler('c') 29 | rt_dep = compiler.find_library('rt', required : true) 30 | 31 | input_emulator_dep = [ 32 | rt_dep, 33 | ] 34 | 35 | executable('input-emulator', 36 | input_emulator_sources, 37 | c_args: input_emulator_c_args, 38 | dependencies: input_emulator_dep, 39 | install: true ) 40 | 41 | -------------------------------------------------------------------------------- /src/message.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "message.h" 34 | #include "print.h" 35 | 36 | #define MAX_CLIENTS 1 37 | #define MSG_SOCKET_NAME "input-emulator.socket" 38 | 39 | static int srv_sockfd; 40 | static int new_srv_sockfd; 41 | static int cli_sockfd; 42 | static int *sockfd = &new_srv_sockfd; 43 | 44 | bool message_server_running(void) 45 | { 46 | int r; 47 | struct sockaddr_un serv_addr; 48 | int sockfd; 49 | bool in_use_status = false; 50 | 51 | /* Create socket */ 52 | sockfd = socket(AF_UNIX, SOCK_STREAM, 0); 53 | if (sockfd < 0) 54 | { 55 | error_printf("Opening socket\n"); 56 | exit(EXIT_FAILURE); 57 | } 58 | 59 | /* Initialize socket structure */ 60 | bzero((char *) &serv_addr, sizeof(serv_addr)); 61 | serv_addr.sun_family = AF_UNIX; 62 | strcpy(serv_addr.sun_path + 1, MSG_SOCKET_NAME); // Abstract socket 63 | 64 | /* Bind the host address using bind() call.*/ 65 | r = bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); 66 | if (r < 0) 67 | { 68 | if (errno == EADDRINUSE) 69 | { 70 | in_use_status = true; 71 | } 72 | else 73 | { 74 | error_printf("On binding (%s)\n", strerror(errno)); 75 | exit(EXIT_FAILURE); 76 | } 77 | } 78 | else 79 | { 80 | close(sockfd); 81 | } 82 | 83 | return in_use_status; 84 | } 85 | 86 | void message_client_mode_enable(void) 87 | { 88 | sockfd = &cli_sockfd; 89 | } 90 | 91 | void message_server_open(void) 92 | { 93 | debug_printf("Server opening message socket\n"); 94 | 95 | struct sockaddr_un srv_addr; 96 | 97 | debug_printf("Starting message server..\n"); 98 | 99 | /* Create UNIX file socket */ 100 | srv_sockfd = socket(AF_UNIX, SOCK_STREAM, 0); 101 | if (srv_sockfd < 0) 102 | { 103 | error_printf("Opening socket (%s)\n", strerror(errno)); 104 | exit(EXIT_FAILURE); 105 | } 106 | 107 | /* Initialize socket structure */ 108 | bzero((char *) &srv_addr, sizeof(srv_addr)); 109 | srv_addr.sun_family = AF_UNIX; 110 | strcpy(srv_addr.sun_path + 1, MSG_SOCKET_NAME); // Use abstract socket 111 | 112 | /* Bind the host address using bind() call (creates socket file) */ 113 | if (bind(srv_sockfd, (struct sockaddr *) &srv_addr, sizeof(srv_addr)) < 0) 114 | { 115 | error_printf("On binding (%s)\n", strerror(errno)); 116 | exit(EXIT_FAILURE); 117 | } 118 | } 119 | 120 | void message_server_listen(void (*callback)(void)) 121 | { 122 | struct sockaddr_un cli_addr; 123 | socklen_t cli_len; 124 | 125 | /* Listen and sleep until incoming connection */ 126 | listen(srv_sockfd, MAX_CLIENTS); 127 | cli_len = sizeof(cli_addr); 128 | 129 | while (1) 130 | { 131 | /* Block until a new connection is available */ 132 | new_srv_sockfd = accept(srv_sockfd, (struct sockaddr *) &cli_addr, &cli_len); 133 | if (new_srv_sockfd < 0) 134 | { 135 | error_printf("On accept (%s)\n", strerror(errno)); 136 | exit(EXIT_FAILURE); 137 | } 138 | 139 | /* Do callback which will handle incoming request by reading/writing 140 | * messages */ 141 | callback(); 142 | 143 | /* Close connection when done handling incoming request */ 144 | close(new_srv_sockfd); 145 | } 146 | } 147 | 148 | void message_server_close(void) 149 | { 150 | close(srv_sockfd); 151 | } 152 | 153 | void message_client_open(void) 154 | { 155 | struct sockaddr_un srv_addr; 156 | 157 | debug_printf("Starting socket client\n"); 158 | 159 | /* Create a UNIX file socket */ 160 | cli_sockfd = socket(AF_UNIX, SOCK_STREAM, 0); 161 | if (cli_sockfd < 0) 162 | { 163 | error_printf("Opening socket (%s)\n", strerror(errno)); 164 | exit(EXIT_FAILURE); 165 | } 166 | 167 | /* Initialize socket structure */ 168 | bzero((char *) &srv_addr, sizeof(srv_addr)); 169 | srv_addr.sun_family = AF_UNIX; 170 | strcpy(srv_addr.sun_path + 1, MSG_SOCKET_NAME); // Use abstract socket 171 | 172 | /* Connect to the server */ 173 | if (connect(cli_sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) 174 | { 175 | error_printf("Connect failure (%s)\n", strerror(errno)); 176 | exit(EXIT_FAILURE); 177 | } 178 | 179 | message_client_mode_enable(); 180 | } 181 | 182 | void message_client_close(void) 183 | { 184 | debug_printf("Closing socket client\n"); 185 | close(cli_sockfd); 186 | } 187 | 188 | int msg_create( 189 | void **message, 190 | message_type_t type, 191 | void *payload, 192 | uint32_t payload_length) 193 | { 194 | message_header_t *header; 195 | char *payload_p; 196 | 197 | // Allocate memory for message buffer 198 | *message = malloc(sizeof(message_header_t) + payload_length); 199 | if (*message == NULL) 200 | { 201 | perror("Failed to allocate memory for message\n"); 202 | return -1; 203 | } 204 | 205 | // Create message header 206 | header = *message; 207 | header->type = type; 208 | header->payload_length = payload_length; 209 | 210 | // Copy payload if any 211 | if (payload_length > 0) 212 | { 213 | payload_p = *message; 214 | memcpy(payload_p + sizeof(message_header_t), payload, payload_length); 215 | } 216 | 217 | return 0; 218 | } 219 | 220 | void msg_destroy(void *message) 221 | { 222 | free(message); 223 | } 224 | 225 | int msg_send(void *message) 226 | { 227 | ssize_t bytes_sent; 228 | ssize_t bytes_remaining; 229 | char *message_p = message; 230 | 231 | message_header_t *header = message; 232 | bytes_remaining = sizeof(message_header_t) + header->payload_length; 233 | 234 | while (bytes_remaining) 235 | { 236 | bytes_sent = write(*sockfd, message_p, bytes_remaining); 237 | if (bytes_sent < 0) 238 | { 239 | warning_printf("Writing to socket (%s)\n", strerror(errno)); 240 | return -errno; 241 | } 242 | 243 | bytes_remaining -= bytes_sent; 244 | message_p += bytes_sent; 245 | } 246 | 247 | return 0; 248 | } 249 | 250 | int msg_receive(void **message) 251 | { 252 | message_header_t header; 253 | ssize_t bytes_read; 254 | ssize_t bytes_remaining; 255 | char *message_p; 256 | 257 | /* Read message header */ 258 | bytes_read = read(*sockfd, &header, sizeof(header)); 259 | if (bytes_read < 0) 260 | { 261 | error_printf("Reading from (%s)\n", strerror(errno)); 262 | exit(EXIT_FAILURE); 263 | } 264 | 265 | /* Allocate message (header + payload) receive buffer */ 266 | *message = malloc(sizeof(message_header_t) + header.payload_length); 267 | if (*message == NULL) 268 | { 269 | error_printf("malloc() failed (%s)\n", strerror(errno)); 270 | exit(EXIT_FAILURE); 271 | } 272 | 273 | /* Install header */ 274 | memcpy(*message, &header, sizeof(message_header_t)); 275 | 276 | /* Read message payload */ 277 | bytes_remaining = header.payload_length; 278 | message_p = *message; 279 | message_p += sizeof(message_header_t); 280 | 281 | while (bytes_remaining) 282 | { 283 | bytes_read = read(*sockfd, message_p, bytes_remaining); 284 | if (bytes_read < 0) 285 | { 286 | warning_printf("Reading from socket (%s)\n", strerror(errno)); 287 | return -errno; 288 | } 289 | 290 | bytes_remaining -= bytes_read; 291 | message_p += bytes_read; 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | void msg_send_rsp_ok(void) 298 | { 299 | void *message = NULL; 300 | 301 | // Send response 302 | msg_create(&message, RSP_OK, NULL, 0); 303 | msg_send(message); 304 | msg_destroy(message); 305 | } 306 | 307 | void msg_receive_rsp_ok(void) 308 | { 309 | void *message = NULL; 310 | message_header_t *header; 311 | 312 | // Receive response 313 | msg_receive(&message); 314 | header = message; 315 | if (header->type == RSP_ERROR) 316 | { 317 | // char *rsp_text = message + sizeof(message_header_t); 318 | // error_printf("%s", rsp_text); 319 | } 320 | else if (header->type != RSP_OK) 321 | { 322 | warning_printf("Invalid message type received\n"); 323 | } 324 | 325 | msg_destroy(message); 326 | } 327 | -------------------------------------------------------------------------------- /src/message.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | typedef struct __attribute__((__packed__)) 26 | { 27 | uint8_t type; 28 | uint32_t payload_length; 29 | } message_header_t; 30 | 31 | typedef enum 32 | { 33 | REQ_KBD_START, 34 | REQ_KBD_KEY, 35 | REQ_KBD_KEYDOWN, 36 | REQ_KBD_KEYUP, 37 | REQ_KBD_TYPE, 38 | REQ_MOUSE_START, 39 | REQ_MOUSE_MOVE, 40 | REQ_MOUSE_BUTTON, 41 | REQ_MOUSE_BUTTONDOWN, 42 | REQ_MOUSE_BUTTONUP, 43 | REQ_MOUSE_SCROLL, 44 | REQ_TOUCH_START, 45 | REQ_TOUCH_TAP, 46 | REQ_STATUS, 47 | RSP_STATUS, 48 | REQ_STOP, 49 | RSP_STOP, 50 | RSP_OK, 51 | RSP_ERROR, 52 | } message_type_t; 53 | 54 | void message_server_open(void); 55 | void message_server_close(void); 56 | void message_client_open(void); 57 | void message_client_mode_enable(void); 58 | void message_client_close(void); 59 | void message_server_listen(void (*callback)(void)); 60 | int msg_create(void **message, message_type_t type, void *payload, uint32_t payload_length); 61 | void msg_destroy(void *message); 62 | int msg_send(void *message); 63 | int msg_receive(void **message); 64 | void msg_send_rsp_ok(void); 65 | void msg_receive_rsp_ok(void); 66 | bool message_server_running(void); 67 | -------------------------------------------------------------------------------- /src/misc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "print.h" 27 | 28 | wchar_t *convert_mbs_to_wcs(const char *string) 29 | { 30 | wchar_t *wcs; 31 | size_t mbs_length; 32 | 33 | mbs_length = mbstowcs(NULL, string, 0); 34 | debug_printf("mbs_length = %ld\n", mbs_length); 35 | if (mbs_length == (size_t) -1) 36 | { 37 | perror("mbstowcs"); 38 | exit(EXIT_FAILURE); 39 | } 40 | 41 | wcs = calloc(mbs_length + 1, sizeof(wchar_t)); 42 | if (wcs == NULL) 43 | { 44 | perror("calloc"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | if (mbstowcs(wcs, string, mbs_length + 1) == (size_t) -1) 49 | { 50 | perror("mbstowcs"); 51 | exit(EXIT_FAILURE); 52 | } 53 | 54 | return wcs; 55 | } 56 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | #define UNUSED(expr) do { (void)(expr); } while (0) 25 | 26 | #define SYS_NAME_LENGTH_MAX 50 27 | 28 | #define do_ioctl(fd, request, args...) \ 29 | { \ 30 | int status = ioctl(fd, request, ## args); \ 31 | if (status < 0) \ 32 | { \ 33 | fprintf(stderr, "ioctl error: %s\n", strerror(errno)); \ 34 | exit(EXIT_FAILURE); \ 35 | } \ 36 | } 37 | 38 | wchar_t *convert_mbs_to_wcs(const char *string); 39 | -------------------------------------------------------------------------------- /src/mouse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "options.h" 29 | #include "message.h" 30 | #include "event.h" 31 | #include "mouse.h" 32 | #include "service.h" 33 | #include "print.h" 34 | #include "misc.h" 35 | 36 | static int mouse_fd = -1; 37 | static char sys_name[SYS_NAME_LENGTH_MAX]; 38 | static int mouse_config_x_max; 39 | static int mouse_config_y_max; 40 | 41 | int mouse_x_max(void) 42 | { 43 | return mouse_config_x_max; 44 | } 45 | 46 | int mouse_y_max(void) 47 | { 48 | return mouse_config_y_max; 49 | } 50 | 51 | void mouse_move(int x_rel, int y_rel) 52 | { 53 | /* Do nothing if no device */ 54 | if (mouse_fd < 0) 55 | { 56 | return; 57 | } 58 | 59 | debug_printf("Mouse move %d,%d\n", x_rel, y_rel); 60 | 61 | // Move mouse absolute 62 | // emit(mouse_fd, EV_ABS, ABS_X, x); 63 | // emit(mouse_fd, EV_ABS, ABS_Y, y); 64 | // emit(mouse_fd, EV_SYN, SYN_REPORT, 0); 65 | 66 | // Move mouse relative 67 | emit(mouse_fd, EV_REL, REL_X, x_rel); 68 | emit(mouse_fd, EV_REL, REL_Y, y_rel); 69 | emit(mouse_fd, EV_SYN, SYN_REPORT, 0); 70 | } 71 | 72 | void mouse_press(int button) 73 | { 74 | /* Do nothing if no device */ 75 | if (mouse_fd < 0) 76 | { 77 | return; 78 | } 79 | 80 | // Press button 81 | emit(mouse_fd, EV_KEY, button, 1); 82 | emit(mouse_fd, EV_SYN, SYN_REPORT, 0); 83 | } 84 | 85 | void mouse_release(int button) 86 | { 87 | /* Do nothing if no device */ 88 | if (mouse_fd < 0) 89 | { 90 | return; 91 | } 92 | 93 | // Press button 94 | emit(mouse_fd, EV_KEY, button, 0); 95 | emit(mouse_fd, EV_SYN, SYN_REPORT, 0); 96 | } 97 | 98 | void mouse_click(int button) 99 | { 100 | /* Do nothing if no device */ 101 | if (mouse_fd < 0) 102 | { 103 | return; 104 | } 105 | 106 | debug_printf("Mouse click 0x%x\n", button); 107 | 108 | mouse_press(button); 109 | usleep(1000); 110 | mouse_release(button); 111 | } 112 | 113 | void mouse_scroll(int32_t ticks) 114 | { 115 | /* Do nothing if no device */ 116 | if (mouse_fd < 0) 117 | { 118 | return; 119 | } 120 | 121 | // Scroll wheel number of ticks 122 | emit(mouse_fd, EV_REL, REL_WHEEL, ticks); 123 | emit(mouse_fd, EV_SYN, SYN_REPORT, 0); 124 | } 125 | 126 | int mouse_create(int x_max, int y_max) 127 | { 128 | static struct uinput_setup usetup; 129 | static struct uinput_abs_setup abs_setup; 130 | 131 | if (mouse_fd >= 0) 132 | { 133 | /* Mouse already started */ 134 | return -1; 135 | } 136 | 137 | mouse_config_x_max = x_max; 138 | mouse_config_y_max = y_max; 139 | 140 | mouse_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 141 | if (mouse_fd < 0) 142 | { 143 | error_printf("Could not open /dev/uinput (%s)\n", strerror(errno)); 144 | exit(EXIT_FAILURE); 145 | } 146 | 147 | /* Enable button events */ 148 | do_ioctl(mouse_fd, UI_SET_EVBIT, EV_KEY); 149 | do_ioctl(mouse_fd, UI_SET_KEYBIT, BTN_LEFT); 150 | do_ioctl(mouse_fd, UI_SET_KEYBIT, BTN_MIDDLE); 151 | do_ioctl(mouse_fd, UI_SET_KEYBIT, BTN_RIGHT); 152 | do_ioctl(mouse_fd, UI_SET_KEYBIT, BTN_SIDE); 153 | do_ioctl(mouse_fd, UI_SET_KEYBIT, BTN_EXTRA); 154 | 155 | /* Enable relative movement events */ 156 | do_ioctl(mouse_fd, UI_SET_EVBIT, EV_REL); 157 | do_ioctl(mouse_fd, UI_SET_RELBIT, REL_X); 158 | do_ioctl(mouse_fd, UI_SET_RELBIT, REL_Y); 159 | do_ioctl(mouse_fd, UI_SET_RELBIT, REL_WHEEL); 160 | 161 | /* Enable absolute movement events */ 162 | do_ioctl(mouse_fd, UI_SET_EVBIT, EV_ABS); 163 | do_ioctl(mouse_fd, UI_SET_ABSBIT, ABS_X); 164 | do_ioctl(mouse_fd, UI_SET_ABSBIT, ABS_Y); 165 | 166 | /* Set up mouse properties (resolution) */ 167 | abs_setup.code = ABS_X; 168 | abs_setup.absinfo.minimum = 0; 169 | abs_setup.absinfo.maximum = x_max; 170 | do_ioctl(mouse_fd, UI_ABS_SETUP, &abs_setup); 171 | 172 | abs_setup.code = ABS_Y; 173 | abs_setup.absinfo.minimum = 0; 174 | abs_setup.absinfo.maximum = y_max; 175 | do_ioctl(mouse_fd, UI_ABS_SETUP, &abs_setup); 176 | 177 | /* Set up device */ 178 | memset(&usetup, 0, sizeof(usetup)); 179 | usetup.id.bustype = BUS_USB; 180 | usetup.id.vendor = 0x1111; 181 | usetup.id.product = 0x1111; 182 | usetup.id.version = 1; 183 | strcpy(usetup.name, "Simulated mouse"); 184 | do_ioctl(mouse_fd, UI_DEV_SETUP, &usetup); 185 | 186 | /* Create device */ 187 | do_ioctl(mouse_fd, UI_DEV_CREATE); 188 | 189 | /* 190 | * On UI_DEV_CREATE the kernel will create the device node for this 191 | * device. We are inserting a pause here so that userspace has time 192 | * to detect, initialize the new device, and can start listening to 193 | * the event, otherwise it will not notice the event we are about 194 | * to send. This pause is only needed in our example code! 195 | */ 196 | sleep(1); 197 | 198 | device_ref_count++; 199 | 200 | debug_printf("Created mouse input device with x-max=%d y-max=%d\n", x_max, y_max); 201 | 202 | /* Save sys name */ 203 | do_ioctl(mouse_fd, UI_GET_SYSNAME(50), sys_name); 204 | 205 | return 0; 206 | } 207 | 208 | void mouse_destroy(void) 209 | { 210 | /* 211 | * Give userspace some time to read the events before we destroy the 212 | * device with UI_DEV_DESTROY. 213 | */ 214 | sleep(1); 215 | 216 | if (mouse_fd < 0) 217 | { 218 | return; 219 | } 220 | 221 | debug_printf("Destroying mouse input device\n"); 222 | 223 | do_ioctl(mouse_fd, UI_DEV_DESTROY); 224 | close(mouse_fd); 225 | 226 | mouse_fd = -1; 227 | 228 | sys_name[0] = 0; 229 | 230 | device_ref_count--; 231 | } 232 | 233 | const char* mouse_sys_name(void) 234 | { 235 | return sys_name; 236 | } 237 | 238 | bool mouse_online(void) 239 | { 240 | if (mouse_fd >= 0) 241 | { 242 | return true; 243 | } 244 | 245 | return false; 246 | } 247 | 248 | void do_mouse_click(void *message) 249 | { 250 | message_header_t *header = message; 251 | int *button = message + sizeof(message_header_t); 252 | 253 | if (header->payload_length != sizeof(int)) 254 | { 255 | warning_printf("Invalid payload length"); 256 | return; 257 | } 258 | 259 | mouse_click(*button); 260 | 261 | msg_send_rsp_ok(); 262 | } 263 | 264 | void do_mouse_click_request(int button) 265 | { 266 | void *message = NULL; 267 | 268 | msg_create(&message, REQ_MOUSE_BUTTON, &button, sizeof(button)); 269 | msg_send(message); 270 | msg_destroy(message); 271 | 272 | msg_receive_rsp_ok(); 273 | } 274 | 275 | void do_mouse_scroll(void *message) 276 | { 277 | message_header_t *header = message; 278 | int32_t *ticks = message + sizeof(message_header_t); 279 | 280 | if (header->payload_length != sizeof(int32_t)) 281 | { 282 | warning_printf("Invalid payload length"); 283 | return; 284 | } 285 | 286 | mouse_scroll(*ticks); 287 | 288 | msg_send_rsp_ok(); 289 | } 290 | 291 | void do_mouse_scroll_request(int32_t ticks) 292 | { 293 | void *message = NULL; 294 | 295 | msg_create(&message, REQ_MOUSE_SCROLL, &ticks, sizeof(ticks)); 296 | msg_send(message); 297 | msg_destroy(message); 298 | 299 | msg_receive_rsp_ok(); 300 | } 301 | 302 | void do_mouse_down(void *message) 303 | { 304 | message_header_t *header = message; 305 | int *button = message + sizeof(message_header_t); 306 | 307 | if (header->payload_length != sizeof(int)) 308 | { 309 | warning_printf("Invalid payload length"); 310 | return; 311 | } 312 | 313 | mouse_press(*button); 314 | 315 | msg_send_rsp_ok(); 316 | } 317 | 318 | void do_mouse_down_request(int button) 319 | { 320 | void *message = NULL; 321 | 322 | msg_create(&message, REQ_MOUSE_BUTTONDOWN, &button, sizeof(button)); 323 | msg_send(message); 324 | msg_destroy(message); 325 | 326 | msg_receive_rsp_ok(); 327 | } 328 | 329 | void do_mouse_up(void *message) 330 | { 331 | message_header_t *header = message; 332 | int *button = message + sizeof(message_header_t); 333 | 334 | if (header->payload_length != sizeof(int)) 335 | { 336 | warning_printf("Invalid payload length"); 337 | return; 338 | } 339 | 340 | mouse_release(*button); 341 | 342 | msg_send_rsp_ok(); 343 | } 344 | 345 | void do_mouse_up_request(int button) 346 | { 347 | void *message = NULL; 348 | 349 | msg_create(&message, REQ_MOUSE_BUTTONUP, &button, sizeof(button)); 350 | msg_send(message); 351 | msg_destroy(message); 352 | 353 | msg_receive_rsp_ok(); 354 | } 355 | 356 | void do_mouse_move(void *message) 357 | { 358 | message_header_t *header = message; 359 | mouse_move_data_t *move = message + sizeof(message_header_t); 360 | 361 | if (header->payload_length != sizeof(mouse_move_data_t)) 362 | { 363 | warning_printf("Invalid payload length"); 364 | return; 365 | } 366 | 367 | mouse_move(move->x, move->y); 368 | 369 | msg_send_rsp_ok(); 370 | } 371 | 372 | void do_mouse_move_request(int32_t x, int32_t y) 373 | { 374 | void *message = NULL; 375 | mouse_move_data_t mouse_move_data; 376 | 377 | mouse_move_data.x = x; 378 | mouse_move_data.y = y; 379 | 380 | msg_create(&message, REQ_MOUSE_MOVE, &mouse_move_data, sizeof(mouse_move_data_t)); 381 | msg_send(message); 382 | msg_destroy(message); 383 | 384 | msg_receive_rsp_ok(); 385 | } 386 | 387 | void do_mouse_start(void *message) 388 | { 389 | message_header_t *header = message; 390 | mouse_start_data_t *data = message + sizeof(message_header_t); 391 | 392 | if (header->payload_length != sizeof(mouse_start_data_t)) 393 | { 394 | warning_printf("Warning: Invalid payload length\n"); 395 | return; 396 | } 397 | 398 | mouse_create(data->x_max, data->y_max); 399 | 400 | msg_send_rsp_ok(); 401 | } 402 | 403 | void do_mouse_start_request(uint32_t x_max, uint32_t y_max) 404 | { 405 | void *message = NULL; 406 | mouse_start_data_t data; 407 | 408 | debug_printf("Sending mouse start message!\n"); 409 | 410 | data.x_max = x_max; 411 | data.y_max = y_max; 412 | 413 | msg_create(&message, REQ_MOUSE_START, &data, sizeof(data)); 414 | msg_send(message); 415 | msg_destroy(message); 416 | 417 | msg_receive_rsp_ok(); 418 | } 419 | 420 | -------------------------------------------------------------------------------- /src/mouse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | typedef struct 26 | { 27 | uint32_t x; 28 | uint32_t y; 29 | int32_t x_rel; 30 | int32_t y_rel; 31 | } mouse_move_data_t; 32 | 33 | typedef struct 34 | { 35 | uint32_t x_max; 36 | uint32_t y_max; 37 | } mouse_start_data_t; 38 | 39 | int mouse_create(int x, int y); 40 | void mouse_destroy(void); 41 | bool mouse_online(void); 42 | const char* mouse_sys_name(void); 43 | void mouse_move(int x_rel, int y_rel); 44 | void do_mouse_click(void *message); 45 | void do_mouse_click_request(int button); 46 | void do_mouse_down(void *message); 47 | void do_mouse_down_request(int button); 48 | void do_mouse_up(void *message); 49 | void do_mouse_up_request(int button); 50 | void do_mouse_scroll(void *message); 51 | void do_mouse_scroll_request(int32_t ticks); 52 | void do_mouse_move(void *message); 53 | void do_mouse_move_request(int32_t x, int32_t y); 54 | void do_mouse_start_request(uint32_t x_max, uint32_t y_max); 55 | void do_mouse_start(void *message); 56 | int mouse_x_max(void); 57 | int mouse_y_max(void); 58 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "options.h" 30 | #include "config.h" 31 | #include "print.h" 32 | #include "misc.h" 33 | #include "keyboard.h" 34 | 35 | option_t option = 36 | { 37 | .command = CMD_NONE, 38 | .device = DEV_NONE, 39 | .x_max = 1024, 40 | .y_max = 768, 41 | .slots = 4, 42 | .kbd_action = KBD_NONE, 43 | .string = NULL, 44 | .key = 0, 45 | .type_delay = 15, 46 | .mouse_action = MOUSE_NONE, 47 | .ticks = 0, 48 | .button = -1, 49 | .touch_action = TOUCH_NONE, 50 | .x = -1, 51 | .y = -1, 52 | .duration = 15, 53 | .daemonize = true, 54 | .wc_string = NULL, 55 | }; 56 | 57 | void options_help_print(void) 58 | { 59 | printf("Usage: input-emulator [--version] [--help] []\n"); 60 | printf("\n"); 61 | printf(" -v, --version Display version\n"); 62 | printf(" -h, --help Display help\n"); 63 | printf("\n"); 64 | printf("Available commands:\n"); 65 | printf(" start [] kbd|mouse|touch Create virtual input device\n"); 66 | printf(" kbd Do keyboard action\n"); 67 | printf(" mouse Do mouse action\n"); 68 | printf(" touch Do touch action\n"); 69 | printf(" status Show status of virtual input devices\n"); 70 | printf(" stop kbd|mouse|touch|all Destroy virtual input device\n"); 71 | printf("\n"); 72 | printf("Start options:\n"); 73 | printf(" -x, --x-max Maximum x-coordinate (only for mouse and touch)\n"); 74 | printf(" -y, --y-max Maximum y-coordinate (only for mouse and touch)\n"); 75 | printf(" -s, --slots Maximum number of slots (fingers) recognized (only for touch)\n"); 76 | printf(" -d, --type-delay Type delay (only for keyboard, default: %d)\n", option.type_delay); 77 | printf(" -n, --no-daemonize Run in foreground\n"); 78 | printf("\n"); 79 | printf("Keyboard actions:\n"); 80 | printf(" type Type string\n"); 81 | printf(" key Stroke key (press and release)\n"); 82 | printf(" keydown Press key\n"); 83 | printf(" keyup Release key\n"); 84 | printf("\n"); 85 | printf("Mouse actions:\n"); 86 | printf(" move Move mouse x,y relative\n"); 87 | printf(" button left|middle|right Click mouse button (press and release)\n"); 88 | printf(" buttondown left|middle|right Press mouse button\n"); 89 | printf(" buttonup left|middle|right Release mouse button\n"); 90 | printf(" scroll Scroll mouse wheel number of ticks\n"); 91 | printf("\n"); 92 | printf("Touch actions:\n"); 93 | printf(" tap Tap at x,y coordinate\n"); 94 | printf("\n"); 95 | } 96 | 97 | void options_version_print(void) 98 | { 99 | printf("input-emulator v%s\n", VERSION); 100 | } 101 | 102 | void options_parse(int argc, char *argv[]) 103 | { 104 | int c; 105 | 106 | /* Print help if no arguments */ 107 | if (argc == 1) 108 | { 109 | options_help_print(); 110 | exit(EXIT_SUCCESS); 111 | } 112 | 113 | /* getopt_long stores the option index here */ 114 | int option_index = 0; 115 | 116 | /* Skip ahead past command */ 117 | optind = 2; 118 | 119 | if (strcmp(argv[1], "start") == 0) 120 | { 121 | option.command = CMD_START; 122 | 123 | static struct option long_options[] = 124 | { 125 | {"x-max", required_argument, 0, 'x'}, 126 | {"y-max", required_argument, 0, 'y'}, 127 | {"slots", required_argument, 0, 's'}, 128 | {"type-delay", required_argument, 0, 'd'}, 129 | {"no-daemonize", no_argument, 0, 'n'}, 130 | {0, 0, 0, 0 } 131 | }; 132 | 133 | do 134 | { 135 | /* Parse start options */ 136 | c = getopt_long(argc, argv, "x:y:s:d:n", long_options, &option_index); 137 | 138 | switch (c) 139 | { 140 | case 'x': 141 | option.x_max = atoi(optarg); 142 | break; 143 | 144 | case 'y': 145 | option.y_max = atoi(optarg); 146 | break; 147 | 148 | case 's': 149 | option.slots = atoi(optarg); 150 | break; 151 | 152 | case 'd': 153 | option.type_delay = atoi(optarg); 154 | break; 155 | 156 | case 'n': 157 | option.daemonize = false; 158 | break; 159 | 160 | case '?': 161 | exit(EXIT_FAILURE); 162 | } 163 | } while (c != -1); 164 | } 165 | else if (strcmp(argv[1], "stop") == 0) 166 | { 167 | option.command = CMD_STOP; 168 | } 169 | else if (strcmp(argv[1], "kbd") == 0) 170 | { 171 | option.command = CMD_KBD; 172 | 173 | if (optind != argc) 174 | { 175 | if (strcmp(argv[optind], "type") == 0) 176 | { 177 | debug_printf("type!\n"); 178 | option.kbd_action = KBD_TYPE; 179 | optind++; 180 | if (optind != argc) 181 | { 182 | option.string = strdup(argv[optind]); 183 | option.wc_string = convert_mbs_to_wcs(argv[optind]); 184 | 185 | optind++; 186 | debug_printf("string = '%s'\n", option.string); 187 | } 188 | } 189 | else if (strcmp(argv[optind], "key") == 0) 190 | { 191 | debug_printf("key!\n"); 192 | option.kbd_action = KBD_KEY; 193 | optind++; 194 | if (optind != argc) 195 | { 196 | option.wc_string = convert_mbs_to_wcs(argv[optind]); 197 | wchar_or_alias_to_key(option.wc_string, &option.key); 198 | 199 | optind++; 200 | } 201 | } 202 | else if (strcmp(argv[optind], "keydown") == 0) 203 | { 204 | debug_printf("keydown!\n"); 205 | option.kbd_action = KBD_KEYDOWN; 206 | optind++; 207 | if (optind != argc) 208 | { 209 | option.wc_string = convert_mbs_to_wcs(argv[optind]); 210 | wchar_or_alias_to_key(option.wc_string, &option.key); 211 | 212 | optind++; 213 | } 214 | } 215 | else if (strcmp(argv[optind], "keyup") == 0) 216 | { 217 | debug_printf("keyup!\n"); 218 | option.kbd_action = KBD_KEYUP; 219 | optind++; 220 | if (optind != argc) 221 | { 222 | option.wc_string = convert_mbs_to_wcs(argv[optind]); 223 | wchar_or_alias_to_key(option.wc_string, &option.key); 224 | 225 | optind++; 226 | } 227 | } 228 | } 229 | } 230 | else if (strcmp(argv[1], "mouse") == 0) 231 | { 232 | option.command = CMD_MOUSE; 233 | 234 | } 235 | else if (strcmp(argv[1], "touch") == 0) 236 | { 237 | option.command = CMD_TOUCH; 238 | 239 | } 240 | else if (strcmp(argv[1], "status") == 0) 241 | { 242 | option.command = CMD_STATUS; 243 | 244 | } 245 | else 246 | { 247 | // No command provided so we restore index 248 | optind = 1; 249 | 250 | static struct option long_options[] = 251 | { 252 | {"version", no_argument, 0, 'v'}, 253 | {"help", no_argument, 0, 'h'}, 254 | {0, 0, 0, 0 } 255 | }; 256 | 257 | do 258 | { 259 | /* Parse options */ 260 | c = getopt_long(argc, argv, "vh", long_options, &option_index); 261 | 262 | switch (c) 263 | { 264 | case 'v': 265 | options_version_print(); 266 | exit(EXIT_SUCCESS); 267 | 268 | case 'h': 269 | options_help_print(); 270 | exit(EXIT_SUCCESS); 271 | 272 | case '?': 273 | exit(EXIT_FAILURE); 274 | } 275 | } while (c != -1); 276 | } 277 | 278 | if ((option.command == CMD_NONE) && (optind != argc)) 279 | { 280 | error_printf("Unknown command\n"); 281 | exit(EXIT_FAILURE); 282 | } 283 | 284 | if ((option.command == CMD_START) || (option.command == CMD_STOP)) 285 | { 286 | if (optind != argc) 287 | { 288 | if (strcmp(argv[optind],"kbd") == 0) 289 | { 290 | option.device = DEV_KEYBOARD; 291 | optind++; 292 | } 293 | else if (strcmp(argv[optind],"mouse") == 0) 294 | { 295 | option.device = DEV_MOUSE; 296 | optind++; 297 | } 298 | else if (strcmp(argv[optind],"touch") == 0) 299 | { 300 | option.device = DEV_TOUCH; 301 | optind++; 302 | } 303 | else if (strcmp(argv[optind],"all") == 0) 304 | { 305 | if (option.command == CMD_STOP) 306 | { 307 | option.device = DEV_ALL; 308 | optind++; 309 | } 310 | } 311 | } 312 | 313 | if (option.device == DEV_NONE) 314 | { 315 | if (option.command == CMD_START) 316 | { 317 | error_printf("Please specify which device (kbd, mouse, touch) to start\n"); 318 | } 319 | else 320 | { 321 | error_printf("Please specify which device (kbd, mouse, touch, all) to stop\n"); 322 | } 323 | exit(EXIT_FAILURE); 324 | } 325 | } 326 | 327 | if (option.command == CMD_KBD) 328 | { 329 | if (option.kbd_action == KBD_NONE) 330 | { 331 | error_printf("Please specify kbd \n"); 332 | exit(EXIT_FAILURE); 333 | } 334 | 335 | if ((option.kbd_action == KBD_TYPE) && (option.string == NULL)) 336 | { 337 | error_printf("Please specify type \n"); 338 | exit(EXIT_FAILURE); 339 | } 340 | 341 | if ((option.kbd_action == KBD_KEY) && (option.key == 0)) 342 | { 343 | error_printf("Please specify key \n"); 344 | exit(EXIT_FAILURE); 345 | } 346 | } 347 | 348 | if (option.command == CMD_TOUCH) 349 | { 350 | if (optind != argc) 351 | { 352 | if (strcmp(argv[optind], "tap") == 0) 353 | { 354 | option.touch_action = TOUCH_TAP; 355 | optind++; 356 | if (optind != argc) 357 | { 358 | option.x = atoi(argv[optind]); 359 | optind++; 360 | if (optind != argc) 361 | { 362 | option.y = atoi(argv[optind]); 363 | optind++; 364 | } 365 | } 366 | } 367 | } 368 | 369 | if (option.touch_action == TOUCH_TAP) 370 | { 371 | if ((option.x == -1) || (option.y == -1)) 372 | { 373 | error_printf("Please specify tap \n"); 374 | exit(EXIT_FAILURE); 375 | } 376 | } 377 | 378 | if (option.touch_action == TOUCH_NONE) 379 | { 380 | error_printf("Please specify touch \n"); 381 | exit(EXIT_FAILURE); 382 | } 383 | } 384 | 385 | if (option.command == CMD_MOUSE) 386 | { 387 | if (optind != argc) 388 | { 389 | if (strcmp(argv[optind], "move") == 0) 390 | { 391 | option.mouse_action = MOUSE_MOVE; 392 | optind++; 393 | if (optind != argc) 394 | { 395 | option.x = atoi(argv[optind]); 396 | optind++; 397 | if (optind != argc) 398 | { 399 | option.y = atoi(argv[optind]); 400 | optind++; 401 | } 402 | } 403 | } 404 | else if (strcmp(argv[optind], "button") == 0) 405 | { 406 | option.mouse_action = MOUSE_BUTTON; 407 | optind++; 408 | if (optind != argc) 409 | { 410 | if (strcmp(argv[optind],"left") == 0) 411 | { 412 | option.button = BTN_LEFT; 413 | } 414 | else if (strcmp(argv[optind],"middle") == 0) 415 | { 416 | option.button = BTN_MIDDLE; 417 | } 418 | else if (strcmp(argv[optind],"right") == 0) 419 | { 420 | option.button = BTN_RIGHT; 421 | } 422 | optind++; 423 | } 424 | } 425 | else if (strcmp(argv[optind], "buttondown") == 0) 426 | { 427 | option.mouse_action = MOUSE_BUTTONDOWN; 428 | optind++; 429 | if (optind != argc) 430 | { 431 | if (strcmp(argv[optind],"left") == 0) 432 | { 433 | option.button = BTN_LEFT; 434 | } 435 | else if (strcmp(argv[optind],"middle") == 0) 436 | { 437 | option.button = BTN_MIDDLE; 438 | } 439 | else if (strcmp(argv[optind],"right") == 0) 440 | { 441 | option.button = BTN_RIGHT; 442 | } 443 | optind++; 444 | } 445 | } 446 | else if (strcmp(argv[optind], "buttonup") == 0) 447 | { 448 | option.mouse_action = MOUSE_BUTTONUP; 449 | optind++; 450 | if (optind != argc) 451 | { 452 | if (strcmp(argv[optind],"left") == 0) 453 | { 454 | option.button = BTN_LEFT; 455 | } 456 | else if (strcmp(argv[optind],"middle") == 0) 457 | { 458 | option.button = BTN_MIDDLE; 459 | } 460 | else if (strcmp(argv[optind],"right") == 0) 461 | { 462 | option.button = BTN_RIGHT; 463 | } 464 | optind++; 465 | } 466 | } 467 | else if (strcmp(argv[optind], "scroll") == 0) 468 | { 469 | option.mouse_action = MOUSE_SCROLL; 470 | optind++; 471 | if (optind != argc) 472 | { 473 | option.ticks = atoi(argv[optind]); 474 | optind++; 475 | } 476 | } 477 | } 478 | 479 | if (option.mouse_action == MOUSE_BUTTON) 480 | { 481 | if (option.button == -1) 482 | { 483 | error_printf("Please specify button left|middle|right\n"); 484 | exit(EXIT_FAILURE); 485 | } 486 | } 487 | 488 | if (option.mouse_action == MOUSE_MOVE) 489 | { 490 | if ((option.x == -1) || (option.y == -1)) 491 | { 492 | error_printf("Please specify move \n"); 493 | exit(EXIT_FAILURE); 494 | } 495 | } 496 | 497 | if (option.mouse_action == MOUSE_NONE) 498 | { 499 | error_printf("Please specify mouse \n"); 500 | exit(EXIT_FAILURE); 501 | } 502 | } 503 | 504 | 505 | /* Print any unknown arguments */ 506 | if (optind < argc) 507 | { 508 | error_printf_raw("Unknown arguments: "); 509 | while (optind < argc) 510 | error_printf_raw("%s ", argv[optind++]); 511 | error_printf_raw("\n"); 512 | exit(EXIT_FAILURE); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | typedef enum 28 | { 29 | CMD_START, 30 | CMD_STOP, 31 | CMD_KBD, 32 | CMD_MOUSE, 33 | CMD_TOUCH, 34 | CMD_STATUS, 35 | CMD_NONE 36 | } command_t; 37 | 38 | typedef enum 39 | { 40 | DEV_KEYBOARD, 41 | DEV_MOUSE, 42 | DEV_TOUCH, 43 | DEV_ALL, 44 | DEV_NONE, 45 | } device_t; 46 | 47 | typedef enum 48 | { 49 | KBD_KEY, 50 | KBD_KEYDOWN, 51 | KBD_KEYUP, 52 | KBD_TYPE, 53 | KBD_NONE, 54 | } kbd_action_t; 55 | 56 | typedef enum 57 | { 58 | MOUSE_MOVE, 59 | MOUSE_BUTTON, 60 | MOUSE_BUTTONDOWN, 61 | MOUSE_BUTTONUP, 62 | MOUSE_SCROLL, 63 | MOUSE_NONE, 64 | } mouse_action_t; 65 | 66 | typedef enum 67 | { 68 | TOUCH_TAP, 69 | TOUCH_NONE, 70 | } touch_action_t; 71 | 72 | typedef struct 73 | { 74 | command_t command; 75 | device_t device; 76 | uint32_t x_max; 77 | uint32_t y_max; 78 | int slots; 79 | kbd_action_t kbd_action; 80 | char *string; 81 | wchar_t *wc_string; 82 | uint32_t key; 83 | uint32_t type_delay; 84 | mouse_action_t mouse_action; 85 | int32_t ticks; 86 | int button; 87 | touch_action_t touch_action; 88 | int32_t x; 89 | int32_t y; 90 | uint32_t duration; 91 | bool daemonize; 92 | } option_t; 93 | 94 | extern option_t option; 95 | 96 | void options_help_print(void); 97 | void options_parse(int argc, char *argv[]); 98 | -------------------------------------------------------------------------------- /src/print.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | */ 4 | 5 | #include 6 | #include "print.h" 7 | #include "misc.h" 8 | 9 | void debug_print_hex_dump(void *data, int length) 10 | { 11 | 12 | #ifdef DEBUG 13 | 14 | char *bufferp = data; 15 | int i; 16 | 17 | for (i=0; i 24 | 25 | #define error_printf(format, args...) \ 26 | fprintf(stderr, "Error: " format, ## args) 27 | #define error_printf_raw(format, args...) \ 28 | fprintf(stderr, "" format, ## args) 29 | 30 | #define warning_printf(format, args...) \ 31 | fprintf(stderr, "Warning: " format, ## args) 32 | #define warning_printf_raw(format, args...) \ 33 | fprintf(stderr, "" format, ## args) 34 | 35 | #ifdef DEBUG 36 | #define debug_printf(format, args...) \ 37 | fprintf(stdout, "[debug] " format, ## args) 38 | #define debug_printf_raw(format, args...) \ 39 | fprintf(stdout, "" format, ## args) 40 | #else 41 | #define debug_printf(format, args...) 42 | #define debug_printf_raw(format, args...) 43 | #endif 44 | 45 | void debug_print_hex_dump(void *data, int length); 46 | -------------------------------------------------------------------------------- /src/service.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "message.h" 27 | #include "options.h" 28 | #include "keyboard.h" 29 | #include "mouse.h" 30 | #include "touch.h" 31 | #include "print.h" 32 | #include "misc.h" 33 | 34 | int device_ref_count = 0; 35 | 36 | bool devices_online(void) 37 | { 38 | if (device_ref_count > 0) 39 | { 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | 46 | bool service_running(void) 47 | { 48 | return message_server_running(); 49 | } 50 | 51 | void daemonize(void) 52 | { 53 | pid_t pid, sid; 54 | 55 | /* Already a daemon */ 56 | if ( getppid() == 1 ) return; 57 | 58 | /* Fork off the parent process */ 59 | pid = fork(); 60 | if (pid < 0) 61 | { 62 | exit(EXIT_FAILURE); 63 | } 64 | 65 | /* If we got a good PID, then we can exit the parent process. */ 66 | if (pid > 0) 67 | { 68 | /* Wait some time for everything to have been initialized */ 69 | sleep(3); 70 | exit(EXIT_SUCCESS); 71 | } 72 | 73 | /* At this point we are executing as the child process */ 74 | 75 | /* Change the file mode mask */ 76 | umask(0); 77 | 78 | /* Create a new SID for the child process (child becomes session leader)*/ 79 | sid = setsid(); 80 | if (sid < 0) 81 | { 82 | error_printf("Failed to become session leader\n"); 83 | exit(EXIT_FAILURE); 84 | } 85 | 86 | /* Change the current working directory. This prevents the current 87 | directory from being locked; hence not being able to remove it. */ 88 | if ((chdir("/")) < 0) 89 | { 90 | error_printf("Failed to change directory\n"); 91 | exit(EXIT_FAILURE); 92 | } 93 | 94 | 95 | /* Close all open file descriptors */ 96 | int x; 97 | for (x = sysconf(_SC_OPEN_MAX); x>=0; x--) 98 | { 99 | debug_printf("Closing file descriptor %d\n", x); 100 | close (x); 101 | } 102 | 103 | /* Redirect standard files to /dev/null */ 104 | freopen( "/dev/null", "r", stdin); 105 | freopen( "/dev/null", "w", stdout); 106 | freopen( "/dev/null", "w", stderr); 107 | } 108 | 109 | void do_service_stop(void *message) 110 | { 111 | message_header_t *header = message; 112 | device_t *device = message + sizeof(message_header_t); 113 | 114 | if (header->payload_length != sizeof(device_t)) 115 | { 116 | warning_printf("Invalid payload length\n"); 117 | return; 118 | } 119 | 120 | switch(*device) 121 | { 122 | case DEV_KEYBOARD: 123 | keyboard_destroy(); 124 | break; 125 | 126 | case DEV_MOUSE: 127 | mouse_destroy(); 128 | break; 129 | 130 | case DEV_TOUCH: 131 | touch_destroy(); 132 | break; 133 | 134 | case DEV_ALL: 135 | keyboard_destroy(); 136 | mouse_destroy(); 137 | touch_destroy(); 138 | break; 139 | 140 | case DEV_NONE: 141 | break; 142 | } 143 | 144 | msg_send_rsp_ok(); 145 | } 146 | 147 | void do_service_stop_request(device_t device) 148 | { 149 | void *message = NULL; 150 | 151 | debug_printf("Sending stop message!\n"); 152 | 153 | msg_create(&message, REQ_STOP, &device, sizeof(device)); 154 | 155 | msg_send(message); 156 | msg_destroy(message); 157 | 158 | msg_receive_rsp_ok(); 159 | } 160 | 161 | void do_service_status(void *message) 162 | { 163 | char rsp_text[400]; 164 | char sys_path[] = "/sys/devices/virtual/input"; 165 | char *rsp_text_p = rsp_text; 166 | 167 | sprintf(rsp_text_p, "Online devices:\n"); 168 | rsp_text_p += strlen(rsp_text_p); 169 | 170 | if (keyboard_online()) 171 | { 172 | sprintf(rsp_text_p, " kbd: %s/%s (type-delay: %u)\n", 173 | sys_path, 174 | keyboard_sys_name(), 175 | keyboard_type_delay()); 176 | rsp_text_p += strlen(rsp_text_p); 177 | } 178 | 179 | if (mouse_online()) 180 | { 181 | sprintf(rsp_text_p, 182 | "mouse: %s/%s (x-max: %d y-max: %d)\n", 183 | sys_path, 184 | mouse_sys_name(), 185 | mouse_x_max(), 186 | mouse_y_max()); 187 | rsp_text_p += strlen(rsp_text_p); 188 | } 189 | 190 | if (touch_online()) 191 | { 192 | sprintf(rsp_text_p, 193 | "touch: %s/%s (x-max: %d y-max: %d slots: %d)\n", 194 | sys_path, 195 | touch_sys_name(), 196 | touch_x_max(), 197 | touch_y_max(), 198 | touch_slots()); 199 | } 200 | 201 | // Send response 202 | msg_create(&message, RSP_STATUS, rsp_text, strlen(rsp_text)); 203 | msg_send(message); 204 | msg_destroy(message); 205 | } 206 | 207 | void do_service_status_request(void) 208 | { 209 | void *message = NULL; 210 | message_header_t *header; 211 | 212 | msg_create(&message, REQ_STATUS, NULL, 0); 213 | msg_send(message); 214 | msg_destroy(message); 215 | 216 | // Receive response 217 | msg_receive(&message); 218 | header = message; 219 | if (header->type != RSP_STATUS) 220 | { 221 | warning_printf("Invalid message type received\n"); 222 | } 223 | 224 | char *rsp_text = message + sizeof(message_header_t); 225 | 226 | printf("%s", rsp_text); 227 | msg_destroy(message); 228 | } 229 | -------------------------------------------------------------------------------- /src/service.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | extern int device_ref_count; 26 | 27 | bool devices_online(void); 28 | bool service_running(void); 29 | void daemonize(void); 30 | void do_service_stop_request(device_t device); 31 | void do_service_stop(void *message); 32 | void do_service_status_request(void); 33 | void do_service_status(void *message); 34 | 35 | -------------------------------------------------------------------------------- /src/signals.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #include 22 | #include 23 | #include "print.h" 24 | 25 | void signal_handler(int signum) 26 | { 27 | switch (signum) 28 | { 29 | case SIGHUP: 30 | debug_printf("Received SIGHUP signal!\n"); 31 | break; 32 | case SIGINT: 33 | debug_printf("Received SIGINT signal!\n"); 34 | break; 35 | } 36 | exit(EXIT_FAILURE); 37 | } 38 | 39 | void signal_handlers_install(void) 40 | { 41 | signal(SIGHUP, signal_handler); 42 | signal(SIGINT, signal_handler); 43 | signal(SIGPIPE, SIG_IGN); 44 | } 45 | -------------------------------------------------------------------------------- /src/signals.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022-2023 Martin Lund 3 | * Copyright (C) 2023 DEIF A/S 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18 | * 02110-1301, USA. 19 | */ 20 | 21 | #pragma once 22 | 23 | void signal_handler(int signum); 24 | void signal_handlers_install(void); 25 | -------------------------------------------------------------------------------- /src/touch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "touch.h" 30 | #include "event.h" 31 | #include "options.h" 32 | #include "message.h" 33 | #include "service.h" 34 | #include "print.h" 35 | #include "misc.h" 36 | 37 | static int touch_fd = -1; 38 | static uint32_t touch_id = 0; 39 | static char sys_name[SYS_NAME_LENGTH_MAX]; 40 | static int touch_config_x_max; 41 | static int touch_config_y_max; 42 | static int touch_config_slots; 43 | 44 | int touch_x_max(void) 45 | { 46 | return touch_config_x_max; 47 | } 48 | 49 | int touch_y_max(void) 50 | { 51 | return touch_config_y_max; 52 | } 53 | 54 | int touch_slots(void) 55 | { 56 | return touch_config_slots; 57 | } 58 | 59 | bool touch_online(void) 60 | { 61 | if (touch_fd >= 0) 62 | { 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | 69 | void touch_tap(int x, int y, int duration) 70 | { 71 | /* Do nothing if no device */ 72 | if (touch_fd < 0) 73 | { 74 | return; 75 | } 76 | 77 | // One touch tap 78 | emit(touch_fd, EV_ABS, ABS_MT_TRACKING_ID, touch_id++); 79 | emit(touch_fd, EV_ABS, ABS_MT_POSITION_X, x); 80 | emit(touch_fd, EV_ABS, ABS_MT_POSITION_Y, y); 81 | emit(touch_fd, EV_KEY, BTN_TOUCH, 1); 82 | emit(touch_fd, EV_ABS, ABS_X, x); 83 | emit(touch_fd, EV_ABS, ABS_Y, y); 84 | emit(touch_fd, EV_SYN, SYN_REPORT, 0); 85 | 86 | usleep(duration*1000); 87 | 88 | emit(touch_fd, EV_ABS, ABS_MT_TRACKING_ID, -1); 89 | emit(touch_fd, EV_KEY, BTN_TOUCH, 0); 90 | emit(touch_fd, EV_SYN, SYN_REPORT, 0); 91 | } 92 | 93 | int touch_create(int x_max, int y_max, int slots) 94 | { 95 | static struct uinput_setup usetup; 96 | static struct uinput_abs_setup abs_setup; 97 | 98 | touch_config_x_max = x_max; 99 | touch_config_y_max = y_max; 100 | touch_config_x_max = x_max; 101 | 102 | if (touch_fd >= 0) 103 | { 104 | /* Touch already started */ 105 | return -1; 106 | } 107 | 108 | touch_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 109 | if (touch_fd < 0) 110 | { 111 | error_printf("Could not open /dev/uinput (%s)\n", strerror(errno)); 112 | exit(EXIT_FAILURE); 113 | } 114 | 115 | /* Enable absolute events (touchscreen) */ 116 | do_ioctl(touch_fd, UI_SET_EVBIT, EV_ABS); 117 | 118 | do_ioctl(touch_fd, UI_SET_EVBIT, EV_KEY); 119 | do_ioctl(touch_fd, UI_SET_KEYBIT, BTN_TOUCH); 120 | 121 | do_ioctl(touch_fd, UI_SET_ABSBIT, ABS_MT_SLOT); 122 | do_ioctl(touch_fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); 123 | do_ioctl(touch_fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); 124 | do_ioctl(touch_fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); 125 | 126 | /* Set up touchscreen properties */ 127 | abs_setup.code = ABS_X; 128 | abs_setup.absinfo.minimum = 0; 129 | abs_setup.absinfo.maximum = x_max; 130 | do_ioctl(touch_fd, UI_ABS_SETUP, &abs_setup); 131 | 132 | abs_setup.code = ABS_Y; 133 | abs_setup.absinfo.minimum = 0; 134 | abs_setup.absinfo.maximum = y_max; 135 | do_ioctl(touch_fd, UI_ABS_SETUP, &abs_setup); 136 | 137 | abs_setup.code = ABS_MT_POSITION_X; 138 | abs_setup.absinfo.minimum = 0; 139 | abs_setup.absinfo.maximum = x_max; 140 | do_ioctl(touch_fd, UI_ABS_SETUP, &abs_setup); 141 | 142 | abs_setup.code = ABS_MT_POSITION_Y; 143 | abs_setup.absinfo.minimum = 0; 144 | abs_setup.absinfo.maximum = y_max; 145 | do_ioctl(touch_fd, UI_ABS_SETUP, &abs_setup); 146 | 147 | abs_setup.code = ABS_MT_SLOT; 148 | abs_setup.absinfo.minimum = 0; 149 | abs_setup.absinfo.maximum = slots; 150 | do_ioctl(touch_fd, UI_ABS_SETUP, &abs_setup); 151 | 152 | /* Set up device */ 153 | memset(&usetup, 0, sizeof(usetup)); 154 | usetup.id.bustype = BUS_USB; 155 | usetup.id.vendor = 0x1234; /* sample vendor */ 156 | usetup.id.product = 0x5678; /* sample product */ 157 | strcpy(usetup.name, "Simulated touchscreen"); 158 | do_ioctl(touch_fd, UI_DEV_SETUP, &usetup); 159 | 160 | /* Create device */ 161 | do_ioctl(touch_fd, UI_DEV_CREATE); 162 | 163 | /* 164 | * On UI_DEV_CREATE the kernel will create the device node for this 165 | * device. We are inserting a pause here so that userspace has time 166 | * to detect, initialize the new device, and can start listening to 167 | * the event, otherwise it will not notice the event we are about 168 | * to send. This pause is only needed in our example code! 169 | */ 170 | sleep(1); 171 | 172 | device_ref_count++; 173 | 174 | debug_printf("Created touch input device with x-max=%d, y-max=%d, slots=%d\n", x_max, y_max, slots); 175 | 176 | /* Save sys name */ 177 | do_ioctl(touch_fd, UI_GET_SYSNAME(50), sys_name); 178 | 179 | return 0; 180 | } 181 | 182 | void touch_destroy(void) 183 | { 184 | /* 185 | * Give userspace some time to read the events before we destroy the 186 | * device with UI_DEV_DESTROY. 187 | */ 188 | sleep(1); 189 | 190 | if (touch_fd < 0) 191 | { 192 | return; 193 | } 194 | 195 | debug_printf("Destroying touch input device\n"); 196 | 197 | do_ioctl(touch_fd, UI_DEV_DESTROY); 198 | close(touch_fd); 199 | 200 | touch_fd = -1; 201 | 202 | sys_name[0] = 0; 203 | 204 | device_ref_count--; 205 | } 206 | 207 | const char* touch_sys_name(void) 208 | { 209 | return sys_name; 210 | } 211 | 212 | void do_touch_tap(void *message) 213 | { 214 | message_header_t *header = message; 215 | touch_tap_data_t *tap = message + sizeof(message_header_t); 216 | 217 | if (header->payload_length != sizeof(touch_tap_data_t)) 218 | { 219 | warning_printf("Invalid payload length\n"); 220 | return; 221 | } 222 | 223 | debug_printf("Touch tap at %d,%d for %d ms\n", tap->x, tap->y, tap->duration); 224 | touch_tap(tap->x, tap->y, tap->duration); 225 | 226 | msg_send_rsp_ok(); 227 | } 228 | 229 | void do_touch_tap_request(uint32_t x, uint32_t y, uint32_t duration) 230 | { 231 | void *message = NULL; 232 | touch_tap_data_t touch_tap_data; 233 | 234 | debug_printf("Sending tap message!\n"); 235 | 236 | touch_tap_data.x = x; 237 | touch_tap_data.y = y; 238 | touch_tap_data.duration = duration; 239 | 240 | msg_create(&message, REQ_TOUCH_TAP, &touch_tap_data, sizeof(touch_tap_data_t)); 241 | msg_send(message); 242 | msg_destroy(message); 243 | 244 | msg_receive_rsp_ok(); 245 | } 246 | 247 | void do_touch_start(void *message) 248 | { 249 | message_header_t *header = message; 250 | touch_start_data_t *data = message + sizeof(message_header_t); 251 | 252 | if (header->payload_length != sizeof(touch_start_data_t)) 253 | { 254 | warning_printf("Invalid payload length\n"); 255 | return; 256 | } 257 | 258 | touch_create(data->x_max, data->y_max, data->slots); 259 | 260 | msg_send_rsp_ok(); 261 | } 262 | 263 | void do_touch_start_request(uint32_t x_max, uint32_t y_max, uint8_t slots) 264 | { 265 | void *message = NULL; 266 | touch_start_data_t data; 267 | 268 | data.x_max = x_max; 269 | data.y_max = y_max; 270 | data.slots = slots; 271 | 272 | msg_create(&message, REQ_TOUCH_START, &data, sizeof(data)); 273 | msg_send(message); 274 | msg_destroy(message); 275 | 276 | msg_receive_rsp_ok(); 277 | } 278 | 279 | -------------------------------------------------------------------------------- /src/touch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 DEIF A/S 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | * 02110-1301, USA. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | 25 | typedef struct 26 | { 27 | uint32_t x; 28 | uint32_t y; 29 | uint32_t duration; 30 | } touch_tap_data_t; 31 | 32 | typedef struct 33 | { 34 | uint32_t x_max; 35 | uint32_t y_max; 36 | uint8_t slots; 37 | } touch_start_data_t; 38 | 39 | int touch_create(int x, int y, int slots); 40 | void touch_destroy(void); 41 | bool touch_online(void); 42 | const char* touch_sys_name(void); 43 | void touch_tap(int x, int y, int duration); 44 | void do_touch_tap(void *message); 45 | void do_touch_tap_request(uint32_t x, uint32_t y, uint32_t duration); 46 | void do_touch_start(void *message); 47 | void do_touch_start_request(uint32_t x_max, uint32_t y_max, uint8_t slots); 48 | int touch_x_max(void); 49 | int touch_y_max(void); 50 | int touch_slots(void); 51 | 52 | -------------------------------------------------------------------------------- /test/kbd-stress-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Keyboard stress test 4 | 5 | ie=input-emulator 6 | 7 | ${ie} start --type-delay 0 kbd 8 | ${ie} status 9 | 10 | for (( c=1; c<=1000; c++ )) 11 | do 12 | ${ie} kbd key a 13 | ${ie} kbd key b 14 | ${ie} kbd key c 15 | ${ie} kbd type "123" 16 | done 17 | 18 | ${ie} stop kbd 19 | -------------------------------------------------------------------------------- /test/mouse-stress-test-2.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Mouse stress test 4 | 5 | ie=input-emulator 6 | delay=0.01 7 | 8 | ${ie} start mouse --x-max 1920 --y-max 1080 9 | 10 | ${ie} mouse move -10000 -10000 11 | ${ie} mouse move 960 540 12 | 13 | for (( c=1; c<=1000; c++ )) 14 | do 15 | ${ie} mouse button right 16 | sleep ${delay} 17 | ${ie} mouse button left 18 | sleep ${delay} 19 | done 20 | 21 | ${ie} stop all 22 | -------------------------------------------------------------------------------- /test/mouse-stress-test-3.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Mouse stress test 4 | 5 | ie=input-emulator 6 | delay=0.5 7 | 8 | ${ie} start mouse --x-max 1920 --y-max 1080 9 | 10 | ${ie} mouse move -10000 -10000 11 | ${ie} mouse move 960 540 12 | 13 | for (( c=1; c<=10; c++ )) 14 | do 15 | ${ie} mouse scroll 1 16 | sleep ${delay} 17 | ${ie} mouse scroll -1 18 | sleep ${delay} 19 | done 20 | 21 | ${ie} stop all 22 | -------------------------------------------------------------------------------- /test/mouse-stress-test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Mouse stress test 4 | 5 | ie=input-emulator 6 | delay=0.01 7 | 8 | ${ie} start mouse --x-max 1920 --y-max 1080 9 | 10 | ${ie} mouse move -10000 -10000 11 | ${ie} mouse move 960 540 12 | 13 | for (( c=1; c<=1000; c++ )) 14 | do 15 | ${ie} mouse move 100 0 16 | sleep ${delay} 17 | ${ie} mouse move 0 100 18 | sleep ${delay} 19 | ${ie} mouse move -100 0 20 | sleep ${delay} 21 | ${ie} mouse move 0 -100 22 | sleep ${delay} 23 | done 24 | 25 | ${ie} stop all 26 | --------------------------------------------------------------------------------