parent
a535ed310f
commit
cab2189aa6
@ -0,0 +1,350 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "gesture.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "list.h"
|
||||
#include "log.h"
|
||||
#include "stringop.h"
|
||||
|
||||
const uint8_t GESTURE_FINGERS_ANY = 0;
|
||||
|
||||
// Helper to easily allocate and format string
|
||||
static char *strformat(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int length = vsnprintf(NULL, 0, format, args) + 1;
|
||||
va_end(args);
|
||||
|
||||
char *result = malloc(length);
|
||||
if (result) {
|
||||
va_start(args, format);
|
||||
vsnprintf(result, length, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *gesture_parse(const char *input, struct gesture *output) {
|
||||
// Clear output in case of failure
|
||||
output->type = GESTURE_TYPE_NONE;
|
||||
output->fingers = GESTURE_FINGERS_ANY;
|
||||
output->directions = GESTURE_DIRECTION_NONE;
|
||||
|
||||
// Split input type, fingers and directions
|
||||
list_t *split = split_string(input, ":");
|
||||
if (split->length < 1 || split->length > 3) {
|
||||
return strformat(
|
||||
"expected <gesture>[:<fingers>][:direction], got %s",
|
||||
input);
|
||||
}
|
||||
|
||||
// Parse gesture type
|
||||
if (strcmp(split->items[0], "hold") == 0) {
|
||||
output->type = GESTURE_TYPE_HOLD;
|
||||
} else if (strcmp(split->items[0], "pinch") == 0) {
|
||||
output->type = GESTURE_TYPE_PINCH;
|
||||
} else if (strcmp(split->items[0], "swipe") == 0) {
|
||||
output->type = GESTURE_TYPE_SWIPE;
|
||||
} else {
|
||||
return strformat("expected hold|pinch|swipe, got %s",
|
||||
split->items[0]);
|
||||
}
|
||||
|
||||
// Parse optional arguments
|
||||
if (split->length > 1) {
|
||||
char *next = split->items[1];
|
||||
|
||||
// Try to parse as finger count (1-9)
|
||||
if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
|
||||
output->fingers = atoi(next);
|
||||
|
||||
// Move to next if available
|
||||
next = split->length == 3 ? split->items[2] : NULL;
|
||||
} else if (split->length == 3) {
|
||||
// Fail here if argument can only be finger count
|
||||
return strformat("expected 1-9, got %s", next);
|
||||
}
|
||||
|
||||
// If there is an argument left, try to parse as direction
|
||||
if (next) {
|
||||
list_t *directions = split_string(next, "+");
|
||||
|
||||
for (int i = 0; i < directions->length; ++i) {
|
||||
const char *item = directions->items[i];
|
||||
if (strcmp(item, "any") == 0) {
|
||||
continue;
|
||||
} else if (strcmp(item, "up") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_UP;
|
||||
} else if (strcmp(item, "down") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_DOWN;
|
||||
} else if (strcmp(item, "left") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_LEFT;
|
||||
} else if (strcmp(item, "right") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_RIGHT;
|
||||
} else if (strcmp(item, "inward") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_INWARD;
|
||||
} else if (strcmp(item, "outward") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_OUTWARD;
|
||||
} else if (strcmp(item, "clockwise") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_CLOCKWISE;
|
||||
} else if (strcmp(item, "counterclockwise") == 0) {
|
||||
output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
|
||||
} else {
|
||||
return strformat("expected direction, got %s", item);
|
||||
}
|
||||
}
|
||||
list_free_items_and_destroy(directions);
|
||||
}
|
||||
} // if optional args
|
||||
|
||||
list_free_items_and_destroy(split);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *gesture_type_string(enum gesture_type type) {
|
||||
switch (type) {
|
||||
case GESTURE_TYPE_NONE:
|
||||
return "none";
|
||||
case GESTURE_TYPE_HOLD:
|
||||
return "hold";
|
||||
case GESTURE_TYPE_PINCH:
|
||||
return "pinch";
|
||||
case GESTURE_TYPE_SWIPE:
|
||||
return "swipe";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *gesture_direction_string(enum gesture_direction direction) {
|
||||
switch (direction) {
|
||||
case GESTURE_DIRECTION_NONE:
|
||||
return "none";
|
||||
case GESTURE_DIRECTION_UP:
|
||||
return "up";
|
||||
case GESTURE_DIRECTION_DOWN:
|
||||
return "down";
|
||||
case GESTURE_DIRECTION_LEFT:
|
||||
return "left";
|
||||
case GESTURE_DIRECTION_RIGHT:
|
||||
return "right";
|
||||
case GESTURE_DIRECTION_INWARD:
|
||||
return "inward";
|
||||
case GESTURE_DIRECTION_OUTWARD:
|
||||
return "outward";
|
||||
case GESTURE_DIRECTION_CLOCKWISE:
|
||||
return "clockwise";
|
||||
case GESTURE_DIRECTION_COUNTERCLOCKWISE:
|
||||
return "counterclockwise";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Helper to turn combination of directions flags into string representation.
|
||||
static char *gesture_directions_to_string(uint32_t directions) {
|
||||
char *result = NULL;
|
||||
|
||||
for (uint8_t bit = 0; bit < 32; bit++) {
|
||||
uint32_t masked = directions & (1 << bit);
|
||||
if (masked) {
|
||||
const char *name = gesture_direction_string(masked);
|
||||
|
||||
if (!name) {
|
||||
name = "unknown";
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = strdup(name);
|
||||
} else {
|
||||
char *new = strformat("%s+%s", result, name);
|
||||
free(result);
|
||||
result = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!result) {
|
||||
return strdup("any");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
char *gesture_to_string(struct gesture *gesture) {
|
||||
char *directions = gesture_directions_to_string(gesture->directions);
|
||||
char *result = strformat("%s:%u:%s",
|
||||
gesture_type_string(gesture->type),
|
||||
gesture->fingers, directions);
|
||||
free(directions);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
|
||||
// Check that gesture type matches
|
||||
if (target->type != type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that finger count matches
|
||||
if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
|
||||
// Check type and fingers
|
||||
if (!gesture_check(target, to_match->type, to_match->fingers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enforce exact matches ...
|
||||
if (exact && target->directions != to_match->directions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... or ensure all target directions are matched
|
||||
return (target->directions & to_match->directions) == target->directions;
|
||||
}
|
||||
|
||||
bool gesture_equal(struct gesture *a, struct gesture *b) {
|
||||
return a->type == b->type
|
||||
&& a->fingers == b->fingers
|
||||
&& a->directions == b->directions;
|
||||
}
|
||||
|
||||
// Return count of set bits in directions bit field.
|
||||
static uint8_t gesture_directions_count(uint32_t directions) {
|
||||
uint8_t count = 0;
|
||||
for (; directions; directions >>= 1) {
|
||||
count += directions & 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Compare direction bit count of two direction.
|
||||
static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
|
||||
return gesture_directions_count(a) - gesture_directions_count(b);
|
||||
}
|
||||
|
||||
// Compare two direction based on their direction bit count
|
||||
int8_t gesture_compare(struct gesture *a, struct gesture *b) {
|
||||
return gesture_directions_compare(a->directions, b->directions);
|
||||
}
|
||||
|
||||
void gesture_tracker_begin(struct gesture_tracker *tracker,
|
||||
enum gesture_type type, uint8_t fingers) {
|
||||
tracker->type = type;
|
||||
tracker->fingers = fingers;
|
||||
|
||||
tracker->dx = 0.0;
|
||||
tracker->dy = 0.0;
|
||||
tracker->scale = 1.0;
|
||||
tracker->rotation = 0.0;
|
||||
|
||||
sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
|
||||
gesture_type_string(type), fingers);
|
||||
}
|
||||
|
||||
bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
|
||||
return tracker->type == type;
|
||||
}
|
||||
|
||||
void gesture_tracker_update(struct gesture_tracker *tracker,
|
||||
double dx, double dy, double scale, double rotation) {
|
||||
if (tracker->type == GESTURE_TYPE_HOLD) {
|
||||
sway_assert(false, "hold does not update.");
|
||||
return;
|
||||
}
|
||||
|
||||
tracker->dx += dx;
|
||||
tracker->dy += dy;
|
||||
|
||||
if (tracker->type == GESTURE_TYPE_PINCH) {
|
||||
tracker->scale = scale;
|
||||
tracker->rotation += rotation;
|
||||
}
|
||||
|
||||
sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
|
||||
gesture_type_string(tracker->type),
|
||||
tracker->fingers,
|
||||
tracker->dx, tracker->dy,
|
||||
tracker->scale, tracker->rotation);
|
||||
}
|
||||
|
||||
void gesture_tracker_cancel(struct gesture_tracker *tracker) {
|
||||
sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
|
||||
gesture_type_string(tracker->type), tracker->fingers);
|
||||
|
||||
tracker->type = GESTURE_TYPE_NONE;
|
||||
}
|
||||
|
||||
struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
|
||||
struct gesture *result = calloc(1, sizeof(struct gesture));
|
||||
|
||||
// Ignore gesture under some threshold
|
||||
// TODO: Make configurable
|
||||
const double min_rotation = 5;
|
||||
const double min_scale_delta = 0.1;
|
||||
|
||||
// Determine direction
|
||||
switch(tracker->type) {
|
||||
// Gestures with scale and rotation
|
||||
case GESTURE_TYPE_PINCH:
|
||||
if (tracker->rotation > min_rotation) {
|
||||
result->directions |= GESTURE_DIRECTION_CLOCKWISE;
|
||||
}
|
||||
if (tracker->rotation < -min_rotation) {
|
||||
result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
|
||||
}
|
||||
|
||||
if (tracker->scale > (1.0 + min_scale_delta)) {
|
||||
result->directions |= GESTURE_DIRECTION_OUTWARD;
|
||||
}
|
||||
if (tracker->scale < (1.0 - min_scale_delta)) {
|
||||
result->directions |= GESTURE_DIRECTION_INWARD;
|
||||
}
|
||||
__attribute__ ((fallthrough));
|
||||
// Gestures with dx and dy
|
||||
case GESTURE_TYPE_SWIPE:
|
||||
if (fabs(tracker->dx) > fabs(tracker->dy)) {
|
||||
if (tracker->dx > 0) {
|
||||
result->directions |= GESTURE_DIRECTION_RIGHT;
|
||||
} else {
|
||||
result->directions |= GESTURE_DIRECTION_LEFT;
|
||||
}
|
||||
} else {
|
||||
if (tracker->dy > 0) {
|
||||
result->directions |= GESTURE_DIRECTION_DOWN;
|
||||
} else {
|
||||
result->directions |= GESTURE_DIRECTION_UP;
|
||||
}
|
||||
}
|
||||
// Gesture without any direction
|
||||
case GESTURE_TYPE_HOLD:
|
||||
break;
|
||||
// Not tracking any gesture
|
||||
case GESTURE_TYPE_NONE:
|
||||
sway_assert(false, "Not tracking any gesture.");
|
||||
return result;
|
||||
}
|
||||
|
||||
result->type = tracker->type;
|
||||
result->fingers = tracker->fingers;
|
||||
|
||||
char *description = gesture_to_string(result);
|
||||
sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
|
||||
free(description);
|
||||
|
||||
tracker->type = GESTURE_TYPE_NONE;
|
||||
|
||||
return result;
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
#ifndef _SWAY_GESTURE_H
|
||||
#define _SWAY_GESTURE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A gesture type used in binding.
|
||||
*/
|
||||
enum gesture_type {
|
||||
GESTURE_TYPE_NONE = 0,
|
||||
GESTURE_TYPE_HOLD,
|
||||
GESTURE_TYPE_PINCH,
|
||||
GESTURE_TYPE_SWIPE,
|
||||
};
|
||||
|
||||
// Turns single type enum value to constant string representation.
|
||||
const char *gesture_type_string(enum gesture_type direction);
|
||||
|
||||
// Value to use to accept any finger count
|
||||
extern const uint8_t GESTURE_FINGERS_ANY;
|
||||
|
||||
/**
|
||||
* A gesture direction used in binding.
|
||||
*/
|
||||
enum gesture_direction {
|
||||
GESTURE_DIRECTION_NONE = 0,
|
||||
// Directions based on delta x and y
|
||||
GESTURE_DIRECTION_UP = 1 << 0,
|
||||
GESTURE_DIRECTION_DOWN = 1 << 1,
|
||||
GESTURE_DIRECTION_LEFT = 1 << 2,
|
||||
GESTURE_DIRECTION_RIGHT = 1 << 3,
|
||||
// Directions based on scale
|
||||
GESTURE_DIRECTION_INWARD = 1 << 4,
|
||||
GESTURE_DIRECTION_OUTWARD = 1 << 5,
|
||||
// Directions based on rotation
|
||||
GESTURE_DIRECTION_CLOCKWISE = 1 << 6,
|
||||
GESTURE_DIRECTION_COUNTERCLOCKWISE = 1 << 7,
|
||||
};
|
||||
|
||||
// Turns single direction enum value to constant string representation.
|
||||
const char *gesture_direction_string(enum gesture_direction direction);
|
||||
|
||||
/**
|
||||
* Struct representing a pointer gesture
|
||||
*/
|
||||
struct gesture {
|
||||
enum gesture_type type;
|
||||
uint8_t fingers;
|
||||
uint32_t directions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses gesture from <gesture>[:<fingers>][:<directions>] string.
|
||||
*
|
||||
* Return NULL on success, otherwise error message string
|
||||
*/
|
||||
char *gesture_parse(const char *input, struct gesture *output);
|
||||
|
||||
// Turns gesture into string representation
|
||||
char *gesture_to_string(struct gesture *gesture);
|
||||
|
||||
// Check if gesture is of certain type and finger count.
|
||||
bool gesture_check(struct gesture *target,
|
||||
enum gesture_type type, uint8_t fingers);
|
||||
|
||||
// Check if a gesture target/binding is match by other gesture/input
|
||||
bool gesture_match(struct gesture *target,
|
||||
struct gesture *to_match, bool exact);
|
||||
|
||||
// Returns true if gesture are exactly the same
|
||||
bool gesture_equal(struct gesture *a, struct gesture *b);
|
||||
|
||||
// Compare distance between two matched target gestures.
|
||||
int8_t gesture_compare(struct gesture *a, struct gesture *b);
|
||||
|
||||
// Small helper struct to track gestures over time
|
||||
struct gesture_tracker {
|
||||
enum gesture_type type;
|
||||
uint8_t fingers;
|
||||
double dx, dy;
|
||||
double scale;
|
||||
double rotation;
|
||||
};
|
||||
|
||||
// Begin gesture tracking
|
||||
void gesture_tracker_begin(struct gesture_tracker *tracker,
|
||||
enum gesture_type type, uint8_t fingers);
|
||||
|
||||
// Check if the provides type is currently being tracked
|
||||
bool gesture_tracker_check(struct gesture_tracker *tracker,
|
||||
enum gesture_type type);
|
||||
|
||||
// Update gesture track with new data point
|
||||
void gesture_tracker_update(struct gesture_tracker *tracker, double dx,
|
||||
double dy, double scale, double rotation);
|
||||
|
||||
// Reset tracker
|
||||
void gesture_tracker_cancel(struct gesture_tracker *tracker);
|
||||
|
||||
// Reset tracker and return gesture tracked
|
||||
struct gesture *gesture_tracker_end(struct gesture_tracker *tracker);
|
||||
|
||||
#endif
|
@ -0,0 +1,166 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "sway/config.h"
|
||||
|
||||
#include "gesture.h"
|
||||
#include "log.h"
|
||||
#include "stringop.h"
|
||||
#include "sway/commands.h"
|
||||
|
||||
void free_gesture_binding(struct sway_gesture_binding *binding) {
|
||||
if (!binding) {
|
||||
return;
|
||||
}
|
||||
free(binding->input);
|
||||
free(binding->command);
|
||||
free(binding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the bindings have the same gesture type, direction, etc
|
||||
*/
|
||||
static bool binding_gesture_equal(struct sway_gesture_binding *binding_a,
|
||||
struct sway_gesture_binding *binding_b) {
|
||||
if (strcmp(binding_a->input, binding_b->input) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gesture_equal(&binding_a->gesture, &binding_b->gesture)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((binding_a->flags & BINDING_EXACT) !=
|
||||
(binding_b->flags & BINDING_EXACT)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add gesture binding to config
|
||||
*/
|
||||
static struct cmd_results *gesture_binding_add(
|
||||
struct sway_gesture_binding *binding,
|
||||
const char *gesturecombo, bool warn) {
|
||||
list_t *mode_bindings = config->current_mode->gesture_bindings;
|
||||
// overwrite the binding if it already exists
|
||||
bool overwritten = false;
|
||||
for (int i = 0; i < mode_bindings->length; ++i) {
|
||||
struct sway_gesture_binding *config_binding = mode_bindings->items[i];
|
||||
if (binding_gesture_equal(binding, config_binding)) {
|
||||
sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`",
|
||||
gesturecombo, binding->command, config_binding->command);
|
||||
if (warn) {
|
||||
config_add_swaynag_warning("Overwriting binding"
|
||||
"'%s' to `%s` from `%s`",
|
||||
gesturecombo, binding->command,
|
||||
config_binding->command);
|
||||
}
|
||||
free_gesture_binding(config_binding);
|
||||
mode_bindings->items[i] = binding;
|
||||
overwritten = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!overwritten) {
|
||||
list_add(mode_bindings, binding);
|
||||
sway_log(SWAY_DEBUG, "bindgesture - Bound %s to command `%s`",
|
||||
gesturecombo, binding->command);
|
||||
}
|
||||
|
||||
return cmd_results_new(CMD_SUCCESS, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove gesture binding from config
|
||||
*/
|
||||
static struct cmd_results *gesture_binding_remove(
|
||||
struct sway_gesture_binding *binding, const char *gesturecombo) {
|
||||
list_t *mode_bindings = config->current_mode->gesture_bindings;
|
||||
for (int i = 0; i < mode_bindings->length; ++i) {
|
||||
struct sway_gesture_binding *config_binding = mode_bindings->items[i];
|
||||
if (binding_gesture_equal(binding, config_binding)) {
|
||||
free_gesture_binding(config_binding);
|
||||
free_gesture_binding(binding);
|
||||
list_del(mode_bindings, i);
|
||||
sway_log(SWAY_DEBUG, "unbindgesture - Unbound %s gesture",
|
||||
gesturecombo);
|
||||
return cmd_results_new(CMD_SUCCESS, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
free_gesture_binding(binding);
|
||||
return cmd_results_new(CMD_FAILURE, "Could not find gesture binding `%s`",
|
||||
gesturecombo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and execute bindgesture or unbindgesture command.
|
||||
*/
|
||||
static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, bool unbind) {
|
||||
int minargs = 2;
|
||||
char *bindtype = "bindgesture";
|
||||
if (unbind) {
|
||||
minargs--;
|
||||
bindtype = "unbindgesture";
|
||||
}
|
||||
|
||||
struct cmd_results *error = NULL;
|
||||
if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) {
|
||||
return error;
|
||||
}
|
||||
struct sway_gesture_binding *binding = calloc(1, sizeof(struct sway_gesture_binding));
|
||||
if (!binding) {
|
||||
return cmd_results_new(CMD_FAILURE, "Unable to allocate binding");
|
||||
}
|
||||
binding->input = strdup("*");
|
||||
|
||||
bool warn = true;
|
||||
|
||||
// Handle flags
|
||||
while (argc > 0) {
|
||||
if (strcmp("--exact", argv[0]) == 0) {
|
||||
binding->flags |= BINDING_EXACT;
|
||||
} else if (strcmp("--no-warn", argv[0]) == 0) {
|
||||
warn = false;
|
||||
} else if (strncmp("--input-device=", argv[0],
|
||||
strlen("--input-device=")) == 0) {
|
||||
free(binding->input);
|
||||
binding->input = strdup(argv[0] + strlen("--input-device="));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
argv++;
|
||||
argc--;
|
||||
}
|
||||
|
||||
if (argc < minargs) {
|
||||
free(binding);
|
||||
return cmd_results_new(CMD_FAILURE,
|
||||
"Invalid %s command (expected at least %d "
|
||||
"non-option arguments, got %d)", bindtype, minargs, argc);
|
||||
}
|
||||
|
||||
char* errmsg = NULL;
|
||||
if ((errmsg = gesture_parse(argv[0], &binding->gesture))) {
|
||||
free(binding);
|
||||
struct cmd_results *final = cmd_results_new(CMD_FAILURE,
|
||||
"Invalid %s command (%s)",
|
||||
bindtype, errmsg);
|
||||
free(errmsg);
|
||||
return final;
|
||||
}
|
||||
|
||||
if (unbind) {
|
||||
return gesture_binding_remove(binding, argv[0]);
|
||||
}
|
||||
binding->command = join_args(argv + 1, argc - 1);
|
||||
return gesture_binding_add(binding, argv[0], warn);
|
||||
}
|
||||
|
||||
struct cmd_results *cmd_bindgesture(int argc, char **argv) {
|
||||
return cmd_bind_or_unbind_gesture(argc, argv, false);
|
||||
}
|
||||
|
||||
struct cmd_results *cmd_unbindgesture(int argc, char **argv) {
|
||||
return cmd_bind_or_unbind_gesture(argc, argv, true);
|
||||
}
|
Loading…
Reference in new issue