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