diff --git a/common/list.c b/common/list.c index 66d52f700..bd9abcd82 100644 --- a/common/list.c +++ b/common/list.c @@ -147,3 +147,61 @@ void list_stable_sort(list_t *list, int compare(const void *a, const void *b)) { list_inplace_sort(list, 0, list->length - 1, compare); } } + +void list_sortedset_insert(list_t *list, void *item, + int compare(const void *item_left, const void *item_right), + void *replace(void *old_item, void* new_item)) { + if (list->length <= 0) { + list_add(list, item); + return; + } + + size_t lower = 0; + size_t upper = (size_t)list->length - 1; + while (lower <= upper && upper != (size_t)-1) { + size_t div = (lower + upper) / 2; + int cv = compare(list->items[div], item); + if (cv < 0) { + lower = div + 1; + } else if (cv > 0) { + upper = div - 1; + } else { + list->items[div] = replace(list->items[div], item); + return; + } + } + list_insert(list, lower, item); +} + +int list_sortedset_find(list_t *list, + int compare(const void *item, const void *cmp_to), + const void *cmp_to) { + if (list->length <= 0) { + return -1; + } + + size_t lower = 0; + size_t upper = (size_t)list->length - 1; + while (lower <= upper && upper != (size_t)-1) { + size_t div = (lower + upper) / 2; + int cv = compare(list->items[div], cmp_to); + if (cv < 0) { + lower = div + 1; + } else if (cv > 0) { + upper = div - 1; + } else { + return div; + } + } + return -1; +} + +int list_is_sortedset(list_t *list, + int compare(const void *left, const void *right)) { + for (size_t i = 1; i < (size_t)list->length; i++) { + if (compare(list->items[i - 1], list->items[i]) >= 0) { + return 0; + } + } + return 1; +} diff --git a/include/list.h b/include/list.h index 5a0d7d809..e610bb7e9 100644 --- a/include/list.h +++ b/include/list.h @@ -12,6 +12,12 @@ void list_free(list_t *list); void list_foreach(list_t *list, void (*callback)(void* item)); void list_add(list_t *list, void *item); void list_insert(list_t *list, int index, void *item); +// Insert an item into the list which is already sorted strictly ascending +// according to 'compare'. 'replace' is called when displacing an old item +// and returns the item which will take its place. +void list_sortedset_insert(list_t *list, void* item, + int compare(const void *item_left, const void *item_right), + void *replace(void *old_item, void* new_item)); void list_del(list_t *list, int index); void list_cat(list_t *list, list_t *source); // See qsort. Remember to use *_qsort functions as compare functions, @@ -20,6 +26,11 @@ void list_qsort(list_t *list, int compare(const void *left, const void *right)); // Return index for first item in list that returns 0 for given compare // function or -1 if none matches. int list_seq_find(list_t *list, int compare(const void *item, const void *cmp_to), const void *cmp_to); +// Requires a list sorted strictly ascending according to 'compare'; +// returns the index of the matching item, or -1 is no such item exists +int list_sortedset_find(list_t *list, int compare(const void *item, const void *cmp_to), const void *cmp_to); +// Check if a list is sorted strictly ascending according to compare +int list_is_sortedset(list_t *list, int compare(const void *left, const void *right)); // stable sort since qsort is not guaranteed to be stable void list_stable_sort(list_t *list, int compare(const void *a, const void *b)); // swap two elements in a list diff --git a/include/sway/config.h b/include/sway/config.h index f660a269e..aac89622f 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -26,12 +26,14 @@ struct sway_variable { * A key binding and an associated command. */ struct sway_binding { - int order; - bool release; - bool locked; - bool bindcode; - list_t *keys; // sorted in ascending order + // key part + uint32_t *keys; // sorted in ascending order + size_t length; uint32_t modifiers; + bool release; + // value part + int order; + bool locked; char *command; }; @@ -467,14 +469,8 @@ int workspace_output_cmp_workspace(const void *a, const void *b); int sway_binding_cmp(const void *a, const void *b); -int sway_binding_cmp_qsort(const void *a, const void *b); - -int sway_binding_cmp_keys(const void *a, const void *b); - void free_sway_binding(struct sway_binding *sb); -struct sway_binding *sway_binding_dup(struct sway_binding *sb); - void load_swaybars(); void invoke_swaybar(struct bar_config *bar); diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 83e9e432a..342960e59 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -20,53 +20,55 @@ void free_sway_binding(struct sway_binding *binding) { return; } - if (binding->keys) { - free_flat_list(binding->keys); - } + free(binding->keys); free(binding->command); free(binding); } +static void *replace_binding(void *old_item, void *new_item) { + struct sway_binding *old_binding = (struct sway_binding *)old_item; + struct sway_binding *new_binding = (struct sway_binding *)new_item; + wlr_log(WLR_DEBUG, "overwriting old binding command '%s' with command '%s'", + old_binding->command, new_binding->command); + free_sway_binding(old_binding); + return new_item; +} + /** - * Returns true if the bindings have the same key and modifier combinations. - * Note that keyboard layout is not considered, so the bindings might actually - * not be equivalent on some layouts. + * Comparison function for bindings. + * + * Returns -1 if a < b, 0 if a == b, and 1 is a > b */ -static bool binding_key_compare(struct sway_binding *binding_a, - struct sway_binding *binding_b) { +int sway_binding_cmp(const void *a, const void *b) { + const struct sway_binding *binding_a = (const struct sway_binding *)a; + const struct sway_binding *binding_b = (const struct sway_binding *)b; + if (binding_a->release != binding_b->release) { - return false; + return binding_a->release < binding_b->release ? -1 : 1; } - if (binding_a->bindcode != binding_b->bindcode) { - return false; + if (binding_a->modifiers != binding_b->modifiers) { + return binding_a->modifiers < binding_b->modifiers ? -1 : 1; } - if (binding_a->modifiers ^ binding_b->modifiers) { - return false; - } - - if (binding_a->keys->length != binding_b->keys->length) { - return false; + if (binding_a->length != binding_b->length) { + return binding_a->length < binding_b->length ? -1 : 1; } // Keys are sorted - int keys_len = binding_a->keys->length; - for (int i = 0; i < keys_len; ++i) { - uint32_t key_a = *(uint32_t *)binding_a->keys->items[i]; - uint32_t key_b = *(uint32_t *)binding_b->keys->items[i]; - if (key_a != key_b) { - return false; + for (size_t i = 0; i < binding_a->length; ++i) { + if (binding_a->keys[i] != binding_b->keys[i]) { + return binding_a->keys[i] < binding_b->keys[i] ? -1 : 1; } } - return true; + return 0; } -static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) { - uint32_t key_a = **(uint32_t **)keyp_a; - uint32_t key_b = **(uint32_t **)keyp_b; - return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0); +static int key_qsort_cmp(const void *pkey_a, const void *pkey_b) { + uint32_t a = *(uint32_t *)pkey_a; + uint32_t b = *(uint32_t *)pkey_b; + return (a < b) ? -1 : ((a > b) ? 1 : 0); } static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, @@ -83,11 +85,11 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, return cmd_results_new(CMD_FAILURE, bindtype, "Unable to allocate binding"); } - binding->keys = create_list(); + binding->keys = NULL; + binding->length = 0; binding->modifiers = 0; binding->release = false; binding->locked = false; - binding->bindcode = bindcode; // Handle --release and --locked while (argc > 0) { @@ -111,19 +113,31 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, binding->command = join_args(argv + 1, argc - 1); list_t *split = split_string(argv[0], "+"); + int nonmodifiers = 0; for (int i = 0; i < split->length; ++i) { // Check for a modifier key uint32_t mod; if ((mod = get_modifier_mask_by_name(split->items[i])) > 0) { binding->modifiers |= mod; continue; + } else { + nonmodifiers++; + } + } + binding->keys = (uint32_t *)calloc(nonmodifiers, sizeof (uint32_t)); + if (!binding->keys) { + return cmd_results_new(CMD_FAILURE, bindtype, + "Unable to allocate binding key list"); + } + for (int i = 0; i < split->length; ++i) { + // Check for a modifier key + if (get_modifier_mask_by_name(split->items[i]) > 0) { + continue; } - xkb_keycode_t keycode; - xkb_keysym_t keysym; if (bindcode) { // parse keycode - keycode = (int)strtol(split->items[i], NULL, 10); + xkb_keycode_t keycode = strtol(split->items[i], NULL, 10); if (!xkb_keycode_is_legal_ext(keycode)) { error = cmd_results_new(CMD_INVALID, "bindcode", @@ -132,9 +146,10 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, list_free(split); return error; } + binding->keys[binding->length++] = (uint32_t)keycode; } else { // Check for xkb key - keysym = xkb_keysym_from_name(split->items[i], + xkb_keysym_t keysym = xkb_keysym_from_name(split->items[i], XKB_KEYSYM_CASE_INSENSITIVE); // Check for mouse binding @@ -149,28 +164,14 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, free_flat_list(split); return ret; } + binding->keys[binding->length++] = (uint32_t)keysym; } - uint32_t *key = calloc(1, sizeof(uint32_t)); - if (!key) { - free_sway_binding(binding); - free_flat_list(split); - return cmd_results_new(CMD_FAILURE, bindtype, - "Unable to allocate binding"); - } - - if (bindcode) { - *key = (uint32_t)keycode; - } else { - *key = (uint32_t)keysym; - } - - list_add(binding->keys, key); } free_flat_list(split); binding->order = binding_order++; // sort ascending - list_qsort(binding->keys, key_qsort_cmp); + qsort(binding->keys, binding->length, sizeof(uint32_t), key_qsort_cmp); list_t *mode_bindings; if (bindcode) { @@ -179,22 +180,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, mode_bindings = config->current_mode->keysym_bindings; } - // overwrite the binding if it already exists - bool overwritten = false; - for (int i = 0; i < mode_bindings->length; ++i) { - struct sway_binding *config_binding = mode_bindings->items[i]; - if (binding_key_compare(binding, config_binding)) { - wlr_log(WLR_DEBUG, "overwriting old binding with command '%s'", - config_binding->command); - free_sway_binding(config_binding); - mode_bindings->items[i] = binding; - overwritten = true; - } - } - - if (!overwritten) { - list_add(mode_bindings, binding); - } + list_sortedset_insert(mode_bindings, binding, sway_binding_cmp, replace_binding); wlr_log(WLR_DEBUG, "%s - Bound %s to command %s", bindtype, argv[0], binding->command); diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index ede38519c..71e139c60 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -65,10 +65,10 @@ static void update_shortcut_state(struct sway_shortcut_state *state, bool last_key_was_a_modifier = raw_modifiers != state->last_raw_modifiers; state->last_raw_modifiers = raw_modifiers; - if (last_key_was_a_modifier && state->last_keycode) { - // Last pressed key before this one was a modifier - state_erase_key(state, state->last_keycode); - } + if (last_key_was_a_modifier && state->last_keycode) { + // Last pressed key before this one was a modifier + state_erase_key(state, state->last_keycode); + } if (event->state == WLR_KEY_PRESSED) { // Add current key to set; there may be duplicates @@ -86,36 +86,25 @@ static void update_shortcut_state(struct sway_shortcut_state *state, static void get_active_binding(const struct sway_shortcut_state *state, list_t *bindings, struct sway_binding **current_binding, uint32_t modifiers, bool release, bool locked) { - for (int i = 0; i < bindings->length; ++i) { - struct sway_binding *binding = bindings->items[i]; + struct sway_binding match_binding = { + (uint32_t *)state->pressed_keys, state->npressed, modifiers, release, + 0, false, NULL + }; - if (modifiers ^ binding->modifiers || - state->npressed != (size_t)binding->keys->length || - locked > binding->locked || - release != binding->release) { - continue; - } - - bool match = true; - for (size_t j = 0; j < state->npressed; j++) { - uint32_t key = *(uint32_t *)binding->keys->items[j]; - if (key != state->pressed_keys[j]) { - match = false; - break; - } - } - if (!match) { - continue; - } - - if (*current_binding && *current_binding != binding) { - wlr_log(WLR_DEBUG, "encountered duplicate bindings %d and %d", - (*current_binding)->order, binding->order); - } else { - *current_binding = binding; - } + int index = list_sortedset_find(bindings, sway_binding_cmp, &match_binding); + if (index < 0) { return; } + struct sway_binding *binding = bindings->items[index]; + if (locked > binding->locked) { + return; + } + if (*current_binding && *current_binding != binding) { + wlr_log(WLR_DEBUG, "encountered duplicate bindings %d and %d", + (*current_binding)->order, binding->order); + } else { + *current_binding = binding; + } } /** diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 2a2d834a5..ea9f33a0b 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -106,6 +106,88 @@ static bool workspace_valid_on_output(const char *output_name, return true; } +static void workspace_name_from_binding(const struct sway_binding * binding, + const char* output_name, int *min_order, char **earliest_name) { + char *cmdlist = strdup(binding->command); + char *dup = cmdlist; + char *name = NULL; + + // workspace n + char *cmd = argsep(&cmdlist, " "); + if (cmdlist) { + name = argsep(&cmdlist, ",;"); + } + + if (strcmp("workspace", cmd) == 0 && name) { + char *_target = strdup(name); + _target = do_var_replacement(_target); + strip_quotes(_target); + while (isspace(*_target)) { + memmove(_target, _target+1, strlen(_target+1)); + } + wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'", + _target); + + // Make sure that the command references an actual workspace + // not a command about workspaces + if (strcmp(_target, "next") == 0 || + strcmp(_target, "prev") == 0 || + strcmp(_target, "next_on_output") == 0 || + strcmp(_target, "prev_on_output") == 0 || + strcmp(_target, "number") == 0 || + strcmp(_target, "back_and_forth") == 0 || + strcmp(_target, "current") == 0) + { + free(_target); + free(dup); + return; + } + + // If the command is workspace number , isolate the name + if (strncmp(_target, "number ", strlen("number ")) == 0) { + size_t length = strlen(_target) - strlen("number ") + 1; + char *temp = malloc(length); + strncpy(temp, _target + strlen("number "), length - 1); + temp[length - 1] = '\0'; + free(_target); + _target = temp; + wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target); + + // Make sure the workspace number doesn't already exist + if (workspace_by_number(_target)) { + free(_target); + free(dup); + return; + } + } + + // Make sure that the workspace doesn't already exist + if (workspace_by_name(_target)) { + free(_target); + free(dup); + return; + } + + // make sure that the workspace can appear on the given + // output + if (!workspace_valid_on_output(output_name, _target)) { + free(_target); + free(dup); + return; + } + + if (binding->order < *min_order) { + *min_order = binding->order; + free(*earliest_name); + *earliest_name = _target; + wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target); + } else { + free(_target); + } + } + free(dup); +} + char *workspace_next_name(const char *output_name) { wlr_log(WLR_DEBUG, "Workspace: Generating new workspace name for output %s", output_name); @@ -113,89 +195,15 @@ char *workspace_next_name(const char *output_name) { // if none are found/available then default to a number struct sway_mode *mode = config->current_mode; - // TODO: iterate over keycode bindings too int order = INT_MAX; char *target = NULL; for (int i = 0; i < mode->keysym_bindings->length; ++i) { - struct sway_binding *binding = mode->keysym_bindings->items[i]; - char *cmdlist = strdup(binding->command); - char *dup = cmdlist; - char *name = NULL; - - // workspace n - char *cmd = argsep(&cmdlist, " "); - if (cmdlist) { - name = argsep(&cmdlist, ",;"); - } - - if (strcmp("workspace", cmd) == 0 && name) { - char *_target = strdup(name); - _target = do_var_replacement(_target); - strip_quotes(_target); - while (isspace(*_target)) { - memmove(_target, _target+1, strlen(_target+1)); - } - wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'", - _target); - - // Make sure that the command references an actual workspace - // not a command about workspaces - if (strcmp(_target, "next") == 0 || - strcmp(_target, "prev") == 0 || - strcmp(_target, "next_on_output") == 0 || - strcmp(_target, "prev_on_output") == 0 || - strcmp(_target, "number") == 0 || - strcmp(_target, "back_and_forth") == 0 || - strcmp(_target, "current") == 0) - { - free(_target); - free(dup); - continue; - } - - // If the command is workspace number , isolate the name - if (strncmp(_target, "number ", strlen("number ")) == 0) { - size_t length = strlen(_target) - strlen("number ") + 1; - char *temp = malloc(length); - strncpy(temp, _target + strlen("number "), length - 1); - temp[length - 1] = '\0'; - free(_target); - _target = temp; - wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target); - - // Make sure the workspace number doesn't already exist - if (workspace_by_number(_target)) { - free(_target); - free(dup); - continue; - } - } - - // Make sure that the workspace doesn't already exist - if (workspace_by_name(_target)) { - free(_target); - free(dup); - continue; - } - - // make sure that the workspace can appear on the given - // output - if (!workspace_valid_on_output(output_name, _target)) { - free(_target); - free(dup); - continue; - } - - if (binding->order < order) { - order = binding->order; - free(target); - target = _target; - wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target); - } else { - free(_target); - } - } - free(dup); + workspace_name_from_binding(mode->keysym_bindings->items[i], + output_name, &order, &target); + } + for (int i = 0; i < mode->keycode_bindings->length; ++i) { + workspace_name_from_binding(mode->keycode_bindings->items[i], + output_name, &order, &target); } if (target != NULL) { return target;