source: mainline/uspace/srv/hid/display/seat.c@ a0d4afe

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a0d4afe was a0d4afe, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Make sure input device configuration is destroyed together with seat

When a seat is destroyed without unassigning devices first, this causes
a dangling seat pointer that would cause the display server to crash
if the corresponding device generates an event.

  • Property mode set to 100644
File size: 14.4 KB
Line 
1/*
2 * Copyright (c) 2023 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup display
30 * @{
31 */
32/**
33 * @file Display server seat
34 */
35
36#include <adt/list.h>
37#include <errno.h>
38#include <gfx/color.h>
39#include <gfx/render.h>
40#include <stdlib.h>
41#include <str.h>
42#include "client.h"
43#include "cursor.h"
44#include "display.h"
45#include "idevcfg.h"
46#include "seat.h"
47#include "window.h"
48
49static void ds_seat_get_pointer_rect(ds_seat_t *, gfx_rect_t *);
50static errno_t ds_seat_repaint_pointer(ds_seat_t *, gfx_rect_t *);
51
52/** Create seat.
53 *
54 * @param display Parent display
55 * @param name Seat name
56 * @param rseat Place to store pointer to new seat.
57 * @return EOK on success, ENOMEM if out of memory
58 */
59errno_t ds_seat_create(ds_display_t *display, const char *name,
60 ds_seat_t **rseat)
61{
62 ds_seat_t *seat;
63 ds_seat_t *s0;
64
65 s0 = ds_display_first_seat(display);
66 while (s0 != NULL) {
67 if (str_cmp(s0->name, name) == 0)
68 return EEXIST;
69 s0 = ds_display_next_seat(s0);
70 }
71
72 seat = calloc(1, sizeof(ds_seat_t));
73 if (seat == NULL)
74 return ENOMEM;
75
76 seat->name = str_dup(name);
77 if (seat->name == NULL) {
78 free(seat);
79 return ENOMEM;
80 }
81
82 list_initialize(&seat->idevcfgs);
83
84 ds_display_add_seat(display, seat);
85 seat->pntpos.x = 0;
86 seat->pntpos.y = 0;
87
88 seat->client_cursor = display->cursor[dcurs_arrow];
89 seat->wm_cursor = NULL;
90
91 *rseat = seat;
92 return EOK;
93}
94
95/** Destroy seat.
96 *
97 * @param seat Seat
98 */
99void ds_seat_destroy(ds_seat_t *seat)
100{
101 ds_idevcfg_t *idevcfg;
102
103 /* Remove all input device configuration entries pointing to this seat */
104 idevcfg = ds_seat_first_idevcfg(seat);
105 while (idevcfg != NULL) {
106 ds_idevcfg_destroy(idevcfg);
107 idevcfg = ds_seat_first_idevcfg(seat);
108 }
109
110 ds_display_remove_seat(seat);
111
112 free(seat->name);
113 free(seat);
114}
115
116/** Set seat focus to a window.
117 *
118 * @param seat Seat
119 * @param wnd Window to focus
120 */
121void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd)
122{
123 errno_t rc;
124
125 if (wnd == seat->focus) {
126 /* Focus is not changing */
127 return;
128 }
129
130 if (wnd != NULL) {
131 rc = ds_window_unminimize(wnd);
132 if (rc != EOK)
133 return;
134 }
135
136 if (seat->focus != NULL)
137 ds_window_post_unfocus_event(seat->focus);
138
139 seat->focus = wnd;
140
141 if (wnd != NULL) {
142 ds_window_post_focus_event(wnd);
143 ds_window_bring_to_top(wnd);
144 }
145
146 /* When focus changes, popup window should be closed */
147 ds_seat_set_popup(seat, NULL);
148}
149
150/** Set seat popup window.
151 *
152 * @param seat Seat
153 * @param wnd Popup window
154 */
155void ds_seat_set_popup(ds_seat_t *seat, ds_window_t *wnd)
156{
157 if (wnd == seat->popup)
158 return;
159
160 if (seat->popup != NULL) {
161 /* Window is no longer the popup window, send close request */
162 ds_client_post_close_event(seat->popup->client,
163 seat->popup);
164 }
165
166 seat->popup = wnd;
167}
168
169/** Evacuate seat references to window.
170 *
171 * If seat's focus is @a wnd, it will be set to NULL.
172 * If seat's popup window is @a wnd, it will be set to NULL.
173 *
174 * @param seat Seat
175 * @param wnd Window to evacuate references from
176 */
177void ds_seat_evac_wnd_refs(ds_seat_t *seat, ds_window_t *wnd)
178{
179 if (seat->focus == wnd)
180 ds_seat_set_focus(seat, NULL);
181
182 if (seat->popup == wnd)
183 ds_seat_set_popup(seat, NULL);
184}
185
186/** Unfocus window.
187 *
188 * If seat's focus is @a wnd, it will be set to a different window
189 * that is not minimized, preferably not a system window.
190 *
191 * @param seat Seat
192 * @param wnd Window to remove focus from
193 */
194void ds_seat_unfocus_wnd(ds_seat_t *seat, ds_window_t *wnd)
195{
196 ds_window_t *nwnd;
197
198 if (seat->focus != wnd)
199 return;
200
201 /* Find alternate window that is neither system nor minimized */
202 nwnd = ds_window_find_alt(wnd, ~(wndf_minimized | wndf_system));
203
204 if (nwnd == NULL) {
205 /* Find alternate window that is not minimized */
206 nwnd = ds_window_find_alt(wnd, ~wndf_minimized);
207 }
208
209 ds_seat_set_focus(seat, nwnd);
210}
211
212/** Switch focus to another window.
213 *
214 * @param seat Seat
215 * @param wnd Window to evacuate focus from
216 */
217void ds_seat_switch_focus(ds_seat_t *seat)
218{
219 ds_window_t *nwnd;
220
221 /* Find alternate window that is not a system window */
222 nwnd = ds_window_find_alt(seat->focus, ~wndf_system);
223
224 /* Only switch focus if there is another window */
225 if (nwnd != NULL)
226 ds_seat_set_focus(seat, nwnd);
227}
228
229/** Post keyboard event to the seat's focused window.
230 *
231 * @param seat Seat
232 * @param event Event
233 *
234 * @return EOK on success or an error code
235 */
236errno_t ds_seat_post_kbd_event(ds_seat_t *seat, kbd_event_t *event)
237{
238 ds_window_t *dwindow;
239 bool alt_or_shift;
240
241 alt_or_shift = event->mods & (KM_SHIFT | KM_ALT);
242 if (event->type == KEY_PRESS && alt_or_shift && event->key == KC_TAB) {
243 /* On Alt-Tab or Shift-Tab, switch focus to next window */
244 ds_seat_switch_focus(seat);
245 return EOK;
246 }
247
248 dwindow = seat->popup;
249 if (dwindow == NULL)
250 dwindow = seat->focus;
251
252 if (dwindow == NULL)
253 return EOK;
254
255 return ds_window_post_kbd_event(dwindow, event);
256}
257
258/** Get current cursor used by seat.
259 *
260 * @param wmcurs WM curor
261 * @param ccurs Client cursor
262 * @return
263 */
264static ds_cursor_t *ds_seat_compute_cursor(ds_cursor_t *wmcurs, ds_cursor_t *ccurs)
265{
266 if (wmcurs != NULL)
267 return wmcurs;
268
269 return ccurs;
270}
271
272/** Get current cursor used by seat.
273 *
274 * @param seat Seat
275 * @return Current cursor
276 */
277static ds_cursor_t *ds_seat_get_cursor(ds_seat_t *seat)
278{
279 return ds_seat_compute_cursor(seat->wm_cursor, seat->client_cursor);
280}
281
282/** Set client cursor.
283 *
284 * Set cursor selected by client. This may update the actual cursor
285 * if WM is not overriding the cursor.
286 *
287 * @param seat Seat
288 * @param cursor Client cursor
289 */
290static void ds_seat_set_client_cursor(ds_seat_t *seat, ds_cursor_t *cursor)
291{
292 ds_cursor_t *old_cursor;
293 ds_cursor_t *new_cursor;
294 gfx_rect_t old_rect;
295
296 old_cursor = ds_seat_get_cursor(seat);
297 new_cursor = ds_seat_compute_cursor(seat->wm_cursor, cursor);
298
299 if (new_cursor != old_cursor) {
300 ds_seat_get_pointer_rect(seat, &old_rect);
301 seat->client_cursor = cursor;
302 ds_seat_repaint_pointer(seat, &old_rect);
303 } else {
304 seat->client_cursor = cursor;
305 }
306}
307
308/** Set WM cursor.
309 *
310 * Set cursor override for window management.
311 *
312 * @param seat Seat
313 * @param cursor WM cursor override or @c NULL not to override the cursor
314 */
315void ds_seat_set_wm_cursor(ds_seat_t *seat, ds_cursor_t *cursor)
316{
317 ds_cursor_t *old_cursor;
318 ds_cursor_t *new_cursor;
319 gfx_rect_t old_rect;
320
321 old_cursor = ds_seat_get_cursor(seat);
322 new_cursor = ds_seat_compute_cursor(cursor, seat->client_cursor);
323
324 if (new_cursor != old_cursor) {
325 ds_seat_get_pointer_rect(seat, &old_rect);
326 seat->wm_cursor = cursor;
327 ds_seat_repaint_pointer(seat, &old_rect);
328 } else {
329 seat->wm_cursor = cursor;
330 }
331}
332
333/** Get rectangle covered by pointer.
334 *
335 * @param seat Seat
336 * @param rect Place to store rectangle
337 */
338void ds_seat_get_pointer_rect(ds_seat_t *seat, gfx_rect_t *rect)
339{
340 ds_cursor_t *cursor;
341
342 cursor = ds_seat_get_cursor(seat);
343 ds_cursor_get_rect(cursor, &seat->pntpos, rect);
344}
345
346/** Repaint seat pointer
347 *
348 * Repaint the pointer after it has moved or changed. This is done by
349 * repainting the area of the display previously (@a old_rect) and currently
350 * covered by the pointer.
351 *
352 * @param seat Seat
353 * @param old_rect Rectangle previously covered by pointer
354 *
355 * @return EOK on success or an error code
356 */
357static errno_t ds_seat_repaint_pointer(ds_seat_t *seat, gfx_rect_t *old_rect)
358{
359 gfx_rect_t new_rect;
360 gfx_rect_t envelope;
361 errno_t rc;
362
363 ds_seat_get_pointer_rect(seat, &new_rect);
364
365 if (gfx_rect_is_incident(old_rect, &new_rect)) {
366 /* Rectangles do not intersect. Repaint them separately. */
367 rc = ds_display_paint(seat->display, &new_rect);
368 if (rc != EOK)
369 return rc;
370
371 rc = ds_display_paint(seat->display, old_rect);
372 if (rc != EOK)
373 return rc;
374 } else {
375 /*
376 * Rectangles intersect. As an optimization, repaint them
377 * in a single operation.
378 */
379 gfx_rect_envelope(old_rect, &new_rect, &envelope);
380
381 rc = ds_display_paint(seat->display, &envelope);
382 if (rc != EOK)
383 return rc;
384 }
385
386 return EOK;
387}
388
389/** Post pointing device event to the seat
390 *
391 * Update pointer position and generate position event.
392 *
393 * @param seat Seat
394 * @param event Event
395 *
396 * @return EOK on success or an error code
397 */
398errno_t ds_seat_post_ptd_event(ds_seat_t *seat, ptd_event_t *event)
399{
400 ds_display_t *disp = seat->display;
401 gfx_coord2_t npos;
402 gfx_rect_t old_rect;
403 ds_window_t *wnd;
404 pos_event_t pevent;
405 errno_t rc;
406
407 wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
408
409 /* Focus window on button press */
410 if (event->type == PTD_PRESS && event->btn_num == 1) {
411 if (wnd != NULL && (wnd->flags & wndf_popup) == 0) {
412 ds_seat_set_focus(seat, wnd);
413 }
414 }
415
416 if (event->type == PTD_PRESS || event->type == PTD_RELEASE ||
417 event->type == PTD_DCLICK) {
418 pevent.pos_id = event->pos_id;
419 switch (event->type) {
420 case PTD_PRESS:
421 pevent.type = POS_PRESS;
422 break;
423 case PTD_RELEASE:
424 pevent.type = POS_RELEASE;
425 break;
426 case PTD_DCLICK:
427 pevent.type = POS_DCLICK;
428 break;
429 default:
430 assert(false);
431 }
432
433 pevent.btn_num = event->btn_num;
434 pevent.hpos = seat->pntpos.x;
435 pevent.vpos = seat->pntpos.y;
436
437 rc = ds_seat_post_pos_event(seat, &pevent);
438 if (rc != EOK)
439 return rc;
440 }
441
442 if (event->type == PTD_MOVE) {
443 gfx_coord2_add(&seat->pntpos, &event->dmove, &npos);
444 gfx_coord2_clip(&npos, &disp->rect, &npos);
445
446 ds_seat_get_pointer_rect(seat, &old_rect);
447 seat->pntpos = npos;
448
449 pevent.pos_id = event->pos_id;
450 pevent.type = POS_UPDATE;
451 pevent.btn_num = 0;
452 pevent.hpos = seat->pntpos.x;
453 pevent.vpos = seat->pntpos.y;
454
455 rc = ds_seat_post_pos_event(seat, &pevent);
456 if (rc != EOK)
457 return rc;
458
459 ds_seat_repaint_pointer(seat, &old_rect);
460 }
461
462 if (event->type == PTD_ABS_MOVE) {
463 /*
464 * Project input device area onto display area. Technically
465 * we probably want to project onto the area of a particular
466 * display device. The tricky part is figuring out which
467 * display device the input device is associated with.
468 */
469 gfx_coord2_project(&event->apos, &event->abounds,
470 &disp->rect, &npos);
471
472 gfx_coord2_clip(&npos, &disp->rect, &npos);
473
474 ds_seat_get_pointer_rect(seat, &old_rect);
475 seat->pntpos = npos;
476
477 pevent.pos_id = event->pos_id;
478 pevent.type = POS_UPDATE;
479 pevent.btn_num = 0;
480 pevent.hpos = seat->pntpos.x;
481 pevent.vpos = seat->pntpos.y;
482
483 rc = ds_seat_post_pos_event(seat, &pevent);
484 if (rc != EOK)
485 return rc;
486
487 ds_seat_repaint_pointer(seat, &old_rect);
488 }
489
490 return EOK;
491}
492
493/** Post position event to seat.
494 *
495 * Deliver event to relevant windows.
496 *
497 * @param seat Seat
498 * @param event Position event
499 */
500errno_t ds_seat_post_pos_event(ds_seat_t *seat, pos_event_t *event)
501{
502 ds_window_t *wnd;
503 errno_t rc;
504
505 wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
506
507 /* Click outside popup window */
508 if (event->type == POS_PRESS && wnd != seat->popup) {
509 /* Close popup window */
510 ds_seat_set_popup(seat, NULL);
511 }
512
513 /* Deliver event to popup window. */
514 if (seat->popup != NULL) {
515 rc = ds_window_post_pos_event(seat->popup, event);
516 if (rc != EOK)
517 return rc;
518 }
519
520 if (seat->focus != wnd && seat->focus != NULL) {
521 rc = ds_window_post_pos_event(seat->focus, event);
522 if (rc != EOK)
523 return rc;
524
525 /* Only deliver release events to the focused window */
526 if (event->type == POS_RELEASE)
527 return EOK;
528 }
529
530 if (wnd != NULL) {
531 /* Moving over a window */
532 ds_seat_set_client_cursor(seat, wnd->cursor);
533
534 /*
535 * Only deliver event if we didn't already deliver it
536 * to the same window above.
537 */
538 if (wnd != seat->popup) {
539 rc = ds_window_post_pos_event(wnd, event);
540 if (rc != EOK)
541 return rc;
542 }
543 } else {
544 /* Not over a window */
545 ds_seat_set_client_cursor(seat, seat->display->cursor[dcurs_arrow]);
546 }
547
548 return EOK;
549}
550
551/** Paint seat pointer.
552 *
553 * @param seat Seat whose pointer to paint
554 * @param rect Clipping rectangle
555 */
556errno_t ds_seat_paint_pointer(ds_seat_t *seat, gfx_rect_t *rect)
557{
558 ds_cursor_t *cursor;
559
560 cursor = ds_seat_get_cursor(seat);
561 return ds_cursor_paint(cursor, &seat->pntpos, rect);
562}
563
564/** Add input device configuration entry to seat.
565 *
566 * @param seat Seat
567 * @param idevcfg Input device configuration
568 */
569void ds_seat_add_idevcfg(ds_seat_t *seat, ds_idevcfg_t *idevcfg)
570{
571 assert(idevcfg->seat == NULL);
572 assert(!link_used(&idevcfg->lseatidcfgs));
573
574 idevcfg->seat = seat;
575 list_append(&idevcfg->lseatidcfgs, &seat->idevcfgs);
576}
577
578/** Remove input device configuration entry from seat.
579 *
580 * @param idevcfg Input device configuration entry
581 */
582void ds_seat_remove_idevcfg(ds_idevcfg_t *idevcfg)
583{
584 list_remove(&idevcfg->lseatidcfgs);
585 idevcfg->seat = NULL;
586}
587
588/** Get first input device configuration entry in seat.
589 *
590 * @param disp Display
591 * @return First input device configuration entry or @c NULL if there is none
592 */
593ds_idevcfg_t *ds_seat_first_idevcfg(ds_seat_t *seat)
594{
595 link_t *link = list_first(&seat->idevcfgs);
596
597 if (link == NULL)
598 return NULL;
599
600 return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
601}
602
603/** Get next input device configuration entry in seat.
604 *
605 * @param idevcfg Current input device configuration entry
606 * @return Next input device configuration entry or @c NULL if there is none
607 */
608ds_idevcfg_t *ds_seat_next_idevcfg(ds_idevcfg_t *idevcfg)
609{
610 link_t *link = list_next(&idevcfg->lseatidcfgs, &idevcfg->seat->idevcfgs);
611
612 if (link == NULL)
613 return NULL;
614
615 return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
616}
617
618/** @}
619 */
Note: See TracBrowser for help on using the repository browser.