├── .gitignore ├── Makefile ├── README.md ├── examples ├── midi_chord_typer.moon └── midi_shift.moon ├── luajit_test.moon ├── uinput-dev-1.rockspec └── uinput.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: local 3 | 4 | uinput.so: uinput.c 5 | gcc -O2 -fPIC -I/usr/include/lua5.1 -c uinput.c -o uinput.o 6 | gcc -shared -o uinput.so -L/usr/lib uinput.o 7 | 8 | local:: 9 | luarocks make --local uinput-dev-1.rockspec 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-uinput 2 | 3 | Lua bindings for uinput on Linux. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ luarocks install https://raw.githubusercontent.com/leafo/lua-uinput/master/uinput-dev-1.rockspec 9 | ``` 10 | 11 | ## Example 12 | 13 | ```lua 14 | local uinput = require("uinput") 15 | 16 | -- luasocket is used for a sleep function 17 | local socket = require("socket") 18 | 19 | -- create a virtual keyboard 20 | local keyboard = uinput.create_keyboard() 21 | 22 | -- we sleep after creating a keyboard before sending a key press to give X a 23 | -- chance to discover the keyboard. This is not required if your program naturally 24 | -- has some delay before key events are sent or you arent in X 25 | socket.sleep(0.1) 26 | 27 | -- press the a key 28 | keyboard:press(uinput.KEY_A) 29 | 30 | -- release the a key 31 | keyboard:release(uinput.KEY_A) 32 | ``` 33 | 34 | Make sure `uinput` is loaded before running the script: 35 | 36 | ```bash 37 | $ modprobe uinput 38 | ``` 39 | 40 | When running your script you'll have to use `sudo` unless you've changed the 41 | permissions on `/dev/uinput` 42 | 43 | 44 | ## Key constants 45 | 46 | Can be found by printing the contents of the module: 47 | 48 | 49 | ```lua 50 | for key in pairs(require("uinput")) do 51 | if key:match("^KEY_") then 52 | print(key) 53 | end 54 | end 55 | 56 | ``` 57 | 58 | 59 | # Contact 60 | 61 | Author: Leaf Corcoran (leafo) ([@moonscript](http://twitter.com/moonscript)) 62 | Email: leafot@gmail.com 63 | Homepage: 64 | 65 | ## License (MIT) 66 | 67 | Copyright (C) 2014 by Leaf Corcoran 68 | 69 | Permission is hereby granted, free of charge, to any person obtaining a copy 70 | of this software and associated documentation files (the "Software"), to deal 71 | in the Software without restriction, including without limitation the rights 72 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 73 | copies of the Software, and to permit persons to whom the Software is 74 | furnished to do so, subject to the following conditions: 75 | 76 | The above copyright notice and this permission notice shall be included in 77 | all copies or substantial portions of the Software. 78 | 79 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 80 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 81 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 82 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 83 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 84 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 85 | THE SOFTWARE. 86 | 87 | -------------------------------------------------------------------------------- /examples/midi_chord_typer.moon: -------------------------------------------------------------------------------- 1 | 2 | file = io.popen "amidi -d -p hw:2,0,0", "r" 3 | hex = "([%a%d][%a%d])" 4 | patt = "#{hex} #{hex} #{hex}$" 5 | 6 | total_down = 0 7 | is_down = {} 8 | 9 | uinput = require "uinput" 10 | keyboard = uinput.create_keyboard! 11 | 12 | OFFSET = 48 13 | 14 | -- Left hand 15 | -- thumb: 7 16 | -- pointer: 5 17 | -- middle: 4 18 | -- ring: 2 19 | -- pinky: 0 20 | 21 | -- Right hand 22 | -- thumb: 12 23 | -- pointer: 14 24 | -- middle: 16 25 | -- ring: 17 26 | -- pinky: 19 27 | 28 | CHORDS = { 29 | "0": { "KEY_A" } 30 | "2": { "KEY_B" } 31 | "4": { "KEY_C" } 32 | "5": { "KEY_D" } 33 | "7": { "KEY_E" } 34 | 35 | "12": { "KEY_F" } 36 | "14": { "KEY_G" } 37 | "16": { "KEY_H" } 38 | "17": { "KEY_I" } 39 | "19": { "KEY_J" } 40 | 41 | -- thumb on opposite 42 | "0-12": { "KEY_K" } 43 | "2-12": { "KEY_L" } 44 | "4-12": { "KEY_M" } 45 | "5-12": { "KEY_N" } 46 | "7-12": { "KEY_O" } 47 | 48 | "7-13": { "KEY_P" } 49 | "7-14": { "KEY_Q" } 50 | "7-16": { "KEY_R" } 51 | "7-17": { "KEY_S" } 52 | "7-19": { "KEY_T" } 53 | 54 | -- pointer on opposite 55 | "0-14": { "KEY_U" } 56 | "2-14": { "KEY_V" } 57 | "4-14": { "KEY_W" } 58 | "5-14": { "KEY_X" } 59 | "7-14": { "KEY_Y" } 60 | 61 | "5-12": { "KEY_Z" } 62 | "5-14": { "KEY_SPACE" } 63 | "5-16": { "KEY_BACKSPACE" } 64 | "5-17": { "KEY_ENTER" } 65 | -- "5-19": { } 66 | } 67 | 68 | send_keys = (keys) -> 69 | for k in *keys 70 | keyboard\press assert uinput[k], "invalid key" 71 | 72 | for k in *keys 73 | keyboard\release assert uinput[k], "invalid key" 74 | 75 | handle_chord = (keys) -> 76 | for k, key in pairs keys 77 | keys[k] = keys[k] - OFFSET 78 | 79 | table.sort keys 80 | chord_string = table.concat keys, "-" 81 | keys = CHORDS[chord_string] 82 | if keys 83 | send_keys keys 84 | else 85 | print "unknown chord", chord_string 86 | 87 | read_event = (file) -> 88 | buff = "" 89 | while true 90 | buff ..= file\read 1 91 | a,b,c = buff\match patt 92 | return a,b,c if a 93 | 94 | while true 95 | a,b,c = read_event file 96 | 97 | event = tonumber a, 16 98 | key = tonumber b, 16 99 | velocity = tonumber c, 16 100 | 101 | switch event 102 | when 176 -- foot pedal 103 | if velocity == 0 104 | keyboard\press uinput.KEY_LEFTSHIFT 105 | else 106 | keyboard\release uinput.KEY_LEFTSHIFT 107 | 108 | when 144 -- down 109 | continue if is_down[key] 110 | is_down[key] = true 111 | total_down += 1 112 | 113 | when 128 -- up 114 | continue unless is_down[key] 115 | total_down -= 1 116 | 117 | if total_down == 0 118 | handle_chord [key for key in pairs is_down] 119 | is_down = {} 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /examples/midi_shift.moon: -------------------------------------------------------------------------------- 1 | -- Converts the sustain pedal on a midi device into a shfit key 2 | 3 | file = io.popen "amidi -d -p hw:2,0,0", "r" 4 | 5 | hex = "([%a%d][%a%d])" 6 | patt = "#{hex} #{hex} #{hex}$" 7 | 8 | down = false 9 | 10 | uinput = require "uinput" 11 | keyboard = uinput.create_keyboard! 12 | 13 | read_event = (file) -> 14 | buff = "" 15 | while true 16 | buff ..= file\read 1 17 | a,b,c = buff\match patt 18 | return a,b,c if a 19 | 20 | while true 21 | a,b,c = read_event file 22 | switch a 23 | when "B0" 24 | if c == "00" 25 | continue if down 26 | down = true 27 | keyboard\press uinput.KEY_LEFTSHIFT 28 | else 29 | continue unless down 30 | down = false 31 | keyboard\release uinput.KEY_LEFTSHIFT 32 | 33 | -------------------------------------------------------------------------------- /luajit_test.moon: -------------------------------------------------------------------------------- 1 | 2 | -- adapted from: http://thiemonge.org/getting-started-with-uinput 3 | 4 | ffi = require "ffi" 5 | import bor from require "bit" 6 | 7 | ffi.cdef [[ 8 | typedef int ssize_t; 9 | 10 | static const int O_WRONLY = 1; 11 | static const int O_NONBLOCK = 2048; 12 | 13 | static const int EV_SYN = 0; 14 | static const int EV_KEY = 1; 15 | 16 | static const int KEY_LEFTSHIFT = 42; 17 | 18 | static const int UI_SET_EVBIT = 1074025828; 19 | static const int UI_SET_KEYBIT = 1074025829; 20 | static const int UI_SET_RELBIT = 1074025830; 21 | static const int UI_DEV_CREATE = 21761; 22 | static const int UI_DEV_DESTROY = 21762; 23 | 24 | static const int BUS_USB = 3; 25 | 26 | int open(const char *path, int oflag, ...); 27 | int ioctl(int fildes, int request, ...); 28 | ssize_t write(int fildes, const void *buf, size_t nbyte); 29 | 30 | unsigned int sleep(unsigned int seconds); 31 | 32 | typedef unsigned int __u32; 33 | typedef signed int __s32; 34 | 35 | typedef unsigned short __u16; 36 | typedef signed short __s16; 37 | 38 | struct input_id { 39 | __u16 bustype; 40 | __u16 vendor; 41 | __u16 product; 42 | __u16 version; 43 | }; 44 | 45 | struct uinput_user_dev { 46 | char name[80]; 47 | struct input_id id; 48 | __u32 ff_effects_max; 49 | __s32 absmax[64]; 50 | __s32 absmin[64]; 51 | __s32 absfuzz[64]; 52 | __s32 absflat[64]; 53 | }; 54 | 55 | struct timeval { 56 | long tv_sec; 57 | long tv_usec; 58 | }; 59 | 60 | struct input_event { 61 | struct timeval time; 62 | __u16 type; 63 | __u16 code; 64 | __s32 value; 65 | }; 66 | ]] 67 | 68 | import 69 | O_WRONLY, O_NONBLOCK 70 | EV_KEY, EV_SYN 71 | UI_SET_EVBIT, UI_SET_KEYBIT 72 | UI_DEV_CREATE, UI_DEV_DESTROY 73 | 74 | KEY_LEFTSHIFT 75 | BUS_USB 76 | from ffi.C 77 | 78 | fd = ffi.C.open "/dev/uinput", bor(O_WRONLY, O_NONBLOCK) 79 | print bor(O_WRONLY, O_NONBLOCK) 80 | 81 | if fd < 0 82 | print "Failed to open uinput file #{fd}" 83 | os.exit 1 84 | 85 | assert 0 <= ffi.C.ioctl fd, UI_SET_EVBIT, EV_KEY 86 | assert 0 <= ffi.C.ioctl fd, UI_SET_EVBIT, EV_SYN 87 | assert 0 <= ffi.C.ioctl fd, UI_SET_KEYBIT, KEY_LEFTSHIFT 88 | 89 | device = ffi.new "struct uinput_user_dev" 90 | device.name = "uinput-sample" 91 | device.id.bustype = BUS_USB 92 | device.id.version = 1 93 | device.id.vendor = 1 94 | device.id.product = 1 95 | 96 | assert 0 <= ffi.C.write fd, device, ffi.sizeof(device) 97 | assert 0 <= ffi.C.ioctl fd, UI_DEV_CREATE 98 | 99 | print "Creating..." 100 | ffi.C.sleep 10 -- wait for X to find it 101 | 102 | send_event = (typ, code, value)-> 103 | input = ffi.new "struct input_event" 104 | input.type = typ if typ 105 | input.code = code if code 106 | input.value = value if value 107 | assert 0 <= ffi.C.write fd, input, ffi.sizeof(input) 108 | 109 | send_event EV_KEY, KEY_LEFTSHIFT, 1 110 | send_event EV_SYN 111 | 112 | print "Shift on..." 113 | ffi.C.sleep 10 114 | 115 | send_event EV_KEY, KEY_LEFTSHIFT, 0 116 | send_event EV_SYN 117 | 118 | print "Shift off..." 119 | ffi.C.sleep 1 120 | 121 | ffi.C.ioctl fd, UI_DEV_DESTROY 122 | -------------------------------------------------------------------------------- /uinput-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "uinput" 2 | version = "dev-1" 3 | 4 | source = { 5 | url = "git://github.com/leafo/lua-uinput.git" 6 | } 7 | 8 | description = { 9 | summary = "A library for creating a virtual keyboard on Linux with uinput", 10 | homepage = "https://github.com/leafo/lua-uinput", 11 | license = "MIT" 12 | } 13 | 14 | dependencies = { 15 | "lua >= 5.1" 16 | } 17 | 18 | supported_platforms = { 19 | "linux" 20 | } 21 | 22 | build = { 23 | type = "builtin", 24 | modules = { 25 | uinput = { 26 | sources = {"uinput.c"}, 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /uinput.c: -------------------------------------------------------------------------------- 1 | 2 | #include "lua.h" 3 | #include "lualib.h" 4 | #include "lauxlib.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | typedef struct { 18 | int fd; 19 | int open; 20 | } Keyboard; 21 | 22 | struct KeyTuple { 23 | const char* name; 24 | const int code; 25 | }; 26 | 27 | #define $(val) {#val, val} 28 | 29 | static const struct KeyTuple key_names[] = { 30 | $(KEY_RESERVED), 31 | $(KEY_ESC), 32 | $(KEY_1), 33 | $(KEY_2), 34 | $(KEY_3), 35 | $(KEY_4), 36 | $(KEY_5), 37 | $(KEY_6), 38 | $(KEY_7), 39 | $(KEY_8), 40 | $(KEY_9), 41 | $(KEY_0), 42 | $(KEY_MINUS), 43 | $(KEY_EQUAL), 44 | $(KEY_BACKSPACE), 45 | $(KEY_TAB), 46 | $(KEY_Q), 47 | $(KEY_W), 48 | $(KEY_E), 49 | $(KEY_R), 50 | $(KEY_T), 51 | $(KEY_Y), 52 | $(KEY_U), 53 | $(KEY_I), 54 | $(KEY_O), 55 | $(KEY_P), 56 | $(KEY_LEFTBRACE), 57 | $(KEY_RIGHTBRACE), 58 | $(KEY_ENTER), 59 | $(KEY_LEFTCTRL), 60 | $(KEY_A), 61 | $(KEY_S), 62 | $(KEY_D), 63 | $(KEY_F), 64 | $(KEY_G), 65 | $(KEY_H), 66 | $(KEY_J), 67 | $(KEY_K), 68 | $(KEY_L), 69 | $(KEY_SEMICOLON), 70 | $(KEY_APOSTROPHE), 71 | $(KEY_GRAVE), 72 | $(KEY_LEFTSHIFT), 73 | $(KEY_BACKSLASH), 74 | $(KEY_Z), 75 | $(KEY_X), 76 | $(KEY_C), 77 | $(KEY_V), 78 | $(KEY_B), 79 | $(KEY_N), 80 | $(KEY_M), 81 | $(KEY_COMMA), 82 | $(KEY_DOT), 83 | $(KEY_SLASH), 84 | $(KEY_RIGHTSHIFT), 85 | $(KEY_KPASTERISK), 86 | $(KEY_LEFTALT), 87 | $(KEY_SPACE), 88 | $(KEY_CAPSLOCK), 89 | $(KEY_F1), 90 | $(KEY_F2), 91 | $(KEY_F3), 92 | $(KEY_F4), 93 | $(KEY_F5), 94 | $(KEY_F6), 95 | $(KEY_F7), 96 | $(KEY_F8), 97 | $(KEY_F9), 98 | $(KEY_F10), 99 | $(KEY_NUMLOCK), 100 | $(KEY_SCROLLLOCK), 101 | $(KEY_KP7), 102 | $(KEY_KP8), 103 | $(KEY_KP9), 104 | $(KEY_KPMINUS), 105 | $(KEY_KP4), 106 | $(KEY_KP5), 107 | $(KEY_KP6), 108 | $(KEY_KPPLUS), 109 | $(KEY_KP1), 110 | $(KEY_KP2), 111 | $(KEY_KP3), 112 | $(KEY_KP0), 113 | $(KEY_KPDOT), 114 | 115 | $(KEY_ZENKAKUHANKAKU), 116 | $(KEY_102ND), 117 | $(KEY_F11), 118 | $(KEY_F12), 119 | $(KEY_RO), 120 | $(KEY_KATAKANA), 121 | $(KEY_HIRAGANA), 122 | $(KEY_HENKAN), 123 | $(KEY_KATAKANAHIRAGANA), 124 | $(KEY_MUHENKAN), 125 | $(KEY_KPJPCOMMA), 126 | $(KEY_KPENTER), 127 | $(KEY_RIGHTCTRL), 128 | $(KEY_KPSLASH), 129 | $(KEY_SYSRQ), 130 | $(KEY_RIGHTALT), 131 | $(KEY_LINEFEED), 132 | $(KEY_HOME), 133 | $(KEY_UP), 134 | $(KEY_PAGEUP), 135 | $(KEY_LEFT), 136 | $(KEY_RIGHT), 137 | $(KEY_END), 138 | $(KEY_DOWN), 139 | $(KEY_PAGEDOWN), 140 | $(KEY_INSERT), 141 | $(KEY_DELETE), 142 | $(KEY_MACRO), 143 | $(KEY_MUTE), 144 | $(KEY_VOLUMEDOWN), 145 | $(KEY_VOLUMEUP), 146 | $(KEY_POWER), 147 | $(KEY_KPEQUAL), 148 | $(KEY_KPPLUSMINUS), 149 | $(KEY_PAUSE), 150 | $(KEY_SCALE), 151 | 152 | {0, 0} 153 | }; 154 | 155 | #define check_keyboard(l, idx)\ 156 | (Keyboard*)luaL_checkudata(l, idx, "uinput_keyboard") 157 | 158 | static int create_keyboard(lua_State *l) { 159 | int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 160 | 161 | if (fd < 0) { 162 | return luaL_error(l, "failed to open /dev/uinput: %d", fd); 163 | } 164 | 165 | ioctl(fd, UI_SET_EVBIT, EV_KEY); 166 | ioctl(fd, UI_SET_EVBIT, EV_SYN); 167 | 168 | int i = 0; 169 | while (true) { 170 | struct KeyTuple tuple = key_names[i++]; 171 | 172 | if (tuple.name == 0) { 173 | break; 174 | } 175 | 176 | ioctl(fd, UI_SET_KEYBIT, tuple.code); 177 | } 178 | 179 | ioctl(fd, UI_SET_KEYBIT, KEY_LEFTSHIFT); 180 | 181 | struct uinput_user_dev uidev; 182 | memset(&uidev, 0, sizeof(uidev)); 183 | snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "uinput-sample"); 184 | uidev.id.bustype = BUS_USB; 185 | uidev.id.vendor = 1; 186 | uidev.id.product = 1; 187 | uidev.id.version = 1; 188 | 189 | write(fd, &uidev, sizeof(uidev)); 190 | ioctl(fd, UI_DEV_CREATE); 191 | 192 | Keyboard* keyboard = lua_newuserdata(l, sizeof(Keyboard)); 193 | luaL_getmetatable(l, "uinput_keyboard"); 194 | lua_setmetatable(l, -2); 195 | 196 | keyboard->fd = fd; 197 | keyboard->open = true; 198 | 199 | return 1; 200 | } 201 | 202 | int send_key(int fd, int code, int value) { 203 | struct input_event ev; 204 | memset(&ev, 0, sizeof(ev)); 205 | 206 | ev.type = EV_KEY; 207 | ev.code = code; 208 | ev.value = value; 209 | write(fd, &ev, sizeof(ev)); 210 | 211 | ev.type = EV_SYN; 212 | ev.code = 0; 213 | ev.value = 0; 214 | write(fd, &ev, sizeof(ev)); 215 | } 216 | 217 | static int keyboard_press(lua_State* l) { 218 | Keyboard* keyboard = check_keyboard(l, 1); 219 | int key = luaL_checkint(l, 2); 220 | send_key(keyboard->fd, key, 1); 221 | return 0; 222 | } 223 | 224 | static int keyboard_release(lua_State *l) { 225 | Keyboard* keyboard = check_keyboard(l, 1); 226 | int key = luaL_checkint(l, 2); 227 | send_key(keyboard->fd, key, 0); 228 | return 0; 229 | } 230 | 231 | static int keyboard_close(lua_State *l) { 232 | Keyboard* keyboard = check_keyboard(l, 1); 233 | 234 | if (keyboard->open) { 235 | ioctl(keyboard->fd, UI_DEV_DESTROY); 236 | close(keyboard->fd); 237 | keyboard->open = false; 238 | } 239 | 240 | return 0; 241 | } 242 | 243 | static int keyboard_gc(lua_State *l) { 244 | return keyboard_close(l); 245 | } 246 | 247 | static const struct luaL_Reg uinput_funcs[] = { 248 | {"create_keyboard", create_keyboard}, 249 | {NULL, NULL} 250 | }; 251 | 252 | static const struct luaL_Reg keyboard_methods[] = { 253 | {"close", keyboard_close}, 254 | {"press", keyboard_press}, 255 | {"release", keyboard_release}, 256 | {NULL, NULL} 257 | }; 258 | 259 | int luaopen_uinput(lua_State *l) { 260 | luaL_newmetatable(l, "uinput_keyboard"); 261 | 262 | lua_newtable(l); 263 | luaL_register(l, NULL, keyboard_methods); 264 | lua_setfield(l, -2, "__index"); 265 | 266 | lua_pushcfunction(l, keyboard_gc); 267 | lua_setfield(l, -2, "__gc"); 268 | 269 | luaL_register(l, "uinput", uinput_funcs); 270 | 271 | int i = 0; 272 | while (true) { 273 | struct KeyTuple tuple = key_names[i++]; 274 | 275 | if (tuple.name == 0) { 276 | break; 277 | } 278 | 279 | lua_pushnumber(l, tuple.code); 280 | lua_setfield(l, -2, tuple.name); 281 | } 282 | 283 | return 1; 284 | } 285 | 286 | --------------------------------------------------------------------------------