hkd/hkd.c
Alessandro Mauri 6691a616e3 basic xkbcommon support
everything is broken, hotkeys are recognized twice, multi key
recognition is broken and modifier keys get stuck upon pressing them, but
at least it compiles
2020-11-25 23:12:52 +01:00

969 lines
25 KiB
C

/*
* Copyright (c) 2020 Alessandro Mauri
*
* 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.
*/
#define _POSIX_C_SOURCE 200809L
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <locale.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <sys/inotify.h>
#include <wordexp.h>
#include <ctype.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <xkbcommon/xkbcommon.h>
#include <linux/input.h>
/* Value defines */
#define FILE_NAME_MAX_LENGTH 255
#define KEY_BUFFER_SIZE 10
#define BLOCK_SIZE 512
/* ANSI colors escape codes */
#define ANSI_COLOR_RED "\x1b[31m"
#define ANSI_COLOR_GREEN "\x1b[32m"
#define ANSI_COLOR_YELLOW "\x1b[33m"
#define ANSI_COLOR_BLUE "\x1b[34m"
#define ANSI_COLOR_MAGENTA "\x1b[35m"
#define ANSI_COLOR_CYAN "\x1b[36m"
#define ANSI_COLOR_RESET "\x1b[0m"
/* Macro functions */
#define yellow(str) (ANSI_COLOR_YELLOW str ANSI_COLOR_RESET)
#define green(str) (ANSI_COLOR_GREEN str ANSI_COLOR_RESET)
#define red(str) (ANSI_COLOR_RED str ANSI_COLOR_RESET)
#define test_bit(yalv, abs_b) ((((char *)abs_b)[yalv/8] & (1<<yalv%8)) > 0)
#define array_size(val) (val ? sizeof(val)/sizeof(val[0]) : 0)
#define array_size_const(val) ((int)(sizeof(val)/sizeof(val[0])))
#define EVENT_SIZE (sizeof(struct inotify_event))
#define EVENT_BUF_LEN (1024*(EVENT_SIZE+16))
#define EVDEV_ROOT_DIR "/dev/input/"
#define LOCK_FILE "/tmp/hkd.lock"
const char *config_paths[] = {
"$XDG_CONFIG_HOME/hkd/config",
"$HOME/.config/hkd/config",
"/etc/hkd/config",
};
struct key_buffer {
xkb_keysym_t buf[KEY_BUFFER_SIZE];
unsigned int size;
};
/* Hotkey list: linked list that holds all valid hoteys parsed from the
* config file and the corresponding command */
union hotkey_main_data {
struct key_buffer kb;
char * name;
};
struct hotkey_list_e {
union hotkey_main_data data;
char *command;
int fuzzy;
struct hotkey_list_e *next;
};
struct {
struct xkb_context *context;
struct xkb_rule_names names;
struct xkb_keymap *keymap;
struct xkb_state *state;
// struct xkb_compose_state *comp_state;
} keyboard;
struct hotkey_list_e *hotkey_list = NULL;
unsigned long hotkey_size_mask = 0;
char *ext_config_file = NULL;
/* Global flags */
int vflag = 0;
int dead = 0; /* Exit flag */
/* key buffer operations */
int key_buffer_add (struct key_buffer*, xkb_keysym_t);
int key_buffer_remove (struct key_buffer*, xkb_keysym_t);
int key_buffer_compare_fuzzy (struct key_buffer *, struct key_buffer *);
int key_buffer_compare (struct key_buffer *, struct key_buffer *);
void key_buffer_reset (struct key_buffer *);
/* Other operations */
void int_handler (int signum);
void exec_command (char *);
void parse_config_file (void);
void update_descriptors_list (int **, int *);
void remove_lock (void);
void die (const char *, ...);
void usage (void);
int prepare_epoll (int *, int, int);
unsigned short key_to_code (char *);
const char * code_to_name (unsigned int);
/* hotkey list operations */
void hotkey_list_add (struct hotkey_list_e *, union hotkey_main_data *, char *, int);
void hotkey_list_destroy (struct hotkey_list_e *);
void hotkey_list_remove (struct hotkey_list_e *, struct hotkey_list_e *);
int main (int argc, char *argv[])
{
int fd_num = 0;
int *fds = NULL;
int lock_file_descriptor;
int opc;
int ev_fd;
int event_watcher = inotify_init1(IN_NONBLOCK);
int dump = 0;
ssize_t read_b; /* Read buffer */
struct flock fl;
struct sigaction action;
struct input_event event;
struct key_buffer pb = {{0}, 0}; /* Pressed keys buffer */
// struct xkb_compose_table_t *comp_table = NULL;
// const char *locale = NULL;
/* Parse command line arguments */
while ((opc = getopt(argc, argv, "vc:dh")) != -1) {
switch (opc) {
case 'v':
vflag = 1;
break;
case 'c':
ext_config_file = malloc(strlen(optarg) + 1);
if (!ext_config_file)
die("malloc in main():");
strcpy(ext_config_file, optarg);
break;
case 'd':
dump = 1;
break;
case 'h':
usage();
break;
break;
}
}
/* Handle SIGINT */
dead = 0;
memset(&action, 0, sizeof(action));
action.sa_handler = int_handler;
sigaction(SIGINT, &action, NULL);
sigaction(SIGUSR1, &action, NULL);
sigaction(SIGCHLD, &action, NULL);
/* Initialize xkbcommon */
if (!(keyboard.context = xkb_context_new(XKB_CONTEXT_NO_FLAGS)))
die("Error initializing xkbcommon context: ");
keyboard.names = (struct xkb_rule_names){
.rules = NULL,
.model = NULL,
.layout = "gb",
.variant = NULL,
.options = NULL,
};
if (!(keyboard.keymap = xkb_keymap_new_from_names(keyboard.context,
&keyboard.names, XKB_KEYMAP_COMPILE_NO_FLAGS)))
die("Error compiling keymap: ");
if (!(keyboard.state = xkb_state_new(keyboard.keymap)))
die("Error creating keyboard state: ");
/*
locale = setlocale(LC_CTYPE, NULL);
if (!(comp_table = xkb_compose_table_new_from_locale(keyboard.context,
locale, XKB_COMPOSE_COMPILE_NO_FLAGS)))
die("Error compiling compose table: ");
if (!(keyboard.comp_state = xkb_compose_state_new(comp_table,
XKB_COMPOSE_TABLE_NO_FLAGS)))
die("Error creating compose state: ");
*/
/* Parse config file */
parse_config_file();
/* Check if hkd is already running */
lock_file_descriptor = open(LOCK_FILE, O_RDWR | O_CREAT, 0600);
if (lock_file_descriptor < 0)
die("Can't open lock file:");
fl.l_start = 0;
fl.l_len = 0;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if (fcntl(lock_file_descriptor, F_SETLK, &fl) < 0)
die("hkd is already running");
atexit(remove_lock);
/* If a dump is requested print the hotkey list then exit */
if (dump) {
char key_name[64];
printf("DUMPING HOTKEY LIST\n\n");
for (struct hotkey_list_e *tmp = hotkey_list; tmp; tmp = tmp->next) {
printf("Hotkey\n");
printf("\tKeys: ");
for (unsigned int i = 0; i < tmp->data.kb.size; i++) {
xkb_keysym_get_name(tmp->data.kb.buf[i], key_name, 64);
printf("%s ", key_name);
}
printf("\n\tMatching: %s\n", tmp->fuzzy ? "fuzzy" : "ordered");
printf("\tCommand: %s\n\n", tmp->command);
}
exit(EXIT_SUCCESS);
}
/* Load descriptors */
update_descriptors_list(&fds, &fd_num);
/* Prepare directory update watcher */
if (event_watcher < 0)
die("Could not call inotify_init:");
if (inotify_add_watch(event_watcher, EVDEV_ROOT_DIR, IN_CREATE | IN_DELETE) < 0)
die("Could not add /dev/input to the watch list:");
/* Prepare epoll list */
ev_fd = prepare_epoll(fds, fd_num, event_watcher);
/* MAIN EVENT LOOP */
mainloop_begin:
for (;;) {
int t = 0;
static unsigned int prev_size;
static struct epoll_event ev_type;
static xkb_keycode_t keycode;
static xkb_keysym_t keysym;
// enum xkb_state_component changed;
struct hotkey_list_e *tmp;
char buf[EVENT_BUF_LEN];
static char key_name[64];
// enum xkb_compose_status status;
/* On linux use epoll(2) as it gives better performance */
if (epoll_wait(ev_fd, &ev_type, fd_num, -1) < 0 || dead) {
if (errno != EINTR)
break;
}
if (ev_type.events != EPOLLIN)
continue;
if (read(event_watcher, buf, EVENT_BUF_LEN) >= 0) {
sleep(1); // wait for devices to settle
update_descriptors_list(&fds, &fd_num);
if (close(ev_fd) < 0)
die("Could not close event filedescriptors list (ev_fd):");
ev_fd = prepare_epoll(fds, fd_num, event_watcher);
goto mainloop_begin;
}
prev_size = pb.size;
for (int i = 0; i < fd_num; i++) {
read_b = read(fds[i], &event, sizeof(struct input_event));
if (read_b != sizeof(struct input_event)) continue;
if (event.type != EV_KEY)
continue;
// evdev offset
keycode = event.code + 8;
if (event.value == 2 && !xkb_keymap_key_repeats(
keyboard.keymap, keycode))
continue;
if (event.value) {
keysym = xkb_state_key_get_one_sym(keyboard.state, keycode);
// kbd_compose_state_feed(keyboard.comp_state, keysym);
}
/* status = xkb_compose_state_get_status(keyboard.comp_state);
if (status == XKB_COMPOSE_CANCELLED || status == XKB_COMPOSE_COMPOSED)
xkb_compose_state_reset(kbd->compose_state);
*/
if (event.value) { /* Key pressed */
xkb_state_update_key(keyboard.state,
keycode, XKB_KEY_UP);
key_buffer_add(&pb, keysym);
} else {/* Key released */
xkb_state_update_key(keyboard.state,
keycode, XKB_KEY_DOWN);
key_buffer_remove(&pb, keysym);
}
}
if (pb.size <= prev_size)
continue;
if (vflag) {
printf("Pressed keys: ");
for (unsigned int i = 0; i < pb.size; i++)
xkb_keysym_get_name(pb.buf[i], key_name, 64);
printf("%s ", key_name);
putchar('\n');
}
if (hotkey_size_mask & 1 << (pb.size - 1)) {
for (tmp = hotkey_list; tmp != NULL; tmp = tmp->next) {
if (tmp->fuzzy)
t = key_buffer_compare_fuzzy(&pb, &tmp->data.kb);
else
t = key_buffer_compare(&pb, &tmp->data.kb);
if (t)
exec_command(tmp->command);
}
}
}
// TODO: better child handling, for now all children receive the same
// interrupts as the father so everything should work fine
wait(NULL);
if (!dead)
fprintf(stderr, red("An error occured: %s\n"), errno ? strerror(errno): "idk");
xkb_state_unref(keyboard.state);
xkb_keymap_unref(keyboard.keymap);
// xkb_compose_state_unref(keyboard.comp_state);
xkb_context_unref(keyboard.context);
close(ev_fd);
close(event_watcher);
for (int i = 0; i < fd_num; i++)
if (close(fds[i]) == -1)
die("Error closing file descriptors:");
return 0;
}
/* Adds a keycode to the pressed buffer if it is not already present
* Returns non zero if the key was not added. */
int key_buffer_add (struct key_buffer *pb, xkb_keysym_t key)
{
if (!pb) return 1;
/* Linear search if the key is already buffered */
for (unsigned int i = 0; i < pb->size; i++)
if (key == pb->buf[i]) return 1;
if (pb->size >= KEY_BUFFER_SIZE)
return 1;
pb->buf[pb->size++] = key;
return 0;
}
/* Removes a keycode from a pressed buffer if it is present returns
* non zero in case of failure (key not present or buffer empty). */
int key_buffer_remove (struct key_buffer *pb, xkb_keysym_t key)
{
if (!pb) return 1;
for (unsigned int i = 0; i < pb->size; i++) {
if (pb->buf[i] == key) {
pb->size--;
pb->buf[i] = pb->buf[pb->size];
return 0;
}
}
return 1;
}
void key_buffer_reset (struct key_buffer *kb)
{
kb->size = 0;
memset(kb->buf, 0, KEY_BUFFER_SIZE * sizeof(unsigned short));
}
void int_handler (int signum)
{
switch (signum) {
case SIGINT:
if (dead)
die("An error occured, exiting");
if (vflag)
printf(yellow("Received interrupt signal, exiting gracefully...\n"));
dead = 1;
break;
case SIGUSR1:
parse_config_file();
break;
case SIGCHLD:
wait(NULL);
break;
}
}
/* Executes a command from a string */
void exec_command (char *command)
{
static wordexp_t result;
/* Expand the string for the program to run */
switch (wordexp (command, &result, 0)) {
case 0:
break;
case WRDE_NOSPACE:
/* If the error was WRDE_NOSPACE,
* then perhaps part of the result was allocated */
wordfree (&result);
return;
default:
/* Some other error */
fprintf(stderr, "Could not parse, %s is not valid\n", command);
return;
}
pid_t cpid;
switch (cpid = fork()) {
case -1:
fprintf(stderr, "Could not create child process: %s", strerror(errno));
wordfree(&result);
break;
case 0:
/* This is the child process, execute the command */
execvp(result.we_wordv[0], result.we_wordv);
die("%s:", command);
break;
default:
while (waitpid(cpid, NULL, WNOHANG) == -1) {}
wordfree(&result);
break;
}
}
void update_descriptors_list (int **fds, int *fd_num)
{
struct dirent *file_ent;
char ev_path[sizeof(EVDEV_ROOT_DIR) + FILE_NAME_MAX_LENGTH + 1];
void *tmp_p;
int tmp_fd;
unsigned char evtype_b[EV_MAX];
/* Open the event directory */
DIR *ev_dir = opendir(EVDEV_ROOT_DIR);
if (!ev_dir)
die("Could not open /dev/input:");
(*fd_num) = 0;
for (;;) {
if ((file_ent = readdir(ev_dir)) == NULL)
break;
/* Filter out non character devices */
if (file_ent->d_type != DT_CHR)
continue;
/* Compose absolute path from relative */
strncpy(ev_path, EVDEV_ROOT_DIR, sizeof(EVDEV_ROOT_DIR) + FILE_NAME_MAX_LENGTH);
strncat(ev_path, file_ent->d_name, sizeof(EVDEV_ROOT_DIR) + FILE_NAME_MAX_LENGTH);
/* Open device and check if it can give key events otherwise ignore it */
tmp_fd = open(ev_path, O_RDONLY | O_NONBLOCK);
if (tmp_fd < 0) {
if (vflag)
printf(red("Could not open device %s\n"), ev_path);
continue;
}
memset(evtype_b, 0, sizeof(evtype_b));
if (ioctl(tmp_fd, EVIOCGBIT(0, EV_MAX), evtype_b) < 0) {
if (vflag)
printf(red("Could not read capabilities of device %s\n"),ev_path);
close(tmp_fd);
continue;
}
if (!test_bit(EV_KEY, evtype_b)) {
if (vflag)
printf(yellow("Ignoring device %s\n"), ev_path);
close(tmp_fd);
continue;
}
tmp_p = realloc((*fds), sizeof(int) * ((*fd_num) + 1));
if (!tmp_p)
die("realloc file descriptors:");
(*fds) = (int *) tmp_p;
(*fds)[(*fd_num)] = tmp_fd;
(*fd_num)++;
}
closedir(ev_dir);
if (*fd_num) {
if (vflag)
printf(green("Monitoring %d devices\n"), *fd_num);
} else {
die("Could not open any devices, exiting");
}
}
int prepare_epoll (int *fds, int fd_num, int event_watcher)
{
int ev_fd = epoll_create(1);
static struct epoll_event epoll_read_ev;
epoll_read_ev.events = EPOLLIN;
if (ev_fd < 0)
die("epoll_create failed in prepare_epoll:");
if (epoll_ctl(ev_fd, EPOLL_CTL_ADD, event_watcher, &epoll_read_ev) < 0)
die("Could not add file descriptor to the epoll list:");
for (int i = 0; i < fd_num; i++)
if (epoll_ctl(ev_fd, EPOLL_CTL_ADD, fds[i], &epoll_read_ev) < 0)
die("Could not add file descriptor to the epoll list:");
return ev_fd;
}
/* Checks if two key buffers contain the same keys in no specified order */
int key_buffer_compare_fuzzy (struct key_buffer *haystack, struct key_buffer *needle)
{
int ff = 0;
if (haystack->size != needle->size)
return 0;
for (int x = needle->size - 1; x >= 0; x--) {
for (unsigned int i = 0; i < haystack->size; i++)
ff += (needle->buf[x] == haystack->buf[i]);
if (!ff)
return 0;
ff = 0;
}
return 1;
}
/* Checks if two key buffers are the same (same order) */
int key_buffer_compare (struct key_buffer *haystack, struct key_buffer *needle)
{
if (haystack->size != needle->size)
return 0;
for (unsigned int i = 0; i < needle->size; i++) {
if (needle->buf[i] != haystack->buf[i])
return 0;
}
return 1;
}
void hotkey_list_destroy (struct hotkey_list_e *head)
{
struct hotkey_list_e *tmp;
for (; head; free(tmp)) {
if (head->command)
free(head->command);
tmp = head;
head = head->next;
}
}
// FIXME: use **head or hardcode to hotkey_list
void hotkey_list_add (struct hotkey_list_e *head, union hotkey_main_data *dt, char *cmd, int f)
{
int size;
struct hotkey_list_e *tmp;
if (!(size = strlen(cmd)))
return;
if (!(tmp = malloc(sizeof(struct hotkey_list_e))))
die("Memory allocation failed in hotkey_list_add():");
if (!(tmp->command = malloc(size + 1)))
die("Memory allocation failed in hotkey_list_add():");
strcpy(tmp->command, cmd);
tmp->data = *dt;
tmp->fuzzy = f;
tmp->next = NULL;
if (head) {
for (; head->next; head = head->next);
head->next = tmp;
} else
hotkey_list = tmp;
}
void hotkey_list_remove (struct hotkey_list_e *head, struct hotkey_list_e *elem)
{
if(!head)
return;
if (head == elem) {
hotkey_list = head->next;
} else {
for (; head && head->next != elem; head = head->next);
if (head && head->next)
head->next = head->next->next;
}
if (elem) {
if (elem->fuzzy == -1)
free(elem->data.name);
free(elem->command);
free(elem);
}
}
void parse_config_file (void)
{
wordexp_t result = {0};
FILE *fd;
/* normal, skip line, get matching, get keys, get command, output */
enum {NORM, LINE_SKIP, GET_TYPE, GET_KEYS, GET_CMD, LAST} parse_state = NORM;
enum {CONT, NEW_BL, LAST_BL, END} block_state = CONT; /* continue, new block, last block, end */
enum {HK_NORM = 0, HK_FUZZY = 1, ALIAS = -1} type;
int cmd_is_alias = 0;
int alloc_tmp = 0, alloc_size = 0;
int i_tmp = 0, linenum = 1;
char block[BLOCK_SIZE + 1] = {0};
char *bb = NULL;
char *keys = NULL;
char *cmd = NULL;
char *cp_tmp = NULL;
union hotkey_main_data dt = {0};
xkb_keysym_t ks_tmp = 0;
if (ext_config_file) {
switch (wordexp(ext_config_file, &result, 0)) {
case 0:
break;
case WRDE_NOSPACE:
/* If the error was WRDE_NOSPACE,
* then perhaps part of the result was allocated */
wordfree (&result);
die("Not enough space:");
default:
die("Path not valid:");
}
fd = fopen(result.we_wordv[0], "r");
wordfree(&result);
if (!fd)
die("Error opening config file:");
free(ext_config_file);
ext_config_file = NULL;
} else {
for (int i = 0; i < array_size_const(config_paths); i++) {
switch (wordexp(config_paths[i], &result, 0)) {
case 0:
break;
case WRDE_NOSPACE:
/* If the error was WRDE_NOSPACE,
* then perhaps part of the result was allocated */
wordfree (&result);
die("Not enough space:");
default:
die("Path not valid:");
}
fd = fopen(result.we_wordv[0], "r");
wordfree(&result);
if (fd)
break;
if (vflag)
printf(yellow("config file not found at %s\n"), config_paths[i]);
}
if (!fd)
die("Could not open any config files, check the log for more details");
}
hotkey_list_destroy(hotkey_list);
hotkey_list = NULL;
while (block_state != END) {
int tmp = 0;
memset(block, 0, BLOCK_SIZE + 1);
tmp = fread(block, sizeof(char), BLOCK_SIZE, fd);
if (!tmp)
break;
if (tmp < BLOCK_SIZE || feof(fd))
block_state = LAST_BL;
else
block_state = CONT;
bb = block;
while (block_state == CONT || block_state == LAST_BL) {
switch (parse_state) {
// First state
case NORM:
// remove whitespaces
while (isblank(*bb) && *bb)
bb++;
// get state
switch (*bb) {
#if defined(__X86_64__) || defined(__i386__)
case EOF:
#endif
case '\0':
// If it is the end of the last block exit
block_state = block_state == LAST_BL ? END : NEW_BL;
break;
case '\n':
case '#':
parse_state = LINE_SKIP;
break;
default:
parse_state = GET_TYPE;
break;
}
break;
// Skip line (comment)
case LINE_SKIP:
while (*bb != '\n' && *bb)
bb++;
if (*bb) {
bb++;
linenum++;
parse_state = NORM;
} else {
block_state = NEW_BL;
}
break;
// Get compairson method
case GET_TYPE:
switch (*bb) {
case '-':
type = HK_NORM;
break;
case '*':
type = HK_FUZZY;
break;
case '@':
type = ALIAS;
break;
default:
die("Error at line %d: "
"hotkey definition must start with '-', '*' or '@'",
linenum);
break;
}
bb++;
parse_state = GET_KEYS;
break;
// Get keys
case GET_KEYS:
if (!keys) {
if (!(keys = malloc(alloc_size = (sizeof(char) * 64))))
die("malloc for keys in parse_config_file():");
memset(keys, 0, alloc_size);
} else if (alloc_tmp >= alloc_size) {
if (!(keys = realloc(keys, alloc_size = alloc_size * 2)))
die("realloc for keys in parse_config_file():");
memset(&keys[alloc_size / 2], 0, alloc_size / 2);
}
for (alloc_tmp = 0; bb[alloc_tmp] &&
bb[alloc_tmp] != ':' &&
bb[alloc_tmp] != '<' &&
bb[alloc_tmp] != '\n' &&
alloc_tmp < alloc_size; alloc_tmp++);
if (!bb[alloc_tmp] || alloc_tmp == alloc_size) {
strncat(keys, bb, alloc_tmp);
bb += alloc_tmp;
if (block_state == LAST_BL)
die("Keys not finished before end of file");
else
block_state = NEW_BL;
break;
} else if (bb[alloc_tmp] == ':' || bb[alloc_tmp] == '<') {
cmd_is_alias = (bb[alloc_tmp] == '<');
strncat(keys, bb, alloc_tmp);
bb += alloc_tmp + 1;
parse_state = GET_CMD;
break;
} else {
die("Error at line %d: "
"no command specified, missing ':' or '<' after keys",
linenum);
}
break;
// Get command
case GET_CMD:
if (!cmd) {
if (!(cmd = malloc(alloc_size = (sizeof(char) * 128))))
die("malloc for cmd in parse_config_file():");
memset(cmd, 0, alloc_size);
} else if (alloc_tmp >= alloc_size) {
if (!(cmd = realloc(cmd, alloc_size = alloc_size * 2)))
die("realloc for cmd in parse_config_file():");
memset(&cmd[alloc_size / 2], 0, alloc_size / 2);
}
for (alloc_tmp = 0; bb[alloc_tmp] && bb[alloc_tmp] != '\n' &&
alloc_tmp < alloc_size; alloc_tmp++);
if (!bb[alloc_tmp] || alloc_tmp == alloc_size) {
strncat(cmd, bb, alloc_tmp);
bb += alloc_tmp;
if (block_state == LAST_BL)
die("Command not finished before end of file");
else
block_state = NEW_BL;
break;
} else {
strncat(cmd, bb, alloc_tmp);
if (!(bb[alloc_tmp - 1] == '\\'))
parse_state = LAST;
bb += alloc_tmp + 1;
linenum++;
break;
}
break;
case LAST:
if (!keys)
die("error");
i_tmp = strlen(keys);
for (int i = 0; i < i_tmp; i++) {
if (isblank(keys[i])) {
memmove(&keys[i], &keys[i + 1], --i_tmp);
keys[i_tmp] = '\0';
}
}
cp_tmp = strtok(keys, ",");
if(!cp_tmp)
die("Error at line %d: "
"keys not present", linenum - 1);
if (type != ALIAS) {
do {
if ((ks_tmp = xkb_keysym_from_name(cp_tmp, XKB_KEYSYM_NO_FLAGS)) == XKB_KEY_NoSymbol) {
die("Error at line %d: "
"%s is not a valid key",
linenum - 1, cp_tmp);
}
if (key_buffer_add(&dt.kb, ks_tmp))
die("Too many keys");
} while ((cp_tmp = strtok(NULL, ",")));
} else {
if (!(dt.name = malloc(strlen(cp_tmp) + 1)))
die("malloc in parse_config_file():");
strcpy(dt.name, cp_tmp);
}
cp_tmp = cmd;
while (isblank(*cp_tmp))
cp_tmp++;
if (*cp_tmp == '\0')
die("Error at line %d: "
"command not present", linenum - 1);
if (cmd_is_alias) {
struct hotkey_list_e *hkl = hotkey_list;
// stolen way of removing leading spaces
char * end = cp_tmp + strlen(cp_tmp) - 1;
while(end > cp_tmp && isspace((unsigned char)*end)) end--;
end[1] = '\0';
while (hkl && !(hkl->fuzzy == ALIAS && strstr(hkl->data.name, cp_tmp)))
hkl = hkl->next;
if (hkl) {
cp_tmp = hkl->command;
} else {
die("Error at line %d: "
"alias %s not found", linenum - 1,
cp_tmp);
}
}
hotkey_list_add(hotkey_list, &dt, cp_tmp, type);
if (type != ALIAS)
key_buffer_reset(&dt.kb);
free(keys);
free(cmd);
cp_tmp = keys = cmd = NULL;
i_tmp = 0;
parse_state = NORM;
break;
default:
die("Unknown state in parse_config_file");
break;
}
}
}
for (struct hotkey_list_e *hkl = hotkey_list, *tmp; hkl; hkl = hkl->next) {
if (hkl->fuzzy == ALIAS) {
tmp = hkl;
hkl = hkl->next;
hotkey_list_remove(hotkey_list, tmp);
} else {
hotkey_size_mask |= 1 << (hkl->data.kb.size - 1);
}
}
}
/*
unsigned short key_to_code (char *key)
{
for (char *tmp = key; *tmp; tmp++) {
if (islower(*tmp))
*tmp += 'A' - 'a';
}
for (int i = 0; i < array_size_const(key_conversion_table); i++) {
if (!strcmp(key_conversion_table[i].name, key))
return key_conversion_table[i].value;
}
return 0;
}
*/
void remove_lock (void)
{
unlink(LOCK_FILE);
}
void die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fputs(ANSI_COLOR_RED, stderr);
vfprintf(stderr, fmt, ap);
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
fputs(ANSI_COLOR_RESET, stderr);
va_end(ap);
exit(errno ? errno : 1);
}
/*
const char * code_to_name (unsigned int code)
{
for (int i = 0; i < array_size_const(key_conversion_table); i++) {
if (key_conversion_table[i].value == code)
return key_conversion_table[i].name;
}
return "Key not recognized";
}
*/
void usage (void)
{
puts("Usage: hkd [-vdh] [-c file]\n"
"\t-v verbose, prints all the key presses and debug information\n"
"\t-d dump, dumps the hotkey list and exits\n"
"\t-h prints this help message\n"
"\t-c file uses the specified file as config\n");
exit(EXIT_SUCCESS);
}