First commit.

master
Raheman Vaiya 5 years ago
commit 5fecce7720
  1. 7
      .gitignore
  2. 21
      LICENSE
  3. 16
      Makefile
  4. 100
      README.md
  5. 4
      TODO
  6. BIN
      keyd.1.gz
  7. 11
      keyd.service
  8. BIN
      man.1.gz
  9. 190
      man.md
  10. 601
      src/config.c
  11. 68
      src/config.h
  12. 482
      src/keys.h
  13. 666
      src/main.c

7
.gitignore vendored

@ -0,0 +1,7 @@
*.swp
*.swn
*.swo
*.out
tags
bin/
*.gch

@ -0,0 +1,21 @@
MIT/X Consortium License
© 2020 Raheman Vaiya <r.vaiya@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

@ -0,0 +1,16 @@
.PHONY: clean all utils
all: clean utils
-mkdir bin
gcc -DGIT_COMMIT_HASH=\"$(shell git rev-parse HEAD)\" -DDEBUG -g -Wno-unused-parameter -Wall -Wextra -ludev src/*.c -o bin/keyd
man:
pandoc -s -t man man.md | gzip > keyd.1.gz
clean:
-rm -rf bin
install:
-mkdir /etc/keyd
-install -m755 keyd.service /etc/systemd/system/
-install -m755 bin/keyd /usr/bin
-install -m644 keyd.1.gz /usr/share/man/man1/
-systemctl enable keyd
-systemctl start keyd

@ -0,0 +1,100 @@
# Impetus
Linux lacks a good key remapping solution. In order to achieve satisfactory
results a medley of tools need to be employed (e.g xcape, xmodmap) with the end
result often being tethered to a specified environment (X11). keyd attempts to
solve this problem by providing a flexible system wide daemon which remaps keys
using kernel level input primitives (evdev, uinput).
# Features
keyd has several unique features many of which are traditionally only
found in custom keyboard firmware like [QMK](https://github.com/qmk/qmk_firmware).
Some of the more interesting ones include:
- Layers.
- Key overloading (different behaviour on tap/hold).
- Per keyboard configuration.
- Instantaneous remapping (no flashing required).
- A simple and intuitive config format.
- Being display server agnostic (works on wayland and virtual console alike).
# Dependencies
- make
- gcc
- libudev
# Installation
git clone https://github.com/rvaiya/keyd
cd keyd
sudo apt-get install libudev-dev # Debian specific install the corresponding package on your distribution
make && sudo make install
# Quickstart
1. Install keyd
2. Put the following in `/etc/keyd/default.cfg`:
```
# Turns capslock into an escape key when pressed and a control key when held.
capslock = mods_on_hold(C, esc)
# Remaps the escape key to capslock
esc = capslock
```
3. Run `sudo systemctl restart keyd`
4. See the [man page](man.md) for a comprehensive list of all config options.
# Sample Config File
# Maps escape to the escape layer when held and the escape key when pressed
esc = layer_on_hold(escape_layer, esc)
[escape_layer]
1 = layer_toggle(dvorak)
2 = layer_toggle(default)
# Creates a dvorak layer which inherits from the default layer. Without
# explicitly inheriting from another layer unmapped keys would be ignored.
[dvorak:default]
q = apostrophe
w = comma
e = dot
r = p
t = y
y = f
u = g
i = c
o = r
p = l
a = a
s = o
d = e
f = u
g = i
h = d
j = h
k = t
l = n
semicolon = s
z = semicolon
x = q
c = j
v = k
b = x
n = b
m = m
comma = w
dot = v
slash = z

@ -0,0 +1,4 @@
Make layers inheritable.
Add configurable time based double tap detection.
Document and publish.
Simplify code and special key naming schemes (use a parser and don't manually implement each of HXTXDX).

Binary file not shown.

@ -0,0 +1,11 @@
[Install]
WantedBy=sysinit.target
[Unit]
Description=key remapping daemon
DefaultDependencies=no
After=local-fs-pre.target
[Service]
Type=simple
ExecStart=/usr/bin/keyd

Binary file not shown.

190
man.md

@ -0,0 +1,190 @@
% WARP(1)
% Raheman Vaiya
# OVERVIEW
A system-wide key remapping daemon.
# USAGE
keyd [-m] [-l]
# ARGS
**-m**: Run in monitor mode. (ensure keyd is not running to see untranslated events).
**-l**: List all valid key names.
# OVERVIEW
keyd is intended to be run as a system wide daemon via systemd. If run
directly it will run in the foreground and print diagnostic information to
stderr.
Control can be exercised using the standard systemd mechanisms. E.G:
- journalctl -fu keyd # Print diagnostic information.
- systemctl restart keyd # Restart the daemon to effect configuration reload.
# CONFIGURATION
All configuration files are stored in /etc/keyd. The name of each file should
correspond to the device name (see `-m`) to which it is to be applied followed
by .cfg (e.g /etc/keyd/Magic\ Keyboard.cfg). Configuration files are loaded
upon initialization, thus restarting the daemon is necessary for changes
to take effect.
If no configuration file exists for a given keyboard and default.cfg is present, it is used.
Each line in a configuration file consists of a mapping of the following form:
<key> = <action>|<keyseq>
or else represents the beginning of a new layer. E.G:
[<layer name>]
Where `<keyseq>` has the form: `[<modifier1>-[<modifier2>-]]<key>`
and each modifier is one of:
\ **C** - Control\
\ **M** - Meta/Super\
\ **A** - Alt\
\ **S** - Shift
In addition to simple key mappings keyd can remap keys to actions which
can conditionally send keystrokes or transform the state of the keymap.
It is, for instance, possible to map a key to escape when tapped and control when held
by assigning it to mods_on_hold(C, esc). A complete list of available actions can be
found in ACTIONS.
## LAYERS
Each configuration file consists of one or more layers. Each layer is a keymap
unto itself and can be activated by a key mapped to the appropriate
action (see ACTIONS).
By default layers do not have a parent, that is, unmapped keys will have no
effect. The default layer is called 'default' and is used for mappings which
are not explicitly assigned to a layer.
For example the following configuration creates a new layer called 'symbols' which
is activated while the caps lock key is depressed.
capslock = layer(symbols)
[symbols]
f = S-grave
d = slash
Pressing capslock+f thus produces a tilde.
By default unmapped keys inside of a layer do nothing, however
a layer may optionally have a parent from which mappings are
drawn for keys which are not explicitly mapped. A parent
is specified by appending `:<parent layer>` to the layer
name. This is particularly useful for custom letter layouts
like dvorak which remap a subset of keys but otherwise
leave the default mappings in tact.
## ACTIONS
**oneshot(mods)**: If tapped activate a modifier sequence for the next keypress, otherwise act as a normal modifier key when held.
**mods_on_hold(mods, keyseq)**: Activates the given set of modifiers whilst held and emits keysequence when tapped.
**layer_on_hold(layer, keyseq)**: Activates the given layer whilst held and emits keysequence when tapped.
**layer_toggle(layer)**: Permanently activate a layer when tapped. *Note*: You will need to explicitly map a toggle in the destination layer if you wish to return.
**layer(layer)**: Activate the given layer while the key is held down.
**oneshot_layer(layer)**: If tapped activate a layer for the duration of the next keypress, otherwise act as a normal layer key when held.
### Legend:
- `<mods>` = A set of modifiers of the form: `<mod1>[-<mod2>...]` (e.g C-M = control + meta).
- `<keyseq>` = A key sequence consisting of zero or more control characters and a key (e.g C-a = control+a).
- `<layer>` = The name of a layer.
## Examples
Example 1
# Maps capslock to control when held and escape when tapped.
capslock = mods_on_hold(C, esc)
# Makes the shift key sticky for one keystroke.
leftshift = oneshot(S)
rightshift = oneshot(S)
Example 2
# Maps escape to the escape layer when held and the escape key when pressed
esc = layer_on_hold(escape_layer, esc)
[escape_layer]
1 = layer_toggle(dvorak)
2 = layer_toggle(default)
# Creates a dvorak layer which inherits from the default layer. Without
# explicitly inheriting from another layer unmapped keys would be ignored.
[dvorak:default]
q = apostrophe
w = comma
e = dot
r = p
t = y
y = f
u = g
i = c
o = r
p = l
a = a
s = o
d = e
f = u
g = i
h = d
j = h
k = t
l = n
semicolon = s
z = semicolon
x = q
c = j
v = k
b = x
n = b
m = m
comma = w
dot = v
slash = z
# NOTES
- Because of the way keyd works it is possible to render your machine unusable with a bad
config file. This can usually be resolved by plugging in a different keyboard, however
if *default.cfg* has been misconfigured you will have to find an alternate way to kill
the daemon (e.g SSH).
# AUTHOR
- Written by Raheman Vaiya (2017-).
# BUGS
Please file any bugs or feature requests at the following url:
https://github.com/rvaiya/keyd/issues

@ -0,0 +1,601 @@
/* Copyright © 2019 Raheman Vaiya.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
#include "config.h"
#include "keys.h"
#define MAX_ARGS 10
struct keyboard_config *configs = NULL;
static char layers[MAX_LAYERS][MAX_LAYER_NAME_LEN]; //Layer names
static size_t nlayers;
static int parents[MAX_LAYERS];
static int lnum = 0;
static char path[PATH_MAX];
#define err(fmt, ...) fprintf(stderr, "%s: ERROR on line %d: "fmt"\n", path, lnum, ##__VA_ARGS__)
static const char *modseq_to_string(uint8_t mods) {
static char s[32];
int i = 0;
s[0] = '\0';
if(mods & MOD_CTRL) {
strcpy(s+i, "-C");
i+=2;
}
if(mods & MOD_SHIFT) {
strcpy(s+i, "-S");
i+=2;
}
if(mods & MOD_SUPER) {
strcpy(s+i, "-M");
i+=2;
}
if(mods & MOD_ALT) {
strcpy(s+i, "-A");
i+=2;
}
if(i)
return s+1;
else
return s;
}
static const char *keyseq_to_string(uint32_t keyseq) {
int i = 0;
static char s[256];
uint8_t mods = keyseq >> 16;
uint16_t code = keyseq & 0x00FF;
const char *key = keycode_strings[code];
if(mods & MOD_CTRL) {
strcpy(s+i, "C-");
i+=2;
}
if(mods & MOD_SHIFT) {
strcpy(s+i, "S-");
i+=2;
}
if(mods & MOD_SUPER) {
strcpy(s+i, "M-");
i+=2;
}
if(mods & MOD_ALT) {
strcpy(s+i, "A-");
i+=2;
}
if(key)
strcpy(s+i, keycode_strings[code]);
else
strcpy(s+i, "UNKNOWN");
return s;
}
static int parse_mods(const char *s, uint8_t *mods)
{
*mods = 0;
while(*s) {
switch(*s) {
case 'C':
*mods |= MOD_CTRL;
break;
case 'M':
*mods |= MOD_SUPER;
break;
case 'A':
*mods |= MOD_ALT;
break;
case 'S':
*mods |= MOD_SHIFT;
break;
default:
return -1;
break;
}
if(s[1] == 0)
return 0;
else if(s[1] != '-') {
err("%s is not a valid modifier set", s);
return -1;
}
s+=2;
}
return 0;
}
static int parse_keyseq(const char *s, uint16_t *keycode, uint8_t *mods) {
const char *c = s;
size_t i;
*mods = 0;
*keycode = 0;
while(*c && c[1] == '-') {
switch(*c) {
case 'C':
*mods |= MOD_CTRL;
break;
case 'M':
*mods |= MOD_SUPER;
break;
case 'A':
*mods |= MOD_ALT;
break;
case 'S':
*mods |= MOD_SHIFT;
break;
default:
return -1;
break;
}
c += 2;
}
for(i = 0;i < sizeof keycode_strings / sizeof keycode_strings[0];i++) {
if(keycode_strings[i] && !strcmp(keycode_strings[i], c)) {
*keycode |= i;
return 0;
}
}
return -1;
}
static int parse_kvp(char *s, char **_k, char **_v)
{
char *v = NULL, *k = NULL;
while(*s && isspace(*s)) s++;
if(!*s) return -1;
k = s;
while(*s && !isspace(*s) && *s != '=') s++;
if(!*s) return -1;
while(*s && *s != '=') {
*s = 0;
s++;
}
if(!*s) return -1;
*s++ = 0;
while(*s && isspace(*s)) *s++ = 0;
if(!*s) return -1;
v = s;
*_k = k;
*_v = v;
return 0;
}
static void parse_layer_names()
{
size_t i;
char *line = NULL;
size_t n;
ssize_t len;
char strparents[MAX_LAYERS][MAX_LAYER_NAME_LEN] = {0};
FILE *fh = fopen(path, "r");
if(!fh) {
fprintf(stderr, "ERROR: Failed to open %s\n", path);
perror("fopen");
exit(-1);
}
nlayers = 0;
strcpy(layers[nlayers++], "default");
while((len=getline(&line, &n, fh)) != -1) {
char *s = line;
while(len && isspace(s[0])) {
s++;
len--;
}
if(len > 2 && s[0] == '[' && s[len-2] == ']') {
const char *name = s+1;
size_t idx = nlayers;
char *c;
s[len-2] = '\0';
if((c=strchr(name, ':'))) {
const char *parent = c+1;
*c = '\0';
strcpy(strparents[idx], parent);
}
strcpy(layers[idx], name);
nlayers++;
}
free(line);
line = NULL;
n = 0;
}
free(line);
fclose(fh);
for(i = 0;i < nlayers;i++) {
size_t j;
parents[i] = -1;
for(j = 0;j < nlayers;j++) {
if(!strcmp(layers[j], strparents[i])) {
parents[i] = j;
}
}
if(strparents[i][0] != 0 && parents[i] == -1) {
err("%s is not a valid parent layer", strparents[i]);
}
}
}
int parse_fn(char *s, char **fn_name, char *args[MAX_ARGS], size_t *nargs)
{
int openparen = 0;
*fn_name = s;
*nargs = 0;
while(*s && *s != ')') {
switch(*s) {
case '(':
openparen++;
*s = '\0';
s++;
while(*s && isspace(*s))
s++;
if(!*s) {
err("Missing closing parenthesis.");
return -1;
}
if(*s == ')') { //No args
*s = '\0';
return 0;
}
args[(*nargs)++] = s;
break;
case ',':
*s = '\0';
s++;
while(*s && isspace(*s))
s++;
if(!*s) {
err("Missing closing parenthesis.");
return -1;
}
args[(*nargs)++] = s;
break;
}
s++;
}
if(*s != ')') {
if(openparen)
err("Missing closing parenthesis.");
else
err("Invalid function or key sequence.");
return -1;
}
*s = '\0';
return 0;
}
int parse_val(const char *_s, struct key_descriptor *desc)
{
uint16_t code;
uint8_t mods;
char *s = strdup(_s);
char *fn;
char *args[MAX_ARGS];
size_t nargs;
if(!parse_keyseq(s, &code, &mods)) {
desc->action = ACTION_KEYSEQ;
desc->arg.keyseq = ((uint32_t)mods << 16) | (uint32_t)code;
goto cleanup;
}
if(parse_fn(s, &fn, args, &nargs))
goto fail;
if(!strcmp(fn, "oneshot_layer") && nargs == 1) {
size_t i;
for(i = 0;i < nlayers;i++)
if(!strcmp(args[0], layers[i])) {
desc->action = ACTION_LAYER_ONESHOT;
desc->arg.layer = i;
goto cleanup;
}
err("%s is not a valid layer name.", args[0]);
goto fail;
} else if(!strcmp(fn, "layer") && nargs == 1) {
size_t i;
for(i = 0;i < nlayers;i++)
if(!strcmp(args[0], layers[i])) {
desc->action = ACTION_LAYER;
desc->arg.layer = i;
goto cleanup;
}
err("%s is not a valid layer name.", args[0]);
goto fail;
} else if(!strcmp(fn, "layer_toggle") && nargs == 1) {
size_t i;
for(i = 0;i < nlayers;i++)
if(!strcmp(args[0], layers[i])) {
desc->action = ACTION_LAYER_TOGGLE;
desc->arg.layer = i;
goto cleanup;
}
err("%s is not a valid layer name.", args[0]);
goto fail;
} else if(!strcmp(fn, "oneshot") && nargs == 1) {
if(parse_mods(args[0], &mods))
goto fail;
desc->action = ACTION_ONESHOT;
desc->arg.mods = mods;
} else if(!strcmp(fn, "layer_on_hold") && nargs == 2) {
size_t i;
desc->action = ACTION_DOUBLE_LAYER;
if(parse_keyseq(args[1], &code, &mods)) {
err("%s is not a vaid keysequence.", args[1]);
goto fail;
}
desc->arg2.keyseq = ((uint32_t)mods << 16) | (uint32_t)code;
for(i = 0;i < nlayers;i++)
if(!strcmp(args[0], layers[i])) {
desc->arg.layer = i;
goto cleanup;
}
err("%s is not a valid layer.", args[0]);
goto fail;
} else if(!strcmp(fn, "mods_on_hold") && nargs == 2) {
desc->action = ACTION_DOUBLE_MODIFIER;
if(parse_mods(args[0], &mods))
goto fail;
desc->arg.mods = mods;
if(parse_keyseq(args[1], &code, &mods)) {
err("%s is not a vaid keysequence.", args[1]);
goto fail;
}
desc->arg2.keyseq = ((uint32_t)mods << 16) | (uint32_t)code;
} else {
err("%s is not a valid action or key.", _s);
goto fail;
}
cleanup:
free(s);
return 0;
fail:
free(s);
return -1;
}
static void parse(struct keyboard_config *cfg)
{
size_t i, j;
char *line = NULL;
size_t n = 0;
ssize_t len;
lnum = 0;
parse_layer_names();
FILE *fh = fopen(path, "r");
if(!fh) {
perror("fopen");
exit(-1);
}
int current_layer = 0;
struct key_descriptor *layer = cfg->layers[0];
lnum = 0;
while((len=getline(&line, &n, fh)) != -1) {
lnum++;
char *s = line;
while(len && isspace(s[0])) { //Trim leading whitespace
s++;
len--;
}
if(len && s[len-1] == '\n') //Strip tailing newline (not present on EOF)
s[--len] = '\0';
if(len == 0 || s[0] == '#')
continue;
if(s[0] == '[' && s[len-1] == ']') {
layer = cfg->layers[++current_layer];
} else if(layer) {
struct key_descriptor desc;
char *key, *val;
uint16_t code;
uint8_t mods;
if(parse_kvp(s, &key, &val)) {
err("Invalid key value pair.");
goto next;
}
if(parse_keyseq(key, &code, &mods)) {
err("'%s' is not a valid key.", key);
goto next;
}
if(mods != 0) {
err("key cannot contain modifiers.");
goto next;
}
if(parse_val(val, &desc))
goto next;
layer[code] = desc;
}
next:
free(line);
line = NULL;
n = 0;
}
free(line);
fclose(fh);
for(i = 0;i < nlayers;i++) {
int p = parents[i];
if(p != -1) {
for(j = 0;j < KEY_CNT;j++) {
if(cfg->layers[i][j].action == ACTION_DEFAULT)
cfg->layers[i][j] = cfg->layers[p][j];
}
}
}
}
void config_free()
{
struct keyboard_config *cfg = configs;
while(cfg) {
struct keyboard_config *tmp = cfg;
cfg = cfg->next;
free(tmp);
};
}
void config_generate()
{
struct dirent *ent;
DIR *dh = opendir(CONFIG_DIR);
if(!dh) {
perror("opendir");
exit(-1);
}
while((ent=readdir(dh))) {
size_t i;
struct keyboard_config *cfg;
int len = strlen(ent->d_name);
if(len <= 4 || strcmp(ent->d_name+len-4, ".cfg"))
continue;
sprintf(path, "%s/%s", CONFIG_DIR, ent->d_name);
cfg = calloc(1, sizeof(struct keyboard_config));
for(i = 0;i < KEY_CNT;i++) {
struct key_descriptor *desc = &cfg->layers[0][i];
desc->action = ACTION_KEYSEQ;
desc->arg.keyseq = i;
}
strcpy(cfg->name, ent->d_name);
cfg->name[len-4] = '\0';
cfg->next = configs;
parse(cfg);
configs = cfg;
}
closedir(dh);
}

@ -0,0 +1,68 @@
/* Copyright © 2019 Raheman Vaiya.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef __H_CONFIG_
#define __H_CONFIG_
#include <linux/input-event-codes.h>
#include <stdint.h>
#include <stddef.h>
#include "keys.h"
#define MAX_LAYERS 8
#define CONFIG_DIR "/etc/keyd"
#define MAX_LAYER_NAME_LEN 256
enum action {
ACTION_DEFAULT,
ACTION_DOUBLE_MODIFIER,
ACTION_DOUBLE_LAYER,
ACTION_KEYSEQ,
ACTION_LAYER_TOGGLE,
ACTION_LAYER_ONESHOT,
ACTION_LAYER,
ACTION_ONESHOT
};
struct key_descriptor
{
enum action action;
union {
uint32_t keyseq;
uint8_t mods;
uint8_t layer;
} arg, arg2;
};
struct keyboard_config {
char name[256];
struct key_descriptor layers[MAX_LAYERS][KEY_CNT];
struct keyboard_config *next;
};
extern struct keyboard_config *configs;
void config_generate();
void config_free();
#endif

@ -0,0 +1,482 @@
/* Copyright © 2019 Raheman Vaiya.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef _KEYS_H_
#define _KEYS_H_
#define _KEYS_H_
#include <linux/input-event-codes.h>
#include <stdint.h>
#define ISMOD(keycode) \
(((keycode) == KEY_LEFTCTRL) ||\
((keycode) == KEY_LEFTALT) ||\
((keycode) == KEY_LEFTSHIFT) ||\
((keycode) == KEY_LEFTMETA) ||\
((keycode) == KEY_RIGHTCTRL) ||\
((keycode) == KEY_RIGHTALT) ||\
((keycode) == KEY_RIGHTSHIFT) ||\
((keycode) == KEY_RIGHTMETA))
#define MOD_CTRL 0x8
#define MOD_SHIFT 0x4
#define MOD_SUPER 0x2
#define MOD_ALT 0x1
static const char *keycode_strings[] = {
[KEY_ESC] = "esc",
[KEY_1] = "1",
[KEY_2] = "2",
[KEY_3] = "3",
[KEY_4] = "4",
[KEY_5] = "5",
[KEY_6] = "6",
[KEY_7] = "7",
[KEY_8] = "8",
[KEY_9] = "9",
[KEY_0] = "0",
[KEY_MINUS] = "minus",
[KEY_EQUAL] = "equal",
[KEY_BACKSPACE] = "backspace",
[KEY_TAB] = "tab",
[KEY_Q] = "q",
[KEY_W] = "w",
[KEY_E] = "e",
[KEY_R] = "r",
[KEY_T] = "t",
[KEY_Y] = "y",
[KEY_U] = "u",
[KEY_I] = "i",
[KEY_O] = "o",
[KEY_P] = "p",
[KEY_LEFTBRACE] = "leftbrace",
[KEY_RIGHTBRACE] = "rightbrace",
[KEY_ENTER] = "enter",
[KEY_LEFTCTRL] = "leftctrl",
[KEY_A] = "a",
[KEY_S] = "s",
[KEY_D] = "d",
[KEY_F] = "f",
[KEY_G] = "g",
[KEY_H] = "h",
[KEY_J] = "j",
[KEY_K] = "k",
[KEY_L] = "l",
[KEY_SEMICOLON] = "semicolon",
[KEY_APOSTROPHE] = "apostrophe",
[KEY_GRAVE] = "grave",
[KEY_LEFTSHIFT] = "leftshift",
[KEY_BACKSLASH] = "backslash",
[KEY_Z] = "z",
[KEY_X] = "x",
[KEY_C] = "c",
[KEY_V] = "v",
[KEY_B] = "b",
[KEY_N] = "n",
[KEY_M] = "m",
[KEY_COMMA] = "comma",
[KEY_DOT] = "dot",
[KEY_SLASH] = "slash",
[KEY_RIGHTSHIFT] = "rightshift",
[KEY_KPASTERISK] = "kpasterisk",
[KEY_LEFTALT] = "leftalt",
[KEY_SPACE] = "space",
[KEY_CAPSLOCK] = "capslock",
[KEY_F1] = "f1",
[KEY_F2] = "f2",
[KEY_F3] = "f3",
[KEY_F4] = "f4",
[KEY_F5] = "f5",
[KEY_F6] = "f6",
[KEY_F7] = "f7",
[KEY_F8] = "f8",
[KEY_F9] = "f9",
[KEY_F10] = "f10",
[KEY_NUMLOCK] = "numlock",
[KEY_SCROLLLOCK] = "scrolllock",
[KEY_KP7] = "kp7",
[KEY_KP8] = "kp8",
[KEY_KP9] = "kp9",
[KEY_KPMINUS] = "kpminus",
[KEY_KP4] = "kp4",
[KEY_KP5] = "kp5",
[KEY_KP6] = "kp6",
[KEY_KPPLUS] = "kpplus",
[KEY_KP1] = "kp1",
[KEY_KP2] = "kp2",
[KEY_KP3] = "kp3",
[KEY_KP0] = "kp0",
[KEY_KPDOT] = "kpdot",
[KEY_ZENKAKUHANKAKU] = "zenkakuhankaku",
[KEY_102ND] = "102nd",
[KEY_F11] = "f11",
[KEY_F12] = "f12",
[KEY_RO] = "ro",
[KEY_KATAKANA] = "katakana",
[KEY_HIRAGANA] = "hiragana",
[KEY_HENKAN] = "henkan",
[KEY_KATAKANAHIRAGANA] = "katakanahiragana",
[KEY_MUHENKAN] = "muhenkan",
[KEY_KPJPCOMMA] = "kpjpcomma",
[KEY_KPENTER] = "kpenter",
[KEY_RIGHTCTRL] = "rightctrl",
[KEY_KPSLASH] = "kpslash",
[KEY_SYSRQ] = "sysrq",
[KEY_RIGHTALT] = "rightalt",
[KEY_LINEFEED] = "linefeed",
[KEY_HOME] = "home",
[KEY_UP] = "up",
[KEY_PAGEUP] = "pageup",
[KEY_LEFT] = "left",
[KEY_RIGHT] = "right",
[KEY_END] = "end",
[KEY_DOWN] = "down",
[KEY_PAGEDOWN] = "pagedown",
[KEY_INSERT] = "insert",
[KEY_DELETE] = "delete",
[KEY_MACRO] = "macro",
[KEY_MUTE] = "mute",
[KEY_VOLUMEDOWN] = "volumedown",
[KEY_VOLUMEUP] = "volumeup",
[KEY_POWER] = "power",
[KEY_KPEQUAL] = "kpequal",
[KEY_KPPLUSMINUS] = "kpplusminus",
[KEY_PAUSE] = "pause",
[KEY_SCALE] = "scale",
[KEY_KPCOMMA] = "kpcomma",
[KEY_HANGEUL] = "hangeul",
[KEY_HANJA] = "hanja",
[KEY_YEN] = "yen",
[KEY_LEFTMETA] = "leftmeta",
[KEY_RIGHTMETA] = "rightmeta",
[KEY_COMPOSE] = "compose",
[KEY_STOP] = "stop",
[KEY_AGAIN] = "again",
[KEY_PROPS] = "props",
[KEY_UNDO] = "undo",
[KEY_FRONT] = "front",
[KEY_COPY] = "copy",
[KEY_OPEN] = "open",
[KEY_PASTE] = "paste",
[KEY_FIND] = "find",
[KEY_CUT] = "cut",
[KEY_HELP] = "help",
[KEY_MENU] = "menu",
[KEY_CALC] = "calc",
[KEY_SETUP] = "setup",
[KEY_SLEEP] = "sleep",
[KEY_WAKEUP] = "wakeup",
[KEY_FILE] = "file",
[KEY_SENDFILE] = "sendfile",
[KEY_DELETEFILE] = "deletefile",
[KEY_XFER] = "xfer",
[KEY_PROG1] = "prog1",
[KEY_PROG2] = "prog2",
[KEY_WWW] = "www",
[KEY_MSDOS] = "msdos",
[KEY_COFFEE] = "coffee",
[KEY_ROTATE_DISPLAY] = "display",
[KEY_CYCLEWINDOWS] = "cyclewindows",
[KEY_MAIL] = "mail",
[KEY_BOOKMARKS] = "bookmarks",
[KEY_COMPUTER] = "computer",
[KEY_BACK] = "back",
[KEY_FORWARD] = "forward",
[KEY_CLOSECD] = "closecd",
[KEY_EJECTCD] = "ejectcd",
[KEY_EJECTCLOSECD] = "ejectclosecd",
[KEY_NEXTSONG] = "nextsong",
[KEY_PLAYPAUSE] = "playpause",
[KEY_PREVIOUSSONG] = "previoussong",
[KEY_STOPCD] = "stopcd",
[KEY_RECORD] = "record",
[KEY_REWIND] = "rewind",
[KEY_PHONE] = "phone",
[KEY_ISO] = "iso",
[KEY_CONFIG] = "config",
[KEY_HOMEPAGE] = "homepage",
[KEY_REFRESH] = "refresh",
[KEY_EXIT] = "exit",
[KEY_MOVE] = "move",
[KEY_EDIT] = "edit",
[KEY_SCROLLUP] = "scrollup",
[KEY_SCROLLDOWN] = "scrolldown",
[KEY_KPLEFTPAREN] = "kpleftparen",
[KEY_KPRIGHTPAREN] = "kprightparen",
[KEY_NEW] = "new",
[KEY_REDO] = "redo",
[KEY_F13] = "f13",
[KEY_F14] = "f14",
[KEY_F15] = "f15",
[KEY_F16] = "f16",
[KEY_F17] = "f17",
[KEY_F18] = "f18",
[KEY_F19] = "f19",
[KEY_F20] = "f20",
[KEY_F21] = "f21",
[KEY_F22] = "f22",
[KEY_F23] = "f23",
[KEY_F24] = "f24",
[KEY_PLAYCD] = "playcd",
[KEY_PAUSECD] = "pausecd",
[KEY_PROG3] = "prog3",
[KEY_PROG4] = "prog4",
[KEY_DASHBOARD] = "dashboard",
[KEY_SUSPEND] = "suspend",
[KEY_CLOSE] = "close",
[KEY_PLAY] = "play",
[KEY_FASTFORWARD] = "fastforward",
[KEY_BASSBOOST] = "bassboost",
[KEY_PRINT] = "print",
[KEY_HP] = "hp",
[KEY_CAMERA] = "camera",
[KEY_SOUND] = "sound",
[KEY_QUESTION] = "question",
[KEY_EMAIL] = "email",
[KEY_CHAT] = "chat",
[KEY_SEARCH] = "search",
[KEY_CONNECT] = "connect",
[KEY_FINANCE] = "finance",
[KEY_SPORT] = "sport",
[KEY_SHOP] = "shop",
[KEY_ALTERASE] = "alterase",
[KEY_CANCEL] = "cancel",
[KEY_BRIGHTNESSDOWN] = "brightnessdown",
[KEY_BRIGHTNESSUP] = "brightnessup",
[KEY_MEDIA] = "media",
[KEY_SWITCHVIDEOMODE] = "switchvideomode",
[KEY_KBDILLUMTOGGLE] = "kbdillumtoggle",
[KEY_KBDILLUMDOWN] = "kbdillumdown",
[KEY_KBDILLUMUP] = "kbdillumup",
[KEY_SEND] = "send",
[KEY_REPLY] = "reply",
[KEY_FORWARDMAIL] = "forwardmail",
[KEY_SAVE] = "save",
[KEY_DOCUMENTS] = "documents",
[KEY_BATTERY] = "battery",
[KEY_BLUETOOTH] = "bluetooth",
[KEY_WLAN] = "wlan",
[KEY_UWB] = "uwb",
[KEY_UNKNOWN] = "unknown",
[KEY_VIDEO_NEXT] = "next",
[KEY_VIDEO_PREV] = "prev",
[KEY_BRIGHTNESS_CYCLE] = "cycle",
[KEY_BRIGHTNESS_AUTO] = "auto",
[KEY_DISPLAY_OFF] = "off",
[KEY_WWAN] = "wwan",
[KEY_RFKILL] = "rfkill",
[KEY_MICMUTE] = "micmute",
[KEY_OK] = "ok",
[KEY_SELECT] = "select",
[KEY_GOTO] = "goto",
[KEY_CLEAR] = "clear",
[KEY_POWER2] = "power2",
[KEY_OPTION] = "option",
[KEY_INFO] = "info",
[KEY_TIME] = "time",
[KEY_VENDOR] = "vendor",
[KEY_ARCHIVE] = "archive",
[KEY_PROGRAM] = "program",
[KEY_CHANNEL] = "channel",
[KEY_FAVORITES] = "favorites",
[KEY_EPG] = "epg",
[KEY_PVR] = "pvr",
[KEY_MHP] = "mhp",
[KEY_LANGUAGE] = "language",
[KEY_TITLE] = "title",
[KEY_SUBTITLE] = "subtitle",
[KEY_ANGLE] = "angle",
[KEY_ZOOM] = "zoom",
[KEY_MODE] = "mode",
[KEY_KEYBOARD] = "keyboard",
[KEY_SCREEN] = "screen",
[KEY_PC] = "pc",
[KEY_TV] = "tv",
[KEY_TV2] = "tv2",
[KEY_VCR] = "vcr",
[KEY_VCR2] = "vcr2",
[KEY_SAT] = "sat",
[KEY_SAT2] = "sat2",
[KEY_CD] = "cd",
[KEY_TAPE] = "tape",
[KEY_RADIO] = "radio",
[KEY_TUNER] = "tuner",
[KEY_PLAYER] = "player",
[KEY_TEXT] = "text",
[KEY_DVD] = "dvd",
[KEY_AUX] = "aux",
[KEY_MP3] = "mp3",
[KEY_AUDIO] = "audio",
[KEY_VIDEO] = "video",
[KEY_DIRECTORY] = "directory",
[KEY_LIST] = "list",
[KEY_MEMO] = "memo",
[KEY_CALENDAR] = "calendar",
[KEY_RED] = "red",
[KEY_GREEN] = "green",
[KEY_YELLOW] = "yellow",
[KEY_BLUE] = "blue",
[KEY_CHANNELUP] = "channelup",
[KEY_CHANNELDOWN] = "channeldown",
[KEY_FIRST] = "first",
[KEY_LAST] = "last",
[KEY_AB] = "ab",
[KEY_NEXT] = "next",
[KEY_RESTART] = "restart",
[KEY_SLOW] = "slow",
[KEY_SHUFFLE] = "shuffle",
[KEY_BREAK] = "break",
[KEY_PREVIOUS] = "previous",
[KEY_DIGITS] = "digits",
[KEY_TEEN] = "teen",
[KEY_TWEN] = "twen",
[KEY_VIDEOPHONE] = "videophone",
[KEY_GAMES] = "games",
[KEY_ZOOMIN] = "zoomin",
[KEY_ZOOMOUT] = "zoomout",
[KEY_ZOOMRESET] = "zoomreset",
[KEY_WORDPROCESSOR] = "wordprocessor",
[KEY_EDITOR] = "editor",
[KEY_SPREADSHEET] = "spreadsheet",
[KEY_GRAPHICSEDITOR] = "graphicseditor",
[KEY_PRESENTATION] = "presentation",
[KEY_DATABASE] = "database",
[KEY_NEWS] = "news",
[KEY_VOICEMAIL] = "voicemail",
[KEY_ADDRESSBOOK] = "addressbook",
[KEY_MESSENGER] = "messenger",
[KEY_DISPLAYTOGGLE] = "displaytoggle",
[KEY_SPELLCHECK] = "spellcheck",
[KEY_LOGOFF] = "logoff",
[KEY_DOLLAR] = "dollar",
[KEY_EURO] = "euro",
[KEY_FRAMEBACK] = "frameback",
[KEY_FRAMEFORWARD] = "frameforward",
[KEY_CONTEXT_MENU] = "menu",
[KEY_MEDIA_REPEAT] = "repeat",
[KEY_10CHANNELSUP] = "10channelsup",
[KEY_10CHANNELSDOWN] = "10channelsdown",
[KEY_IMAGES] = "images",
[KEY_DEL_EOL] = "eol",
[KEY_DEL_EOS] = "eos",
[KEY_INS_LINE] = "line",
[KEY_DEL_LINE] = "line",
[KEY_FN] = "fn",
[KEY_FN_ESC] = "esc",
[KEY_FN_F1] = "f1",
[KEY_FN_F2] = "f2",
[KEY_FN_F3] = "f3",
[KEY_FN_F4] = "f4",
[KEY_FN_F5] = "f5",
[KEY_FN_F6] = "f6",
[KEY_FN_F7] = "f7",
[KEY_FN_F8] = "f8",
[KEY_FN_F9] = "f9",
[KEY_FN_F10] = "f10",
[KEY_FN_F11] = "f11",
[KEY_FN_F12] = "f12",
[KEY_FN_1] = "1",
[KEY_FN_2] = "2",
[KEY_FN_D] = "d",
[KEY_FN_E] = "e",
[KEY_FN_F] = "f",
[KEY_FN_S] = "s",
[KEY_FN_B] = "b",
[KEY_BRL_DOT1] = "dot1",
[KEY_BRL_DOT2] = "dot2",
[KEY_BRL_DOT3] = "dot3",
[KEY_BRL_DOT4] = "dot4",
[KEY_BRL_DOT5] = "dot5",
[KEY_BRL_DOT6] = "dot6",
[KEY_BRL_DOT7] = "dot7",
[KEY_BRL_DOT8] = "dot8",
[KEY_BRL_DOT9] = "dot9",
[KEY_BRL_DOT10] = "dot10",
[KEY_NUMERIC_0] = "0",
[KEY_NUMERIC_1] = "1",
[KEY_NUMERIC_2] = "2",
[KEY_NUMERIC_3] = "3",
[KEY_NUMERIC_4] = "4",
[KEY_NUMERIC_5] = "5",
[KEY_NUMERIC_6] = "6",
[KEY_NUMERIC_7] = "7",
[KEY_NUMERIC_8] = "8",
[KEY_NUMERIC_9] = "9",
[KEY_NUMERIC_STAR] = "star",
[KEY_NUMERIC_POUND] = "pound",
[KEY_NUMERIC_A] = "a",
[KEY_NUMERIC_B] = "b",
[KEY_NUMERIC_C] = "c",
[KEY_NUMERIC_D] = "d",
[KEY_CAMERA_FOCUS] = "focus",
[KEY_WPS_BUTTON] = "button",
[KEY_TOUCHPAD_TOGGLE] = "toggle",
[KEY_TOUCHPAD_ON] = "on",
[KEY_TOUCHPAD_OFF] = "off",
[KEY_CAMERA_ZOOMIN] = "zoomin",
[KEY_CAMERA_ZOOMOUT] = "zoomout",
[KEY_CAMERA_UP] = "up",
[KEY_CAMERA_DOWN] = "down",
[KEY_CAMERA_LEFT] = "left",
[KEY_CAMERA_RIGHT] = "right",
[KEY_ATTENDANT_ON] = "on",
[KEY_ATTENDANT_OFF] = "off",
[KEY_ATTENDANT_TOGGLE] = "toggle",
[KEY_LIGHTS_TOGGLE] = "toggle",
[KEY_ALS_TOGGLE] = "toggle",
[KEY_BUTTONCONFIG] = "buttonconfig",
[KEY_TASKMANAGER] = "taskmanager",
[KEY_JOURNAL] = "journal",
[KEY_CONTROLPANEL] = "controlpanel",
[KEY_APPSELECT] = "appselect",
[KEY_SCREENSAVER] = "screensaver",
[KEY_VOICECOMMAND] = "voicecommand",
[KEY_BRIGHTNESS_MIN] = "min",
[KEY_BRIGHTNESS_MAX] = "max",
[KEY_KBDINPUTASSIST_PREV] = "prev",
[KEY_KBDINPUTASSIST_NEXT] = "next",
[KEY_KBDINPUTASSIST_PREVGROUP] = "prevgroup",
[KEY_KBDINPUTASSIST_NEXTGROUP] = "nextgroup",
[KEY_KBDINPUTASSIST_ACCEPT] = "accept",
[KEY_KBDINPUTASSIST_CANCEL] = "cancel",
[KEY_RIGHT_UP] = "up",
[KEY_RIGHT_DOWN] = "down",
[KEY_LEFT_UP] = "up",
[KEY_LEFT_DOWN] = "down",
[KEY_ROOT_MENU] = "menu",
[KEY_MEDIA_TOP_MENU] = "menu",
[KEY_NUMERIC_11] = "11",
[KEY_NUMERIC_12] = "12",
[KEY_AUDIO_DESC] = "desc",
[KEY_3D_MODE] = "mode",
[KEY_NEXT_FAVORITE] = "favorite",
[KEY_STOP_RECORD] = "record",
[KEY_PAUSE_RECORD] = "record",
[KEY_VOD] = "vod",
[KEY_UNMUTE] = "unmute",
[KEY_FASTREVERSE] = "fastreverse",
[KEY_SLOWREVERSE] = "slowreverse",
[KEY_DATA] = "data",
[KEY_MAX] = "max"
};
#endif

@ -0,0 +1,666 @@
/* Copyright © 2019 Raheman Vaiya.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/types.h>
#include <linux/limits.h>
#include <sys/file.h>
#include <dirent.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <linux/input.h>
#include <libudev.h>
#include <stdint.h>
#include <stdarg.h>
#include <linux/uinput.h>
#include <stdlib.h>
#include <fcntl.h>
#include "keys.h"
#include "config.h"
#define UINPUT_DEVICE_NAME "keyd virtual keyboard"
#define MAX_KEYBOARDS 256
#define LOCK_FILE "/var/lock/keyd.lock"
static int ufd = -1;
static struct udev *udev;
static struct udev_monitor *udevmon;
static uint8_t keystate[KEY_CNT] = {0};
//Active keyboard state.
struct keyboard {
int fd;
char devnode[256];
struct keyboard_config *cfg;
struct keyboard *next;
};
static struct keyboard *keyboards = NULL;
static int debug_flag = 0;
static void warn(char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
static void die(char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
exit(-1);
}
static void dbg(char *fmt, ...)
{
if(debug_flag) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
}
static int is_keyboard(struct udev_device *dev)
{
int is_keyboard = 0;
const char *path = udev_device_get_devnode(dev);
if(!path || !strstr(path, "event")) //Filter out non evdev devices.
return 0;
struct udev_list_entry *prop;
udev_list_entry_foreach(prop, udev_device_get_properties_list_entry(dev)) {
//Some mice can also send keypresses, ignore these
if(!strcmp(udev_list_entry_get_name(prop), "ID_INPUT_MOUSE") &&
!strcmp(udev_list_entry_get_value(prop), "1")) {
return 0;
}
if(!strcmp(udev_list_entry_get_name(prop), "ID_INPUT_KEYBOARD") &&
!strcmp(udev_list_entry_get_value(prop), "1")) {
is_keyboard = 1;
}
}
return is_keyboard;
}
static void get_keyboard_nodes(char *nodes[MAX_KEYBOARDS], int *sz)
{
struct udev *udev;
struct udev_enumerate *enumerate;
struct udev_list_entry *devices, *ent;
udev = udev_new();
if (!udev)
die("Cannot create udev context.");
enumerate = udev_enumerate_new(udev);
if (!enumerate)
die("Cannot create enumerate context.");
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
devices = udev_enumerate_get_list_entry(enumerate);
if (!devices)
die("Failed to get device list.");
*sz = 0;
udev_list_entry_foreach(ent, devices) {
const char *name = udev_list_entry_get_name(ent);;
struct udev_device *dev = udev_device_new_from_syspath(udev, name);
const char *path = udev_device_get_devnode(dev);
if(is_keyboard(dev)) {
nodes[*sz] = malloc(strlen(path)+1);
strcpy(nodes[*sz], path);
(*sz)++;
assert(*sz <= MAX_KEYBOARDS);
}
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate);
udev_unref(udev);
}
static int create_uinput_fd()
{
size_t i;
struct uinput_setup usetup;
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if(fd < 0) {
perror("open");
exit(-1);
}
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_SYN);
for(i = 1;i < KEY_CNT;i++) {
if((i >= 0x2c0 && i <= 0x2e7) || //Mouse button ranges.
(i > 248 && i <= 0x151))
continue;
ioctl(fd, UI_SET_KEYBIT, i);
}
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x046d;
usetup.id.product = 0xc52b;
strcpy(usetup.name, UINPUT_DEVICE_NAME);
ioctl(fd, UI_DEV_SETUP, &usetup);
ioctl(fd, UI_DEV_CREATE);
return fd;
}
static void send_key(uint16_t code, int is_pressed)
{
keystate[code] = is_pressed;
struct input_event ev;
ev.type = EV_KEY;
ev.code = code;
ev.value = is_pressed;
ev.time.tv_sec = 0;
ev.time.tv_usec = 0;
write(ufd, &ev, sizeof(ev));
ev.type = EV_SYN;
ev.code = 0;
ev.value = 0;
write(ufd, &ev, sizeof(ev));
}
static void send_mods(uint16_t mods, int pressed)
{
if(mods & MOD_CTRL)
send_key(KEY_LEFTCTRL, pressed);
if(mods & MOD_SHIFT)
send_key(KEY_LEFTSHIFT, pressed);
if(mods & MOD_SUPER)
send_key(KEY_LEFTMETA, pressed);
if(mods & MOD_ALT)
send_key(KEY_LEFTALT, pressed);
}
static void send_keyseq(uint32_t keyseq, int pressed)
{
uint8_t mods = keyseq >> 16;
uint16_t code = keyseq;
send_mods(mods, pressed);
send_key(code, pressed);
}
//Where the magic happens
static void process_event(struct keyboard *kbd, struct input_event *ev)
{
static struct keyboard *current_kbd = NULL;
static uint16_t oneshotmods = 0;
static struct key_descriptor *last_pressed = NULL;
static uint8_t layer = 0;
static uint8_t main_layer = 0;
static struct key_descriptor *pressed_keys[KEY_CNT];
static uint8_t oneshotlayer = 0;
struct key_descriptor *desc;
int code = ev->code;
int pressed = ev->value;
uint32_t keypressed = 0;
if(current_kbd != kbd) { //Reset state when switching keyboards.
main_layer = 0;
layer = 0;
current_kbd = kbd;
}
if(ev->type != EV_KEY || pressed == 2)
return;
//Ensure that key descriptors are consistent accross key up/down event pairs.
//This is necessary to accomodate layer changes midkey.
if(pressed_keys[code])
desc = pressed_keys[code];
else
desc = &kbd->cfg->layers[layer][code];
if(pressed)
pressed_keys[code] = desc;
else
pressed_keys[code] = NULL;
if(oneshotlayer) {
layer = main_layer;
oneshotlayer = 0;
}
switch(desc->action) {
uint32_t keyseq;
case ACTION_KEYSEQ:
keyseq = desc->arg.keyseq;
send_keyseq(keyseq, pressed);
keypressed = keyseq;
break;
case ACTION_LAYER:
if(pressed)
layer = desc->arg.layer;
else
layer = main_layer;
break;
case ACTION_LAYER_ONESHOT:
if(pressed) {
layer = desc->arg.layer;
} else {
if(last_pressed == desc) //If tapped
oneshotlayer++;
else
layer = main_layer;
}
break;
case ACTION_LAYER_TOGGLE:
if(pressed) {
main_layer = desc->arg.layer;
layer = main_layer;
}
break;
case ACTION_ONESHOT:
if(pressed)
send_mods(desc->arg.mods, 1);
else if(last_pressed == desc) //If no key has been interposed make the modifiers transient
oneshotmods |= desc->arg.mods;
else //otherwise treat as a normal modifier.
send_mods(desc->arg.mods, 0);
break;
case ACTION_DOUBLE_MODIFIER:
if(pressed)
send_mods(desc->arg.mods, 1);
else {
send_mods(desc->arg.mods, 0);
if(last_pressed == desc) {
send_keyseq(desc->arg2.keyseq, 1);
send_keyseq(desc->arg2.keyseq, 0);
keypressed = desc->arg2.keyseq;
}
}
break;
case ACTION_DOUBLE_LAYER:
if(pressed) {
layer = desc->arg.layer;
} else {
layer = main_layer;
if(last_pressed == desc) {
send_keyseq(desc->arg2.keyseq, 1);
send_keyseq(desc->arg2.keyseq, 0);
keypressed = desc->arg2.keyseq;
}
}
break;
default:
dbg("Ignoring action %d generated by %d", desc->action, code);
break;
}
if(keypressed && !ISMOD(keypressed & 0xFFFF)) {
send_mods(oneshotmods, 0);
oneshotmods = 0;
}
if(pressed)
last_pressed = desc;
}
static const char *evdev_device_name(const char *devnode)
{
static char name[256];
int fd = open(devnode, O_RDONLY);
if(fd < 0) {
perror("open");
exit(-1);
}
if(ioctl(fd, EVIOCGNAME(sizeof(name)), &name) == -1) {
perror("ioctl");
exit(-1);
}
close(fd);
return name;
}
static int manage_keyboard(const char *devnode)
{
int fd;
const char *name = evdev_device_name(devnode);
struct keyboard *kbd;
struct keyboard_config *cfg = NULL;
struct keyboard_config *default_cfg = NULL;
if(!strcmp(name, UINPUT_DEVICE_NAME)) //Don't manage virtual keyboard
return 0;
for(cfg = configs;cfg;cfg = cfg->next) {
if(!strcmp(cfg->name, "default"))
default_cfg = cfg;
if(!strcmp(cfg->name, name))
break;
}
if(!cfg) {
if(default_cfg) {
warn("No config found for %s (%s), falling back to default.cfg", name, devnode);
cfg = default_cfg;
} else {
//Don't manage keyboards for which there is no configuration.
warn("No config found for %s (%s), ignoring", name, devnode);
return 0;
}
}
if((fd = open(devnode, O_RDONLY | O_NONBLOCK)) < 0) {
perror("open");
exit(1);
}
kbd = malloc(sizeof(struct keyboard));
kbd->fd = fd;
kbd->cfg = cfg;
strcpy(kbd->devnode, devnode);
//Grab the keyboard.
if(ioctl(fd, EVIOCGRAB, (void *)1) < 0) {
perror("EVIOCGRAB");
exit(-1);
}
kbd->next = keyboards;
keyboards = kbd;
warn("Managing %s", evdev_device_name(devnode));
return 1;
}
static int destroy_keyboard(const char *devnode)
{
struct keyboard **ent = &keyboards;
while(*ent) {
if(!strcmp((*ent)->devnode, devnode)) {
dbg("Destroying %s", devnode);
struct keyboard *kbd = *ent;
*ent = kbd->next;
//Attempt to ungrab the the keyboard (assuming it still exists)
if(ioctl(kbd->fd, EVIOCGRAB, (void *)1) < 0) {
perror("EVIOCGRAB");
}
close(kbd->fd);
free(kbd);
return 1;
}
ent = &(*ent)->next;
}
return 0;
}
static void evdev_monitor_loop(int *fds, int sz)
{
struct input_event ev;
fd_set fdset;
int i;
char names[256][256];
for(i = 0;i < sz;i++) {
int fd = fds[i];
if(ioctl(fd, EVIOCGNAME(sizeof(names[fd])), names[fd]) == -1) {
perror("ioctl");
exit(-1);
}
}
while(1) {
int i;
int maxfd = fds[0];
FD_ZERO(&fdset);
for(i = 0;i < sz;i++) {
if(maxfd < fds[i]) maxfd = fds[i];
FD_SET(fds[i], &fdset);
}
select(maxfd+1, &fdset, NULL, NULL, NULL);
for(i = 0;i < sz;i++) {
int fd = fds[i];
if(FD_ISSET(fd, &fdset)) {
while(read(fd, &ev, sizeof(ev)) > 0) {
if(ev.type == EV_KEY && ev.value != 2) {
fprintf(stderr, "%s: %s %s\n",
names[fd],
keycode_strings[ev.code],
ev.value == 0 ? "up" : "down");
}
}
}
}
}
}
static int monitor_loop()
{
char *devnodes[256];
int sz, i;
int fd = -1;
int fds[256];
int nfds = 0;
get_keyboard_nodes(devnodes, &sz);
for(i = 0;i < sz;i++) {
fd = open(devnodes[i], O_RDONLY | O_NONBLOCK);
if(fd < 0) {
perror("open");
exit(-1);
}
fds[nfds++] = fd;
}
evdev_monitor_loop(fds, nfds);
return 0;
}
static void main_loop()
{
struct keyboard *kbd;
int monfd;
int i, sz;
char *devs[MAX_KEYBOARDS];
get_keyboard_nodes(devs, &sz);
for(i = 0;i < sz;i++) {
manage_keyboard(devs[i]);
free(devs[i]);
}
udev = udev_new();
udevmon = udev_monitor_new_from_netlink(udev, "udev");
if (!udev)
die("Can't create udev.");
udev_monitor_filter_add_match_subsystem_devtype(udevmon, "input", NULL);
udev_monitor_enable_receiving(udevmon);
monfd = udev_monitor_get_fd(udevmon);
int exit = 0;
while(!exit) {
int maxfd;
fd_set fds;
struct udev_device *dev;
FD_ZERO(&fds);
FD_SET(monfd, &fds);
maxfd = monfd;
for(kbd = keyboards;kbd;kbd=kbd->next) {
int fd = kbd->fd;
maxfd = maxfd > fd ? maxfd : fd;
FD_SET(fd, &fds);
}
if(select(maxfd+1, &fds, NULL, NULL, NULL) > 0) {
if(FD_ISSET(monfd, &fds)) {
dev = udev_monitor_receive_device(udevmon);
const char *devnode = udev_device_get_devnode(dev);
if(devnode && is_keyboard(dev)) {
const char *action = udev_device_get_action(dev);
if(!strcmp(action, "add"))
manage_keyboard(devnode);
else if(!strcmp(action, "remove"))
destroy_keyboard(devnode);
}
udev_device_unref(dev);
}
for(kbd = keyboards;kbd;kbd=kbd->next) {
int fd = kbd->fd;
if(FD_ISSET(fd, &fds)) {
struct input_event ev;
while(read(fd, &ev, sizeof(ev)) > 0) {
process_event(kbd, &ev);
}
}
}
}
}
}
static void cleanup()
{
struct keyboard *kbd = keyboards;
config_free();
while(kbd) {
struct keyboard *tmp = kbd;
kbd = kbd->next;
free(tmp);
}
udev_unref(udev);
udev_monitor_unref(udevmon);
}
static void lock()
{
int fd;
if((fd=open(LOCK_FILE, O_CREAT | O_RDWR, 0600)) == -1) {
perror("open");
exit(1);
}
if(flock(fd, LOCK_EX | LOCK_NB) == -1)
die("Another instance of keyd is already running.");
}
static void exit_signal_handler(int sig)
{
warn("%s received, cleaning up and termianting...", sig == SIGINT ? "SIGINT" : "SIGTERM");
cleanup();
exit(0);
}
int main(int argc, char *argv[])
{
if(argc > 1 && !strcmp(argv[1], "-m"))
return monitor_loop();
if(argc > 1 && !strcmp(argv[1], "-l")) {
size_t i;
for(i = 0; i < sizeof(keycode_strings)/sizeof(keycode_strings[0]);i++)
if(keycode_strings[i])
printf("%s\n", keycode_strings[i]);
return 0;
}
lock();
signal(SIGINT, exit_signal_handler);
signal(SIGTERM, exit_signal_handler);
config_generate();
ufd = create_uinput_fd();
main_loop();
}
Loading…
Cancel
Save