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

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

Switch focus to the right window when window is closed

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