kopia lustrzana https://github.com/nanosyzygy/ShuttlePRO
810 wiersze
17 KiB
C
810 wiersze
17 KiB
C
|
|
/*
|
|
|
|
Copyright 2013 Eric Messick (FixedImagePhoto.com/Contact)
|
|
|
|
Read and process the configuration file ~/.shuttlepro
|
|
|
|
Lines starting with # are comments.
|
|
|
|
Sequence of sections defining translation classes, each section is:
|
|
|
|
[name] regex
|
|
K<1..15> output
|
|
S<-7..7> output
|
|
J<LR> output
|
|
|
|
When focus is on a window whose title matches regex, the following
|
|
translation class is in effect. An empty regex for the last class
|
|
will always match, allowing default translations. Any output
|
|
sequences not bound in a matched section will be loaded from the
|
|
default section if they are bound there.
|
|
|
|
Each "[name] regex" line introduces the list of key and shuttle
|
|
translations for the named translation class. The name is only used
|
|
for debugging output, and needn't be unique. The following lines
|
|
with K, S, and J labels indicate what output should be produced for
|
|
the given keypress, shuttle position, or jog direction.
|
|
|
|
output is a sequence of one or more key codes with optional up/down
|
|
indicators, or strings of printable characters enclosed in double
|
|
quotes, separated by whitespace. Sequences bound to keys may have
|
|
separate press and release sequences, separated by the word RELEASE.
|
|
|
|
Examples:
|
|
|
|
K1 "qwer"
|
|
K2 XK_Right
|
|
K3 XK_Alt_L/D XK_Right
|
|
K4 "V" XK_Left XK_Page_Up "v"
|
|
K5 XK_Alt_L/D "v" XK_Alt_L/U "x" RELEASE "q"
|
|
|
|
Any keycode can be followed by an optional /D, /U, or /H, indicating
|
|
that the key is just going down (without being released), going up,
|
|
or going down and being held until the shuttlepro key is released.
|
|
|
|
So, in general, modifier key codes will be followed by /D, and
|
|
precede the keycodes they are intended to modify. If a sequence
|
|
requires different sets of modifiers for different keycodes, /U can
|
|
be used to release a modifier that was previously pressed with /D.
|
|
|
|
At the end of shuttle and jog sequences, all down keys will be
|
|
released.
|
|
|
|
Keypresses translate to separate press and release sequences.
|
|
|
|
At the end of the press sequence for key sequences, all down keys
|
|
marked by /D will be released, and the last key not marked by /D,
|
|
/U, or /H will remain pressed. The release sequence will begin by
|
|
releasing the last held key. If keys are to be pressed as part of
|
|
the release sequence, then any keys marked with /D will be repressed
|
|
before continuing the sequence. Keycodes marked with /H remain held
|
|
between the press and release sequences.
|
|
|
|
*/
|
|
|
|
#include "shuttle.h"
|
|
|
|
int debug_regex = 0;
|
|
int debug_strokes = 0;
|
|
|
|
char *
|
|
allocate(size_t len)
|
|
{
|
|
char *ret = (char *)malloc(len);
|
|
if (ret == NULL) {
|
|
fprintf(stderr, "Out of memory!\n");
|
|
exit(1);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
char *
|
|
alloc_strcat(char *a, char *b)
|
|
{
|
|
size_t len = 0;
|
|
char *result;
|
|
|
|
if (a != NULL) {
|
|
len += strlen(a);
|
|
}
|
|
if (b != NULL) {
|
|
len += strlen(b);
|
|
}
|
|
result = allocate(len+1);
|
|
result[0] = '\0';
|
|
if (a != NULL) {
|
|
strcpy(result, a);
|
|
}
|
|
if (b != NULL) {
|
|
strcat(result, b);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static char *read_line_buffer = NULL;
|
|
static int read_line_buffer_length = 0;
|
|
|
|
#define BUF_GROWTH_STEP 1024
|
|
|
|
|
|
// read a line of text from the given file into a managed buffer.
|
|
// returns a partial line at EOF if the file does not end with \n.
|
|
// exits with error message on read error.
|
|
char *
|
|
read_line(FILE *f, char *name)
|
|
{
|
|
int pos = 0;
|
|
char *new_buffer;
|
|
int new_buffer_length;
|
|
|
|
if (read_line_buffer == NULL) {
|
|
read_line_buffer_length = BUF_GROWTH_STEP;
|
|
read_line_buffer = allocate(read_line_buffer_length);
|
|
read_line_buffer[0] = '\0';
|
|
}
|
|
|
|
while (1) {
|
|
read_line_buffer[read_line_buffer_length-1] = '\377';
|
|
if (fgets(read_line_buffer+pos, read_line_buffer_length-pos, f) == NULL) {
|
|
if (feof(f)) {
|
|
if (pos > 0) {
|
|
// partial line at EOF
|
|
return read_line_buffer;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
perror(name);
|
|
exit(1);
|
|
}
|
|
if (read_line_buffer[read_line_buffer_length-1] != '\0') {
|
|
return read_line_buffer;
|
|
}
|
|
if (read_line_buffer[read_line_buffer_length-2] == '\n') {
|
|
return read_line_buffer;
|
|
}
|
|
new_buffer_length = read_line_buffer_length + BUF_GROWTH_STEP;
|
|
new_buffer = allocate(new_buffer_length);
|
|
memcpy(new_buffer, read_line_buffer, read_line_buffer_length);
|
|
free(read_line_buffer);
|
|
pos = read_line_buffer_length-1;
|
|
read_line_buffer = new_buffer;
|
|
read_line_buffer_length = new_buffer_length;
|
|
}
|
|
}
|
|
|
|
static translation *first_translation_section = NULL;
|
|
static translation *last_translation_section = NULL;
|
|
|
|
translation *default_translation;
|
|
|
|
translation *
|
|
new_translation_section(char *name, char *regex)
|
|
{
|
|
translation *ret = (translation *)allocate(sizeof(translation));
|
|
int err;
|
|
int i;
|
|
|
|
if (debug_strokes) {
|
|
printf("------------------------\n[%s] %s\n\n", name, regex);
|
|
}
|
|
ret->next = NULL;
|
|
ret->name = alloc_strcat(name, NULL);
|
|
if (regex == NULL || *regex == '\0') {
|
|
ret->is_default = 1;
|
|
default_translation = ret;
|
|
} else {
|
|
ret->is_default = 0;
|
|
err = regcomp(&ret->regex, regex, REG_NOSUB);
|
|
if (err != 0) {
|
|
regerror(err, &ret->regex, read_line_buffer, read_line_buffer_length);
|
|
fprintf(stderr, "error compiling regex for [%s]: %s\n", name, read_line_buffer);
|
|
regfree(&ret->regex);
|
|
free(ret->name);
|
|
free(ret);
|
|
return NULL;
|
|
}
|
|
}
|
|
for (i=0; i<NUM_KEYS; i++) {
|
|
ret->key_down[i] = NULL;
|
|
ret->key_up[i] = NULL;
|
|
}
|
|
for (i=0; i<NUM_SHUTTLES; i++) {
|
|
ret->shuttle[i] = NULL;
|
|
}
|
|
for (i=0; i<NUM_JOGS; i++) {
|
|
ret->jog[i] = NULL;
|
|
}
|
|
if (first_translation_section == NULL) {
|
|
first_translation_section = ret;
|
|
last_translation_section = ret;
|
|
} else {
|
|
last_translation_section->next = ret;
|
|
last_translation_section = ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
free_strokes(stroke *s)
|
|
{
|
|
stroke *next;
|
|
while (s != NULL) {
|
|
next = s->next;
|
|
free(s);
|
|
s = next;
|
|
}
|
|
}
|
|
|
|
void
|
|
free_translation_section(translation *tr)
|
|
{
|
|
int i;
|
|
|
|
if (tr != NULL) {
|
|
free(tr->name);
|
|
if (!tr->is_default) {
|
|
regfree(&tr->regex);
|
|
}
|
|
for (i=0; i<NUM_KEYS; i++) {
|
|
free_strokes(tr->key_down[i]);
|
|
free_strokes(tr->key_up[i]);
|
|
}
|
|
for (i=0; i<NUM_SHUTTLES; i++) {
|
|
free_strokes(tr->shuttle[i]);
|
|
}
|
|
for (i=0; i<NUM_JOGS; i++) {
|
|
free_strokes(tr->jog[i]);
|
|
}
|
|
free(tr);
|
|
}
|
|
}
|
|
|
|
void
|
|
free_all_translations(void)
|
|
{
|
|
translation *tr = first_translation_section;
|
|
translation *next;
|
|
|
|
while (tr != NULL) {
|
|
next = tr->next;
|
|
free_translation_section(tr);
|
|
tr = next;
|
|
}
|
|
first_translation_section = NULL;
|
|
last_translation_section = NULL;
|
|
}
|
|
|
|
static char *config_file_name = NULL;
|
|
static time_t config_file_modification_time;
|
|
|
|
static char *token_src = NULL;
|
|
|
|
// similar to strtok, but it tells us what delimiter was found at the
|
|
// end of the token, handles double quoted strings specially, and
|
|
// hardcodes the delimiter set.
|
|
char *
|
|
token(char *src, char *delim_found)
|
|
{
|
|
char *delims = " \t\n/\"";
|
|
char *d;
|
|
char *token_start;
|
|
|
|
if (src == NULL) {
|
|
src = token_src;
|
|
}
|
|
if (src == NULL) {
|
|
*delim_found = '\0';
|
|
return NULL;
|
|
}
|
|
token_start = src;
|
|
while (*src) {
|
|
d = delims;
|
|
while (*d && *src != *d) {
|
|
d++;
|
|
}
|
|
if (*d) {
|
|
if (src == token_start) {
|
|
src++;
|
|
token_start = src;
|
|
if (*d == '"') {
|
|
while (*src && *src != '"' && *src != '\n') {
|
|
src++;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
*delim_found = *d;
|
|
if (*src) {
|
|
*src = '\0';
|
|
token_src = src+1;
|
|
} else {
|
|
token_src = NULL;
|
|
}
|
|
return token_start;
|
|
}
|
|
src++;
|
|
}
|
|
token_src = NULL;
|
|
*delim_found = '\0';
|
|
if (src == token_start) {
|
|
return NULL;
|
|
}
|
|
return token_start;
|
|
}
|
|
|
|
typedef struct _keysymmapping {
|
|
char *str;
|
|
KeySym sym;
|
|
} keysymmapping;
|
|
|
|
static keysymmapping key_sym_mapping[] = {
|
|
#include "keys.h"
|
|
{ "XK_Button_1", XK_Button_1 },
|
|
{ "XK_Button_2", XK_Button_2 },
|
|
{ "XK_Button_3", XK_Button_3 },
|
|
{ "XK_Scroll_Up", XK_Scroll_Up },
|
|
{ "XK_Scroll_Down", XK_Scroll_Down },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
KeySym
|
|
string_to_KeySym(char *str)
|
|
{
|
|
size_t len = strlen(str) + 1;
|
|
int i = 0;
|
|
|
|
while (key_sym_mapping[i].str != NULL) {
|
|
if (!strncmp(str, key_sym_mapping[i].str, len)) {
|
|
return key_sym_mapping[i].sym;
|
|
}
|
|
i++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
KeySym_to_string(KeySym ks)
|
|
{
|
|
int i = 0;
|
|
|
|
while (key_sym_mapping[i].sym != 0) {
|
|
if (key_sym_mapping[i].sym == ks) {
|
|
return key_sym_mapping[i].str;
|
|
}
|
|
i++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
print_stroke(stroke *s)
|
|
{
|
|
char *str;
|
|
|
|
if (s != NULL) {
|
|
str = KeySym_to_string(s->keysym);
|
|
if (str == NULL) {
|
|
printf("0x%x", (int)s->keysym);
|
|
str = "???";
|
|
}
|
|
printf("%s/%c ", str, s->press ? 'D' : 'U');
|
|
}
|
|
}
|
|
|
|
void
|
|
print_stroke_sequence(char *name, char *up_or_down, stroke *s)
|
|
{
|
|
printf("%s[%s]: ", name, up_or_down);
|
|
while (s) {
|
|
print_stroke(s);
|
|
s = s->next;
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
stroke **first_stroke;
|
|
stroke *last_stroke;
|
|
stroke **press_first_stroke;
|
|
stroke **release_first_stroke;
|
|
int is_keystroke;
|
|
char *current_translation;
|
|
char *key_name;
|
|
int first_release_stroke; // is this the first stroke of a release?
|
|
KeySym regular_key_down;
|
|
|
|
#define NUM_MODIFIERS 64
|
|
|
|
stroke modifiers_down[NUM_MODIFIERS];
|
|
int modifier_count;
|
|
|
|
void
|
|
append_stroke(KeySym sym, int press)
|
|
{
|
|
stroke *s = (stroke *)allocate(sizeof(stroke));
|
|
|
|
s->next = NULL;
|
|
s->keysym = sym;
|
|
s->press = press;
|
|
if (*first_stroke) {
|
|
last_stroke->next = s;
|
|
} else {
|
|
*first_stroke = s;
|
|
}
|
|
last_stroke = s;
|
|
}
|
|
|
|
// s->press values in modifiers_down:
|
|
// PRESS -> down
|
|
// HOLD -> held
|
|
// PRESS_RELEASE -> released, but to be re-pressed if necessary
|
|
// RELEASE -> up
|
|
|
|
void
|
|
mark_as_down(KeySym sym, int hold)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<modifier_count; i++) {
|
|
if (modifiers_down[i].keysym == sym) {
|
|
modifiers_down[i].press = hold ? HOLD : PRESS;
|
|
return;
|
|
}
|
|
}
|
|
if (modifier_count > NUM_MODIFIERS) {
|
|
fprintf(stderr, "too many modifiers down in [%s]%s\n", current_translation, key_name);
|
|
return;
|
|
}
|
|
modifiers_down[modifier_count].keysym = sym;
|
|
modifiers_down[modifier_count].press = hold ? HOLD : PRESS;
|
|
modifier_count++;
|
|
}
|
|
|
|
void
|
|
mark_as_up(KeySym sym)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<modifier_count; i++) {
|
|
if (modifiers_down[i].keysym == sym) {
|
|
modifiers_down[i].press = RELEASE;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
release_modifiers(int allkeys)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<modifier_count; i++) {
|
|
if (modifiers_down[i].press == PRESS) {
|
|
append_stroke(modifiers_down[i].keysym, 0);
|
|
modifiers_down[i].press = PRESS_RELEASE;
|
|
} else if (allkeys && modifiers_down[i].press == HOLD) {
|
|
append_stroke(modifiers_down[i].keysym, 0);
|
|
modifiers_down[i].press = RELEASE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
re_press_temp_modifiers(void)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<modifier_count; i++) {
|
|
if (modifiers_down[i].press == PRESS_RELEASE) {
|
|
append_stroke(modifiers_down[i].keysym, 1);
|
|
modifiers_down[i].press = PRESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
start_translation(translation *tr, char *which_key)
|
|
{
|
|
char c;
|
|
int k;
|
|
int n;
|
|
|
|
//printf("start_translation(%s)\n", which_key);
|
|
|
|
if (tr == NULL) {
|
|
fprintf(stderr, "need to start translation section before defining key: %s\n", which_key);
|
|
return 1;
|
|
}
|
|
current_translation = tr->name;
|
|
key_name = which_key;
|
|
is_keystroke = 0;
|
|
first_release_stroke = 0;
|
|
regular_key_down = 0;
|
|
modifier_count = 0;
|
|
// JL, JR
|
|
if (tolower(which_key[0]) == 'j' &&
|
|
(tolower(which_key[1]) == 'l' || tolower(which_key[1]) == 'r') &&
|
|
which_key[2] == '\0') {
|
|
k = tolower(which_key[1]) == 'l' ? 0 : 1;
|
|
first_stroke = &(tr->jog[k]);
|
|
} else {
|
|
n = 0;
|
|
sscanf(which_key, "%c%d%n", &c, &k, &n);
|
|
if (n != (int)strlen(which_key)) {
|
|
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
|
|
return 1;
|
|
}
|
|
switch (c) {
|
|
case 'k':
|
|
case 'K':
|
|
// K1 .. K15
|
|
k = k - 1;
|
|
if (k < 0 || k >= NUM_KEYS) {
|
|
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
|
|
return 1;
|
|
}
|
|
first_stroke = &(tr->key_down[k]);
|
|
release_first_stroke = &(tr->key_up[k]);
|
|
is_keystroke = 1;
|
|
break;
|
|
case 's':
|
|
case 'S':
|
|
// S-7 .. S7
|
|
if (k < -7 || k > 7) {
|
|
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
|
|
return 1;
|
|
}
|
|
first_stroke = &(tr->shuttle[k+7]);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "bad key name: [%s]%s\n", current_translation, which_key);
|
|
return 1;
|
|
}
|
|
}
|
|
if (*first_stroke != NULL) {
|
|
fprintf(stderr, "can't redefine key: [%s]%s\n", current_translation, which_key);
|
|
return 1;
|
|
}
|
|
press_first_stroke = first_stroke;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
add_keysym(KeySym sym, int press_release)
|
|
{
|
|
//printf("add_keysym(0x%x, %d)\n", (int)sym, press_release);
|
|
switch (press_release) {
|
|
case PRESS:
|
|
append_stroke(sym, 1);
|
|
mark_as_down(sym, 0);
|
|
break;
|
|
case RELEASE:
|
|
append_stroke(sym, 0);
|
|
mark_as_up(sym);
|
|
break;
|
|
case HOLD:
|
|
append_stroke(sym, 1);
|
|
mark_as_down(sym, 1);
|
|
break;
|
|
case PRESS_RELEASE:
|
|
default:
|
|
if (first_release_stroke) {
|
|
re_press_temp_modifiers();
|
|
}
|
|
if (regular_key_down != 0) {
|
|
append_stroke(regular_key_down, 0);
|
|
}
|
|
append_stroke(sym, 1);
|
|
regular_key_down = sym;
|
|
first_release_stroke = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
add_release(int all_keys)
|
|
{
|
|
//printf("add_release(%d)\n", all_keys);
|
|
release_modifiers(all_keys);
|
|
if (!all_keys) {
|
|
first_stroke = release_first_stroke;
|
|
}
|
|
if (regular_key_down != 0) {
|
|
append_stroke(regular_key_down, 0);
|
|
}
|
|
regular_key_down = 0;
|
|
first_release_stroke = 1;
|
|
}
|
|
|
|
void
|
|
add_keystroke(char *keySymName, int press_release)
|
|
{
|
|
KeySym sym;
|
|
|
|
if (is_keystroke && !strncmp(keySymName, "RELEASE", 8)) {
|
|
add_release(0);
|
|
return;
|
|
}
|
|
sym = string_to_KeySym(keySymName);
|
|
if (sym != 0) {
|
|
add_keysym(sym, press_release);
|
|
} else {
|
|
fprintf(stderr, "unrecognized KeySym: %s\n", keySymName);
|
|
}
|
|
}
|
|
|
|
void
|
|
add_string(char *str)
|
|
{
|
|
while (str && *str) {
|
|
if (*str >= ' ' && *str <= '~') {
|
|
add_keysym((KeySym)(*str), PRESS_RELEASE);
|
|
}
|
|
str++;
|
|
}
|
|
}
|
|
|
|
void
|
|
finish_translation(void)
|
|
{
|
|
//printf("finish_translation()\n");
|
|
if (is_keystroke) {
|
|
add_release(0);
|
|
}
|
|
add_release(1);
|
|
if (debug_strokes) {
|
|
if (is_keystroke) {
|
|
print_stroke_sequence(key_name, "D", *press_first_stroke);
|
|
print_stroke_sequence(key_name, "U", *release_first_stroke);
|
|
} else {
|
|
print_stroke_sequence(key_name, "", *first_stroke);
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
read_config_file(void)
|
|
{
|
|
struct stat buf;
|
|
char *home;
|
|
char *line;
|
|
char *s;
|
|
char *name;
|
|
char *regex;
|
|
char *tok;
|
|
char *which_key;
|
|
char *updown;
|
|
char delim;
|
|
translation *tr = NULL;
|
|
FILE *f;
|
|
|
|
if (config_file_name == NULL) {
|
|
config_file_name = getenv("SHUTTLE_CONFIG_FILE");
|
|
if (config_file_name == NULL) {
|
|
home = getenv("HOME");
|
|
config_file_name = alloc_strcat(home, "/.shuttlerc");
|
|
} else {
|
|
config_file_name = alloc_strcat(config_file_name, NULL);
|
|
}
|
|
config_file_modification_time = 0;
|
|
}
|
|
if (stat(config_file_name, &buf) < 0) {
|
|
perror(config_file_name);
|
|
return;
|
|
}
|
|
if (buf.st_mtime == 0) {
|
|
buf.st_mtime = 1;
|
|
}
|
|
if (buf.st_mtime > config_file_modification_time) {
|
|
config_file_modification_time = buf.st_mtime;
|
|
|
|
f = fopen(config_file_name, "r");
|
|
if (f == NULL) {
|
|
perror(config_file_name);
|
|
return;
|
|
}
|
|
|
|
free_all_translations();
|
|
debug_regex = 0;
|
|
debug_strokes = 0;
|
|
|
|
while ((line=read_line(f, config_file_name)) != NULL) {
|
|
//printf("line: %s", line);
|
|
|
|
s = line;
|
|
while (*s && isspace(*s)) {
|
|
s++;
|
|
}
|
|
if (*s == '#') {
|
|
continue;
|
|
}
|
|
if (*s == '[') {
|
|
// [name] regex\n
|
|
name = ++s;
|
|
while (*s && *s != ']') {
|
|
s++;
|
|
}
|
|
regex = NULL;
|
|
if (*s) {
|
|
*s = '\0';
|
|
s++;
|
|
while (*s && isspace(*s)) {
|
|
s++;
|
|
}
|
|
regex = s;
|
|
while (*s) {
|
|
s++;
|
|
}
|
|
s--;
|
|
while (s > regex && isspace(*s)) {
|
|
s--;
|
|
}
|
|
s[1] = '\0';
|
|
}
|
|
tr = new_translation_section(name, regex);
|
|
continue;
|
|
}
|
|
|
|
tok = token(s, &delim);
|
|
if (tok == NULL) {
|
|
continue;
|
|
}
|
|
if (!strcmp(tok, "DEBUG_REGEX")) {
|
|
debug_regex = 1;
|
|
continue;
|
|
}
|
|
if (!strcmp(tok, "DEBUG_STROKES")) {
|
|
debug_strokes = 1;
|
|
continue;
|
|
}
|
|
which_key = tok;
|
|
if (start_translation(tr, which_key)) {
|
|
continue;
|
|
}
|
|
tok = token(NULL, &delim);
|
|
while (tok != NULL) {
|
|
if (delim != '"' && tok[0] == '#') {
|
|
break; // skip rest as comment
|
|
}
|
|
//printf("token: [%s] delim [%d]\n", tok, delim);
|
|
switch (delim) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
add_keystroke(tok, PRESS_RELEASE);
|
|
break;
|
|
case '"':
|
|
add_string(tok);
|
|
break;
|
|
default: // should be slash
|
|
updown = token(NULL, &delim);
|
|
if (updown != NULL) {
|
|
switch (updown[0]) {
|
|
case 'U':
|
|
add_keystroke(tok, RELEASE);
|
|
break;
|
|
case 'D':
|
|
add_keystroke(tok, PRESS);
|
|
break;
|
|
case 'H':
|
|
add_keystroke(tok, HOLD);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "invalid up/down modifier [%s]%s: %s\n", name, which_key, updown);
|
|
add_keystroke(tok, PRESS);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tok = token(NULL, &delim);
|
|
}
|
|
finish_translation();
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
}
|
|
}
|
|
|
|
translation *
|
|
get_translation(char *win_title)
|
|
{
|
|
translation *tr;
|
|
|
|
read_config_file();
|
|
tr = first_translation_section;
|
|
while (tr != NULL) {
|
|
if (tr->is_default) {
|
|
return tr;
|
|
}
|
|
if (regexec(&tr->regex, win_title, 0, NULL, 0) == 0) {
|
|
return tr;
|
|
}
|
|
tr = tr->next;
|
|
}
|
|
return NULL;
|
|
}
|