Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-input-remote"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <glib/gi18n-lib.h>
10 : : #include <gtk/gtk.h>
11 : : #include <libvalent-core.h>
12 : : #include <libvalent-input.h>
13 : :
14 : : #include "valent-input-remote.h"
15 : : #include "valent-ui-utils.h"
16 : : #include "valent-ui-utils-private.h"
17 : :
18 : : #define CAPTURE_THRESHOLD_MS 50
19 : :
20 : :
21 : : struct _ValentInputRemote
22 : : {
23 : : AdwWindow parent_instance;
24 : :
25 : : GListModel *adapters;
26 : : ValentInputAdapter *adapter;
27 : :
28 : : /* Pointer State */
29 : : unsigned int claimed : 1;
30 : : uint32_t timestamp;
31 : :
32 : : double last_x;
33 : : double last_y;
34 : : double last_v;
35 : : int scale;
36 : :
37 : : /* template */
38 : : GtkDropDown *input_adapter;
39 : : GtkWidget *editor;
40 : : GtkEventController *keyboard;
41 : : GtkWidget *touchpad;
42 : : GtkGesture *pointer_scroll;
43 : : GtkGesture *touch_single;
44 : : GtkGesture *touch_double;
45 : : GtkGesture *touch_triple;
46 : : };
47 : :
48 [ + + + - ]: 77 : G_DEFINE_FINAL_TYPE (ValentInputRemote, valent_input_remote, ADW_TYPE_WINDOW)
49 : :
50 : : enum {
51 : : PROP_0,
52 : : PROP_ADAPTERS,
53 : : N_PROPERTIES
54 : : };
55 : :
56 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
57 : :
58 : :
59 : : static inline gboolean
60 : 2 : valent_input_remote_check_adapter (ValentInputRemote *self)
61 : : {
62 [ + - ]: 2 : g_assert (VALENT_IS_INPUT_REMOTE (self));
63 : :
64 [ + + ]: 2 : if G_UNLIKELY (self->adapter == NULL)
65 : : {
66 : 1 : self->claimed = FALSE;
67 : 1 : self->timestamp = 0;
68 : 1 : self->last_x = 0.0;
69 : 1 : self->last_y = 0.0;
70 : 1 : self->last_v = 0.0;
71 : :
72 : 1 : return FALSE;
73 : : }
74 : :
75 : : return TRUE;
76 : : }
77 : :
78 : : /*
79 : : * Keyboard Input
80 : : */
81 : : static gboolean
82 : 0 : on_key_pressed (GtkEventControllerKey *controller,
83 : : unsigned int keyval,
84 : : unsigned int keycode,
85 : : GdkModifierType state,
86 : : ValentInputRemote *self)
87 : : {
88 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
89 : :
90 [ # # ]: 0 : if (valent_input_remote_check_adapter (self))
91 : 0 : valent_input_adapter_keyboard_keysym (self->adapter, keyval, TRUE);
92 : :
93 : 0 : return TRUE;
94 : : }
95 : :
96 : : static gboolean
97 : 0 : on_key_released (GtkEventControllerKey *controller,
98 : : unsigned int keyval,
99 : : unsigned int keycode,
100 : : GdkModifierType state,
101 : : ValentInputRemote *self)
102 : : {
103 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
104 : :
105 [ # # ]: 0 : if (valent_input_remote_check_adapter (self))
106 : 0 : valent_input_adapter_keyboard_keysym (self->adapter, keyval, FALSE);
107 : :
108 : 0 : return TRUE;
109 : : }
110 : :
111 : : /*
112 : : * Pointer Input
113 : : */
114 : : static inline void
115 : 0 : get_last_update_time (GtkGesture *gesture,
116 : : GdkEventSequence *sequence,
117 : : uint32_t *timestamp)
118 : : {
119 : 0 : GdkEvent *event = NULL;
120 : :
121 [ # # ]: 0 : if (sequence != NULL)
122 : 0 : event = gtk_gesture_get_last_event (gesture, sequence);
123 : :
124 [ # # ]: 0 : if (event != NULL)
125 : 0 : *timestamp = gdk_event_get_time (event);
126 : 0 : }
127 : :
128 : : static inline gboolean
129 : 0 : calculate_delta (ValentInputRemote *self,
130 : : double dx,
131 : : double dy,
132 : : uint32_t dt,
133 : : double *cx,
134 : : double *cy)
135 : : {
136 : 0 : double dr, v, m;
137 : :
138 : 0 : dr = sqrt (pow (dx, 2) + pow (dy, 2));
139 : 0 : v = dr / dt;
140 : :
141 [ # # # # ]: 0 : if (!G_APPROX_VALUE (self->last_v, 0.0, 0.01))
142 : 0 : self->last_v = (v + self->last_v) / 2;
143 : : else
144 : 0 : self->last_v = v;
145 : :
146 : : // TODO: acceleration setting
147 : 0 : m = pow (self->last_v, 1.0);
148 : 0 : m = fmin (4.0, fmax (m, 0.25));
149 : :
150 : 0 : *cx = round (dx * m);
151 : 0 : *cy = round (dy * m);
152 : :
153 : 0 : return dt >= CAPTURE_THRESHOLD_MS;
154 : : }
155 : :
156 : : static inline void
157 : 0 : valent_input_remote_pointer_reset (ValentInputRemote *self)
158 : : {
159 : 0 : self->claimed = FALSE;
160 : 0 : self->last_v = 0.0;
161 : 0 : self->last_x = 0.0;
162 : 0 : self->last_y = 0.0;
163 : 0 : self->timestamp = 0;
164 : 0 : }
165 : :
166 : : /*
167 : : * Scroll Mapping
168 : : */
169 : : static gboolean
170 : 0 : on_scroll (GtkEventControllerScroll *controller,
171 : : double dx,
172 : : double dy,
173 : : ValentInputRemote *self)
174 : : {
175 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
176 : :
177 [ # # ]: 0 : if (valent_input_remote_check_adapter (self))
178 : 0 : valent_input_adapter_pointer_axis (self->adapter, dx, dy);
179 : :
180 : 0 : return TRUE;
181 : : }
182 : :
183 : : /*
184 : : * Pointer Button Mapping
185 : : *
186 : : * This gesture maps pointer button presses and releases directly, except in the
187 : : * case of a press-move sequence of the primary button, which is used to emulate
188 : : * touchpad motion.
189 : : */
190 : : static void
191 : 0 : on_single_begin (GtkGestureDrag *gesture,
192 : : double start_x,
193 : : double start_y,
194 : : ValentInputRemote *self)
195 : : {
196 : 0 : GtkGestureSingle *single = GTK_GESTURE_SINGLE (gesture);
197 : 0 : unsigned int button = 0;
198 : :
199 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
200 : :
201 [ # # ]: 0 : if (!valent_input_remote_check_adapter (self))
202 : : return;
203 : :
204 : : /* Relative pointer motion is only emulated for the primary button, otherwise
205 : : * presses and releases are mapped directly to the adapter. */
206 : 0 : button = gtk_gesture_single_get_current_button (single);
207 : :
208 [ # # ]: 0 : if (button == GDK_BUTTON_PRIMARY)
209 : : {
210 : 0 : GdkEventSequence *sequence = NULL;
211 : 0 : uint32_t timestamp = 0;
212 : :
213 : 0 : sequence = gtk_gesture_single_get_current_sequence (single);
214 : 0 : get_last_update_time (GTK_GESTURE (gesture), sequence, ×tamp);
215 : :
216 : 0 : self->last_x = start_x;
217 : 0 : self->last_y = start_y;
218 : 0 : self->timestamp = timestamp;
219 : : }
220 : :
221 : : /* Always pass through the button press, since pointer motion is only
222 : : * emulated behaviour. */
223 : 0 : valent_input_adapter_pointer_button (self->adapter, button, TRUE);
224 : : }
225 : :
226 : : static void
227 : 0 : on_single_update (GtkGesture *gesture,
228 : : GdkEventSequence *sequence,
229 : : ValentInputRemote *self)
230 : : {
231 : 0 : unsigned int button = 0;
232 : 0 : uint32_t timestamp = 0;
233 : 0 : double x, y;
234 : 0 : double dx, dy, dt;
235 : 0 : double cx, cy;
236 : :
237 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
238 : :
239 [ # # ]: 0 : if (!valent_input_remote_check_adapter (self))
240 : 0 : return;
241 : :
242 : 0 : button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
243 : :
244 [ # # ]: 0 : if (button != GDK_BUTTON_PRIMARY)
245 : : return;
246 : :
247 : 0 : get_last_update_time (gesture, sequence, ×tamp);
248 : 0 : gtk_gesture_get_point (gesture, sequence, &x, &y);
249 : :
250 : 0 : dt = timestamp - self->timestamp;
251 : 0 : dx = (x - self->last_x) * self->scale;
252 : 0 : dy = (y - self->last_y) * self->scale;
253 : :
254 [ # # ]: 0 : if (!calculate_delta (self, dx, dy, dt, &cx, &cy))
255 : : return;
256 : :
257 [ # # # # : 0 : if (dx >= 1.0 || dx <= -1.0 || dy >= 1.0 || dy <= -1.0)
# # # # ]
258 : : {
259 : 0 : self->claimed = TRUE;
260 : 0 : gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
261 : :
262 : 0 : self->last_x = x;
263 : 0 : self->last_y = y;
264 : 0 : self->timestamp = timestamp;
265 : :
266 : 0 : valent_input_adapter_pointer_motion (self->adapter, cx, cy);
267 : : }
268 : : }
269 : :
270 : : static void
271 : 0 : on_single_end (GtkGestureDrag *gesture,
272 : : double offset_x,
273 : : double offset_y,
274 : : ValentInputRemote *self)
275 : : {
276 : 0 : unsigned int button = 0;
277 : :
278 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
279 : :
280 [ # # ]: 0 : if (!valent_input_remote_check_adapter (self))
281 : : return;
282 : :
283 : 0 : button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
284 : 0 : valent_input_adapter_pointer_button (self->adapter, button, FALSE);
285 : 0 : valent_input_remote_pointer_reset (self);
286 : : }
287 : :
288 : : /*
289 : : * Touchpad Emulation
290 : : *
291 : : * These callbacks map gestures on the "touchpad" area to events including:
292 : : *
293 : : * - two-finger tap -> right click
294 : : * - three-finger tap -> middle click
295 : : */
296 : : static void
297 : 0 : on_double_begin (GtkGestureDrag *gesture,
298 : : double start_x,
299 : : double start_y,
300 : : ValentInputRemote *self)
301 : : {
302 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
303 : :
304 : : // TODO: In order to map two-finger presses directly to the input adapter,
305 : : // the implementation has to handle unpaired press-release sequences.
306 : : #if 0
307 : : if (!valent_input_remote_check_adapter (self))
308 : : return;
309 : :
310 : : valent_input_adapter_pointer_button (self->adapter,
311 : : GDK_BUTTON_SECONDARY,
312 : : TRUE);
313 : : #endif
314 : 0 : }
315 : :
316 : : static void
317 : 0 : on_double_end (GtkGestureDrag *gesture,
318 : : double offset_x,
319 : : double offset_y,
320 : : ValentInputRemote *self)
321 : : {
322 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
323 : :
324 [ # # ]: 0 : if (!valent_input_remote_check_adapter (self))
325 : : return;
326 : :
327 : : /* If the two-finger press wasn't claimed as a scroll event on the y-axis,
328 : : * simulate a right click by pressing and releasing the secondary button. */
329 : 0 : valent_input_adapter_pointer_button (self->adapter,
330 : : GDK_BUTTON_SECONDARY,
331 : : TRUE);
332 : 0 : valent_input_adapter_pointer_button (self->adapter,
333 : : GDK_BUTTON_SECONDARY,
334 : : FALSE);
335 : : }
336 : :
337 : : static void
338 : 0 : on_triple_begin (GtkGestureDrag *gesture,
339 : : double offset_x,
340 : : double offset_y,
341 : : ValentInputRemote *self)
342 : : {
343 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
344 : :
345 [ # # ]: 0 : if (!valent_input_remote_check_adapter (self))
346 : : return;
347 : :
348 : : /* Since there is no high-level event for three-finger drags, three-finger
349 : : * presses and releases can be mapped directly. */
350 : 0 : gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
351 : 0 : valent_input_adapter_pointer_button (self->adapter,
352 : : GDK_BUTTON_MIDDLE,
353 : : TRUE);
354 : : }
355 : :
356 : : static void
357 : 0 : on_triple_end (GtkGestureDrag *gesture,
358 : : double offset_x,
359 : : double offset_y,
360 : : ValentInputRemote *self)
361 : : {
362 [ # # ]: 0 : g_assert (VALENT_IS_INPUT_REMOTE (self));
363 : :
364 [ # # ]: 0 : if (!valent_input_remote_check_adapter (self))
365 : : return;
366 : :
367 : 0 : valent_input_adapter_pointer_button (self->adapter,
368 : : GDK_BUTTON_MIDDLE,
369 : : FALSE);
370 : : }
371 : :
372 : : static void
373 : 3 : on_selected_item (GObject *object,
374 : : GParamSpec *pspec,
375 : : ValentInputRemote *self)
376 : : {
377 : 3 : ValentInputAdapter *adapter = NULL;
378 : :
379 [ + - ]: 3 : g_assert (VALENT_IS_INPUT_REMOTE (self));
380 : :
381 : 3 : adapter = gtk_drop_down_get_selected_item (GTK_DROP_DOWN (object));
382 : :
383 [ + + ]: 3 : if (g_set_object (&self->adapter, adapter))
384 : 2 : valent_input_remote_check_adapter (self);
385 : 3 : }
386 : :
387 : : static char *
388 : 2 : dup_adapter_name (ValentInputAdapter *adapter)
389 : : {
390 : 2 : GObject *object = NULL;
391 : 2 : GParamSpec *pspec = NULL;
392 : 4 : g_autofree char *name = NULL;
393 : :
394 [ + - ]: 2 : g_assert (VALENT_IS_INPUT_ADAPTER (adapter));
395 : :
396 : 2 : object = valent_extension_get_object (VALENT_EXTENSION (adapter));
397 : :
398 [ - + ]: 2 : if (object != NULL)
399 : 0 : pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), "name");
400 : :
401 [ # # ]: 0 : if (pspec != NULL)
402 : 0 : g_object_get (object, "name", &name, NULL);
403 : :
404 [ + - ]: 2 : if (name == NULL)
405 [ - + ]: 4 : return g_strdup (G_OBJECT_TYPE_NAME (adapter));
406 : :
407 : 0 : return g_steal_pointer (&name);
408 : : }
409 : :
410 : : /*
411 : : * GObject
412 : : */
413 : : static void
414 : 1 : valent_input_remote_constructed (GObject *object)
415 : : {
416 : 1 : ValentInputRemote *self = VALENT_INPUT_REMOTE (object);
417 : 2 : g_autoptr (GtkExpression) expression = NULL;
418 : :
419 : 1 : expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
420 : : 0, NULL,
421 : : G_CALLBACK (dup_adapter_name),
422 : : NULL, NULL);
423 : :
424 : 1 : gtk_drop_down_set_expression (self->input_adapter, expression);
425 : 1 : gtk_drop_down_set_model (self->input_adapter, self->adapters);
426 : :
427 [ + - ]: 1 : G_OBJECT_CLASS (valent_input_remote_parent_class)->constructed (object);
428 : 1 : }
429 : :
430 : : static void
431 : 1 : valent_input_remote_dispose (GObject *object)
432 : : {
433 : 1 : ValentInputRemote *self = VALENT_INPUT_REMOTE (object);
434 : :
435 [ - + ]: 1 : g_clear_object (&self->adapter);
436 [ + - ]: 1 : g_clear_object (&self->adapters);
437 : :
438 : 1 : gtk_widget_dispose_template (GTK_WIDGET (object), VALENT_TYPE_INPUT_REMOTE);
439 : :
440 : 1 : G_OBJECT_CLASS (valent_input_remote_parent_class)->dispose (object);
441 : 1 : }
442 : :
443 : : static void
444 : 1 : valent_input_remote_get_property (GObject *object,
445 : : guint prop_id,
446 : : GValue *value,
447 : : GParamSpec *pspec)
448 : : {
449 : 1 : ValentInputRemote *self = VALENT_INPUT_REMOTE (object);
450 : :
451 [ + - ]: 1 : switch (prop_id)
452 : : {
453 : 1 : case PROP_ADAPTERS:
454 : 1 : g_value_set_object (value, self->adapters);
455 : 1 : break;
456 : :
457 : 0 : default:
458 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
459 : : }
460 : 1 : }
461 : :
462 : : static void
463 : 1 : valent_input_remote_set_property (GObject *object,
464 : : guint prop_id,
465 : : const GValue *value,
466 : : GParamSpec *pspec)
467 : : {
468 : 1 : ValentInputRemote *self = VALENT_INPUT_REMOTE (object);
469 : :
470 [ + - ]: 1 : switch (prop_id)
471 : : {
472 : 1 : case PROP_ADAPTERS:
473 : 1 : self->adapters = g_value_dup_object (value);
474 : 1 : break;
475 : :
476 : 0 : default:
477 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
478 : : }
479 : 1 : }
480 : :
481 : : static void
482 : 1 : valent_input_remote_class_init (ValentInputRemoteClass *klass)
483 : : {
484 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
485 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
486 : :
487 : 1 : object_class->constructed = valent_input_remote_constructed;
488 : 1 : object_class->dispose = valent_input_remote_dispose;
489 : 1 : object_class->get_property = valent_input_remote_get_property;
490 : 1 : object_class->set_property = valent_input_remote_set_property;
491 : :
492 : 1 : gtk_widget_class_set_template_from_resource (widget_class, "/ca/andyholmes/Valent/ui/valent-input-remote.ui");
493 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, input_adapter);
494 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, editor);
495 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, keyboard);
496 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, pointer_scroll);
497 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, touchpad);
498 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, touch_single);
499 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, touch_double);
500 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentInputRemote, touch_triple);
501 : :
502 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_selected_item);
503 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_key_pressed);
504 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_key_released);
505 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_scroll);
506 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_single_begin);
507 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_single_update);
508 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_single_end);
509 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_double_begin);
510 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_double_end);
511 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_triple_begin);
512 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_triple_end);
513 : :
514 : 2 : properties [PROP_ADAPTERS] =
515 : 1 : g_param_spec_object ("adapters", NULL, NULL,
516 : : G_TYPE_LIST_MODEL,
517 : : (G_PARAM_READWRITE |
518 : : G_PARAM_CONSTRUCT_ONLY |
519 : : G_PARAM_EXPLICIT_NOTIFY |
520 : : G_PARAM_STATIC_STRINGS));
521 : :
522 : 1 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
523 : 1 : }
524 : :
525 : : static void
526 : 1 : valent_input_remote_init (ValentInputRemote *self)
527 : : {
528 : 1 : gtk_widget_init_template (GTK_WIDGET (self));
529 : :
530 : 1 : gtk_gesture_group (self->touch_single, self->touch_double);
531 : 1 : gtk_gesture_group (self->touch_single, self->touch_triple);
532 : :
533 : 1 : self->scale = gtk_widget_get_scale_factor (GTK_WIDGET (self));
534 : 1 : }
535 : :
|