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