Merge pull request #2303 from frsfnrrg/mouse-bindings

Implement mouse bindings
This commit is contained in:
Ryan Dwyer 2018-07-24 19:48:27 +10:00 committed by GitHub
commit 1a9179dbab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 272 additions and 78 deletions

View file

@ -22,14 +22,28 @@ struct sway_variable {
char *value;
};
enum binding_input_type {
BINDING_KEYCODE,
BINDING_KEYSYM,
BINDING_MOUSE,
};
enum binding_flags {
BINDING_RELEASE=1,
BINDING_LOCKED=2, // keyboard only
BINDING_BORDER=4, // mouse only; trigger on container border
BINDING_CONTENTS=8, // mouse only; trigger on container contents
BINDING_TITLEBAR=16 // mouse only; trigger on container titlebar
};
/**
* A key binding and an associated command.
*/
struct sway_binding {
enum binding_input_type type;
int order;
bool release;
bool locked;
bool bindcode;
uint32_t flags;
list_t *keys; // sorted in ascending order
uint32_t modifiers;
char *command;
@ -50,6 +64,7 @@ struct sway_mode {
char *name;
list_t *keysym_bindings;
list_t *keycode_bindings;
list_t *mouse_bindings;
bool pango;
};
@ -482,6 +497,8 @@ void free_sway_binding(struct sway_binding *sb);
struct sway_binding *sway_binding_dup(struct sway_binding *sb);
void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding);
void load_swaybars();
void invoke_swaybar(struct bar_config *bar);

View file

@ -3,6 +3,8 @@
#include <stdint.h>
#include "sway/input/seat.h"
#define SWAY_CURSOR_PRESSED_BUTTONS_CAP 32
struct sway_cursor {
struct sway_seat *seat;
struct wlr_cursor *cursor;
@ -29,6 +31,10 @@ struct sway_cursor {
uint32_t tool_buttons;
struct wl_listener request_set_cursor;
// Mouse binding state
uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP];
size_t pressed_button_count;
};
void sway_cursor_destroy(struct sway_cursor *cursor);

View file

@ -34,11 +34,14 @@ void free_sway_binding(struct sway_binding *binding) {
*/
static bool binding_key_compare(struct sway_binding *binding_a,
struct sway_binding *binding_b) {
if (binding_a->release != binding_b->release) {
if (binding_a->type != binding_b->type) {
return false;
}
if (binding_a->bindcode != binding_b->bindcode) {
uint32_t conflict_generating_flags = BINDING_RELEASE | BINDING_BORDER
| BINDING_CONTENTS | BINDING_TITLEBAR;
if ((binding_a->flags & conflict_generating_flags) !=
(binding_b->flags & conflict_generating_flags)) {
return false;
}
@ -69,6 +72,66 @@ static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) {
return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0);
}
/**
* From a keycode, bindcode, or bindsym name and the most likely binding type,
* identify the appropriate numeric value corresponding to the key. Return NULL
* and set *key_val if successful, otherwise return a specific error. Change
* the value of *type if the initial type guess was incorrect and if this
* was the first identified key.
*/
static struct cmd_results *identify_key(const char* name, bool first_key,
uint32_t* key_val, enum binding_input_type* type) {
if (*type == BINDING_KEYCODE) {
// check for keycode
xkb_keycode_t keycode = strtol(name, NULL, 10);
if (!xkb_keycode_is_legal_ext(keycode)) {
return cmd_results_new(CMD_INVALID, "bindcode",
"Invalid keycode '%s'", name);
}
*key_val = keycode;
} else {
// check for keysym
xkb_keysym_t keysym = xkb_keysym_from_name(name,
XKB_KEYSYM_CASE_INSENSITIVE);
// Check for mouse binding
uint32_t button = 0;
if (strncasecmp(name, "button", strlen("button")) == 0 &&
strlen(name) == strlen("button0")) {
button = name[strlen("button")] - '1' + BTN_LEFT;
}
if (*type == BINDING_KEYSYM) {
if (button) {
if (first_key) {
*type = BINDING_MOUSE;
*key_val = button;
} else {
return cmd_results_new(CMD_INVALID, "bindsym",
"Mixed button '%s' into key sequence", name);
}
} else if (keysym) {
*key_val = keysym;
} else {
return cmd_results_new(CMD_INVALID, "bindsym",
"Unknown key '%s'", name);
}
} else {
if (button) {
*key_val = button;
} else if (keysym) {
return cmd_results_new(CMD_INVALID, "bindsym",
"Mixed keysym '%s' into button sequence", name);
} else {
return cmd_results_new(CMD_INVALID, "bindsym",
"Unknown button '%s'", name);
}
}
}
return NULL;
}
static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
bool bindcode) {
const char *bindtype = bindcode ? "bindcode" : "bindsym";
@ -85,22 +148,34 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
}
binding->keys = create_list();
binding->modifiers = 0;
binding->release = false;
binding->locked = false;
binding->bindcode = bindcode;
binding->flags = 0;
binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM;
bool exclude_titlebar = false;
// Handle --release and --locked
while (argc > 0) {
if (strcmp("--release", argv[0]) == 0) {
binding->release = true;
binding->flags |= BINDING_RELEASE;
} else if (strcmp("--locked", argv[0]) == 0) {
binding->locked = true;
binding->flags |= BINDING_LOCKED;
} else if (strcmp("--whole-window", argv[0]) == 0) {
binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR;
} else if (strcmp("--border", argv[0]) == 0) {
binding->flags |= BINDING_BORDER;
} else if (strcmp("--exclude-titlebar", argv[0]) == 0) {
exclude_titlebar = true;
} else {
break;
}
argv++;
argc--;
}
if (binding->flags & (BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR)
|| exclude_titlebar) {
binding->type = BINDING_MOUSE;
}
if (argc < 2) {
free_sway_binding(binding);
return cmd_results_new(CMD_FAILURE, bindtype,
@ -119,64 +194,47 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
continue;
}
xkb_keycode_t keycode;
xkb_keysym_t keysym;
if (bindcode) {
// parse keycode
keycode = (int)strtol(split->items[i], NULL, 10);
if (!xkb_keycode_is_legal_ext(keycode)) {
error =
cmd_results_new(CMD_INVALID, "bindcode",
"Invalid keycode '%s'", (char *)split->items[i]);
free_sway_binding(binding);
list_free(split);
return error;
}
} else {
// Check for xkb key
keysym = xkb_keysym_from_name(split->items[i],
XKB_KEYSYM_CASE_INSENSITIVE);
// Check for mouse binding
if (strncasecmp(split->items[i], "button", strlen("button")) == 0 &&
strlen(split->items[i]) == strlen("button0")) {
keysym = ((char *)split->items[i])[strlen("button")] - '1' + BTN_LEFT;
}
if (!keysym) {
struct cmd_results *ret = cmd_results_new(CMD_INVALID, "bindsym",
"Unknown key '%s'", (char *)split->items[i]);
free_sway_binding(binding);
free_flat_list(split);
return ret;
}
// Identify the key and possibly change binding->type
uint32_t key_val = 0;
error = identify_key(split->items[i], binding->keys->length == 0,
&key_val, &binding->type);
if (error) {
free_sway_binding(binding);
list_free(split);
return error;
}
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");
"Unable to allocate binding key");
}
if (bindcode) {
*key = (uint32_t)keycode;
} else {
*key = (uint32_t)keysym;
}
*key = key_val;
list_add(binding->keys, key);
}
free_flat_list(split);
binding->order = binding_order++;
// refine region of interest for mouse binding once we are certain
// that this is one
if (exclude_titlebar) {
binding->flags &= ~BINDING_TITLEBAR;
} else if (binding->type == BINDING_MOUSE) {
binding->flags |= BINDING_TITLEBAR;
}
// sort ascending
list_qsort(binding->keys, key_qsort_cmp);
list_t *mode_bindings;
if (bindcode) {
if (binding->type == BINDING_KEYCODE) {
mode_bindings = config->current_mode->keycode_bindings;
} else {
} else if (binding->type == BINDING_KEYSYM) {
mode_bindings = config->current_mode->keysym_bindings;
} else {
mode_bindings = config->current_mode->mouse_bindings;
}
// overwrite the binding if it already exists
@ -209,3 +267,19 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) {
struct cmd_results *cmd_bindcode(int argc, char **argv) {
return cmd_bindsym_or_bindcode(argc, argv, true);
}
/**
* Execute the command associated to a binding
*/
void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding) {
wlr_log(WLR_DEBUG, "running command for binding: %s",
binding->command);
config->handler_context.seat = seat;
struct cmd_results *results = execute_command(binding->command, NULL);
if (results->status != CMD_SUCCESS) {
wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
binding->command, results->error);
}
free_cmd_results(results);
}

View file

@ -56,6 +56,12 @@ static void free_mode(struct sway_mode *mode) {
}
list_free(mode->keycode_bindings);
}
if (mode->mouse_bindings) {
for (i = 0; i < mode->mouse_bindings->length; i++) {
free_sway_binding(mode->mouse_bindings->items[i]);
}
list_free(mode->mouse_bindings);
}
free(mode);
}
@ -172,6 +178,7 @@ static void config_defaults(struct sway_config *config) {
strcpy(config->current_mode->name, "default");
if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup;
if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
list_add(config->modes, config->current_mode);
config->floating_mod = 0;

View file

@ -469,6 +469,83 @@ static void dispatch_cursor_button_floating(struct sway_cursor *cursor,
seat_pointer_notify_button(seat, time_msec, button, state);
}
/**
* Remove a button (and duplicates) to the sorted list of currently pressed buttons
*/
static void state_erase_button(struct sway_cursor *cursor, uint32_t button) {
size_t j = 0;
for (size_t i = 0; i < cursor->pressed_button_count; ++i) {
if (i > j) {
cursor->pressed_buttons[j] = cursor->pressed_buttons[i];
}
if (cursor->pressed_buttons[i] != button) {
++j;
}
}
while (cursor->pressed_button_count > j) {
--cursor->pressed_button_count;
cursor->pressed_buttons[cursor->pressed_button_count] = 0;
}
}
/**
* Add a button to the sorted list of currently pressed buttons, if there
* is space.
*/
static void state_add_button(struct sway_cursor *cursor, uint32_t button) {
if (cursor->pressed_button_count >= SWAY_CURSOR_PRESSED_BUTTONS_CAP) {
return;
}
size_t i = 0;
while (i < cursor->pressed_button_count && cursor->pressed_buttons[i] < button) {
++i;
}
size_t j = cursor->pressed_button_count;
while (j > i) {
cursor->pressed_buttons[j] = cursor->pressed_buttons[j - 1];
--j;
}
cursor->pressed_buttons[i] = button;
cursor->pressed_button_count++;
}
/**
* Return the mouse binding which matches modifier, click location, release,
* and pressed button state, otherwise return null.
*/
static struct sway_binding* get_active_mouse_binding(const struct sway_cursor *cursor,
list_t *bindings, uint32_t modifiers, bool release, bool on_titlebar,
bool on_border, bool on_content) {
uint32_t click_region = (on_titlebar ? BINDING_TITLEBAR : 0) |
(on_border ? BINDING_BORDER : 0) |
(on_content ? BINDING_CONTENTS : 0);
for (int i = 0; i < bindings->length; ++i) {
struct sway_binding *binding = bindings->items[i];
if (modifiers ^ binding->modifiers ||
cursor->pressed_button_count != (size_t)binding->keys->length ||
release != (binding->flags & BINDING_RELEASE) ||
!(click_region & binding->flags)) {
continue;
}
bool match = true;
for (size_t j = 0; j < cursor->pressed_button_count; j++) {
uint32_t key = *(uint32_t *)binding->keys->items[j];
if (key != cursor->pressed_buttons[j]) {
match = false;
break;
}
}
if (!match) {
continue;
}
return binding;
}
return NULL;
}
void dispatch_cursor_button(struct sway_cursor *cursor,
uint32_t time_msec, uint32_t button, enum wlr_button_state state) {
if (cursor->seat->operation != OP_NONE &&
@ -485,6 +562,31 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
double sx, sy;
struct sway_container *cont = container_at_coords(cursor->seat,
cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
// Handle mouse bindings
bool on_border = find_resize_edge(cont, cursor) != WLR_EDGE_NONE;
bool on_contents = !on_border && surface;
bool on_titlebar = !on_border && !surface;
struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(cursor->seat->wlr_seat);
uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
struct sway_binding *binding = NULL;
if (state == WLR_BUTTON_PRESSED) {
state_add_button(cursor, button);
binding = get_active_mouse_binding(cursor,
config->current_mode->mouse_bindings, modifiers, false,
on_titlebar, on_border, on_contents);
} else {
binding = get_active_mouse_binding(cursor,
config->current_mode->mouse_bindings, modifiers, true,
on_titlebar, on_border, on_contents);
state_erase_button(cursor, button);
}
if (binding) {
seat_execute_command(cursor->seat, binding);
// TODO: do we want to pass on the event?
}
if (surface && wlr_surface_is_layer_surface(surface)) {
struct wlr_layer_surface *layer =
wlr_layer_surface_from_wlr_surface(surface);

View file

@ -3,11 +3,11 @@
#include <wlr/backend/multi.h>
#include <wlr/backend/session.h>
#include <wlr/types/wlr_idle.h>
#include "sway/desktop/transaction.h"
#include "sway/input/seat.h"
#include "sway/input/keyboard.h"
#include "sway/input/input-manager.h"
#include "sway/commands.h"
#include "sway/desktop/transaction.h"
#include "sway/input/input-manager.h"
#include "sway/input/keyboard.h"
#include "sway/input/seat.h"
#include "log.h"
/**
@ -88,11 +88,13 @@ static void get_active_binding(const struct sway_shortcut_state *state,
uint32_t modifiers, bool release, bool locked) {
for (int i = 0; i < bindings->length; ++i) {
struct sway_binding *binding = bindings->items[i];
bool binding_locked = binding->flags & BINDING_LOCKED;
bool binding_release = binding->flags & BINDING_RELEASE;
if (modifiers ^ binding->modifiers ||
state->npressed != (size_t)binding->keys->length ||
locked > binding->locked ||
release != binding->release) {
release != binding_release ||
locked > binding_locked) {
continue;
}
@ -118,23 +120,6 @@ static void get_active_binding(const struct sway_shortcut_state *state,
}
}
/**
* Execute the command associated to a binding
*/
static void keyboard_execute_command(struct sway_keyboard *keyboard,
struct sway_binding *binding) {
wlr_log(WLR_DEBUG, "running command for binding: %s",
binding->command);
config->handler_context.seat = keyboard->seat_device->sway_seat;
struct cmd_results *results = execute_command(binding->command, NULL);
transaction_commit_dirty();
if (results->status != CMD_SUCCESS) {
wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
binding->command, results->error);
}
free_cmd_results(results);
}
/**
* Execute a built-in, hardcoded compositor binding. These are triggered from a
* single keysym.
@ -211,12 +196,13 @@ static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard,
static void handle_keyboard_key(struct wl_listener *listener, void *data) {
struct sway_keyboard *keyboard =
wl_container_of(listener, keyboard, keyboard_key);
struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
struct sway_seat* seat = keyboard->seat_device->sway_seat;
struct wlr_seat *wlr_seat = seat->wlr_seat;
struct wlr_input_device *wlr_device =
keyboard->seat_device->input_device->wlr_device;
wlr_idle_notify_activity(keyboard->seat_device->sway_seat->input->server->idle, wlr_seat);
wlr_idle_notify_activity(seat->input->server->idle, wlr_seat);
struct wlr_event_keyboard_key *event = data;
bool input_inhibited = keyboard->seat_device->sway_seat->exclusive_client != NULL;
bool input_inhibited = seat->exclusive_client != NULL;
// Identify new keycode, raw keysym(s), and translated keysym(s)
xkb_keycode_t keycode = event->keycode + 8;
@ -266,7 +252,7 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
// Execute stored release binding once no longer active
if (keyboard->held_binding && binding_released != keyboard->held_binding &&
event->state == WLR_KEY_RELEASED) {
keyboard_execute_command(keyboard, keyboard->held_binding);
seat_execute_command(seat, keyboard->held_binding);
handled = true;
}
if (binding_released != keyboard->held_binding) {
@ -290,7 +276,7 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
raw_modifiers, false, input_inhibited);
if (binding_pressed) {
keyboard_execute_command(keyboard, binding_pressed);
seat_execute_command(seat, binding_pressed);
handled = true;
}
}
@ -312,6 +298,8 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
event->keycode, event->state);
}
transaction_commit_dirty();
}
static void handle_keyboard_modifiers(struct wl_listener *listener,