commit
5fecce7720
13 changed files with 2166 additions and 0 deletions
@ -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). |
||||
@ -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 |
||||
@ -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…
Reference in new issue