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

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 24be331e 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
RevLine 
[cf32dbd]1/*
[17c0f5d]2 * Copyright (c) 2023 Jiri Svoboda
[cf32dbd]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>
[4fbdc3d]38#include <gfx/color.h>
39#include <gfx/render.h>
[cf32dbd]40#include <stdlib.h>
[d8503fd]41#include <str.h>
[cf32dbd]42#include "client.h"
[4d8002d]43#include "cursor.h"
[cf32dbd]44#include "display.h"
[a0d4afe]45#include "idevcfg.h"
[cf32dbd]46#include "seat.h"
47#include "window.h"
48
[978c9bc5]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 *);
[9901f267]51
[cf32dbd]52/** Create seat.
53 *
54 * @param display Parent display
[d8503fd]55 * @param name Seat name
[cf32dbd]56 * @param rseat Place to store pointer to new seat.
57 * @return EOK on success, ENOMEM if out of memory
58 */
[d8503fd]59errno_t ds_seat_create(ds_display_t *display, const char *name,
60 ds_seat_t **rseat)
[cf32dbd]61{
62 ds_seat_t *seat;
[d8503fd]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 }
[cf32dbd]71
72 seat = calloc(1, sizeof(ds_seat_t));
73 if (seat == NULL)
74 return ENOMEM;
75
[d8503fd]76 seat->name = str_dup(name);
77 if (seat->name == NULL) {
78 free(seat);
79 return ENOMEM;
80 }
81
[a0d4afe]82 list_initialize(&seat->idevcfgs);
83
[cf32dbd]84 ds_display_add_seat(display, seat);
[c2250702]85 seat->pntpos.x = 0;
86 seat->pntpos.y = 0;
[cf32dbd]87
[9901f267]88 seat->client_cursor = display->cursor[dcurs_arrow];
89 seat->wm_cursor = NULL;
[4d8002d]90
[cf32dbd]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{
[a0d4afe]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
[3e7e226]110 /* Remove this seat's focus */
111 if (seat->focus != NULL)
112 ds_window_post_unfocus_event(seat->focus);
113
[cf32dbd]114 ds_display_remove_seat(seat);
[a0d4afe]115
[d8503fd]116 free(seat->name);
[cf32dbd]117 free(seat);
118}
119
[4fbdc3d]120/** Set seat focus to a window.
121 *
122 * @param seat Seat
123 * @param wnd Window to focus
124 */
[cf32dbd]125void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd)
126{
[06176e1]127 errno_t rc;
128
[554a5f1]129 if (wnd == seat->focus) {
130 /* Focus is not changing */
131 return;
132 }
133
[06176e1]134 if (wnd != NULL) {
135 rc = ds_window_unminimize(wnd);
136 if (rc != EOK)
137 return;
138 }
139
[b0a94854]140 if (seat->focus != NULL)
141 ds_window_post_unfocus_event(seat->focus);
142
[cf32dbd]143 seat->focus = wnd;
[b0a94854]144
[ef30659]145 if (wnd != NULL) {
[b0a94854]146 ds_window_post_focus_event(wnd);
[ef30659]147 ds_window_bring_to_top(wnd);
148 }
[d7f82635]149
150 /* When focus changes, popup window should be closed */
151 ds_seat_set_popup(seat, NULL);
[cf32dbd]152}
153
[9e84d2c]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{
[d7f82635]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
[9e84d2c]170 seat->popup = wnd;
171}
172
173/** Evacuate seat references to window.
[4fbdc3d]174 *
[ededdc4]175 * If seat's focus is @a wnd, it will be set to NULL.
[9e84d2c]176 * If seat's popup window is @a wnd, it will be set to NULL.
[4fbdc3d]177 *
178 * @param seat Seat
[17c0f5d]179 * @param wnd Window to evacuate references from
[4fbdc3d]180 */
[9e84d2c]181void ds_seat_evac_wnd_refs(ds_seat_t *seat, ds_window_t *wnd)
[cf32dbd]182{
[ededdc4]183 if (seat->focus == wnd)
184 ds_seat_set_focus(seat, NULL);
[9e84d2c]185
186 if (seat->popup == wnd)
[d7f82635]187 ds_seat_set_popup(seat, NULL);
[cf32dbd]188}
189
[17c0f5d]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 */
[acd7ac2]206 nwnd = ds_window_find_prev(wnd, ~(wndf_minimized | wndf_system));
[17c0f5d]207
208 if (nwnd == NULL) {
209 /* Find alternate window that is not minimized */
[acd7ac2]210 nwnd = ds_window_find_prev(wnd, ~wndf_minimized);
[17c0f5d]211 }
212
213 ds_seat_set_focus(seat, nwnd);
214}
215
[0da03df]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
[ca9aa89]225 /* Find alternate window that is not a system window */
[acd7ac2]226 nwnd = ds_window_find_next(seat->focus, ~wndf_system);
[0da03df]227
[ca9aa89]228 /* Only switch focus if there is another window */
[0da03df]229 if (nwnd != NULL)
230 ds_seat_set_focus(seat, nwnd);
231}
232
[cf32dbd]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{
[79949f3]242 ds_window_t *dwindow;
243 bool alt_or_shift;
[cf32dbd]244
[79949f3]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 */
[0da03df]248 ds_seat_switch_focus(seat);
[79949f3]249 return EOK;
250 }
251
[9e84d2c]252 dwindow = seat->popup;
253 if (dwindow == NULL)
254 dwindow = seat->focus;
255
[cf32dbd]256 if (dwindow == NULL)
257 return EOK;
258
[338d0935]259 return ds_window_post_kbd_event(dwindow, event);
[cf32dbd]260}
261
[9901f267]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;
[978c9bc5]298 gfx_rect_t old_rect;
[9901f267]299
300 old_cursor = ds_seat_get_cursor(seat);
301 new_cursor = ds_seat_compute_cursor(seat->wm_cursor, cursor);
302
[978c9bc5]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 }
[9901f267]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;
[978c9bc5]323 gfx_rect_t old_rect;
[9901f267]324
325 old_cursor = ds_seat_get_cursor(seat);
326 new_cursor = ds_seat_compute_cursor(cursor, seat->client_cursor);
327
[978c9bc5]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 }
[9901f267]335}
336
[978c9bc5]337/** Get rectangle covered by pointer.
[4fbdc3d]338 *
339 * @param seat Seat
[978c9bc5]340 * @param rect Place to store rectangle
[4fbdc3d]341 */
[978c9bc5]342void ds_seat_get_pointer_rect(ds_seat_t *seat, gfx_rect_t *rect)
[4fbdc3d]343{
[9901f267]344 ds_cursor_t *cursor;
345
346 cursor = ds_seat_get_cursor(seat);
[978c9bc5]347 ds_cursor_get_rect(cursor, &seat->pntpos, rect);
[4fbdc3d]348}
349
[978c9bc5]350/** Repaint seat pointer
351 *
352 * Repaint the pointer after it has moved or changed. This is done by
[d70e7b7b]353 * repainting the area of the display previously (@a old_rect) and currently
[978c9bc5]354 * covered by the pointer.
[28db46b]355 *
356 * @param seat Seat
[978c9bc5]357 * @param old_rect Rectangle previously covered by pointer
[28db46b]358 *
359 * @return EOK on success or an error code
360 */
[978c9bc5]361static errno_t ds_seat_repaint_pointer(ds_seat_t *seat, gfx_rect_t *old_rect)
[28db46b]362{
[978c9bc5]363 gfx_rect_t new_rect;
364 gfx_rect_t envelope;
365 errno_t rc;
[9901f267]366
[978c9bc5]367 ds_seat_get_pointer_rect(seat, &new_rect);
[28db46b]368
[6301a24f]369 if (gfx_rect_is_incident(old_rect, &new_rect)) {
[978c9bc5]370 /* Rectangles do not intersect. Repaint them separately. */
371 rc = ds_display_paint(seat->display, &new_rect);
372 if (rc != EOK)
373 return rc;
[28db46b]374
[978c9bc5]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;
[28db46b]391}
392
[4fbdc3d]393/** Post pointing device event to the seat
[a40ae0d]394 *
395 * Update pointer position and generate position event.
[4fbdc3d]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{
[e1f2079]404 ds_display_t *disp = seat->display;
[4fbdc3d]405 gfx_coord2_t npos;
[978c9bc5]406 gfx_rect_t old_rect;
[a40ae0d]407 ds_window_t *wnd;
408 pos_event_t pevent;
409 errno_t rc;
[4fbdc3d]410
[a40ae0d]411 wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
412
[4fbdc3d]413 /* Focus window on button press */
[a40ae0d]414 if (event->type == PTD_PRESS && event->btn_num == 1) {
[9e84d2c]415 if (wnd != NULL && (wnd->flags & wndf_popup) == 0) {
[4fbdc3d]416 ds_seat_set_focus(seat, wnd);
417 }
418 }
419
[8edec53]420 if (event->type == PTD_PRESS || event->type == PTD_RELEASE ||
421 event->type == PTD_DCLICK) {
[60ebe63]422 pevent.pos_id = event->pos_id;
[8edec53]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
[a40ae0d]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
[4fbdc3d]446 if (event->type == PTD_MOVE) {
447 gfx_coord2_add(&seat->pntpos, &event->dmove, &npos);
[e1f2079]448 gfx_coord2_clip(&npos, &disp->rect, &npos);
[4fbdc3d]449
[978c9bc5]450 ds_seat_get_pointer_rect(seat, &old_rect);
[4fbdc3d]451 seat->pntpos = npos;
[a40ae0d]452
[60ebe63]453 pevent.pos_id = event->pos_id;
[a40ae0d]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
[978c9bc5]463 ds_seat_repaint_pointer(seat, &old_rect);
[4fbdc3d]464 }
465
[1388f7f0]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
[978c9bc5]478 ds_seat_get_pointer_rect(seat, &old_rect);
[1388f7f0]479 seat->pntpos = npos;
480
[60ebe63]481 pevent.pos_id = event->pos_id;
[1388f7f0]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
[978c9bc5]491 ds_seat_repaint_pointer(seat, &old_rect);
[1388f7f0]492 }
493
[4fbdc3d]494 return EOK;
495}
496
[a40ae0d]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);
[d7f82635]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. */
[5823aef3]518 if (seat->popup != NULL) {
[9e84d2c]519 rc = ds_window_post_pos_event(seat->popup, event);
520 if (rc != EOK)
521 return rc;
522 }
523
[0da03df]524 if (seat->focus != wnd && seat->focus != NULL) {
[bc492d5]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
[a40ae0d]534 if (wnd != NULL) {
[9242ad9]535 /* Moving over a window */
[9901f267]536 ds_seat_set_client_cursor(seat, wnd->cursor);
[9242ad9]537
[5823aef3]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 }
[9242ad9]547 } else {
548 /* Not over a window */
[9901f267]549 ds_seat_set_client_cursor(seat, seat->display->cursor[dcurs_arrow]);
[a40ae0d]550 }
551
552 return EOK;
553}
554
[978c9bc5]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);
[62018a0]565 return ds_cursor_paint(cursor, &seat->pntpos, rect);
[978c9bc5]566}
567
[a0d4afe]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
[cf32dbd]622/** @}
623 */
Note: See TracBrowser for help on using the repository browser.