aboutsummaryrefslogtreecommitdiffstats
path: root/common/gesture.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/gesture.c')
-rw-r--r--common/gesture.c332
1 files changed, 332 insertions, 0 deletions
diff --git a/common/gesture.c b/common/gesture.c
new file mode 100644
index 00000000..272aa837
--- /dev/null
+++ b/common/gesture.c
@@ -0,0 +1,332 @@
1#include "gesture.h"
2
3#include <math.h>
4#include <stdarg.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include "list.h"
9#include "log.h"
10#include "stringop.h"
11
12const uint8_t GESTURE_FINGERS_ANY = 0;
13
14char *gesture_parse(const char *input, struct gesture *output) {
15 // Clear output in case of failure
16 output->type = GESTURE_TYPE_NONE;
17 output->fingers = GESTURE_FINGERS_ANY;
18 output->directions = GESTURE_DIRECTION_NONE;
19
20 // Split input type, fingers and directions
21 list_t *split = split_string(input, ":");
22 if (split->length < 1 || split->length > 3) {
23 return format_str(
24 "expected <gesture>[:<fingers>][:direction], got %s",
25 input);
26 }
27
28 // Parse gesture type
29 if (strcmp(split->items[0], "hold") == 0) {
30 output->type = GESTURE_TYPE_HOLD;
31 } else if (strcmp(split->items[0], "pinch") == 0) {
32 output->type = GESTURE_TYPE_PINCH;
33 } else if (strcmp(split->items[0], "swipe") == 0) {
34 output->type = GESTURE_TYPE_SWIPE;
35 } else {
36 return format_str("expected hold|pinch|swipe, got %s",
37 (const char *)split->items[0]);
38 }
39
40 // Parse optional arguments
41 if (split->length > 1) {
42 char *next = split->items[1];
43
44 // Try to parse as finger count (1-9)
45 if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
46 output->fingers = atoi(next);
47
48 // Move to next if available
49 next = split->length == 3 ? split->items[2] : NULL;
50 } else if (split->length == 3) {
51 // Fail here if argument can only be finger count
52 return format_str("expected 1-9, got %s", next);
53 }
54
55 // If there is an argument left, try to parse as direction
56 if (next) {
57 list_t *directions = split_string(next, "+");
58
59 for (int i = 0; i < directions->length; ++i) {
60 const char *item = directions->items[i];
61 if (strcmp(item, "any") == 0) {
62 continue;
63 } else if (strcmp(item, "up") == 0) {
64 output->directions |= GESTURE_DIRECTION_UP;
65 } else if (strcmp(item, "down") == 0) {
66 output->directions |= GESTURE_DIRECTION_DOWN;
67 } else if (strcmp(item, "left") == 0) {
68 output->directions |= GESTURE_DIRECTION_LEFT;
69 } else if (strcmp(item, "right") == 0) {
70 output->directions |= GESTURE_DIRECTION_RIGHT;
71 } else if (strcmp(item, "inward") == 0) {
72 output->directions |= GESTURE_DIRECTION_INWARD;
73 } else if (strcmp(item, "outward") == 0) {
74 output->directions |= GESTURE_DIRECTION_OUTWARD;
75 } else if (strcmp(item, "clockwise") == 0) {
76 output->directions |= GESTURE_DIRECTION_CLOCKWISE;
77 } else if (strcmp(item, "counterclockwise") == 0) {
78 output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
79 } else {
80 return format_str("expected direction, got %s", item);
81 }
82 }
83 list_free_items_and_destroy(directions);
84 }
85 } // if optional args
86
87 list_free_items_and_destroy(split);
88
89 return NULL;
90}
91
92const char *gesture_type_string(enum gesture_type type) {
93 switch (type) {
94 case GESTURE_TYPE_NONE:
95 return "none";
96 case GESTURE_TYPE_HOLD:
97 return "hold";
98 case GESTURE_TYPE_PINCH:
99 return "pinch";
100 case GESTURE_TYPE_SWIPE:
101 return "swipe";
102 }
103
104 return NULL;
105}
106
107const char *gesture_direction_string(enum gesture_direction direction) {
108 switch (direction) {
109 case GESTURE_DIRECTION_NONE:
110 return "none";
111 case GESTURE_DIRECTION_UP:
112 return "up";
113 case GESTURE_DIRECTION_DOWN:
114 return "down";
115 case GESTURE_DIRECTION_LEFT:
116 return "left";
117 case GESTURE_DIRECTION_RIGHT:
118 return "right";
119 case GESTURE_DIRECTION_INWARD:
120 return "inward";
121 case GESTURE_DIRECTION_OUTWARD:
122 return "outward";
123 case GESTURE_DIRECTION_CLOCKWISE:
124 return "clockwise";
125 case GESTURE_DIRECTION_COUNTERCLOCKWISE:
126 return "counterclockwise";
127 }
128
129 return NULL;
130}
131
132// Helper to turn combination of directions flags into string representation.
133static char *gesture_directions_to_string(uint32_t directions) {
134 char *result = NULL;
135
136 for (uint8_t bit = 0; bit < 32; bit++) {
137 uint32_t masked = directions & (1 << bit);
138 if (masked) {
139 const char *name = gesture_direction_string(masked);
140
141 if (!name) {
142 name = "unknown";
143 }
144
145 if (!result) {
146 result = strdup(name);
147 } else {
148 char *new = format_str("%s+%s", result, name);
149 free(result);
150 result = new;
151 }
152 }
153 }
154
155 if(!result) {
156 return strdup("any");
157 }
158
159 return result;
160}
161
162char *gesture_to_string(struct gesture *gesture) {
163 char *directions = gesture_directions_to_string(gesture->directions);
164 char *result = format_str("%s:%u:%s",
165 gesture_type_string(gesture->type),
166 gesture->fingers, directions);
167 free(directions);
168 return result;
169}
170
171bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
172 // Check that gesture type matches
173 if (target->type != type) {
174 return false;
175 }
176
177 // Check that finger count matches
178 if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
179 return false;
180 }
181
182 return true;
183}
184
185bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
186 // Check type and fingers
187 if (!gesture_check(target, to_match->type, to_match->fingers)) {
188 return false;
189 }
190
191 // Enforce exact matches ...
192 if (exact && target->directions != to_match->directions) {
193 return false;
194 }
195
196 // ... or ensure all target directions are matched
197 return (target->directions & to_match->directions) == target->directions;
198}
199
200bool gesture_equal(struct gesture *a, struct gesture *b) {
201 return a->type == b->type
202 && a->fingers == b->fingers
203 && a->directions == b->directions;
204}
205
206// Return count of set bits in directions bit field.
207static uint8_t gesture_directions_count(uint32_t directions) {
208 uint8_t count = 0;
209 for (; directions; directions >>= 1) {
210 count += directions & 1;
211 }
212 return count;
213}
214
215// Compare direction bit count of two direction.
216static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
217 return gesture_directions_count(a) - gesture_directions_count(b);
218}
219
220// Compare two direction based on their direction bit count
221int8_t gesture_compare(struct gesture *a, struct gesture *b) {
222 return gesture_directions_compare(a->directions, b->directions);
223}
224
225void gesture_tracker_begin(struct gesture_tracker *tracker,
226 enum gesture_type type, uint8_t fingers) {
227 tracker->type = type;
228 tracker->fingers = fingers;
229
230 tracker->dx = 0.0;
231 tracker->dy = 0.0;
232 tracker->scale = 1.0;
233 tracker->rotation = 0.0;
234
235 sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
236 gesture_type_string(type), fingers);
237}
238
239bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
240 return tracker->type == type;
241}
242
243void gesture_tracker_update(struct gesture_tracker *tracker,
244 double dx, double dy, double scale, double rotation) {
245 if (tracker->type == GESTURE_TYPE_HOLD) {
246 sway_assert(false, "hold does not update.");
247 return;
248 }
249
250 tracker->dx += dx;
251 tracker->dy += dy;
252
253 if (tracker->type == GESTURE_TYPE_PINCH) {
254 tracker->scale = scale;
255 tracker->rotation += rotation;
256 }
257
258 sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
259 gesture_type_string(tracker->type),
260 tracker->fingers,
261 tracker->dx, tracker->dy,
262 tracker->scale, tracker->rotation);
263}
264
265void gesture_tracker_cancel(struct gesture_tracker *tracker) {
266 sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
267 gesture_type_string(tracker->type), tracker->fingers);
268
269 tracker->type = GESTURE_TYPE_NONE;
270}
271
272struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
273 struct gesture *result = calloc(1, sizeof(struct gesture));
274
275 // Ignore gesture under some threshold
276 // TODO: Make configurable
277 const double min_rotation = 5;
278 const double min_scale_delta = 0.1;
279
280 // Determine direction
281 switch(tracker->type) {
282 // Gestures with scale and rotation
283 case GESTURE_TYPE_PINCH:
284 if (tracker->rotation > min_rotation) {
285 result->directions |= GESTURE_DIRECTION_CLOCKWISE;
286 }
287 if (tracker->rotation < -min_rotation) {
288 result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
289 }
290
291 if (tracker->scale > (1.0 + min_scale_delta)) {
292 result->directions |= GESTURE_DIRECTION_OUTWARD;
293 }
294 if (tracker->scale < (1.0 - min_scale_delta)) {
295 result->directions |= GESTURE_DIRECTION_INWARD;
296 }
297 __attribute__ ((fallthrough));
298 // Gestures with dx and dy
299 case GESTURE_TYPE_SWIPE:
300 if (fabs(tracker->dx) > fabs(tracker->dy)) {
301 if (tracker->dx > 0) {
302 result->directions |= GESTURE_DIRECTION_RIGHT;
303 } else {
304 result->directions |= GESTURE_DIRECTION_LEFT;
305 }
306 } else {
307 if (tracker->dy > 0) {
308 result->directions |= GESTURE_DIRECTION_DOWN;
309 } else {
310 result->directions |= GESTURE_DIRECTION_UP;
311 }
312 }
313 // Gesture without any direction
314 case GESTURE_TYPE_HOLD:
315 break;
316 // Not tracking any gesture
317 case GESTURE_TYPE_NONE:
318 sway_assert(false, "Not tracking any gesture.");
319 return result;
320 }
321
322 result->type = tracker->type;
323 result->fingers = tracker->fingers;
324
325 char *description = gesture_to_string(result);
326 sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
327 free(description);
328
329 tracker->type = GESTURE_TYPE_NONE;
330
331 return result;
332}