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

Last change on this file was 0d00e53, checked in by Jiri Svoboda <jiri@…>, 8 months ago

Shut down dialog

  • Property mode set to 100644
File size: 15.9 KB
Line 
1/*
2 * Copyright (c) 2024 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 <sif.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <str.h>
44#include "client.h"
45#include "cursor.h"
46#include "display.h"
47#include "idevcfg.h"
48#include "seat.h"
49#include "window.h"
50
51static void ds_seat_get_pointer_rect(ds_seat_t *, gfx_rect_t *);
52static errno_t ds_seat_repaint_pointer(ds_seat_t *, gfx_rect_t *);
53
54/** Create seat.
55 *
56 * @param display Parent display
57 * @param name Seat name
58 * @param rseat Place to store pointer to new seat.
59 * @return EOK on success, ENOMEM if out of memory
60 */
61errno_t ds_seat_create(ds_display_t *display, const char *name,
62 ds_seat_t **rseat)
63{
64 ds_seat_t *seat;
65 ds_seat_t *s0;
66
67 s0 = ds_display_first_seat(display);
68 while (s0 != NULL) {
69 if (str_cmp(s0->name, name) == 0)
70 return EEXIST;
71 s0 = ds_display_next_seat(s0);
72 }
73
74 seat = calloc(1, sizeof(ds_seat_t));
75 if (seat == NULL)
76 return ENOMEM;
77
78 seat->name = str_dup(name);
79 if (seat->name == NULL) {
80 free(seat);
81 return ENOMEM;
82 }
83
84 list_initialize(&seat->idevcfgs);
85
86 ds_display_add_seat(display, seat);
87 seat->pntpos.x = 0;
88 seat->pntpos.y = 0;
89
90 seat->client_cursor = display->cursor[dcurs_arrow];
91 seat->wm_cursor = NULL;
92 seat->focus = ds_display_first_window(display);
93
94 *rseat = seat;
95 return EOK;
96}
97
98/** Destroy seat.
99 *
100 * @param seat Seat
101 */
102void ds_seat_destroy(ds_seat_t *seat)
103{
104 ds_idevcfg_t *idevcfg;
105
106 /* Remove all input device configuration entries pointing to this seat */
107 idevcfg = ds_seat_first_idevcfg(seat);
108 while (idevcfg != NULL) {
109 ds_idevcfg_destroy(idevcfg);
110 idevcfg = ds_seat_first_idevcfg(seat);
111 }
112
113 /* Remove this seat's focus */
114 if (seat->focus != NULL)
115 ds_window_post_unfocus_event(seat->focus);
116
117 ds_display_remove_seat(seat);
118
119 free(seat->name);
120 free(seat);
121}
122
123/** Load seat from SIF node.
124 *
125 * @param display Display
126 * @param snode Seat node from which to load the seat
127 * @param rseat Place to store pointer to the newly loaded seat
128 *
129 * @return EOK on success or an error code
130 */
131errno_t ds_seat_load(ds_display_t *display, sif_node_t *snode,
132 ds_seat_t **rseat)
133{
134 const char *sid;
135 const char *name;
136 char *endptr;
137 unsigned long id;
138 errno_t rc;
139
140 sid = sif_node_get_attr(snode, "id");
141 if (sid == NULL)
142 return EIO;
143
144 name = sif_node_get_attr(snode, "name");
145 if (name == NULL)
146 return EIO;
147
148 id = strtoul(sid, &endptr, 10);
149 if (*endptr != '\0')
150 return EIO;
151
152 rc = ds_seat_create(display, name, rseat);
153 if (rc != EOK)
154 return EIO;
155
156 (*rseat)->id = id;
157 return EOK;
158}
159
160/** Save seat to SIF node.
161 *
162 * @param seat Seat
163 * @param snode Seat node into which the seat should be saved
164 *
165 * @return EOK on success or an error code
166 */
167errno_t ds_seat_save(ds_seat_t *seat, sif_node_t *snode)
168{
169 char *sid;
170 errno_t rc;
171 int rv;
172
173 rv = asprintf(&sid, "%lu", (unsigned long)seat->id);
174 if (rv < 0) {
175 rc = ENOMEM;
176 return rc;
177 }
178
179 rc = sif_node_set_attr(snode, "id", sid);
180 if (rc != EOK) {
181 free(sid);
182 return rc;
183 }
184
185 free(sid);
186
187 rc = sif_node_set_attr(snode, "name", seat->name);
188 if (rc != EOK)
189 return rc;
190
191 return EOK;
192}
193
194/** Set seat focus to a window.
195 *
196 * @param seat Seat
197 * @param wnd Window to focus
198 */
199void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd)
200{
201 errno_t rc;
202
203 if (wnd == seat->focus) {
204 /* Focus is not changing */
205 return;
206 }
207
208 if (wnd != NULL) {
209 rc = ds_window_unminimize(wnd);
210 if (rc != EOK)
211 return;
212 }
213
214 if (seat->focus != NULL)
215 ds_window_post_unfocus_event(seat->focus);
216
217 seat->focus = wnd;
218
219 if (wnd != NULL) {
220 ds_window_post_focus_event(wnd);
221 ds_window_bring_to_top(wnd);
222 }
223
224 /* When focus changes, popup window should be closed */
225 ds_seat_set_popup(seat, NULL);
226}
227
228/** Set seat popup window.
229 *
230 * @param seat Seat
231 * @param wnd Popup window
232 */
233void ds_seat_set_popup(ds_seat_t *seat, ds_window_t *wnd)
234{
235 if (wnd == seat->popup)
236 return;
237
238 if (seat->popup != NULL) {
239 /* Window is no longer the popup window, send close request */
240 ds_client_post_close_event(seat->popup->client,
241 seat->popup);
242 }
243
244 seat->popup = wnd;
245}
246
247/** Evacuate seat references to window.
248 *
249 * If seat's focus is @a wnd, it will be set to NULL.
250 * If seat's popup window is @a wnd, it will be set to NULL.
251 *
252 * @param seat Seat
253 * @param wnd Window to evacuate references from
254 */
255void ds_seat_evac_wnd_refs(ds_seat_t *seat, ds_window_t *wnd)
256{
257 if (seat->focus == wnd)
258 ds_seat_set_focus(seat, NULL);
259
260 if (seat->popup == wnd)
261 ds_seat_set_popup(seat, NULL);
262}
263
264/** Unfocus window.
265 *
266 * If seat's focus is @a wnd, it will be set to a different window
267 * that is not minimized, preferably not a system window.
268 *
269 * @param seat Seat
270 * @param wnd Window to remove focus from
271 */
272void ds_seat_unfocus_wnd(ds_seat_t *seat, ds_window_t *wnd)
273{
274 ds_window_t *nwnd;
275
276 if (seat->focus != wnd)
277 return;
278
279 /* Find alternate window that is neither system nor minimized */
280 nwnd = ds_window_find_prev(wnd, ~(wndf_minimized | wndf_system));
281
282 if (nwnd == NULL) {
283 /* Find alternate window that is not minimized */
284 nwnd = ds_window_find_prev(wnd, ~wndf_minimized);
285 }
286
287 ds_seat_set_focus(seat, nwnd);
288}
289
290/** Switch focus to another window.
291 *
292 * @param seat Seat
293 * @param wnd Window to evacuate focus from
294 */
295void ds_seat_switch_focus(ds_seat_t *seat)
296{
297 ds_window_t *nwnd;
298
299 if (seat->focus != NULL) {
300 /* Find alternate window that is not a system window */
301 nwnd = ds_window_find_next(seat->focus, ~wndf_system);
302 } else {
303 /* Currently no focus. Focus topmost window. */
304 nwnd = ds_display_first_window(seat->display);
305 }
306
307 /* Only switch focus if there is another window */
308 if (nwnd != NULL)
309 ds_seat_set_focus(seat, nwnd);
310}
311
312/** Post keyboard event to the seat's focused window.
313 *
314 * @param seat Seat
315 * @param event Event
316 *
317 * @return EOK on success or an error code
318 */
319errno_t ds_seat_post_kbd_event(ds_seat_t *seat, kbd_event_t *event)
320{
321 ds_window_t *dwindow;
322 bool alt_or_shift;
323
324 alt_or_shift = event->mods & (KM_SHIFT | KM_ALT);
325 if (event->type == KEY_PRESS && alt_or_shift && event->key == KC_TAB) {
326 /* On Alt-Tab or Shift-Tab, switch focus to next window */
327 ds_seat_switch_focus(seat);
328 return EOK;
329 }
330
331 dwindow = seat->popup;
332 if (dwindow == NULL)
333 dwindow = seat->focus;
334
335 if (dwindow == NULL)
336 return EOK;
337
338 return ds_window_post_kbd_event(dwindow, event);
339}
340
341/** Get current cursor used by seat.
342 *
343 * @param wmcurs WM curor
344 * @param ccurs Client cursor
345 * @return
346 */
347static ds_cursor_t *ds_seat_compute_cursor(ds_cursor_t *wmcurs, ds_cursor_t *ccurs)
348{
349 if (wmcurs != NULL)
350 return wmcurs;
351
352 return ccurs;
353}
354
355/** Get current cursor used by seat.
356 *
357 * @param seat Seat
358 * @return Current cursor
359 */
360static ds_cursor_t *ds_seat_get_cursor(ds_seat_t *seat)
361{
362 return ds_seat_compute_cursor(seat->wm_cursor, seat->client_cursor);
363}
364
365/** Set client cursor.
366 *
367 * Set cursor selected by client. This may update the actual cursor
368 * if WM is not overriding the cursor.
369 *
370 * @param seat Seat
371 * @param cursor Client cursor
372 */
373static void ds_seat_set_client_cursor(ds_seat_t *seat, ds_cursor_t *cursor)
374{
375 ds_cursor_t *old_cursor;
376 ds_cursor_t *new_cursor;
377 gfx_rect_t old_rect;
378
379 old_cursor = ds_seat_get_cursor(seat);
380 new_cursor = ds_seat_compute_cursor(seat->wm_cursor, cursor);
381
382 if (new_cursor != old_cursor) {
383 ds_seat_get_pointer_rect(seat, &old_rect);
384 seat->client_cursor = cursor;
385 ds_seat_repaint_pointer(seat, &old_rect);
386 } else {
387 seat->client_cursor = cursor;
388 }
389}
390
391/** Set WM cursor.
392 *
393 * Set cursor override for window management.
394 *
395 * @param seat Seat
396 * @param cursor WM cursor override or @c NULL not to override the cursor
397 */
398void ds_seat_set_wm_cursor(ds_seat_t *seat, ds_cursor_t *cursor)
399{
400 ds_cursor_t *old_cursor;
401 ds_cursor_t *new_cursor;
402 gfx_rect_t old_rect;
403
404 old_cursor = ds_seat_get_cursor(seat);
405 new_cursor = ds_seat_compute_cursor(cursor, seat->client_cursor);
406
407 if (new_cursor != old_cursor) {
408 ds_seat_get_pointer_rect(seat, &old_rect);
409 seat->wm_cursor = cursor;
410 ds_seat_repaint_pointer(seat, &old_rect);
411 } else {
412 seat->wm_cursor = cursor;
413 }
414}
415
416/** Get rectangle covered by pointer.
417 *
418 * @param seat Seat
419 * @param rect Place to store rectangle
420 */
421void ds_seat_get_pointer_rect(ds_seat_t *seat, gfx_rect_t *rect)
422{
423 ds_cursor_t *cursor;
424
425 cursor = ds_seat_get_cursor(seat);
426 ds_cursor_get_rect(cursor, &seat->pntpos, rect);
427}
428
429/** Repaint seat pointer
430 *
431 * Repaint the pointer after it has moved or changed. This is done by
432 * repainting the area of the display previously (@a old_rect) and currently
433 * covered by the pointer.
434 *
435 * @param seat Seat
436 * @param old_rect Rectangle previously covered by pointer
437 *
438 * @return EOK on success or an error code
439 */
440static errno_t ds_seat_repaint_pointer(ds_seat_t *seat, gfx_rect_t *old_rect)
441{
442 gfx_rect_t new_rect;
443 gfx_rect_t envelope;
444 errno_t rc;
445
446 ds_seat_get_pointer_rect(seat, &new_rect);
447
448 if (gfx_rect_is_incident(old_rect, &new_rect)) {
449 /* Rectangles do not intersect. Repaint them separately. */
450 rc = ds_display_paint(seat->display, &new_rect);
451 if (rc != EOK)
452 return rc;
453
454 rc = ds_display_paint(seat->display, old_rect);
455 if (rc != EOK)
456 return rc;
457 } else {
458 /*
459 * Rectangles intersect. As an optimization, repaint them
460 * in a single operation.
461 */
462 gfx_rect_envelope(old_rect, &new_rect, &envelope);
463
464 rc = ds_display_paint(seat->display, &envelope);
465 if (rc != EOK)
466 return rc;
467 }
468
469 return EOK;
470}
471
472/** Post pointing device event to the seat
473 *
474 * Update pointer position and generate position event.
475 *
476 * @param seat Seat
477 * @param event Event
478 *
479 * @return EOK on success or an error code
480 */
481errno_t ds_seat_post_ptd_event(ds_seat_t *seat, ptd_event_t *event)
482{
483 ds_display_t *disp = seat->display;
484 gfx_coord2_t npos;
485 gfx_rect_t old_rect;
486 ds_window_t *wnd;
487 pos_event_t pevent;
488 errno_t rc;
489
490 wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
491
492 /* Focus window on button press */
493 if (event->type == PTD_PRESS && event->btn_num == 1) {
494 if (wnd != NULL && (wnd->flags & wndf_popup) == 0 &&
495 (wnd->flags & wndf_nofocus) == 0) {
496 ds_seat_set_focus(seat, wnd);
497 }
498 }
499
500 if (event->type == PTD_PRESS || event->type == PTD_RELEASE ||
501 event->type == PTD_DCLICK) {
502 pevent.pos_id = event->pos_id;
503 switch (event->type) {
504 case PTD_PRESS:
505 pevent.type = POS_PRESS;
506 break;
507 case PTD_RELEASE:
508 pevent.type = POS_RELEASE;
509 break;
510 case PTD_DCLICK:
511 pevent.type = POS_DCLICK;
512 break;
513 default:
514 assert(false);
515 }
516
517 pevent.btn_num = event->btn_num;
518 pevent.hpos = seat->pntpos.x;
519 pevent.vpos = seat->pntpos.y;
520
521 rc = ds_seat_post_pos_event(seat, &pevent);
522 if (rc != EOK)
523 return rc;
524 }
525
526 if (event->type == PTD_MOVE) {
527 gfx_coord2_add(&seat->pntpos, &event->dmove, &npos);
528 gfx_coord2_clip(&npos, &disp->rect, &npos);
529
530 ds_seat_get_pointer_rect(seat, &old_rect);
531 seat->pntpos = npos;
532
533 pevent.pos_id = event->pos_id;
534 pevent.type = POS_UPDATE;
535 pevent.btn_num = 0;
536 pevent.hpos = seat->pntpos.x;
537 pevent.vpos = seat->pntpos.y;
538
539 rc = ds_seat_post_pos_event(seat, &pevent);
540 if (rc != EOK)
541 return rc;
542
543 ds_seat_repaint_pointer(seat, &old_rect);
544 }
545
546 if (event->type == PTD_ABS_MOVE) {
547 /*
548 * Project input device area onto display area. Technically
549 * we probably want to project onto the area of a particular
550 * display device. The tricky part is figuring out which
551 * display device the input device is associated with.
552 */
553 gfx_coord2_project(&event->apos, &event->abounds,
554 &disp->rect, &npos);
555
556 gfx_coord2_clip(&npos, &disp->rect, &npos);
557
558 ds_seat_get_pointer_rect(seat, &old_rect);
559 seat->pntpos = npos;
560
561 pevent.pos_id = event->pos_id;
562 pevent.type = POS_UPDATE;
563 pevent.btn_num = 0;
564 pevent.hpos = seat->pntpos.x;
565 pevent.vpos = seat->pntpos.y;
566
567 rc = ds_seat_post_pos_event(seat, &pevent);
568 if (rc != EOK)
569 return rc;
570
571 ds_seat_repaint_pointer(seat, &old_rect);
572 }
573
574 return EOK;
575}
576
577/** Post position event to seat.
578 *
579 * Deliver event to relevant windows.
580 *
581 * @param seat Seat
582 * @param event Position event
583 */
584errno_t ds_seat_post_pos_event(ds_seat_t *seat, pos_event_t *event)
585{
586 ds_window_t *pwindow;
587 ds_window_t *cwindow;
588 errno_t rc;
589
590 /* Window under pointer */
591 pwindow = ds_display_window_by_pos(seat->display, &seat->pntpos);
592
593 /* Current window: popup or focused */
594 cwindow = seat->popup;
595 if (cwindow == NULL)
596 cwindow = seat->focus;
597
598 /*
599 * Deliver move and release event to current window if different
600 * from pwindow
601 */
602 if (event->type != POS_PRESS && cwindow != NULL &&
603 cwindow != pwindow) {
604 rc = ds_window_post_pos_event(cwindow, event);
605 if (rc != EOK)
606 return rc;
607 }
608
609 if (pwindow != NULL) {
610 /* Moving over a window */
611 ds_seat_set_client_cursor(seat, pwindow->cursor);
612
613 rc = ds_window_post_pos_event(pwindow, event);
614 if (rc != EOK)
615 return rc;
616 } else {
617 /* Not over a window */
618 ds_seat_set_client_cursor(seat,
619 seat->display->cursor[dcurs_arrow]);
620 }
621
622 /* Click outside popup window */
623 if (event->type == POS_PRESS && pwindow != seat->popup) {
624 /* Close popup window */
625 ds_seat_set_popup(seat, NULL);
626 }
627
628 return EOK;
629}
630
631/** Paint seat pointer.
632 *
633 * @param seat Seat whose pointer to paint
634 * @param rect Clipping rectangle
635 */
636errno_t ds_seat_paint_pointer(ds_seat_t *seat, gfx_rect_t *rect)
637{
638 ds_cursor_t *cursor;
639
640 cursor = ds_seat_get_cursor(seat);
641 return ds_cursor_paint(cursor, &seat->pntpos, rect);
642}
643
644/** Add input device configuration entry to seat.
645 *
646 * @param seat Seat
647 * @param idevcfg Input device configuration
648 */
649void ds_seat_add_idevcfg(ds_seat_t *seat, ds_idevcfg_t *idevcfg)
650{
651 assert(idevcfg->seat == NULL);
652 assert(!link_used(&idevcfg->lseatidcfgs));
653
654 idevcfg->seat = seat;
655 list_append(&idevcfg->lseatidcfgs, &seat->idevcfgs);
656}
657
658/** Remove input device configuration entry from seat.
659 *
660 * @param idevcfg Input device configuration entry
661 */
662void ds_seat_remove_idevcfg(ds_idevcfg_t *idevcfg)
663{
664 list_remove(&idevcfg->lseatidcfgs);
665 idevcfg->seat = NULL;
666}
667
668/** Get first input device configuration entry in seat.
669 *
670 * @param disp Display
671 * @return First input device configuration entry or @c NULL if there is none
672 */
673ds_idevcfg_t *ds_seat_first_idevcfg(ds_seat_t *seat)
674{
675 link_t *link = list_first(&seat->idevcfgs);
676
677 if (link == NULL)
678 return NULL;
679
680 return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
681}
682
683/** Get next input device configuration entry in seat.
684 *
685 * @param idevcfg Current input device configuration entry
686 * @return Next input device configuration entry or @c NULL if there is none
687 */
688ds_idevcfg_t *ds_seat_next_idevcfg(ds_idevcfg_t *idevcfg)
689{
690 link_t *link = list_next(&idevcfg->lseatidcfgs, &idevcfg->seat->idevcfgs);
691
692 if (link == NULL)
693 return NULL;
694
695 return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
696}
697
698/** @}
699 */
Note: See TracBrowser for help on using the repository browser.