You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1017 lines
22 KiB
1017 lines
22 KiB
/* |
|
* keyd - A key remapping daemon. |
|
* |
|
* © 2019 Raheman Vaiya (see also: LICENSE). |
|
*/ |
|
|
|
#include <assert.h> |
|
#include <fcntl.h> |
|
#include <limits.h> |
|
#include <stdint.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#include <sys/types.h> |
|
#include <unistd.h> |
|
#include <libgen.h> |
|
|
|
#include "keyd.h" |
|
#include "ini.h" |
|
#include "keys.h" |
|
#include "log.h" |
|
#include "string.h" |
|
#include "unicode.h" |
|
|
|
#define MAX_FILE_SZ 65536 |
|
#define MAX_LINE_LEN 256 |
|
|
|
#undef warn |
|
#define warn(fmt, ...) keyd_log("\ty{WARNING:} "fmt"\n", ##__VA_ARGS__) |
|
|
|
static struct { |
|
const char *name; |
|
const char *preferred_name; |
|
uint8_t op; |
|
enum { |
|
ARG_EMPTY, |
|
|
|
ARG_MACRO, |
|
ARG_LAYER, |
|
ARG_LAYOUT, |
|
ARG_TIMEOUT, |
|
ARG_SENSITIVITY, |
|
ARG_DESCRIPTOR, |
|
} args[MAX_DESCRIPTOR_ARGS]; |
|
} actions[] = { |
|
{ "swap", NULL, OP_SWAP, { ARG_LAYER } }, |
|
{ "clear", NULL, OP_CLEAR, {} }, |
|
{ "oneshot", NULL, OP_ONESHOT, { ARG_LAYER } }, |
|
{ "toggle", NULL, OP_TOGGLE, { ARG_LAYER } }, |
|
|
|
{ "clearm", NULL, OP_CLEARM, { ARG_MACRO } }, |
|
{ "swapm", NULL, OP_SWAPM, { ARG_LAYER, ARG_MACRO } }, |
|
{ "togglem", NULL, OP_TOGGLEM, { ARG_LAYER, ARG_MACRO } }, |
|
{ "layerm", NULL, OP_LAYERM, { ARG_LAYER, ARG_MACRO } }, |
|
{ "oneshotm", NULL, OP_ONESHOTM, { ARG_LAYER, ARG_MACRO } }, |
|
|
|
{ "layer", NULL, OP_LAYER, { ARG_LAYER } }, |
|
|
|
{ "overload", NULL, OP_OVERLOAD, { ARG_LAYER, ARG_DESCRIPTOR } }, |
|
{ "overloadt", NULL, OP_OVERLOAD_TIMEOUT, { ARG_LAYER, ARG_DESCRIPTOR, ARG_TIMEOUT } }, |
|
{ "overloadt2", NULL, OP_OVERLOAD_TIMEOUT_TAP, { ARG_LAYER, ARG_DESCRIPTOR, ARG_TIMEOUT } }, |
|
|
|
{ "timeout", NULL, OP_TIMEOUT, { ARG_DESCRIPTOR, ARG_TIMEOUT, ARG_DESCRIPTOR } }, |
|
|
|
{ "macro2", NULL, OP_MACRO2, { ARG_TIMEOUT, ARG_TIMEOUT, ARG_MACRO } }, |
|
{ "setlayout", NULL, OP_LAYOUT, { ARG_LAYOUT } }, |
|
|
|
/* Experimental */ |
|
{ "scrollt", NULL, OP_SCROLL_TOGGLE, {ARG_SENSITIVITY} }, |
|
{ "scroll", NULL, OP_SCROLL, {ARG_SENSITIVITY} }, |
|
|
|
/* TODO: deprecate */ |
|
{ "overload2", "overloadt", OP_OVERLOAD_TIMEOUT, { ARG_LAYER, ARG_DESCRIPTOR, ARG_TIMEOUT } }, |
|
{ "overload3", "overloadt2", OP_OVERLOAD_TIMEOUT_TAP, { ARG_LAYER, ARG_DESCRIPTOR, ARG_TIMEOUT } }, |
|
{ "toggle2", "togglem", OP_TOGGLEM, { ARG_LAYER, ARG_MACRO } }, |
|
{ "swap2", "swapm", OP_SWAPM, { ARG_LAYER, ARG_MACRO } }, |
|
}; |
|
|
|
|
|
static const char *resolve_include_path(const char *path, const char *include_path) |
|
{ |
|
static char resolved_path[PATH_MAX]; |
|
char tmp[PATH_MAX]; |
|
const char *dir; |
|
|
|
if (strstr(include_path, ".")) { |
|
warn("%s: included files may not have a file extension", include_path); |
|
return NULL; |
|
} |
|
|
|
strcpy(tmp, path); |
|
dir = dirname(tmp); |
|
snprintf(resolved_path, sizeof resolved_path, "%s/%s", dir, include_path); |
|
|
|
if (!access(resolved_path, F_OK)) |
|
return resolved_path; |
|
|
|
snprintf(resolved_path, sizeof resolved_path, DATA_DIR"/%s", include_path); |
|
|
|
if (!access(resolved_path, F_OK)) |
|
return resolved_path; |
|
|
|
return NULL; |
|
} |
|
|
|
static char *read_file(const char *path) |
|
{ |
|
const char include_prefix[] = "include "; |
|
|
|
static char buf[MAX_FILE_SZ]; |
|
char line[MAX_LINE_LEN+1]; |
|
int sz = 0; |
|
|
|
FILE *fh = fopen(path, "r"); |
|
if (!fh) { |
|
err("failed to open %s", path); |
|
return NULL; |
|
} |
|
|
|
while (fgets(line, sizeof line, fh)) { |
|
int len = strlen(line); |
|
|
|
if (line[len-1] != '\n') { |
|
if (len >= MAX_LINE_LEN) { |
|
err("maximum line length exceed (%d)", MAX_LINE_LEN); |
|
goto fail; |
|
} else { |
|
line[len++] = '\n'; |
|
} |
|
} |
|
|
|
if ((len+sz) > MAX_FILE_SZ) { |
|
err("maximum file size exceed (%d)", MAX_FILE_SZ); |
|
goto fail; |
|
} |
|
|
|
if (strstr(line, include_prefix) == line) { |
|
int fd; |
|
const char *resolved_path; |
|
char *include_path = line+sizeof(include_prefix)-1; |
|
|
|
line[len-1] = 0; |
|
|
|
while (include_path[0] == ' ') |
|
include_path++; |
|
|
|
resolved_path = resolve_include_path(path, include_path); |
|
|
|
if (!resolved_path) { |
|
warn("failed to resolve include path: %s", include_path); |
|
continue; |
|
} |
|
|
|
fd = open(resolved_path, O_RDONLY); |
|
|
|
if (fd < 0) { |
|
warn("failed to include %s", include_path); |
|
perror("open"); |
|
} else { |
|
int n; |
|
while ((n = read(fd, buf+sz, sizeof(buf)-sz)) > 0) |
|
sz += n; |
|
close(fd); |
|
} |
|
} else { |
|
strcpy(buf+sz, line); |
|
sz += len; |
|
} |
|
} |
|
|
|
fclose(fh); |
|
|
|
buf[sz] = 0; |
|
return buf; |
|
|
|
fail: |
|
fclose(fh); |
|
return NULL; |
|
} |
|
|
|
|
|
/* Return up to two keycodes associated with the given name. */ |
|
static uint8_t lookup_keycode(const char *name) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < 256; i++) { |
|
const struct keycode_table_ent *ent = &keycode_table[i]; |
|
|
|
if (ent->name && |
|
(!strcmp(ent->name, name) || |
|
(ent->alt_name && !strcmp(ent->alt_name, name)))) { |
|
return i; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static struct descriptor *layer_lookup_chord(struct layer *layer, uint8_t *keys, size_t n) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < layer->nr_chords; i++) { |
|
size_t j; |
|
size_t nm = 0; |
|
struct chord *chord = &layer->chords[i]; |
|
|
|
for (j = 0; j < n; j++) { |
|
size_t k; |
|
for (k = 0; k < chord->sz; k++) |
|
if (keys[j] == chord->keys[k]) { |
|
nm++; |
|
break; |
|
} |
|
} |
|
|
|
if (nm == n) |
|
return &chord->d; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
/* |
|
* Consumes a string of the form `[<layer>.]<key> = <descriptor>` and adds the |
|
* mapping to the corresponding layer in the config. |
|
*/ |
|
|
|
static int set_layer_entry(const struct config *config, |
|
struct layer *layer, char *key, |
|
const struct descriptor *d) |
|
{ |
|
size_t i; |
|
int found = 0; |
|
|
|
if (strchr(key, '+')) { |
|
//TODO: Handle aliases |
|
char *tok; |
|
struct descriptor *ld; |
|
uint8_t keys[ARRAY_SIZE(layer->chords[0].keys)]; |
|
size_t n = 0; |
|
|
|
for (tok = strtok(key, "+"); tok; tok = strtok(NULL, "+")) { |
|
uint8_t code = lookup_keycode(tok); |
|
if (!code) { |
|
err("%s is not a valid key", tok); |
|
return -1; |
|
} |
|
|
|
if (n >= ARRAY_SIZE(keys)) { |
|
err("chords cannot contain more than %ld keys", n); |
|
return -1; |
|
} |
|
|
|
keys[n++] = code; |
|
} |
|
|
|
|
|
if ((ld = layer_lookup_chord(layer, keys, n))) { |
|
*ld = *d; |
|
} else { |
|
struct chord *chord; |
|
if (layer->nr_chords >= ARRAY_SIZE(layer->chords)) { |
|
err("max chords exceeded(%ld)", layer->nr_chords); |
|
return -1; |
|
} |
|
|
|
chord = &layer->chords[layer->nr_chords]; |
|
memcpy(chord->keys, keys, sizeof keys); |
|
chord->sz = n; |
|
chord->d = *d; |
|
|
|
layer->nr_chords++; |
|
} |
|
} else { |
|
for (i = 0; i < 256; i++) { |
|
if (!strcmp(config->aliases[i], key)) { |
|
layer->keymap[i] = *d; |
|
found = 1; |
|
} |
|
} |
|
|
|
if (!found) { |
|
uint8_t code; |
|
|
|
if (!(code = lookup_keycode(key))) { |
|
err("%s is not a valid key or alias", key); |
|
return -1; |
|
} |
|
|
|
layer->keymap[code] = *d; |
|
|
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int new_layer(char *s, const struct config *config, struct layer *layer) |
|
{ |
|
uint8_t mods; |
|
char *name; |
|
char *type; |
|
|
|
name = strtok(s, ":"); |
|
type = strtok(NULL, ":"); |
|
|
|
strcpy(layer->name, name); |
|
|
|
layer->nr_chords = 0; |
|
|
|
if (strchr(name, '+')) { |
|
char *layername; |
|
int n = 0; |
|
|
|
layer->type = LT_COMPOSITE; |
|
layer->nr_constituents = 0; |
|
|
|
if (type) { |
|
err("composite layers cannot have a type."); |
|
return -1; |
|
} |
|
|
|
for (layername = strtok(name, "+"); layername; layername = strtok(NULL, "+")) { |
|
int idx = config_get_layer_index(config, layername); |
|
|
|
if (idx < 0) { |
|
err("%s is not a valid layer", layername); |
|
return -1; |
|
} |
|
|
|
if (n >= ARRAY_SIZE(layer->constituents)) { |
|
err("max composite layers (%d) exceeded", ARRAY_SIZE(layer->constituents)); |
|
return -1; |
|
} |
|
|
|
layer->constituents[layer->nr_constituents++] = idx; |
|
} |
|
|
|
} else if (type && !strcmp(type, "layout")) { |
|
layer->type = LT_LAYOUT; |
|
} else if (type && !parse_modset(type, &mods)) { |
|
layer->type = LT_NORMAL; |
|
layer->mods = mods; |
|
} else { |
|
if (type) |
|
warn("\"%s\" is not a valid layer type, ignoring\n", type); |
|
|
|
layer->type = LT_NORMAL; |
|
layer->mods = 0; |
|
} |
|
|
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Returns: |
|
* 1 if the layer exists |
|
* 0 if the layer was created successfully |
|
* < 0 on error |
|
*/ |
|
static int config_add_layer(struct config *config, const char *s) |
|
{ |
|
int ret; |
|
char buf[MAX_LAYER_NAME_LEN+1]; |
|
char *name; |
|
|
|
if (strlen(s) >= sizeof buf) { |
|
err("%s exceeds maximum section length (%d) (ignoring)", s, MAX_LAYER_NAME_LEN); |
|
return -1; |
|
} |
|
|
|
strcpy(buf, s); |
|
name = strtok(buf, ":"); |
|
|
|
if (config_get_layer_index(config, name) != -1) |
|
return 1; |
|
|
|
if (config->nr_layers >= MAX_LAYERS) { |
|
err("max layers (%d) exceeded", MAX_LAYERS); |
|
return -1; |
|
} |
|
|
|
strcpy(buf, s); |
|
ret = new_layer(buf, config, &config->layers[config->nr_layers]); |
|
|
|
if (ret < 0) |
|
return -1; |
|
|
|
config->nr_layers++; |
|
return 0; |
|
} |
|
|
|
/* Modifies the input string */ |
|
static int parse_fn(char *s, |
|
char **name, |
|
char *args[MAX_DESCRIPTOR_ARGS], |
|
size_t *nargs) |
|
{ |
|
char *c, *arg; |
|
|
|
c = s; |
|
while (*c && *c != '(') |
|
c++; |
|
|
|
if (!*c) |
|
return -1; |
|
|
|
*name = s; |
|
*c++ = 0; |
|
|
|
while (*c == ' ') |
|
c++; |
|
|
|
*nargs = 0; |
|
arg = c; |
|
while (1) { |
|
int plvl = 0; |
|
|
|
while (*c) { |
|
switch (*c) { |
|
case '\\': |
|
if (*(c+1)) { |
|
c+=2; |
|
continue; |
|
} |
|
break; |
|
case '(': |
|
plvl++; |
|
break; |
|
case ')': |
|
plvl--; |
|
|
|
if (plvl == -1) |
|
goto exit; |
|
break; |
|
case ',': |
|
if (plvl == 0) |
|
goto exit; |
|
break; |
|
} |
|
|
|
c++; |
|
} |
|
exit: |
|
|
|
if (!*c) |
|
return -1; |
|
|
|
if (arg != c) { |
|
assert(*nargs < MAX_DESCRIPTOR_ARGS); |
|
args[(*nargs)++] = arg; |
|
} |
|
|
|
if (*c == ')') { |
|
*c = 0; |
|
return 0; |
|
} |
|
|
|
*c++ = 0; |
|
while (*c == ' ') |
|
c++; |
|
arg = c; |
|
} |
|
} |
|
|
|
/* |
|
* Parses macro expression placing the result |
|
* in the supplied macro struct. |
|
* |
|
* Returns: |
|
* 0 on success |
|
* -1 in the case of an invalid macro expression |
|
* > 0 for all other errors |
|
*/ |
|
|
|
int parse_macro_expression(const char *s, struct macro *macro) |
|
{ |
|
uint8_t code, mods; |
|
|
|
#define ADD_ENTRY(t, d) do { \ |
|
if (macro->sz >= ARRAY_SIZE(macro->entries)) { \ |
|
err("maximum macro size (%d) exceeded", ARRAY_SIZE(macro->entries)); \ |
|
return 1; \ |
|
} \ |
|
macro->entries[macro->sz].type = t; \ |
|
macro->entries[macro->sz].data = d; \ |
|
macro->sz++; \ |
|
} while(0) |
|
|
|
size_t len = strlen(s); |
|
|
|
char buf[1024]; |
|
char *ptr = buf; |
|
|
|
if (len >= sizeof(buf)) { |
|
err("macro size exceeded maximum size (%ld)\n", sizeof(buf)); |
|
return -1; |
|
} |
|
|
|
strcpy(buf, s); |
|
|
|
if (!strncmp(ptr, "macro(", 6) && ptr[len-1] == ')') { |
|
ptr[len-1] = 0; |
|
ptr += 6; |
|
str_escape(ptr); |
|
} else if (parse_key_sequence(ptr, &code, &mods) && utf8_strlen(ptr) != 1) { |
|
err("Invalid macro"); |
|
return -1; |
|
} |
|
|
|
return macro_parse(ptr, macro) == 0 ? 0 : 1; |
|
} |
|
|
|
static int parse_command(const char *s, struct command *command) |
|
{ |
|
int len = strlen(s); |
|
|
|
if (len == 0 || strstr(s, "command(") != s || s[len-1] != ')') |
|
return -1; |
|
|
|
if (len > (int)sizeof(command->cmd)) { |
|
err("max command length (%ld) exceeded\n", sizeof(command->cmd)); |
|
return 1; |
|
} |
|
|
|
strcpy(command->cmd, s+8); |
|
command->cmd[len-9] = 0; |
|
str_escape(command->cmd); |
|
|
|
return 0; |
|
} |
|
|
|
static int parse_descriptor(char *s, |
|
struct descriptor *d, |
|
struct config *config) |
|
{ |
|
char *fn = NULL; |
|
char *args[MAX_DESCRIPTOR_ARGS]; |
|
size_t nargs = 0; |
|
uint8_t code, mods; |
|
int ret; |
|
struct macro macro; |
|
struct command cmd; |
|
|
|
if (!s || !s[0]) { |
|
d->op = 0; |
|
return 0; |
|
} |
|
|
|
if (!parse_key_sequence(s, &code, &mods)) { |
|
size_t i; |
|
const char *layer = NULL; |
|
|
|
switch (code) { |
|
case KEYD_LEFTSHIFT: layer = "shift"; break; |
|
case KEYD_LEFTCTRL: layer = "control"; break; |
|
case KEYD_LEFTMETA: layer = "meta"; break; |
|
case KEYD_LEFTALT: layer = "alt"; break; |
|
case KEYD_RIGHTALT: layer = "altgr"; break; |
|
} |
|
|
|
if (layer) { |
|
warn("You should use b{layer(%s)} instead of assigning to b{%s} directly.", layer, KEY_NAME(code)); |
|
d->op = OP_LAYER; |
|
d->args[0].idx = config_get_layer_index(config, layer); |
|
|
|
assert(d->args[0].idx != -1); |
|
|
|
return 0; |
|
} |
|
|
|
d->op = OP_KEYSEQUENCE; |
|
d->args[0].code = code; |
|
d->args[1].mods = mods; |
|
|
|
return 0; |
|
} else if ((ret=parse_command(s, &cmd)) >= 0) { |
|
if (ret) { |
|
return -1; |
|
} |
|
|
|
if (config->nr_commands >= ARRAY_SIZE(config->commands)) { |
|
err("max commands (%d), exceeded", ARRAY_SIZE(config->commands)); |
|
return -1; |
|
} |
|
|
|
|
|
d->op = OP_COMMAND; |
|
d->args[0].idx = config->nr_commands; |
|
|
|
config->commands[config->nr_commands++] = cmd; |
|
|
|
return 0; |
|
} else if ((ret=parse_macro_expression(s, ¯o)) >= 0) { |
|
if (ret) |
|
return -1; |
|
|
|
if (config->nr_macros >= ARRAY_SIZE(config->macros)) { |
|
err("max macros (%d), exceeded", ARRAY_SIZE(config->macros)); |
|
return -1; |
|
} |
|
|
|
d->op = OP_MACRO; |
|
d->args[0].idx = config->nr_macros; |
|
|
|
config->macros[config->nr_macros++] = macro; |
|
|
|
return 0; |
|
} else if (!parse_fn(s, &fn, args, &nargs)) { |
|
int i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(actions); i++) { |
|
if (!strcmp(actions[i].name, fn)) { |
|
int j; |
|
|
|
if (actions[i].preferred_name) |
|
warn("%s is deprecated (renamed to %s).", actions[i].name, actions[i].preferred_name); |
|
|
|
d->op = actions[i].op; |
|
|
|
for (j = 0; j < MAX_DESCRIPTOR_ARGS; j++) { |
|
if (!actions[i].args[j]) |
|
break; |
|
} |
|
|
|
if ((int)nargs != j) { |
|
err("%s requires %d %s", actions[i].name, j, j == 1 ? "argument" : "arguments"); |
|
return -1; |
|
} |
|
|
|
while (j--) { |
|
int type = actions[i].args[j]; |
|
union descriptor_arg *arg = &d->args[j]; |
|
char *argstr = args[j]; |
|
struct descriptor desc; |
|
|
|
switch (type) { |
|
case ARG_LAYER: |
|
if (!strcmp(argstr, "main")) { |
|
err("the main layer cannot be toggled"); |
|
return -1; |
|
} |
|
|
|
arg->idx = config_get_layer_index(config, argstr); |
|
if (arg->idx == -1 || config->layers[arg->idx].type == LT_LAYOUT) { |
|
err("%s is not a valid layer", argstr); |
|
return -1; |
|
} |
|
|
|
break; |
|
case ARG_LAYOUT: |
|
arg->idx = config_get_layer_index(config, argstr); |
|
if (arg->idx == -1 || config->layers[arg->idx].type != LT_LAYOUT) { |
|
err("%s is not a valid layout", argstr); |
|
return -1; |
|
} |
|
|
|
break; |
|
case ARG_DESCRIPTOR: |
|
if (parse_descriptor(argstr, &desc, config)) |
|
return -1; |
|
|
|
if (config->nr_descriptors >= ARRAY_SIZE(config->descriptors)) { |
|
err("maximum descriptors exceeded"); |
|
return -1; |
|
} |
|
|
|
config->descriptors[config->nr_descriptors] = desc; |
|
arg->idx = config->nr_descriptors++; |
|
break; |
|
case ARG_SENSITIVITY: |
|
arg->sensitivity = atoi(argstr); |
|
break; |
|
case ARG_TIMEOUT: |
|
arg->timeout = atoi(argstr); |
|
break; |
|
case ARG_MACRO: |
|
if (config->nr_macros >= ARRAY_SIZE(config->macros)) { |
|
err("max macros (%d), exceeded", ARRAY_SIZE(config->macros)); |
|
return -1; |
|
} |
|
|
|
if (parse_macro_expression(argstr, &config->macros[config->nr_macros])) { |
|
return -1; |
|
} |
|
|
|
arg->idx = config->nr_macros; |
|
config->nr_macros++; |
|
|
|
break; |
|
default: |
|
assert(0); |
|
break; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
} |
|
} |
|
|
|
err("invalid key or action"); |
|
return -1; |
|
} |
|
|
|
static void parse_global_section(struct config *config, struct ini_section *section) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < section->nr_entries;i++) { |
|
struct ini_entry *ent = §ion->entries[i]; |
|
|
|
if (!strcmp(ent->key, "macro_timeout")) |
|
config->macro_timeout = atoi(ent->val); |
|
else if (!strcmp(ent->key, "macro_sequence_timeout")) |
|
config->macro_sequence_timeout = atoi(ent->val); |
|
else if (!strcmp(ent->key, "disable_modifier_guard")) |
|
config->disable_modifier_guard = atoi(ent->val); |
|
else if (!strcmp(ent->key, "oneshot_timeout")) |
|
config->oneshot_timeout = atoi(ent->val); |
|
else if (!strcmp(ent->key, "chord_hold_timeout")) |
|
config->chord_hold_timeout = atoi(ent->val); |
|
else if (!strcmp(ent->key, "chord_timeout")) |
|
config->chord_interkey_timeout = atoi(ent->val); |
|
else if (!strcmp(ent->key, "default_layout")) |
|
snprintf(config->default_layout, sizeof config->default_layout, |
|
"%s", ent->val); |
|
else if (!strcmp(ent->key, "macro_repeat_timeout")) |
|
config->macro_repeat_timeout = atoi(ent->val); |
|
else if (!strcmp(ent->key, "layer_indicator")) |
|
config->layer_indicator = atoi(ent->val); |
|
else if (!strcmp(ent->key, "overload_tap_timeout")) |
|
config->overload_tap_timeout = atoi(ent->val); |
|
else |
|
warn("line %zd: %s is not a valid global option", ent->lnum, ent->key); |
|
} |
|
} |
|
|
|
static void parse_id_section(struct config *config, struct ini_section *section) |
|
{ |
|
size_t i; |
|
for (i = 0; i < section->nr_entries; i++) { |
|
uint16_t product, vendor; |
|
|
|
struct ini_entry *ent = §ion->entries[i]; |
|
const char *s = ent->key; |
|
|
|
if (!strcmp(s, "*")) { |
|
config->wildcard = 1; |
|
} else { |
|
if (sscanf(s, "m:%hx:%hx", &vendor, &product) == 2) { |
|
assert(config->nr_ids < ARRAY_SIZE(config->ids)); |
|
config->ids[config->nr_ids].product = product; |
|
config->ids[config->nr_ids].vendor = vendor; |
|
config->ids[config->nr_ids].flags = ID_MOUSE; |
|
|
|
config->nr_ids++; |
|
} else if (sscanf(s, "k:%hx:%hx", &vendor, &product) == 2) { |
|
assert(config->nr_ids < ARRAY_SIZE(config->ids)); |
|
config->ids[config->nr_ids].product = product; |
|
config->ids[config->nr_ids].vendor = vendor; |
|
config->ids[config->nr_ids].flags = ID_KEYBOARD; |
|
|
|
config->nr_ids++; |
|
} else if (sscanf(s, "-%hx:%hx", &vendor, &product) == 2) { |
|
assert(config->nr_ids < ARRAY_SIZE(config->ids)); |
|
config->ids[config->nr_ids].product = product; |
|
config->ids[config->nr_ids].vendor = vendor; |
|
config->ids[config->nr_ids].flags = ID_EXCLUDED; |
|
|
|
config->nr_ids++; |
|
} else if (sscanf(s, "%hx:%hx", &vendor, &product) == 2) { |
|
assert(config->nr_ids < ARRAY_SIZE(config->ids)); |
|
config->ids[config->nr_ids].product = product; |
|
config->ids[config->nr_ids].vendor = vendor; |
|
config->ids[config->nr_ids].flags = ID_KEYBOARD | ID_MOUSE; |
|
|
|
config->nr_ids++; |
|
} |
|
else { |
|
warn("%s is not a valid device id", s); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void parse_alias_section(struct config *config, struct ini_section *section) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < section->nr_entries; i++) { |
|
uint8_t code; |
|
struct ini_entry *ent = §ion->entries[i]; |
|
const char *name = ent->val; |
|
|
|
if ((code = lookup_keycode(ent->key))) { |
|
ssize_t len = strlen(name); |
|
|
|
if (len >= (ssize_t)sizeof(config->aliases[0])) { |
|
warn("%s exceeds the maximum alias length (%ld)", name, sizeof(config->aliases[0])-1); |
|
} else { |
|
uint8_t alias_code; |
|
|
|
if ((alias_code = lookup_keycode(name))) { |
|
struct descriptor *d = &config->layers[0].keymap[code]; |
|
|
|
d->op = OP_KEYSEQUENCE; |
|
d->args[0].code = alias_code; |
|
d->args[1].mods = 0; |
|
} |
|
|
|
strcpy(config->aliases[code], name); |
|
} |
|
} else { |
|
warn("failed to define alias %s, %s is not a valid keycode", name, ent->key); |
|
} |
|
} |
|
} |
|
|
|
|
|
static int config_parse_string(struct config *config, char *content) |
|
{ |
|
size_t i; |
|
struct ini *ini; |
|
|
|
if (!(ini = ini_parse_string(content, NULL))) |
|
return -1; |
|
|
|
/* First pass: create all layers based on section headers. */ |
|
for (i = 0; i < ini->nr_sections; i++) { |
|
struct ini_section *section = &ini->sections[i]; |
|
|
|
if (!strcmp(section->name, "ids")) { |
|
parse_id_section(config, section); |
|
} else if (!strcmp(section->name, "aliases")) { |
|
parse_alias_section(config, section); |
|
} else if (!strcmp(section->name, "global")) { |
|
parse_global_section(config, section); |
|
} else { |
|
if (config_add_layer(config, section->name) < 0) |
|
warn("%s", errstr); |
|
} |
|
} |
|
|
|
/* Populate each layer. */ |
|
for (i = 0; i < ini->nr_sections; i++) { |
|
size_t j; |
|
char *layername; |
|
struct ini_section *section = &ini->sections[i]; |
|
|
|
if (!strcmp(section->name, "ids") || |
|
!strcmp(section->name, "aliases") || |
|
!strcmp(section->name, "global")) |
|
continue; |
|
|
|
layername = strtok(section->name, ":"); |
|
|
|
for (j = 0; j < section->nr_entries;j++) { |
|
char entry[MAX_EXP_LEN]; |
|
struct ini_entry *ent = §ion->entries[j]; |
|
|
|
if (!ent->val) { |
|
warn("invalid binding on line %zd", ent->lnum); |
|
continue; |
|
} |
|
|
|
snprintf(entry, sizeof entry, "%s.%s = %s", layername, ent->key, ent->val); |
|
|
|
if (config_add_entry(config, entry) < 0) |
|
keyd_log("\tr{ERROR:} line m{%zd}: %s\n", ent->lnum, errstr); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void config_init(struct config *config) |
|
{ |
|
size_t i; |
|
|
|
memset(config, 0, sizeof *config); |
|
|
|
char default_config[] = |
|
"[aliases]\n" |
|
|
|
"leftshift = shift\n" |
|
"rightshift = shift\n" |
|
|
|
"leftalt = alt\n" |
|
"rightalt = altgr\n" |
|
|
|
"leftmeta = meta\n" |
|
"rightmeta = meta\n" |
|
|
|
"leftcontrol = control\n" |
|
"rightcontrol = control\n" |
|
|
|
"[main]\n" |
|
|
|
"shift = layer(shift)\n" |
|
"alt = layer(alt)\n" |
|
"altgr = layer(altgr)\n" |
|
"meta = layer(meta)\n" |
|
"control = layer(control)\n" |
|
|
|
"[control:C]\n" |
|
"[shift:S]\n" |
|
"[meta:M]\n" |
|
"[alt:A]\n" |
|
"[altgr:G]\n"; |
|
|
|
config_parse_string(config, default_config); |
|
|
|
/* In ms */ |
|
config->chord_interkey_timeout = 50; |
|
config->chord_hold_timeout = 0; |
|
config->oneshot_timeout = 0; |
|
|
|
config->macro_timeout = 600; |
|
config->macro_repeat_timeout = 50; |
|
} |
|
|
|
int config_parse(struct config *config, const char *path) |
|
{ |
|
char *content; |
|
|
|
if (!(content = read_file(path))) |
|
return -1; |
|
|
|
config_init(config); |
|
snprintf(config->path, sizeof(config->path), "%s", path); |
|
return config_parse_string(config, content); |
|
} |
|
|
|
int config_check_match(struct config *config, uint16_t vendor, uint16_t product, uint8_t flags) |
|
{ |
|
size_t i; |
|
|
|
for (i = 0; i < config->nr_ids; i++) { |
|
if (config->ids[i].product == product && config->ids[i].vendor == vendor) { |
|
if (config->ids[i].flags & ID_EXCLUDED) { |
|
return 0; |
|
} else if (config->ids[i].flags & flags) { |
|
return 2; |
|
} |
|
} |
|
} |
|
|
|
return config->wildcard ? 1 : 0; |
|
} |
|
|
|
int config_get_layer_index(const struct config *config, const char *name) |
|
{ |
|
size_t i; |
|
|
|
if (!name) |
|
return -1; |
|
|
|
for (i = 0; i < config->nr_layers; i++) |
|
if (!strcmp(config->layers[i].name, name)) |
|
return i; |
|
|
|
return -1; |
|
} |
|
|
|
/* |
|
* Adds a binding of the form [<layer>.]<key> = <descriptor expression> |
|
* to the given config. |
|
*/ |
|
int config_add_entry(struct config *config, const char *exp) |
|
{ |
|
char *keyname, *descstr, *dot, *paren, *s; |
|
char *layername = "main"; |
|
struct descriptor d; |
|
struct layer *layer; |
|
int idx; |
|
|
|
static char buf[MAX_EXP_LEN]; |
|
|
|
if (strlen(exp) >= MAX_EXP_LEN) { |
|
err("%s exceeds maximum expression length (%d)", exp, MAX_EXP_LEN); |
|
return -1; |
|
} |
|
|
|
strcpy(buf, exp); |
|
s = buf; |
|
|
|
dot = strchr(s, '.'); |
|
paren = strchr(s, '('); |
|
|
|
if (dot && dot != s && (!paren || dot < paren)) { |
|
layername = s; |
|
*dot = 0; |
|
s = dot+1; |
|
} |
|
|
|
parse_kvp(s, &keyname, &descstr); |
|
idx = config_get_layer_index(config, layername); |
|
|
|
if (idx == -1) { |
|
err("%s is not a valid layer", layername); |
|
return -1; |
|
} |
|
|
|
layer = &config->layers[idx]; |
|
|
|
if (parse_descriptor(descstr, &d, config) < 0) |
|
return -1; |
|
|
|
return set_layer_entry(config, layer, keyname, &d); |
|
} |
|
|
|
|