1 /* GDK - The GIMP Drawing Kit
2 * Copyright (C) 2009 Carlos Garnacho <carlosg@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <math.h>
21
22 #include "gdkdeviceprivate.h"
23 #include "gdkdevicetool.h"
24 #include "gdkdisplayprivate.h"
25 #include "gdkinternals.h"
26 #include "gdkintl.h"
27 #include "gdkkeysprivate.h"
28
29 /**
30 * GdkDevice:
31 *
32 * The `GdkDevice` object represents an input device, such
33 * as a keyboard, a mouse, or a touchpad.
34 *
35 * See the [class@Gdk.Seat] documentation for more information
36 * about the various kinds of devices, and their relationships.
37 */
38
39 typedef struct _GdkAxisInfo GdkAxisInfo;
40
41 struct _GdkAxisInfo
42 {
43 GdkAxisUse use;
44 double min_axis;
45 double max_axis;
46 double min_value;
47 double max_value;
48 double resolution;
49 };
50
51 enum {
52 CHANGED,
53 TOOL_CHANGED,
54 LAST_SIGNAL
55 };
56
57 static guint signals [LAST_SIGNAL] = { 0 };
58
59
60 static void gdk_device_finalize (GObject *object);
61 static void gdk_device_dispose (GObject *object);
62 static void gdk_device_set_property (GObject *object,
63 guint prop_id,
64 const GValue *value,
65 GParamSpec *pspec);
66 static void gdk_device_get_property (GObject *object,
67 guint prop_id,
68 GValue *value,
69 GParamSpec *pspec);
70
71
72 G_DEFINE_ABSTRACT_TYPE (GdkDevice, gdk_device, G_TYPE_OBJECT)
73
74 enum {
75 PROP_0,
76 PROP_DISPLAY,
77 PROP_NAME,
78 PROP_SOURCE,
79 PROP_HAS_CURSOR,
80 PROP_N_AXES,
81 PROP_VENDOR_ID,
82 PROP_PRODUCT_ID,
83 PROP_SEAT,
84 PROP_NUM_TOUCHES,
85 PROP_TOOL,
86 PROP_DIRECTION,
87 PROP_HAS_BIDI_LAYOUTS,
88 PROP_CAPS_LOCK_STATE,
89 PROP_NUM_LOCK_STATE,
90 PROP_SCROLL_LOCK_STATE,
91 PROP_MODIFIER_STATE,
92 LAST_PROP
93 };
94
95 static GParamSpec *device_props[LAST_PROP] = { NULL, };
96
97 static void
gdk_device_class_init(GdkDeviceClass * klass)98 gdk_device_class_init (GdkDeviceClass *klass)
99 {
100 GObjectClass *object_class = G_OBJECT_CLASS (klass);
101
102 object_class->finalize = gdk_device_finalize;
103 object_class->dispose = gdk_device_dispose;
104 object_class->set_property = gdk_device_set_property;
105 object_class->get_property = gdk_device_get_property;
106
107 /**
108 * GdkDevice:display: (attributes org.gtk.Property.get=gdk_device_get_display)
109 *
110 * The `GdkDisplay` the `GdkDevice` pertains to.
111 */
112 device_props[PROP_DISPLAY] =
113 g_param_spec_object ("display",
114 P_("Device Display"),
115 P_("Display which the device belongs to"),
116 GDK_TYPE_DISPLAY,
117 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
118
119 /**
120 * GdkDevice:name: (attributes org.gtk.Property.get=gdk_device_get_name)
121 *
122 * The device name.
123 */
124 device_props[PROP_NAME] =
125 g_param_spec_string ("name",
126 P_("Device name"),
127 P_("Device name"),
128 NULL,
129 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
130 G_PARAM_STATIC_STRINGS);
131
132 /**
133 * GdkDevice:source: (attributes org.gtk.Property.get=gdk_device_get_source)
134 *
135 * Source type for the device.
136 */
137 device_props[PROP_SOURCE] =
138 g_param_spec_enum ("source",
139 P_("Input source"),
140 P_("Source type for the device"),
141 GDK_TYPE_INPUT_SOURCE,
142 GDK_SOURCE_MOUSE,
143 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
144 G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
145
146 /**
147 * GdkDevice:has-cursor: (attributes org.gtk.Property.get=gdk_device_get_has_cursor)
148 *
149 * Whether the device is represented by a cursor on the screen.
150 */
151 device_props[PROP_HAS_CURSOR] =
152 g_param_spec_boolean ("has-cursor",
153 P_("Whether the device has a cursor"),
154 P_("Whether there is a visible cursor following device motion"),
155 FALSE,
156 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
157 G_PARAM_STATIC_STRINGS);
158
159 /**
160 * GdkDevice:n-axes:
161 *
162 * Number of axes in the device.
163 */
164 device_props[PROP_N_AXES] =
165 g_param_spec_uint ("n-axes",
166 P_("Number of axes in the device"),
167 P_("Number of axes in the device"),
168 0, G_MAXUINT,
169 0,
170 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
171
172 /**
173 * GdkDevice:vendor-id: (attributes org.gtk.Property.get=gdk_device_get_vendor_id)
174 *
175 * Vendor ID of this device.
176 *
177 * See [method@Gdk.Device.get_vendor_id].
178 */
179 device_props[PROP_VENDOR_ID] =
180 g_param_spec_string ("vendor-id",
181 P_("Vendor ID"),
182 P_("Vendor ID"),
183 NULL,
184 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
185 G_PARAM_STATIC_STRINGS);
186
187 /**
188 * GdkDevice:product-id: (attributes org.gtk.Property.get=gdk_device_get_product_id)
189 *
190 * Product ID of this device.
191 *
192 * See [method@Gdk.Device.get_product_id].
193 */
194 device_props[PROP_PRODUCT_ID] =
195 g_param_spec_string ("product-id",
196 P_("Product ID"),
197 P_("Product ID"),
198 NULL,
199 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
200 G_PARAM_STATIC_STRINGS);
201
202 /**
203 * GdkDevice:seat: (attributes org.gtk.Property.get=gdk_device_get_seat)
204 *
205 * `GdkSeat` of this device.
206 */
207 device_props[PROP_SEAT] =
208 g_param_spec_object ("seat",
209 P_("Seat"),
210 P_("Seat"),
211 GDK_TYPE_SEAT,
212 G_PARAM_READWRITE |
213 G_PARAM_STATIC_STRINGS);
214
215 /**
216 * GdkDevice:num-touches: (attributes org.gtk.Property.get=gdk_device_get_num_touches)
217 *
218 * The maximal number of concurrent touches on a touch device.
219 *
220 * Will be 0 if the device is not a touch device or if the number
221 * of touches is unknown.
222 */
223 device_props[PROP_NUM_TOUCHES] =
224 g_param_spec_uint ("num-touches",
225 P_("Number of concurrent touches"),
226 P_("Number of concurrent touches"),
227 0, G_MAXUINT,
228 0,
229 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
230 G_PARAM_STATIC_STRINGS);
231
232 /**
233 * GdkDevice:tool: (attributes org.gtk.Property.get=gdk_device_get_device_tool)
234 *
235 * The `GdkDeviceTool` that is currently used with this device.
236 */
237 device_props[PROP_TOOL] =
238 g_param_spec_object ("tool",
239 P_("Tool"),
240 P_("The tool that is currently used with this device"),
241 GDK_TYPE_DEVICE_TOOL,
242 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
243
244 /**
245 * GdkDevice:direction: (attributes org.gtk.Property.get=gdk_device_get_direction)
246 *
247 * The direction of the current layout.
248 *
249 * This is only relevant for keyboard devices.
250 */
251 device_props[PROP_DIRECTION] =
252 g_param_spec_enum ("direction",
253 P_("Direction"),
254 P_("The direction of the current layout of the keyboard"),
255 PANGO_TYPE_DIRECTION, PANGO_DIRECTION_NEUTRAL,
256 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
257
258 /**
259 * GdkDevice:has-bidi-layouts: (attributes org.gtk.Property.get=gdk_device_has_bidi_layouts)
260 *
261 * Whether the device has both right-to-left and left-to-right layouts.
262 *
263 * This is only relevant for keyboard devices.
264 */
265 device_props[PROP_HAS_BIDI_LAYOUTS] =
266 g_param_spec_boolean ("has-bidi-layouts",
267 P_("Has bidi layouts"),
268 P_("Whether the keyboard has bidi layouts"),
269 FALSE,
270 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
271
272 /**
273 * GdkDevice:caps-lock-state: (attributes org.gtk.Property.get=gdk_device_get_caps_lock_state)
274 *
275 * Whether Caps Lock is on.
276 *
277 * This is only relevant for keyboard devices.
278 */
279 device_props[PROP_CAPS_LOCK_STATE] =
280 g_param_spec_boolean ("caps-lock-state",
281 P_("Caps lock state"),
282 P_("Whether the keyboard caps lock is on"),
283 FALSE,
284 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
285
286 /**
287 * GdkDevice:num-lock-state: (attributes org.gtk.Property.get=gdk_device_get_num_lock_state)
288 *
289 * Whether Num Lock is on.
290 *
291 * This is only relevant for keyboard devices.
292 */
293 device_props[PROP_NUM_LOCK_STATE] =
294 g_param_spec_boolean ("num-lock-state",
295 P_("Num lock state"),
296 P_("Whether the keyboard num lock is on"),
297 FALSE,
298 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
299
300 /**
301 * GdkDevice:scroll-lock-state: (attributes org.gtk.Property.get=gdk_device_get_scroll_lock_state)
302 *
303 * Whether Scroll Lock is on.
304 *
305 * This is only relevant for keyboard devices.
306 */
307 device_props[PROP_SCROLL_LOCK_STATE] =
308 g_param_spec_boolean ("scroll-lock-state",
309 P_("Scroll lock state"),
310 P_("Whether the keyboard scroll lock is on"),
311 FALSE,
312 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
313
314 /**
315 * GdkDevice:modifier-state: (attributes org.gtk.Property.get=gdk_device_get_modifier_state)
316 *
317 * The current modifier state of the device.
318 *
319 * This is only relevant for keyboard devices.
320 */
321 device_props[PROP_MODIFIER_STATE] =
322 g_param_spec_flags ("modifier-state",
323 P_("Modifier state"),
324 P_("The modifier state of the keyboard"),
325 GDK_TYPE_MODIFIER_TYPE, 0,
326 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
327
328 g_object_class_install_properties (object_class, LAST_PROP, device_props);
329
330 /**
331 * GdkDevice::changed:
332 * @device: the `GdkDevice`
333 *
334 * Emitted either when the the number of either axes or keys changes.
335 *
336 * On X11 this will normally happen when the physical device
337 * routing events through the logical device changes (for
338 * example, user switches from the USB mouse to a tablet); in
339 * that case the logical device will change to reflect the axes
340 * and keys on the new physical device.
341 */
342 signals[CHANGED] =
343 g_signal_new (g_intern_static_string ("changed"),
344 G_TYPE_FROM_CLASS (object_class),
345 G_SIGNAL_RUN_LAST,
346 0, NULL, NULL,
347 NULL,
348 G_TYPE_NONE, 0);
349
350 /**
351 * GdkDevice::tool-changed:
352 * @device: the `GdkDevice`
353 * @tool: The new current tool
354 *
355 * Emitted on pen/eraser devices whenever tools enter or leave proximity.
356 */
357 signals[TOOL_CHANGED] =
358 g_signal_new (g_intern_static_string ("tool-changed"),
359 G_TYPE_FROM_CLASS (object_class),
360 G_SIGNAL_RUN_LAST,
361 0, NULL, NULL,
362 NULL,
363 G_TYPE_NONE, 1, GDK_TYPE_DEVICE_TOOL);
364 }
365
366 static void
gdk_device_init(GdkDevice * device)367 gdk_device_init (GdkDevice *device)
368 {
369 device->axes = g_array_new (FALSE, TRUE, sizeof (GdkAxisInfo));
370 }
371
372 static void
gdk_device_finalize(GObject * object)373 gdk_device_finalize (GObject *object)
374 {
375 GdkDevice *device = GDK_DEVICE (object);
376
377 if (device->axes)
378 {
379 g_array_free (device->axes, TRUE);
380 device->axes = NULL;
381 }
382
383 g_clear_pointer (&device->name, g_free);
384 g_clear_pointer (&device->vendor_id, g_free);
385 g_clear_pointer (&device->product_id, g_free);
386
387 G_OBJECT_CLASS (gdk_device_parent_class)->finalize (object);
388 }
389
390 static void
gdk_device_dispose(GObject * object)391 gdk_device_dispose (GObject *object)
392 {
393 GdkDevice *device = GDK_DEVICE (object);
394 GdkDevice *associated = device->associated;
395
396 if (associated)
397 _gdk_device_remove_physical_device (associated, device);
398
399 if (associated)
400 {
401 device->associated = NULL;
402
403 if (associated->associated == device)
404 _gdk_device_set_associated_device (associated, NULL);
405
406 g_object_unref (associated);
407 }
408
409 g_clear_object (&device->last_tool);
410
411 G_OBJECT_CLASS (gdk_device_parent_class)->dispose (object);
412 }
413
414 static void
gdk_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)415 gdk_device_set_property (GObject *object,
416 guint prop_id,
417 const GValue *value,
418 GParamSpec *pspec)
419 {
420 GdkDevice *device = GDK_DEVICE (object);
421
422 switch (prop_id)
423 {
424 case PROP_DISPLAY:
425 device->display = g_value_get_object (value);
426 break;
427 case PROP_NAME:
428 g_free (device->name);
429
430 device->name = g_value_dup_string (value);
431 break;
432 case PROP_SOURCE:
433 device->source = g_value_get_enum (value);
434 break;
435 case PROP_HAS_CURSOR:
436 device->has_cursor = g_value_get_boolean (value);
437 break;
438 case PROP_VENDOR_ID:
439 device->vendor_id = g_value_dup_string (value);
440 break;
441 case PROP_PRODUCT_ID:
442 device->product_id = g_value_dup_string (value);
443 break;
444 case PROP_SEAT:
445 device->seat = g_value_get_object (value);
446 break;
447 case PROP_NUM_TOUCHES:
448 device->num_touches = g_value_get_uint (value);
449 break;
450 default:
451 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
452 break;
453 }
454 }
455
456 static void
gdk_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)457 gdk_device_get_property (GObject *object,
458 guint prop_id,
459 GValue *value,
460 GParamSpec *pspec)
461 {
462 GdkDevice *device = GDK_DEVICE (object);
463
464 switch (prop_id)
465 {
466 case PROP_DISPLAY:
467 g_value_set_object (value, device->display);
468 break;
469 case PROP_NAME:
470 g_value_set_string (value, device->name);
471 break;
472 case PROP_SOURCE:
473 g_value_set_enum (value, device->source);
474 break;
475 case PROP_HAS_CURSOR:
476 g_value_set_boolean (value, device->has_cursor);
477 break;
478 case PROP_N_AXES:
479 g_value_set_uint (value, device->axes->len);
480 break;
481 case PROP_VENDOR_ID:
482 g_value_set_string (value, device->vendor_id);
483 break;
484 case PROP_PRODUCT_ID:
485 g_value_set_string (value, device->product_id);
486 break;
487 case PROP_SEAT:
488 g_value_set_object (value, device->seat);
489 break;
490 case PROP_NUM_TOUCHES:
491 g_value_set_uint (value, device->num_touches);
492 break;
493 case PROP_TOOL:
494 g_value_set_object (value, device->last_tool);
495 break;
496 case PROP_DIRECTION:
497 g_value_set_enum (value, gdk_device_get_direction (device));
498 break;
499 case PROP_HAS_BIDI_LAYOUTS:
500 g_value_set_boolean (value, gdk_device_has_bidi_layouts (device));
501 break;
502 case PROP_CAPS_LOCK_STATE:
503 g_value_set_boolean (value, gdk_device_get_caps_lock_state (device));
504 break;
505 case PROP_NUM_LOCK_STATE:
506 g_value_set_boolean (value, gdk_device_get_num_lock_state (device));
507 break;
508 case PROP_SCROLL_LOCK_STATE:
509 g_value_set_boolean (value, gdk_device_get_scroll_lock_state (device));
510 break;
511 case PROP_MODIFIER_STATE:
512 g_value_set_flags (value, gdk_device_get_modifier_state (device));
513 break;
514 default:
515 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
516 break;
517 }
518 }
519
520 /**
521 * gdk_device_get_surface_at_position:
522 * @device: pointer `GdkDevice` to query info to
523 * @win_x: (out) (optional): return location for the X coordinate
524 * of the device location relative to the surface origin
525 * @win_y: (out) (optional): return location for the Y coordinate
526 * of the device location relative to the surface origin
527 *
528 * Obtains the surface underneath @device, returning the location of the
529 * device in @win_x and @win_y.
530 *
531 * Returns %NULL if the surface tree under @device is not known to GDK
532 * (for example, belongs to another application).
533 *
534 * Returns: (nullable) (transfer none): the `GdkSurface` under the
535 * device position
536 */
537 GdkSurface *
gdk_device_get_surface_at_position(GdkDevice * device,double * win_x,double * win_y)538 gdk_device_get_surface_at_position (GdkDevice *device,
539 double *win_x,
540 double *win_y)
541 {
542 double tmp_x, tmp_y;
543 GdkSurface *surface;
544
545 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
546 g_return_val_if_fail (device->source != GDK_SOURCE_KEYBOARD, NULL);
547
548 surface = _gdk_device_surface_at_position (device, &tmp_x, &tmp_y, NULL);
549
550 if (win_x)
551 *win_x = tmp_x;
552 if (win_y)
553 *win_y = tmp_y;
554
555 return surface;
556 }
557
558 /**
559 * gdk_device_get_name:
560 * @device: a GdkDevice`
561 *
562 * The name of the device, suitable for showing in a user interface.
563 *
564 * Returns: a name
565 */
566 const char *
gdk_device_get_name(GdkDevice * device)567 gdk_device_get_name (GdkDevice *device)
568 {
569 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
570
571 return device->name;
572 }
573
574 /**
575 * gdk_device_get_has_cursor: (attributes org.gtk.Method.get_property=has-cursor)
576 * @device: a `GdkDevice`
577 *
578 * Determines whether the pointer follows device motion.
579 *
580 * This is not meaningful for keyboard devices, which
581 * don't have a pointer.
582 *
583 * Returns: %TRUE if the pointer follows device motion
584 */
585 gboolean
gdk_device_get_has_cursor(GdkDevice * device)586 gdk_device_get_has_cursor (GdkDevice *device)
587 {
588 g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
589
590 return device->has_cursor;
591 }
592
593 /**
594 * gdk_device_get_source: (attributes org.gtk.Method.get_property=source)
595 * @device: a `GdkDevice`
596 *
597 * Determines the type of the device.
598 *
599 * Returns: a `GdkInputSource`
600 */
601 GdkInputSource
gdk_device_get_source(GdkDevice * device)602 gdk_device_get_source (GdkDevice *device)
603 {
604 g_return_val_if_fail (GDK_IS_DEVICE (device), 0);
605
606 return device->source;
607 }
608
609 /**
610 * gdk_device_get_axis_use:
611 * @device: a pointer `GdkDevice`
612 * @index_: the index of the axi.
613 *
614 * Returns the axis use for @index_.
615 *
616 * Returns: a `GdkAxisUse` specifying how the axis is used.
617 */
618 GdkAxisUse
gdk_device_get_axis_use(GdkDevice * device,guint index_)619 gdk_device_get_axis_use (GdkDevice *device,
620 guint index_)
621 {
622 GdkAxisInfo *info;
623
624 g_return_val_if_fail (GDK_IS_DEVICE (device), GDK_AXIS_IGNORE);
625 g_return_val_if_fail (device->source != GDK_SOURCE_KEYBOARD, GDK_AXIS_IGNORE);
626 g_return_val_if_fail (index_ < device->axes->len, GDK_AXIS_IGNORE);
627
628 info = &g_array_index (device->axes, GdkAxisInfo, index_);
629
630 return info->use;
631 }
632
633 /**
634 * gdk_device_get_display: (attributes org.gtk.Method.get_property=display)
635 * @device: a `GdkDevice`
636 *
637 * Returns the `GdkDisplay` to which @device pertains.
638 *
639 * Returns: (transfer none): a `GdkDisplay`
640 */
641 GdkDisplay *
gdk_device_get_display(GdkDevice * device)642 gdk_device_get_display (GdkDevice *device)
643 {
644 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
645
646 return device->display;
647 }
648
649 void
_gdk_device_set_associated_device(GdkDevice * device,GdkDevice * associated)650 _gdk_device_set_associated_device (GdkDevice *device,
651 GdkDevice *associated)
652 {
653 g_return_if_fail (GDK_IS_DEVICE (device));
654 g_return_if_fail (associated == NULL || GDK_IS_DEVICE (associated));
655
656 g_set_object (&device->associated, associated);
657 }
658
659 /*
660 * gdk_device_list_physical_devices:
661 * @device: a logical `GdkDevice`
662 *
663 * Returns the list of physical devices attached to the given logical
664 * `GdkDevice`.
665 *
666 * Returns: (nullable) (transfer container) (element-type GdkDevice):
667 * the list of physical devices attached to a logical `GdkDevice`
668 */
669 GList *
gdk_device_list_physical_devices(GdkDevice * device)670 gdk_device_list_physical_devices (GdkDevice *device)
671 {
672 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
673
674 return g_list_copy (device->physical_devices);
675 }
676
677 void
_gdk_device_add_physical_device(GdkDevice * device,GdkDevice * physical)678 _gdk_device_add_physical_device (GdkDevice *device,
679 GdkDevice *physical)
680 {
681 if (!g_list_find (device->physical_devices, physical))
682 device->physical_devices = g_list_prepend (device->physical_devices, physical);
683 }
684
685 void
_gdk_device_remove_physical_device(GdkDevice * device,GdkDevice * physical)686 _gdk_device_remove_physical_device (GdkDevice *device,
687 GdkDevice *physical)
688 {
689 GList *elem;
690
691 elem = g_list_find (device->physical_devices, physical);
692 if (elem == NULL)
693 return;
694
695 device->physical_devices = g_list_delete_link (device->physical_devices, elem);
696 }
697
698 /*
699 * gdk_device_get_n_axes:
700 * @device: a pointer `GdkDevice`
701 *
702 * Returns the number of axes the device currently has.
703 *
704 * Returns: the number of axes.
705 */
706 int
gdk_device_get_n_axes(GdkDevice * device)707 gdk_device_get_n_axes (GdkDevice *device)
708 {
709 g_return_val_if_fail (GDK_IS_DEVICE (device), 0);
710 g_return_val_if_fail (device->source != GDK_SOURCE_KEYBOARD, 0);
711
712 return device->axes->len;
713 }
714
715 /*
716 * gdk_device_get_axis: (skip)
717 * @device: a `GdkDevice`
718 * @axes: (array): pointer to an array of axes
719 * @use: the use to look for
720 * @value: (out): location to store the found value
721 *
722 * Interprets an array of `double` as axis values and get the value
723 * for a given axis use.
724 *
725 * Returns: %TRUE if the given axis use was found, otherwise %FALSE
726 */
727 gboolean
gdk_device_get_axis(GdkDevice * device,double * axes,GdkAxisUse use,double * value)728 gdk_device_get_axis (GdkDevice *device,
729 double *axes,
730 GdkAxisUse use,
731 double *value)
732 {
733 int i;
734
735 g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
736 g_return_val_if_fail (device->source != GDK_SOURCE_KEYBOARD, FALSE);
737
738 if (axes == NULL)
739 return FALSE;
740
741 g_return_val_if_fail (device->axes != NULL, FALSE);
742
743 for (i = 0; i < device->axes->len; i++)
744 {
745 GdkAxisInfo axis_info;
746
747 axis_info = g_array_index (device->axes, GdkAxisInfo, i);
748
749 if (axis_info.use != use)
750 continue;
751
752 if (value)
753 *value = axes[i];
754
755 return TRUE;
756 }
757
758 return FALSE;
759 }
760
761 static GdkEventMask
get_native_grab_event_mask(GdkEventMask grab_mask)762 get_native_grab_event_mask (GdkEventMask grab_mask)
763 {
764 /* Similar to the above but for pointer events only */
765 return
766 GDK_POINTER_MOTION_MASK |
767 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
768 GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
769 GDK_SCROLL_MASK |
770 (grab_mask &
771 ~(GDK_BUTTON_MOTION_MASK |
772 GDK_BUTTON1_MOTION_MASK |
773 GDK_BUTTON2_MOTION_MASK |
774 GDK_BUTTON3_MOTION_MASK));
775 }
776
777 GdkGrabStatus
gdk_device_grab(GdkDevice * device,GdkSurface * surface,gboolean owner_events,GdkEventMask event_mask,GdkCursor * cursor,guint32 time_)778 gdk_device_grab (GdkDevice *device,
779 GdkSurface *surface,
780 gboolean owner_events,
781 GdkEventMask event_mask,
782 GdkCursor *cursor,
783 guint32 time_)
784 {
785 GdkGrabStatus res;
786
787 g_return_val_if_fail (GDK_IS_DEVICE (device), GDK_GRAB_FAILED);
788 g_return_val_if_fail (GDK_IS_SURFACE (surface), GDK_GRAB_FAILED);
789 g_return_val_if_fail (gdk_surface_get_display (surface) == gdk_device_get_display (device), GDK_GRAB_FAILED);
790
791 if (GDK_SURFACE_DESTROYED (surface))
792 return GDK_GRAB_NOT_VIEWABLE;
793
794 res = GDK_DEVICE_GET_CLASS (device)->grab (device,
795 surface,
796 owner_events,
797 get_native_grab_event_mask (event_mask),
798 NULL,
799 cursor,
800 time_);
801
802 if (res == GDK_GRAB_SUCCESS)
803 {
804 GdkDisplay *display;
805 gulong serial;
806
807 display = gdk_surface_get_display (surface);
808 serial = _gdk_display_get_next_serial (display);
809
810 _gdk_display_add_device_grab (display,
811 device,
812 surface,
813 owner_events,
814 event_mask,
815 serial,
816 time_,
817 FALSE);
818 }
819
820 return res;
821 }
822
823 void
gdk_device_ungrab(GdkDevice * device,guint32 time_)824 gdk_device_ungrab (GdkDevice *device,
825 guint32 time_)
826 {
827 g_return_if_fail (GDK_IS_DEVICE (device));
828
829 GDK_DEVICE_GET_CLASS (device)->ungrab (device, time_);
830 }
831
832 /* Private API */
833 void
_gdk_device_reset_axes(GdkDevice * device)834 _gdk_device_reset_axes (GdkDevice *device)
835 {
836 int i;
837
838 for (i = device->axes->len - 1; i >= 0; i--)
839 g_array_remove_index (device->axes, i);
840
841 g_object_notify_by_pspec (G_OBJECT (device), device_props[PROP_N_AXES]);
842 }
843
844 guint
_gdk_device_add_axis(GdkDevice * device,GdkAxisUse use,double min_value,double max_value,double resolution)845 _gdk_device_add_axis (GdkDevice *device,
846 GdkAxisUse use,
847 double min_value,
848 double max_value,
849 double resolution)
850 {
851 GdkAxisInfo axis_info;
852 guint pos;
853
854 axis_info.use = use;
855 axis_info.min_value = min_value;
856 axis_info.max_value = max_value;
857 axis_info.resolution = resolution;
858
859 switch ((guint) use)
860 {
861 case GDK_AXIS_X:
862 case GDK_AXIS_Y:
863 axis_info.min_axis = 0;
864 axis_info.max_axis = 0;
865 break;
866 case GDK_AXIS_XTILT:
867 case GDK_AXIS_YTILT:
868 axis_info.min_axis = -1;
869 axis_info.max_axis = 1;
870 break;
871 default:
872 axis_info.min_axis = 0;
873 axis_info.max_axis = 1;
874 break;
875 }
876
877 device->axes = g_array_append_val (device->axes, axis_info);
878 pos = device->axes->len - 1;
879
880 g_object_notify_by_pspec (G_OBJECT (device), device_props[PROP_N_AXES]);
881
882 return pos;
883 }
884
885 void
_gdk_device_get_axis_info(GdkDevice * device,guint index_,GdkAxisUse * use,double * min_value,double * max_value,double * resolution)886 _gdk_device_get_axis_info (GdkDevice *device,
887 guint index_,
888 GdkAxisUse *use,
889 double *min_value,
890 double *max_value,
891 double *resolution)
892 {
893 GdkAxisInfo *info;
894
895 g_return_if_fail (GDK_IS_DEVICE (device));
896 g_return_if_fail (index_ < device->axes->len);
897
898 info = &g_array_index (device->axes, GdkAxisInfo, index_);
899
900 *use = info->use;
901 *min_value = info->min_value;
902 *max_value = info->max_value;
903 *resolution = info->resolution;
904 }
905
906 static GdkAxisInfo *
find_axis_info(GArray * array,GdkAxisUse use)907 find_axis_info (GArray *array,
908 GdkAxisUse use)
909 {
910 GdkAxisInfo *info;
911 int i;
912
913 for (i = 0; i < GDK_AXIS_LAST; i++)
914 {
915 info = &g_array_index (array, GdkAxisInfo, i);
916
917 if (info->use == use)
918 return info;
919 }
920
921 return NULL;
922 }
923
924 gboolean
_gdk_device_translate_surface_coord(GdkDevice * device,GdkSurface * surface,guint index_,double value,double * axis_value)925 _gdk_device_translate_surface_coord (GdkDevice *device,
926 GdkSurface *surface,
927 guint index_,
928 double value,
929 double *axis_value)
930 {
931 GdkAxisInfo axis_info;
932 GdkAxisInfo *axis_info_x, *axis_info_y;
933 double device_width, device_height;
934 double x_offset, y_offset;
935 double x_scale, y_scale;
936 double x_min, y_min;
937 double x_resolution, y_resolution;
938 double device_aspect;
939 int surface_width, surface_height;
940
941 if (index_ >= device->axes->len)
942 return FALSE;
943
944 axis_info = g_array_index (device->axes, GdkAxisInfo, index_);
945
946 if (axis_info.use != GDK_AXIS_X &&
947 axis_info.use != GDK_AXIS_Y)
948 return FALSE;
949
950 if (axis_info.use == GDK_AXIS_X)
951 {
952 axis_info_x = &axis_info;
953 axis_info_y = find_axis_info (device->axes, GDK_AXIS_Y);
954 if (axis_info_y == NULL)
955 return FALSE;
956 }
957 else
958 {
959 axis_info_x = find_axis_info (device->axes, GDK_AXIS_X);
960 axis_info_y = &axis_info;
961 if (axis_info_x == NULL)
962 return FALSE;
963 }
964
965 device_width = axis_info_x->max_value - axis_info_x->min_value;
966 device_height = axis_info_y->max_value - axis_info_y->min_value;
967
968 x_min = axis_info_x->min_value;
969 y_min = axis_info_y->min_value;
970
971 surface_width = gdk_surface_get_width (surface);
972 surface_height = gdk_surface_get_height (surface);
973
974 x_resolution = axis_info_x->resolution;
975 y_resolution = axis_info_y->resolution;
976
977 /*
978 * Some drivers incorrectly report the resolution of the device
979 * as zero (in partiular linuxwacom < 0.5.3 with usb tablets).
980 * This causes the device_aspect to become NaN and totally
981 * breaks windowed mode. If this is the case, the best we can
982 * do is to assume the resolution is non-zero is equal in both
983 * directions (which is true for many devices). The absolute
984 * value of the resolution doesn't matter since we only use the
985 * ratio.
986 */
987 if (x_resolution == 0 || y_resolution == 0)
988 {
989 x_resolution = 1;
990 y_resolution = 1;
991 }
992
993 device_aspect = (device_height * y_resolution) /
994 (device_width * x_resolution);
995
996 if (device_aspect * surface_width >= surface_height)
997 {
998 /* device taller than surface */
999 x_scale = surface_width / device_width;
1000 y_scale = (x_scale * x_resolution) / y_resolution;
1001
1002 x_offset = 0;
1003 y_offset = - (device_height * y_scale - surface_height) / 2;
1004 }
1005 else
1006 {
1007 /* surface taller than device */
1008 y_scale = surface_height / device_height;
1009 x_scale = (y_scale * y_resolution) / x_resolution;
1010
1011 y_offset = 0;
1012 x_offset = - (device_width * x_scale - surface_width) / 2;
1013 }
1014
1015 if (axis_value)
1016 {
1017 if (axis_info.use == GDK_AXIS_X)
1018 *axis_value = x_offset + x_scale * (value - x_min);
1019 else
1020 *axis_value = y_offset + y_scale * (value - y_min);
1021 }
1022
1023 return TRUE;
1024 }
1025
1026 gboolean
_gdk_device_translate_screen_coord(GdkDevice * device,GdkSurface * surface,double surface_root_x,double surface_root_y,double screen_width,double screen_height,guint index_,double value,double * axis_value)1027 _gdk_device_translate_screen_coord (GdkDevice *device,
1028 GdkSurface *surface,
1029 double surface_root_x,
1030 double surface_root_y,
1031 double screen_width,
1032 double screen_height,
1033 guint index_,
1034 double value,
1035 double *axis_value)
1036 {
1037 GdkAxisInfo axis_info;
1038 double axis_width, scale, offset;
1039
1040 if (index_ >= device->axes->len)
1041 return FALSE;
1042
1043 axis_info = g_array_index (device->axes, GdkAxisInfo, index_);
1044
1045 if (axis_info.use != GDK_AXIS_X &&
1046 axis_info.use != GDK_AXIS_Y)
1047 return FALSE;
1048
1049 axis_width = axis_info.max_value - axis_info.min_value;
1050
1051 if (axis_info.use == GDK_AXIS_X)
1052 {
1053 if (axis_width > 0)
1054 scale = screen_width / axis_width;
1055 else
1056 scale = 1;
1057
1058 offset = - surface_root_x;
1059 }
1060 else
1061 {
1062 if (axis_width > 0)
1063 scale = screen_height / axis_width;
1064 else
1065 scale = 1;
1066
1067 offset = - surface_root_y;
1068 }
1069
1070 if (axis_value)
1071 *axis_value = offset + scale * (value - axis_info.min_value);
1072
1073 return TRUE;
1074 }
1075
1076 gboolean
_gdk_device_translate_axis(GdkDevice * device,guint index_,double value,double * axis_value)1077 _gdk_device_translate_axis (GdkDevice *device,
1078 guint index_,
1079 double value,
1080 double *axis_value)
1081 {
1082 GdkAxisInfo axis_info;
1083 double axis_width, out;
1084
1085 if (index_ >= device->axes->len)
1086 return FALSE;
1087
1088 axis_info = g_array_index (device->axes, GdkAxisInfo, index_);
1089
1090 if (axis_info.use == GDK_AXIS_X ||
1091 axis_info.use == GDK_AXIS_Y)
1092 return FALSE;
1093
1094 axis_width = axis_info.max_value - axis_info.min_value;
1095 out = (axis_info.max_axis * (value - axis_info.min_value) +
1096 axis_info.min_axis * (axis_info.max_value - value)) / axis_width;
1097
1098 if (axis_value)
1099 *axis_value = out;
1100
1101 return TRUE;
1102 }
1103
1104 GdkSurface *
_gdk_device_surface_at_position(GdkDevice * device,double * win_x,double * win_y,GdkModifierType * mask)1105 _gdk_device_surface_at_position (GdkDevice *device,
1106 double *win_x,
1107 double *win_y,
1108 GdkModifierType *mask)
1109 {
1110 return GDK_DEVICE_GET_CLASS (device)->surface_at_position (device,
1111 win_x,
1112 win_y,
1113 mask);
1114 }
1115
1116 /**
1117 * gdk_device_get_vendor_id: (attributes org.gtk.Method.get_property=vendor-id)
1118 * @device: a physical `GdkDevice`
1119 *
1120 * Returns the vendor ID of this device.
1121 *
1122 * This ID is retrieved from the device, and does not change.
1123 *
1124 * This function, together with [method@Gdk.Device.get_product_id],
1125 * can be used to eg. compose `GSettings` paths to store settings
1126 * for this device.
1127 *
1128 * ```c
1129 * static GSettings *
1130 * get_device_settings (GdkDevice *device)
1131 * {
1132 * const char *vendor, *product;
1133 * GSettings *settings;
1134 * GdkDevice *device;
1135 * char *path;
1136 *
1137 * vendor = gdk_device_get_vendor_id (device);
1138 * product = gdk_device_get_product_id (device);
1139 *
1140 * path = g_strdup_printf ("/org/example/app/devices/%s:%s/", vendor, product);
1141 * settings = g_settings_new_with_path (DEVICE_SCHEMA, path);
1142 * g_free (path);
1143 *
1144 * return settings;
1145 * }
1146 * ```
1147 *
1148 * Returns: (nullable): the vendor ID
1149 */
1150 const char *
gdk_device_get_vendor_id(GdkDevice * device)1151 gdk_device_get_vendor_id (GdkDevice *device)
1152 {
1153 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
1154
1155 return device->vendor_id;
1156 }
1157
1158 /**
1159 * gdk_device_get_product_id: (attributes org.gtk.Method.get_property=product-id)
1160 * @device: a physical `GdkDevice`
1161 *
1162 * Returns the product ID of this device.
1163 *
1164 * This ID is retrieved from the device, and does not change.
1165 * See [method@Gdk.Device.get_vendor_id] for more information.
1166 *
1167 * Returns: (nullable): the product ID
1168 */
1169 const char *
gdk_device_get_product_id(GdkDevice * device)1170 gdk_device_get_product_id (GdkDevice *device)
1171 {
1172 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
1173
1174 return device->product_id;
1175 }
1176
1177 void
gdk_device_set_seat(GdkDevice * device,GdkSeat * seat)1178 gdk_device_set_seat (GdkDevice *device,
1179 GdkSeat *seat)
1180 {
1181 g_return_if_fail (GDK_IS_DEVICE (device));
1182 g_return_if_fail (!seat || GDK_IS_SEAT (seat));
1183
1184 if (device->seat == seat)
1185 return;
1186
1187 device->seat = seat;
1188 g_object_notify (G_OBJECT (device), "seat");
1189 }
1190
1191 /**
1192 * gdk_device_get_seat: (attributes org.gtk.Method.get_property=seat)
1193 * @device: A `GdkDevice`
1194 *
1195 * Returns the `GdkSeat` the device belongs to.
1196 *
1197 * Returns: (transfer none): a `GdkSeat`
1198 */
1199 GdkSeat *
gdk_device_get_seat(GdkDevice * device)1200 gdk_device_get_seat (GdkDevice *device)
1201 {
1202 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
1203
1204 return device->seat;
1205 }
1206
1207 void
gdk_device_update_tool(GdkDevice * device,GdkDeviceTool * tool)1208 gdk_device_update_tool (GdkDevice *device,
1209 GdkDeviceTool *tool)
1210 {
1211 g_return_if_fail (GDK_IS_DEVICE (device));
1212
1213 if (g_set_object (&device->last_tool, tool))
1214 {
1215 g_object_notify (G_OBJECT (device), "tool");
1216 g_signal_emit (device, signals[TOOL_CHANGED], 0, tool);
1217 }
1218 }
1219
1220 /**
1221 * gdk_device_get_num_touches:
1222 * @device: a `GdkDevice`
1223 *
1224 * Retrieves the number of touch points associated to @device.
1225 *
1226 * Returns: the number of touch points
1227 */
1228 guint
gdk_device_get_num_touches(GdkDevice * device)1229 gdk_device_get_num_touches (GdkDevice *device)
1230 {
1231 g_return_val_if_fail (GDK_IS_DEVICE (device), 0);
1232
1233 return device->num_touches;
1234 }
1235
1236 /**
1237 * gdk_device_get_device_tool: (attributes org.gtk.Method.get_property=tool)
1238 * @device: a `GdkDevice`
1239 *
1240 * Retrieves the current tool for @device.
1241 *
1242 * Returns: (transfer none): the `GdkDeviceTool`
1243 */
1244 GdkDeviceTool *
gdk_device_get_device_tool(GdkDevice * device)1245 gdk_device_get_device_tool (GdkDevice *device)
1246 {
1247 g_return_val_if_fail (GDK_IS_DEVICE (device), NULL);
1248
1249 return device->last_tool;
1250 }
1251
1252 /**
1253 * gdk_device_get_caps_lock_state: (attributes org.gtk.Method.get_property=caps-lock-state)
1254 * @device: a `GdkDevice`
1255 *
1256 * Retrieves whether the Caps Lock modifier of the keyboard is locked.
1257 *
1258 * This is only relevant for keyboard devices.
1259 *
1260 * Returns: %TRUE if Caps Lock is on for @device
1261 */
1262 gboolean
gdk_device_get_caps_lock_state(GdkDevice * device)1263 gdk_device_get_caps_lock_state (GdkDevice *device)
1264 {
1265 GdkKeymap *keymap = gdk_display_get_keymap (device->display);
1266
1267 if (device->source == GDK_SOURCE_KEYBOARD)
1268 return gdk_keymap_get_caps_lock_state (keymap);
1269
1270 return FALSE;
1271 }
1272
1273 /**
1274 * gdk_device_get_num_lock_state: (attributes org.gtk.Method.get_property=num-lock-state)
1275 * @device: a ``GdkDevice`
1276 *
1277 * Retrieves whether the Num Lock modifier of the keyboard is locked.
1278 *
1279 * This is only relevant for keyboard devices.
1280 *
1281 * Returns: %TRUE if Num Lock is on for @device
1282 */
1283 gboolean
gdk_device_get_num_lock_state(GdkDevice * device)1284 gdk_device_get_num_lock_state (GdkDevice *device)
1285 {
1286 GdkKeymap *keymap = gdk_display_get_keymap (device->display);
1287
1288 if (device->source == GDK_SOURCE_KEYBOARD)
1289 return gdk_keymap_get_num_lock_state (keymap);
1290
1291 return FALSE;
1292 }
1293
1294 /**
1295 * gdk_device_get_scroll_lock_state: (attributes org.gtk.Method.get_property=scroll-lock-state)
1296 * @device: a `GdkDevice`
1297 *
1298 * Retrieves whether the Scroll Lock modifier of the keyboard is locked.
1299 *
1300 * This is only relevant for keyboard devices.
1301 *
1302 * Returns: %TRUE if Scroll Lock is on for @device
1303 */
1304 gboolean
gdk_device_get_scroll_lock_state(GdkDevice * device)1305 gdk_device_get_scroll_lock_state (GdkDevice *device)
1306 {
1307 GdkKeymap *keymap = gdk_display_get_keymap (device->display);
1308
1309 if (device->source == GDK_SOURCE_KEYBOARD)
1310 return gdk_keymap_get_scroll_lock_state (keymap);
1311
1312 return FALSE;
1313 }
1314
1315 /**
1316 * gdk_device_get_modifier_state: (attributes org.gtk.Method.get_property=modifier-state)
1317 * @device: a `GdkDevice`
1318 *
1319 * Retrieves the current modifier state of the keyboard.
1320 *
1321 * This is only relevant for keyboard devices.
1322 *
1323 * Returns: the current modifier state
1324 */
1325 GdkModifierType
gdk_device_get_modifier_state(GdkDevice * device)1326 gdk_device_get_modifier_state (GdkDevice *device)
1327 {
1328 GdkKeymap *keymap = gdk_display_get_keymap (device->display);
1329
1330 if (device->source == GDK_SOURCE_KEYBOARD)
1331 return gdk_keymap_get_modifier_state (keymap);
1332
1333 return 0;
1334 }
1335
1336 /**
1337 * gdk_device_get_direction: (attributes org.gtk.Method.get_property=direction)
1338 * @device: a `GdkDevice`
1339 *
1340 * Returns the direction of effective layout of the keyboard.
1341 *
1342 * This is only relevant for keyboard devices.
1343 *
1344 * The direction of a layout is the direction of the majority
1345 * of its symbols. See [func@Pango.unichar_direction].
1346 *
1347 * Returns: %PANGO_DIRECTION_LTR or %PANGO_DIRECTION_RTL
1348 * if it can determine the direction. %PANGO_DIRECTION_NEUTRAL
1349 * otherwise
1350 */
1351 PangoDirection
gdk_device_get_direction(GdkDevice * device)1352 gdk_device_get_direction (GdkDevice *device)
1353 {
1354 GdkKeymap *keymap = gdk_display_get_keymap (device->display);
1355
1356 if (device->source == GDK_SOURCE_KEYBOARD)
1357 return gdk_keymap_get_direction (keymap);
1358
1359 return PANGO_DIRECTION_NEUTRAL;
1360 }
1361
1362 /**
1363 * gdk_device_has_bidi_layouts: (attributes org.gtk.Method.get_property=has-bidi-layouts)
1364 * @device: a `GdkDevice`
1365 *
1366 * Determines if layouts for both right-to-left and
1367 * left-to-right languages are in use on the keyboard.
1368 *
1369 * This is only relevant for keyboard devices.
1370 *
1371 * Returns: %TRUE if there are layouts with both directions, %FALSE otherwise
1372 */
1373 gboolean
gdk_device_has_bidi_layouts(GdkDevice * device)1374 gdk_device_has_bidi_layouts (GdkDevice *device)
1375 {
1376 GdkKeymap *keymap = gdk_display_get_keymap (device->display);
1377
1378 if (device->source == GDK_SOURCE_KEYBOARD)
1379 return gdk_keymap_have_bidi_layouts (keymap);
1380
1381 return FALSE;
1382 }
1383
1384 void
gdk_device_set_timestamp(GdkDevice * device,guint32 timestamp)1385 gdk_device_set_timestamp (GdkDevice *device,
1386 guint32 timestamp)
1387 {
1388 device->timestamp = timestamp;
1389 }
1390
1391 /**
1392 * gdk_device_get_timestamp:
1393 * @device: a `GdkDevice`
1394 *
1395 * Returns the timestamp of the last activity for this device.
1396 *
1397 * In practice, this means the timestamp of the last event that was
1398 * received from the OS for this device. (GTK may occasionally produce
1399 * events for a device that are not received from the OS, and will not
1400 * update the timestamp).
1401 *
1402 * Returns: the timestamp of the last activity for this device
1403 *
1404 * Since: 4.2
1405 */
1406 guint32
gdk_device_get_timestamp(GdkDevice * device)1407 gdk_device_get_timestamp (GdkDevice *device)
1408 {
1409 g_return_val_if_fail (GDK_IS_DEVICE (device), GDK_CURRENT_TIME);
1410
1411 return device->timestamp;
1412 }
1413