1 | /* GDK - The GIMP Drawing Kit |
2 | * Copyright (C) 2015 Red Hat |
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 | * Author: Carlos Garnacho <carlosg@gnome.org> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include <glib-object.h> |
23 | #include "gdkdisplay.h" |
24 | #include "gdkdevice.h" |
25 | #include "gdkdevicetoolprivate.h" |
26 | #include "gdkseatprivate.h" |
27 | #include "gdkdeviceprivate.h" |
28 | #include "gdkintl.h" |
29 | |
30 | /** |
31 | * GdkSeat: |
32 | * |
33 | * The `GdkSeat` object represents a collection of input devices |
34 | * that belong to a user. |
35 | */ |
36 | |
37 | typedef struct _GdkSeatPrivate GdkSeatPrivate; |
38 | |
39 | struct _GdkSeatPrivate |
40 | { |
41 | GdkDisplay *display; |
42 | }; |
43 | |
44 | enum { |
45 | DEVICE_ADDED, |
46 | DEVICE_REMOVED, |
47 | TOOL_ADDED, |
48 | TOOL_REMOVED, |
49 | N_SIGNALS |
50 | }; |
51 | |
52 | enum { |
53 | PROP_0, |
54 | PROP_DISPLAY, |
55 | N_PROPS |
56 | }; |
57 | |
58 | static guint signals[N_SIGNALS] = { 0 }; |
59 | static GParamSpec *props[N_PROPS] = { NULL }; |
60 | |
61 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkSeat, gdk_seat, G_TYPE_OBJECT) |
62 | |
63 | static void |
64 | gdk_seat_set_property (GObject *object, |
65 | guint prop_id, |
66 | const GValue *value, |
67 | GParamSpec *pspec) |
68 | { |
69 | GdkSeatPrivate *priv = gdk_seat_get_instance_private (GDK_SEAT (object)); |
70 | |
71 | switch (prop_id) |
72 | { |
73 | case PROP_DISPLAY: |
74 | priv->display = g_value_get_object (value); |
75 | break; |
76 | default: |
77 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
78 | break; |
79 | } |
80 | } |
81 | |
82 | static void |
83 | gdk_seat_get_property (GObject *object, |
84 | guint prop_id, |
85 | GValue *value, |
86 | GParamSpec *pspec) |
87 | { |
88 | GdkSeatPrivate *priv = gdk_seat_get_instance_private (GDK_SEAT (object)); |
89 | |
90 | switch (prop_id) |
91 | { |
92 | case PROP_DISPLAY: |
93 | g_value_set_object (value, v_object: priv->display); |
94 | break; |
95 | default: |
96 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
97 | break; |
98 | } |
99 | } |
100 | |
101 | static void |
102 | gdk_seat_class_init (GdkSeatClass *klass) |
103 | { |
104 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
105 | |
106 | object_class->set_property = gdk_seat_set_property; |
107 | object_class->get_property = gdk_seat_get_property; |
108 | |
109 | /** |
110 | * GdkSeat::device-added: |
111 | * @seat: the object on which the signal is emitted |
112 | * @device: the newly added `GdkDevice`. |
113 | * |
114 | * Emitted when a new input device is related to this seat. |
115 | */ |
116 | signals [DEVICE_ADDED] = |
117 | g_signal_new (signal_name: g_intern_static_string (string: "device-added" ), |
118 | G_TYPE_FROM_CLASS (klass), |
119 | signal_flags: G_SIGNAL_RUN_LAST, |
120 | G_STRUCT_OFFSET (GdkSeatClass, device_added), |
121 | NULL, NULL, |
122 | NULL, |
123 | G_TYPE_NONE, n_params: 1, |
124 | GDK_TYPE_DEVICE); |
125 | |
126 | /** |
127 | * GdkSeat::device-removed: |
128 | * @seat: the object on which the signal is emitted |
129 | * @device: the just removed `GdkDevice`. |
130 | * |
131 | * Emitted when an input device is removed (e.g. unplugged). |
132 | */ |
133 | signals [DEVICE_REMOVED] = |
134 | g_signal_new (signal_name: g_intern_static_string (string: "device-removed" ), |
135 | G_TYPE_FROM_CLASS (klass), |
136 | signal_flags: G_SIGNAL_RUN_LAST, |
137 | G_STRUCT_OFFSET (GdkSeatClass, device_removed), |
138 | NULL, NULL, |
139 | NULL, |
140 | G_TYPE_NONE, n_params: 1, |
141 | GDK_TYPE_DEVICE); |
142 | |
143 | /** |
144 | * GdkSeat::tool-added: |
145 | * @seat: the object on which the signal is emitted |
146 | * @tool: the new `GdkDeviceTool` known to the seat |
147 | * |
148 | * Emitted whenever a new tool is made known to the seat. |
149 | * |
150 | * The tool may later be assigned to a device (i.e. on |
151 | * proximity with a tablet). The device will emit the |
152 | * [signal@Gdk.Device::tool-changed] signal accordingly. |
153 | * |
154 | * A same tool may be used by several devices. |
155 | */ |
156 | signals [TOOL_ADDED] = |
157 | g_signal_new (signal_name: g_intern_static_string (string: "tool-added" ), |
158 | G_TYPE_FROM_CLASS (klass), |
159 | signal_flags: G_SIGNAL_RUN_LAST, |
160 | class_offset: 0, NULL, NULL, |
161 | NULL, |
162 | G_TYPE_NONE, n_params: 1, |
163 | GDK_TYPE_DEVICE_TOOL); |
164 | |
165 | /** |
166 | * GdkSeat::tool-removed: |
167 | * @seat: the object on which the signal is emitted |
168 | * @tool: the just removed `GdkDeviceTool` |
169 | * |
170 | * Emitted whenever a tool is no longer known to this @seat. |
171 | */ |
172 | signals [TOOL_REMOVED] = |
173 | g_signal_new (signal_name: g_intern_static_string (string: "tool-removed" ), |
174 | G_TYPE_FROM_CLASS (klass), |
175 | signal_flags: G_SIGNAL_RUN_LAST, |
176 | class_offset: 0, NULL, NULL, |
177 | NULL, |
178 | G_TYPE_NONE, n_params: 1, |
179 | GDK_TYPE_DEVICE_TOOL); |
180 | |
181 | /** |
182 | * GdkSeat:display: (attributes org.gtk.Property.get=gdk_seat_get_display) |
183 | * |
184 | * `GdkDisplay` of this seat. |
185 | */ |
186 | props[PROP_DISPLAY] = |
187 | g_param_spec_object (name: "display" , |
188 | P_("Display" ), |
189 | P_("Display" ), |
190 | GDK_TYPE_DISPLAY, |
191 | flags: G_PARAM_READWRITE | |
192 | G_PARAM_CONSTRUCT_ONLY | |
193 | G_PARAM_STATIC_STRINGS); |
194 | |
195 | g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPS, pspecs: props); |
196 | } |
197 | |
198 | static void |
199 | gdk_seat_init (GdkSeat *seat) |
200 | { |
201 | } |
202 | |
203 | /** |
204 | * gdk_seat_get_capabilities: |
205 | * @seat: a `GdkSeat` |
206 | * |
207 | * Returns the capabilities this `GdkSeat` currently has. |
208 | * |
209 | * Returns: the seat capabilities |
210 | */ |
211 | GdkSeatCapabilities |
212 | gdk_seat_get_capabilities (GdkSeat *seat) |
213 | { |
214 | GdkSeatClass *seat_class; |
215 | |
216 | g_return_val_if_fail (GDK_IS_SEAT (seat), GDK_SEAT_CAPABILITY_NONE); |
217 | |
218 | seat_class = GDK_SEAT_GET_CLASS (seat); |
219 | return seat_class->get_capabilities (seat); |
220 | } |
221 | |
222 | /* |
223 | * gdk_seat_grab: |
224 | * @seat: a `GdkSeat` |
225 | * @surface: the `GdkSurface` which will own the grab |
226 | * @capabilities: capabilities that will be grabbed |
227 | * @owner_events: if %FALSE then all device events are reported with respect to |
228 | * @surface and are only reported if selected by @event_mask. If %TRUE then |
229 | * pointer events for this application are reported as normal, but pointer |
230 | * events outside this application are reported with respect to @surface and |
231 | * only if selected by @event_mask. In either mode, unreported events are |
232 | * discarded. |
233 | * @cursor: (nullable): the cursor to display while the grab is active. |
234 | * If this is %NULL then the normal cursors are used for @surface and |
235 | * its descendants, and the cursor for @surface is used elsewhere. |
236 | * @event: (nullable): the event that is triggering the grab, or %NULL if none |
237 | * is available. |
238 | * @prepare_func: (nullable) (scope call): function to prepare the surface |
239 | * to be grabbed, it can be %NULL if @surface is visible before this call. |
240 | * @prepare_func_data: (closure): user data to pass to @prepare_func |
241 | * |
242 | * Grabs the seat so that all events corresponding to the given @capabilities |
243 | * are passed to this application. |
244 | * |
245 | * The grab remains in place until the seat is ungrabbed with |
246 | * [method@Gdk.Seat.ungrab], or the surface becomes hidden. This overrides |
247 | * any previous grab on the seat by this client. |
248 | * |
249 | * As a rule of thumb, if a grab is desired over %GDK_SEAT_CAPABILITY_POINTER, |
250 | * all other "pointing" capabilities (eg. %GDK_SEAT_CAPABILITY_TOUCH) should |
251 | * be grabbed too, so the user is able to interact with all of those while |
252 | * the grab holds, you should thus use %GDK_SEAT_CAPABILITY_ALL_POINTING most |
253 | * commonly. |
254 | * |
255 | * Grabs are used for operations which need complete control over the |
256 | * events corresponding to the given capabilities. For example in GTK this |
257 | * is used for Drag and Drop operations, popup menus and such. |
258 | * |
259 | * Note that if the event mask of a `GdkSurface` has selected both button press |
260 | * and button release events, or touch begin and touch end, then a press event |
261 | * will cause an automatic grab until the button is released, equivalent to a |
262 | * grab on the surface with @owner_events set to %TRUE. This is done because |
263 | * most applications expect to receive paired press and release events. |
264 | * |
265 | * If you set up anything at the time you take the grab that needs to be |
266 | * cleaned up when the grab ends, you should handle the `GdkEventGrabBroken` |
267 | * events that are emitted when the grab ends unvoluntarily. |
268 | * |
269 | * Returns: %GDK_GRAB_SUCCESS if the grab was successful. |
270 | */ |
271 | GdkGrabStatus |
272 | gdk_seat_grab (GdkSeat *seat, |
273 | GdkSurface *surface, |
274 | GdkSeatCapabilities capabilities, |
275 | gboolean owner_events, |
276 | GdkCursor *cursor, |
277 | GdkEvent *event, |
278 | GdkSeatGrabPrepareFunc prepare_func, |
279 | gpointer prepare_func_data) |
280 | { |
281 | GdkSeatClass *seat_class; |
282 | |
283 | g_return_val_if_fail (GDK_IS_SEAT (seat), GDK_GRAB_FAILED); |
284 | g_return_val_if_fail (GDK_IS_SURFACE (surface), GDK_GRAB_FAILED); |
285 | g_return_val_if_fail (gdk_surface_get_display (surface) == gdk_seat_get_display (seat), GDK_GRAB_FAILED); |
286 | |
287 | capabilities &= GDK_SEAT_CAPABILITY_ALL; |
288 | g_return_val_if_fail (capabilities != GDK_SEAT_CAPABILITY_NONE, GDK_GRAB_FAILED); |
289 | |
290 | seat_class = GDK_SEAT_GET_CLASS (seat); |
291 | |
292 | return seat_class->grab (seat, surface, capabilities, owner_events, cursor, |
293 | event, prepare_func, prepare_func_data); |
294 | } |
295 | |
296 | /* |
297 | * gdk_seat_ungrab: |
298 | * @seat: a `GdkSeat` |
299 | * |
300 | * Releases a grab. |
301 | * |
302 | * See [method@Gdk.Seat.grab] for more information. |
303 | */ |
304 | void |
305 | gdk_seat_ungrab (GdkSeat *seat) |
306 | { |
307 | GdkSeatClass *seat_class; |
308 | |
309 | g_return_if_fail (GDK_IS_SEAT (seat)); |
310 | |
311 | seat_class = GDK_SEAT_GET_CLASS (seat); |
312 | seat_class->ungrab (seat); |
313 | } |
314 | |
315 | /** |
316 | * gdk_seat_get_devices: |
317 | * @seat: a `GdkSeat` |
318 | * @capabilities: capabilities to get devices for |
319 | * |
320 | * Returns the devices that match the given capabilities. |
321 | * |
322 | * Returns: (transfer container) (element-type GdkDevice): A list |
323 | * of `GdkDevices`. The list must be freed with g_list_free(), |
324 | * the elements are owned by GTK and must not be freed. |
325 | */ |
326 | GList * |
327 | gdk_seat_get_devices (GdkSeat *seat, |
328 | GdkSeatCapabilities capabilities) |
329 | { |
330 | GdkSeatClass *seat_class; |
331 | |
332 | g_return_val_if_fail (GDK_IS_SEAT (seat), NULL); |
333 | |
334 | seat_class = GDK_SEAT_GET_CLASS (seat); |
335 | return seat_class->get_devices (seat, capabilities); |
336 | } |
337 | |
338 | /** |
339 | * gdk_seat_get_pointer: |
340 | * @seat: a `GdkSeat` |
341 | * |
342 | * Returns the device that routes pointer events. |
343 | * |
344 | * Returns: (transfer none) (nullable): a `GdkDevice` with pointer |
345 | * capabilities. This object is owned by GTK and must not be freed. |
346 | */ |
347 | GdkDevice * |
348 | gdk_seat_get_pointer (GdkSeat *seat) |
349 | { |
350 | GdkSeatClass *seat_class; |
351 | |
352 | g_return_val_if_fail (GDK_IS_SEAT (seat), NULL); |
353 | |
354 | seat_class = GDK_SEAT_GET_CLASS (seat); |
355 | return seat_class->get_logical_device (seat, GDK_SEAT_CAPABILITY_POINTER); |
356 | } |
357 | |
358 | /** |
359 | * gdk_seat_get_keyboard: |
360 | * @seat: a `GdkSeat` |
361 | * |
362 | * Returns the device that routes keyboard events. |
363 | * |
364 | * Returns: (transfer none) (nullable): a `GdkDevice` with keyboard |
365 | * capabilities. This object is owned by GTK and must not be freed. |
366 | */ |
367 | GdkDevice * |
368 | gdk_seat_get_keyboard (GdkSeat *seat) |
369 | { |
370 | GdkSeatClass *seat_class; |
371 | |
372 | g_return_val_if_fail (GDK_IS_SEAT (seat), NULL); |
373 | |
374 | seat_class = GDK_SEAT_GET_CLASS (seat); |
375 | return seat_class->get_logical_device (seat, GDK_SEAT_CAPABILITY_KEYBOARD); |
376 | } |
377 | |
378 | void |
379 | gdk_seat_device_added (GdkSeat *seat, |
380 | GdkDevice *device) |
381 | { |
382 | gdk_device_set_seat (device, seat); |
383 | g_signal_emit (instance: seat, signal_id: signals[DEVICE_ADDED], detail: 0, device); |
384 | } |
385 | |
386 | void |
387 | gdk_seat_device_removed (GdkSeat *seat, |
388 | GdkDevice *device) |
389 | { |
390 | gdk_device_set_seat (device, NULL); |
391 | g_signal_emit (instance: seat, signal_id: signals[DEVICE_REMOVED], detail: 0, device); |
392 | } |
393 | |
394 | /** |
395 | * gdk_seat_get_display: (attributes org.gtk.Method.get_property=display) |
396 | * @seat: a `GdkSeat` |
397 | * |
398 | * Returns the `GdkDisplay` this seat belongs to. |
399 | * |
400 | * Returns: (transfer none): a `GdkDisplay`. This object |
401 | * is owned by GTK and must not be freed. |
402 | */ |
403 | GdkDisplay * |
404 | gdk_seat_get_display (GdkSeat *seat) |
405 | { |
406 | GdkSeatPrivate *priv = gdk_seat_get_instance_private (self: seat); |
407 | |
408 | g_return_val_if_fail (GDK_IS_SEAT (seat), NULL); |
409 | |
410 | return priv->display; |
411 | } |
412 | |
413 | void |
414 | gdk_seat_tool_added (GdkSeat *seat, |
415 | GdkDeviceTool *tool) |
416 | { |
417 | g_signal_emit (instance: seat, signal_id: signals[TOOL_ADDED], detail: 0, tool); |
418 | } |
419 | |
420 | void |
421 | gdk_seat_tool_removed (GdkSeat *seat, |
422 | GdkDeviceTool *tool) |
423 | { |
424 | g_signal_emit (instance: seat, signal_id: signals[TOOL_REMOVED], detail: 0, tool); |
425 | } |
426 | |
427 | GdkDeviceTool * |
428 | gdk_seat_get_tool (GdkSeat *seat, |
429 | guint64 serial, |
430 | guint64 hw_id, |
431 | GdkDeviceToolType type) |
432 | { |
433 | GdkDeviceTool *match = NULL; |
434 | GList *tools, *l; |
435 | |
436 | tools = gdk_seat_get_tools (seat); |
437 | |
438 | for (l = tools; l; l = l->next) |
439 | { |
440 | GdkDeviceTool *tool = l->data; |
441 | |
442 | if (tool->serial == serial && tool->hw_id == hw_id && tool->type == type) |
443 | { |
444 | match = tool; |
445 | break; |
446 | } |
447 | } |
448 | |
449 | g_list_free (list: tools); |
450 | |
451 | return match; |
452 | } |
453 | |
454 | /** |
455 | * gdk_seat_get_tools: |
456 | * @seat: a `GdkSeat` |
457 | * |
458 | * Returns all `GdkDeviceTools` that are known to the application. |
459 | * |
460 | * Returns: (transfer container) (element-type Gdk.DeviceTool): |
461 | * A list of tools. Free with g_list_free(). |
462 | */ |
463 | GList * |
464 | gdk_seat_get_tools (GdkSeat *seat) |
465 | { |
466 | GdkSeatClass *seat_class; |
467 | |
468 | g_return_val_if_fail (GDK_IS_SEAT (seat), NULL); |
469 | |
470 | seat_class = GDK_SEAT_GET_CLASS (seat); |
471 | return seat_class->get_tools (seat); |
472 | } |
473 | |