diff --git a/src/conf.c b/src/conf.c index 87cac59..44e70d4 100644 --- a/src/conf.c +++ b/src/conf.c @@ -45,8 +45,10 @@ void conf_free_value(struct conf_option *opt) { - if (*(void**)opt->mem != opt->def) + if (*(void**)opt->mem && *(void**)opt->mem != opt->def) { free(*(void**)opt->mem); + *(void**)opt->mem = NULL; + } } int conf_parse_bool(struct conf_option *opt, bool on, const char *arg) @@ -100,44 +102,12 @@ void conf_default_string(struct conf_option *opt) int conf_parse_string_list(struct conf_option *opt, bool on, const char *arg) { - unsigned int i; - unsigned int num, len, size, pos; - char **list, *off; + int ret; + char **list; - num = 0; - size = 0; - len = 0; - for (i = 0; arg[i]; ++i) { - if (arg[i] != ',') { - ++len; - continue; - } - - ++num; - size += len + 1; - len = 0; - } - - if (len > 0 || !i || (i > 0 && arg[i - 1] == ',')) { - ++num; - size += len + 1; - } - - list = malloc(sizeof(char*) * (num + 1) + size); - if (!list) - return -ENOMEM; - - off = (void*)(((char*)list) + (sizeof(char*) * (num + 1))); - i = 0; - for (pos = 0; pos < num; ++pos) { - list[pos] = off; - while (arg[i] && arg[i] != ',') - *off++ = arg[i++]; - if (arg[i]) - ++i; - *off++ = 0; - } - list[pos] = NULL; + ret = shl_split_string(arg, &list, NULL, ',', true); + if (ret) + return ret; opt->type->free(opt); *(void**)opt->mem = list; @@ -149,77 +119,193 @@ void conf_default_string_list(struct conf_option *opt) *(void**)opt->mem = opt->def; } -int conf_parse_grab(struct conf_option *opt, bool on, const char *arg) +static int parse_single_grab(char *arg, unsigned int *mods, + uint32_t *keysym, bool allow_mods) { - char *buf, *tmp, *start; - struct conf_grab grab, *gnew; + char *tmp, *start, *end; - memset(&grab, 0, sizeof(grab)); + tmp = arg; + do { + while (*tmp == ' ') + ++tmp; + if (!allow_mods) + break; + if (*tmp != '<') + break; - buf = strdup(arg); - if (!buf) - return -ENOMEM; - tmp = buf; - -next_mod: - if (*tmp == '<') { start = tmp; while (*tmp && *tmp != '>') ++tmp; if (*tmp != '>') { - log_error("missing '>' in grab '%s' near '%s'", - arg, start); - goto err_free; + log_error("missing '>' near '%s'", start); + return -EFAULT; } *tmp++ = 0; ++start; if (!strcasecmp(start, "shift")) { - grab.mods |= SHL_SHIFT_MASK; + *mods |= SHL_SHIFT_MASK; } else if (!strcasecmp(start, "lock")) { - grab.mods |= SHL_LOCK_MASK; + *mods |= SHL_LOCK_MASK; } else if (!strcasecmp(start, "control") || !strcasecmp(start, "ctrl")) { - grab.mods |= SHL_CONTROL_MASK; + *mods |= SHL_CONTROL_MASK; } else if (!strcasecmp(start, "alt")) { - grab.mods |= SHL_ALT_MASK; + *mods |= SHL_ALT_MASK; } else if (!strcasecmp(start, "logo")) { - grab.mods |= SHL_LOGO_MASK; + *mods |= SHL_LOGO_MASK; } else { - log_error("invalid modifier '%s' in grab '%s'", - start, arg); - goto err_free; + log_error("invalid modifier '%s'", start); + return -EFAULT; + } + } while (1); + + while (*tmp == ' ') + ++tmp; + + start = tmp; + end = start; + do { + while (*tmp && *tmp != ' ') + ++tmp; + end = tmp; + if (!*tmp) + break; + while (*tmp == ' ') + ++tmp; + } while (1); + + if (start == end) + return 0; + if (*end) + *end = 0; + + *keysym = xkb_keysym_from_name(start); + if (!*keysym) { + log_error("invalid key '%s'", start); + return -EFAULT; + } + + return 1; +} + +int conf_parse_grab(struct conf_option *opt, bool on, const char *arg) +{ + char **list, **keys; + unsigned int list_num, key_num, i, j, k, l; + int ret; + struct conf_grab *grab; + + ret = shl_split_string(arg, &list, &list_num, ',', false); + if (ret) + return ret; + + grab = malloc(sizeof(*grab)); + if (!grab) { + ret = -ENOMEM; + goto err_list; + } + memset(grab, 0, sizeof(*grab)); + + if (list_num) { + grab->mods = malloc(sizeof(*grab->mods) * list_num); + if (!grab->mods) { + ret = -ENOMEM; + goto err_grab; + } + memset(grab->mods, 0, sizeof(*grab->mods) * list_num); + + grab->num_syms = malloc(sizeof(*grab->num_syms) * list_num); + if (!grab->num_syms) { + ret = -ENOMEM; + goto err_grab; + } + memset(grab->num_syms, 0, sizeof(*grab->num_syms) * list_num); + + grab->keysyms = malloc(sizeof(*grab->keysyms) * list_num); + if (!grab->keysyms) { + ret = -ENOMEM; + goto err_grab; + } + memset(grab->keysyms, 0, sizeof(*grab->keysyms) * list_num); + } + + l = 0; + for (i = 0; i < list_num; ++i) { + ret = shl_split_string(list[i], &keys, &key_num, '+', false); + if (ret) + goto err_all; + if (!key_num) { + free(keys); + continue; } - goto next_mod; + grab->keysyms[l] = malloc(sizeof(*grab->keysyms[l] * key_num)); + if (!grab->keysyms[l]) { + ret = -ENOMEM; + free(keys); + goto err_all; + } + + k = 0; + for (j = 0; j < key_num; ++j) { + ret = parse_single_grab(keys[j], &grab->mods[l], + &grab->keysyms[l][k], + j == 0); + if (ret < 0) { + log_error("cannot parse grab '%s' in '%s'", + list[i], arg); + free(keys); + goto err_all; + } + k += ret; + } + + free(keys); + if (!k) + continue; + grab->num_syms[l] = k; + ++l; + ++grab->num; } - if (!*tmp) { - log_error("missing key in grab '%s'", arg); - goto err_free; - } - - grab.keysym = xkb_keysym_from_name(tmp); - if (!grab.keysym) { - log_error("invalid key '%s' in grab '%s'", tmp, arg); - goto err_free; - } - - gnew = malloc(sizeof(*gnew)); - if (!gnew) - goto err_free; - memcpy(gnew, &grab, sizeof(*gnew)); - + free(list); opt->type->free(opt); - *(void**)opt->mem = gnew; - free(buf); - + *(void**)opt->mem = grab; return 0; -err_free: - free(buf); - return -EFAULT; +err_all: + for (i = 0; i < list_num; ++i) + free(grab->keysyms[i]); +err_grab: + free(grab->keysyms); + free(grab->num_syms); + free(grab->mods); + free(grab); +err_list: + free(list); + return ret; +} + +void conf_free_grab(struct conf_option *opt) +{ + struct conf_grab *grab; + unsigned int i; + + if (!*(void**)opt->mem || *(void**)opt->mem == opt->def) + return; + + grab = *(void**)opt->mem; + *(void**)opt->mem = NULL; + + for (i = 0; i < grab->num; ++i) + free(grab->keysyms[i]); + + free(grab->keysyms); + free(grab->num_syms); + free(grab->mods); + free(grab); } void conf_default_grab(struct conf_option *opt) @@ -265,7 +351,7 @@ const struct conf_type conf_string_list = { const struct conf_type conf_grab = { .flags = CONF_HAS_ARG, .parse = conf_parse_grab, - .free = conf_free_value, + .free = conf_free_grab, .set_default = conf_default_grab, }; diff --git a/src/conf.h b/src/conf.h index 3499ddf..3a65af6 100644 --- a/src/conf.h +++ b/src/conf.h @@ -35,14 +35,34 @@ #include #include +#include "shl_misc.h" /* parsed types */ struct conf_grab { - unsigned int mods; - uint32_t keysym; + unsigned int num; + unsigned int *mods; + unsigned int *num_syms; + uint32_t **keysyms; }; +static inline bool conf_grab_matches(const struct conf_grab *grab, + unsigned int ev_mods, + unsigned int ev_num_syms, + const uint32_t *ev_syms) +{ + return shl_grab_has_match(ev_mods, ev_num_syms, ev_syms, + grab->num, grab->mods, grab->num_syms, + grab->keysyms); +} + +#define CONF_SINGLE_GRAB(_mods, _sym) { \ + .num = 1, \ + .mods = (unsigned int[]) { (_mods) }, \ + .num_syms = (unsigned int[]) { 1 }, \ + .keysyms = (uint32_t*[]) { (uint32_t[]) { (_sym) } }, \ + } + /* configuration parser */ struct conf_type; diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c index eda166b..5bd7e24 100644 --- a/src/kmscon_conf.c +++ b/src/kmscon_conf.c @@ -233,25 +233,17 @@ static int aftercheck_seats(struct conf_option *opt, int argc, char **argv, static char *def_seats[] = { "seat0", NULL }; -static struct conf_grab def_grab_scroll_up = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Up, -}; +static struct conf_grab def_grab_scroll_up = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Up); -static struct conf_grab def_grab_scroll_down = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Down, -}; +static struct conf_grab def_grab_scroll_down = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Down); -static struct conf_grab def_grab_page_up = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Prior, -}; +static struct conf_grab def_grab_page_up = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Prior); -static struct conf_grab def_grab_page_down = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Next, -}; +static struct conf_grab def_grab_page_down = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Next); struct conf_option options[] = { CONF_OPTION_BOOL('h', "help", aftercheck_help, &kmscon_conf.help, false), diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 3fc0259..a4a7a7d 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -322,29 +322,29 @@ static void input_event(struct uterm_input *input, if (!term->opened || !term->awake || ev->handled) return; - if (UTERM_INPUT_HAS_MODS(ev, kmscon_conf.grab_scroll_up->mods) && - ev->keysyms[0] == kmscon_conf.grab_scroll_up->keysym) { + if (conf_grab_matches(kmscon_conf.grab_scroll_up, + ev->mods, ev->num_syms, ev->keysyms)) { tsm_screen_sb_up(term->console, 1); schedule_redraw(term); ev->handled = true; return; } - if (UTERM_INPUT_HAS_MODS(ev, kmscon_conf.grab_scroll_down->mods) && - ev->keysyms[0] == kmscon_conf.grab_scroll_down->keysym) { + if (conf_grab_matches(kmscon_conf.grab_scroll_down, + ev->mods, ev->num_syms, ev->keysyms)) { tsm_screen_sb_down(term->console, 1); schedule_redraw(term); ev->handled = true; return; } - if (UTERM_INPUT_HAS_MODS(ev, kmscon_conf.grab_page_up->mods) && - ev->keysyms[0] == kmscon_conf.grab_page_up->keysym) { + if (conf_grab_matches(kmscon_conf.grab_page_up, + ev->mods, ev->num_syms, ev->keysyms)) { tsm_screen_sb_page_up(term->console, 1); schedule_redraw(term); ev->handled = true; return; } - if (UTERM_INPUT_HAS_MODS(ev, kmscon_conf.grab_page_down->mods) && - ev->keysyms[0] == kmscon_conf.grab_page_down->keysym) { + if (conf_grab_matches(kmscon_conf.grab_page_down, + ev->mods, ev->num_syms, ev->keysyms)) { tsm_screen_sb_page_down(term->console, 1); schedule_redraw(term); ev->handled = true; diff --git a/src/shl_misc.h b/src/shl_misc.h index faaf74f..803e083 100644 --- a/src/shl_misc.h +++ b/src/shl_misc.h @@ -77,6 +77,69 @@ static inline int shl_dup(void **out, const void *data, size_t size) return 0; } +/* This parses \arg and splits the string into a new allocated array. The array + * is stored in \out and is NULL terminated. Empty entries are removed from the + * array if \keep_empty is false. \out_num is the number of entries in the + * array. You can set it to NULL to not retrieve this value. + * \sep is the separator character which must be a valid ASCII character, + * otherwise this will not be UTF8 safe. */ +static inline int shl_split_string(const char *arg, char ***out, + unsigned int *out_num, char sep, + bool keep_empty) +{ + unsigned int i; + unsigned int num, len, size, pos; + char **list, *off; + + if (!arg || !out || !sep) + return -EINVAL; + + num = 0; + size = 0; + len = 0; + for (i = 0; arg[i]; ++i) { + if (arg[i] != sep) { + ++len; + continue; + } + + if (keep_empty || len) { + ++num; + size += len + 1; + len = 0; + } + } + + if (len > 0 || (keep_empty && (!i || arg[i - 1] == sep))) { + ++num; + size += len + 1; + } + + list = malloc(sizeof(char*) * (num + 1) + size); + if (!list) + return -ENOMEM; + + off = (void*)(((char*)list) + (sizeof(char*) * (num + 1))); + i = 0; + for (pos = 0; pos < num; ) { + list[pos] = off; + while (arg[i] && arg[i] != sep) + *off++ = arg[i++]; + if (arg[i]) + ++i; + if (list[pos] == off && !keep_empty) + continue; + *off++ = 0; + pos++; + } + list[pos] = NULL; + + *out = list; + if (out_num) + *out_num = num; + return 0; +} + /* TODO: xkbcommon should provide these flags! * We currently copy them into each library API we use so we need to keep * them in sync. Currently, they're used in uterm-input and tsm-vte. */ @@ -142,4 +205,44 @@ static inline uint32_t shl_get_ascii(struct xkb_state *state, uint32_t keycode, return XKB_KEY_NoSymbol; } +static inline bool shl_grab_matches(unsigned int ev_mods, + unsigned int ev_num_syms, + const uint32_t *ev_syms, + unsigned int grab_mods, + unsigned int grab_num_syms, + const uint32_t *grab_syms) +{ + if (!SHL_HAS_BITS(ev_mods, grab_mods)) + return false; + + if (grab_num_syms != 0) { + if (ev_num_syms != grab_num_syms) + return false; + if (memcmp(ev_syms, grab_syms, sizeof(uint32_t) * ev_num_syms)) + return false; + } + + return true; +} + +static inline bool shl_grab_has_match(unsigned int ev_mods, + unsigned int ev_num_syms, + const uint32_t *ev_syms, + unsigned int grab_num, + const unsigned int *grab_mods, + const unsigned int *grab_num_syms, + uint32_t **grab_syms) +{ + unsigned int i; + + for (i = 0; i < grab_num; ++i) { + if (shl_grab_matches(ev_mods, ev_num_syms, ev_syms, + grab_mods[i], grab_num_syms[i], + grab_syms[i])) + return true; + } + + return false; +} + #endif /* SHL_MISC_H */ diff --git a/src/wlt_main.c b/src/wlt_main.c index 253c409..094383b 100644 --- a/src/wlt_main.c +++ b/src/wlt_main.c @@ -318,50 +318,32 @@ static int aftercheck_login(struct conf_option *opt, int argc, char **argv, return ret; } -static struct conf_grab def_grab_scroll_up = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Up, -}; +static struct conf_grab def_grab_scroll_up = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Up); -static struct conf_grab def_grab_scroll_down = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Down, -}; +static struct conf_grab def_grab_scroll_down = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Down); -static struct conf_grab def_grab_page_up = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Prior, -}; +static struct conf_grab def_grab_page_up = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Prior); -static struct conf_grab def_grab_page_down = { - .mods = SHL_SHIFT_MASK, - .keysym = XKB_KEY_Next, -}; +static struct conf_grab def_grab_page_down = + CONF_SINGLE_GRAB(SHL_SHIFT_MASK, XKB_KEY_Next); -static struct conf_grab def_grab_fullscreen = { - .mods = 0, - .keysym = XKB_KEY_F11, -}; +static struct conf_grab def_grab_fullscreen = + CONF_SINGLE_GRAB(0, XKB_KEY_F11); -static struct conf_grab def_grab_zoom_in = { - .mods = SHL_CONTROL_MASK, - .keysym = XKB_KEY_plus, -}; +static struct conf_grab def_grab_zoom_in = + CONF_SINGLE_GRAB(SHL_CONTROL_MASK, XKB_KEY_plus); -static struct conf_grab def_grab_zoom_out = { - .mods = SHL_CONTROL_MASK, - .keysym = XKB_KEY_minus, -}; +static struct conf_grab def_grab_zoom_out = + CONF_SINGLE_GRAB(SHL_CONTROL_MASK, XKB_KEY_minus); -static struct conf_grab def_grab_copy = { - .mods = SHL_LOGO_MASK, - .keysym = XKB_KEY_c, -}; +static struct conf_grab def_grab_copy = + CONF_SINGLE_GRAB(SHL_LOGO_MASK, XKB_KEY_c); -static struct conf_grab def_grab_paste = { - .mods = SHL_LOGO_MASK, - .keysym = XKB_KEY_v, -}; +static struct conf_grab def_grab_paste = + CONF_SINGLE_GRAB(SHL_LOGO_MASK, XKB_KEY_v); struct conf_option options[] = { CONF_OPTION_BOOL('h', "help", aftercheck_help, &wlt_conf.help, false), diff --git a/src/wlt_terminal.c b/src/wlt_terminal.c index 96b5bf0..0353a89 100644 --- a/src/wlt_terminal.c +++ b/src/wlt_terminal.c @@ -411,33 +411,33 @@ static bool widget_key(struct wlt_widget *widget, unsigned int mask, ucs4 = xkb_keysym_to_utf32(sym) ? : TSM_VTE_INVALID; - if (SHL_HAS_BITS(mask, wlt_conf.grab_scroll_up->mods) && - sym == wlt_conf.grab_scroll_up->keysym) { + if (conf_grab_matches(wlt_conf.grab_scroll_up, + mask, 1, &sym)) { tsm_screen_sb_up(term->scr, 1); wlt_window_schedule_redraw(term->wnd); return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_scroll_down->mods) && - sym == wlt_conf.grab_scroll_down->keysym) { + if (conf_grab_matches(wlt_conf.grab_scroll_down, + mask, 1, &sym)) { tsm_screen_sb_down(term->scr, 1); wlt_window_schedule_redraw(term->wnd); return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_page_up->mods) && - sym == wlt_conf.grab_page_up->keysym) { + if (conf_grab_matches(wlt_conf.grab_page_up, + mask, 1, &sym)) { tsm_screen_sb_page_up(term->scr, 1); wlt_window_schedule_redraw(term->wnd); return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_page_down->mods) && - sym == wlt_conf.grab_page_down->keysym) { + if (conf_grab_matches(wlt_conf.grab_page_down, + mask, 1, &sym)) { tsm_screen_sb_page_down(term->scr, 1); wlt_window_schedule_redraw(term->wnd); return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_zoom_in->mods) && - sym == wlt_conf.grab_zoom_in->keysym) { + if (conf_grab_matches(wlt_conf.grab_zoom_in, + mask, 1, &sym)) { if (term->font_attr.points + 1 < term->font_attr.points) return true; @@ -454,8 +454,8 @@ static bool widget_key(struct wlt_widget *widget, unsigned int mask, } return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_zoom_out->mods) && - sym == wlt_conf.grab_zoom_out->keysym) { + if (conf_grab_matches(wlt_conf.grab_zoom_out, + mask, 1, &sym)) { if (term->font_attr.points - 1 < 1) return true; @@ -473,8 +473,8 @@ static bool widget_key(struct wlt_widget *widget, unsigned int mask, return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_paste->mods) && - sym == wlt_conf.grab_paste->keysym) { + if (conf_grab_matches(wlt_conf.grab_paste, + mask, 1, &sym)) { if (term->paste) { log_debug("cannot paste selection, previous paste still in progress"); return true; @@ -505,8 +505,8 @@ static bool widget_key(struct wlt_widget *widget, unsigned int mask, return true; } - if (SHL_HAS_BITS(mask, wlt_conf.grab_copy->mods) && - sym == wlt_conf.grab_copy->keysym) { + if (conf_grab_matches(wlt_conf.grab_copy, + mask, 1, &sym)) { if (term->copy) { wl_data_source_destroy(term->copy); free(term->copy_buf); diff --git a/src/wlt_theme.c b/src/wlt_theme.c index 52b7d17..4872da7 100644 --- a/src/wlt_theme.c +++ b/src/wlt_theme.c @@ -539,8 +539,8 @@ static bool widget_key(struct wlt_widget *widget, unsigned int mask, if (handled || state != WL_KEYBOARD_KEY_STATE_PRESSED) return false; - if (SHL_HAS_BITS(mask, wlt_conf.grab_fullscreen->mods) && - sym == wlt_conf.grab_fullscreen->keysym) { + if (conf_grab_matches(wlt_conf.grab_fullscreen, + mask, 1, &sym)) { wlt_window_toggle_fullscreen(theme->wnd); return true; }