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

Last change on this file since c7ecd290 was 9546146, checked in by Jiri Svoboda <jiri@…>, 13 months ago

Persistently store display/seat configuration.

  • 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 ds_seat_set_focus(seat, wnd);
496 }
497 }
498
499 if (event->type == PTD_PRESS || event->type == PTD_RELEASE ||
500 event->type == PTD_DCLICK) {
501 pevent.pos_id = event->pos_id;
502 switch (event->type) {
503 case PTD_PRESS:
504 pevent.type = POS_PRESS;
505 break;
506 case PTD_RELEASE:
507 pevent.type = POS_RELEASE;
508 break;
509 case PTD_DCLICK:
510 pevent.type = POS_DCLICK;
511 break;
512 default:
513 assert(false);
514 }
515
516 pevent.btn_num = event->btn_num;
517 pevent.hpos = seat->pntpos.x;
518 pevent.vpos = seat->pntpos.y;
519
520 rc = ds_seat_post_pos_event(seat, &pevent);
521 if (rc != EOK)
522 return rc;
523 }
524
525 if (event->type == PTD_MOVE) {
526 gfx_coord2_add(&seat->pntpos, &event->dmove, &npos);
527 gfx_coord2_clip(&npos, &disp->rect, &npos);
528
529 ds_seat_get_pointer_rect(seat, &old_rect);
530 seat->pntpos = npos;
531
532 pevent.pos_id = event->pos_id;
533 pevent.type = POS_UPDATE;
534 pevent.btn_num = 0;
535 pevent.hpos = seat->pntpos.x;
536 pevent.vpos = seat->pntpos.y;
537
538 rc = ds_seat_post_pos_event(seat, &pevent);
539 if (rc != EOK)
540 return rc;
541
542 ds_seat_repaint_pointer(seat, &old_rect);
543 }
544
545 if (event->type == PTD_ABS_MOVE) {
546 /*
547 * Project input device area onto display area. Technically
548 * we probably want to project onto the area of a particular
549 * display device. The tricky part is figuring out which
550 * display device the input device is associated with.
551 */
552 gfx_coord2_project(&event->apos, &event->abounds,
553 &disp->rect, &npos);
554
555 gfx_coord2_clip(&npos, &disp->rect, &npos);
556
557 ds_seat_get_pointer_rect(seat, &old_rect);
558 seat->pntpos = npos;
559
560 pevent.pos_id = event->pos_id;
561 pevent.type = POS_UPDATE;
562 pevent.btn_num = 0;
563 pevent.hpos = seat->pntpos.x;
564 pevent.vpos = seat->pntpos.y;
565
566 rc = ds_seat_post_pos_event(seat, &pevent);
567 if (rc != EOK)
568 return rc;
569
570 ds_seat_repaint_pointer(seat, &old_rect);
571 }
572
573 return EOK;
574}
575
576/** Post position event to seat.
577 *
578 * Deliver event to relevant windows.
579 *
580 * @param seat Seat
581 * @param event Position event
582 */
583errno_t ds_seat_post_pos_event(ds_seat_t *seat, pos_event_t *event)
584{
585 ds_window_t *pwindow;
586 ds_window_t *cwindow;
587 errno_t rc;
588
589 /* Window under pointer */
590 pwindow = ds_display_window_by_pos(seat->display, &seat->pntpos);
591
592 /* Current window: popup or focused */
593 cwindow = seat->popup;
594 if (cwindow == NULL)
595 cwindow = seat->focus;
596
597 /*
598 * Deliver move and release event to current window if different
599 * from pwindow
600 */
601 if (event->type != POS_PRESS && cwindow != NULL &&
602 cwindow != pwindow) {
603 rc = ds_window_post_pos_event(cwindow, event);
604 if (rc != EOK)
605 return rc;
606 }
607
608 if (pwindow != NULL) {
609 /* Moving over a window */
610 ds_seat_set_client_cursor(seat, pwindow->cursor);
611
612 rc = ds_window_post_pos_event(pwindow, event);
613 if (rc != EOK)
614 return rc;
615 } else {
616 /* Not over a window */
617 ds_seat_set_client_cursor(seat,
618 seat->display->cursor[dcurs_arrow]);
619 }
620
621 /* Click outside popup window */
622 if (event->type == POS_PRESS && pwindow != seat->popup) {
623 /* Close popup window */
624 ds_seat_set_popup(seat, NULL);
625 }
626
627 return EOK;
628}
629
630/** Paint seat pointer.
631 *
632 * @param seat Seat whose pointer to paint
633 * @param rect Clipping rectangle
634 */
635errno_t ds_seat_paint_pointer(ds_seat_t *seat, gfx_rect_t *rect)
636{
637 ds_cursor_t *cursor;
638
639 cursor = ds_seat_get_cursor(seat);
640 return ds_cursor_paint(cursor, &seat->pntpos, rect);
641}
642
643/** Add input device configuration entry to seat.
644 *
645 * @param seat Seat
646 * @param idevcfg Input device configuration
647 */
648void ds_seat_add_idevcfg(ds_seat_t *seat, ds_idevcfg_t *idevcfg)
649{
650 assert(idevcfg->seat == NULL);
651 assert(!link_used(&idevcfg->lseatidcfgs));
652
653 idevcfg->seat = seat;
654 list_append(&idevcfg->lseatidcfgs, &seat->idevcfgs);
655}
656
657/** Remove input device configuration entry from seat.
658 *
659 * @param idevcfg Input device configuration entry
660 */
661void ds_seat_remove_idevcfg(ds_idevcfg_t *idevcfg)
662{
663 list_remove(&idevcfg->lseatidcfgs);
664 idevcfg->seat = NULL;
665}
666
667/** Get first input device configuration entry in seat.
668 *
669 * @param disp Display
670 * @return First input device configuration entry or @c NULL if there is none
671 */
672ds_idevcfg_t *ds_seat_first_idevcfg(ds_seat_t *seat)
673{
674 link_t *link = list_first(&seat->idevcfgs);
675
676 if (link == NULL)
677 return NULL;
678
679 return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
680}
681
682/** Get next input device configuration entry in seat.
683 *
684 * @param idevcfg Current input device configuration entry
685 * @return Next input device configuration entry or @c NULL if there is none
686 */
687ds_idevcfg_t *ds_seat_next_idevcfg(ds_idevcfg_t *idevcfg)
688{
689 link_t *link = list_next(&idevcfg->lseatidcfgs, &idevcfg->seat->idevcfgs);
690
691 if (link == NULL)
692 return NULL;
693
694 return list_get_instance(link, ds_idevcfg_t, lseatidcfgs);
695}
696
697/** @}
698 */
Note: See TracBrowser for help on using the repository browser.