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

Last change on this file since 8eeffc1 was 1543d4c, checked in by Jiri Svoboda <jiri@…>, 22 months ago

Properly close drop-down menu on second menu-bar entry click

Note that this does not work in console due to current limitations
in libui's window emulation.

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