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

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1215db9 was d7f82635, checked in by jxsvoboda <5887334+jxsvoboda@…>, 4 years ago

Deliver close event to popup window when appropriate

That is, when focus changes or when user clicks outside of the
popup window.

  • Property mode set to 100644
File size: 11.6 KB
Line 
1/*
2 * Copyright (c) 2021 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 "client.h"
42#include "cursor.h"
43#include "display.h"
44#include "seat.h"
45#include "window.h"
46
47static void ds_seat_get_pointer_rect(ds_seat_t *, gfx_rect_t *);
48static errno_t ds_seat_repaint_pointer(ds_seat_t *, gfx_rect_t *);
49
50/** Create seat.
51 *
52 * @param display Parent display
53 * @param rseat Place to store pointer to new seat.
54 * @return EOK on success, ENOMEM if out of memory
55 */
56errno_t ds_seat_create(ds_display_t *display, ds_seat_t **rseat)
57{
58 ds_seat_t *seat;
59
60 seat = calloc(1, sizeof(ds_seat_t));
61 if (seat == NULL)
62 return ENOMEM;
63
64 ds_display_add_seat(display, seat);
65 seat->pntpos.x = 0;
66 seat->pntpos.y = 0;
67
68 seat->client_cursor = display->cursor[dcurs_arrow];
69 seat->wm_cursor = NULL;
70
71 *rseat = seat;
72 return EOK;
73}
74
75/** Destroy seat.
76 *
77 * @param seat Seat
78 */
79void ds_seat_destroy(ds_seat_t *seat)
80{
81 ds_display_remove_seat(seat);
82 free(seat);
83}
84
85/** Set seat focus to a window.
86 *
87 * @param seat Seat
88 * @param wnd Window to focus
89 */
90void ds_seat_set_focus(ds_seat_t *seat, ds_window_t *wnd)
91{
92 if (wnd == seat->focus) {
93 /* Focus is not changing */
94 return;
95 }
96
97 if (seat->focus != NULL)
98 ds_window_post_unfocus_event(seat->focus);
99
100 seat->focus = wnd;
101
102 if (wnd != NULL) {
103 ds_window_post_focus_event(wnd);
104 ds_window_bring_to_top(wnd);
105 }
106
107 /* When focus changes, popup window should be closed */
108 ds_seat_set_popup(seat, NULL);
109}
110
111/** Set seat popup window.
112 *
113 * @param seat Seat
114 * @param wnd Popup window
115 */
116void ds_seat_set_popup(ds_seat_t *seat, ds_window_t *wnd)
117{
118 if (wnd == seat->popup)
119 return;
120
121 if (seat->popup != NULL) {
122 /* Window is no longer the popup window, send close request */
123 ds_client_post_close_event(seat->popup->client,
124 seat->popup);
125 }
126
127 seat->popup = wnd;
128}
129
130/** Evacuate seat references to window.
131 *
132 * If seat's focus is @a wnd, it will be set to a different window.
133 * If seat's popup window is @a wnd, it will be set to NULL.
134 *
135 * @param seat Seat
136 * @param wnd Window to evacuate focus from
137 */
138void ds_seat_evac_wnd_refs(ds_seat_t *seat, ds_window_t *wnd)
139{
140 ds_window_t *nwnd;
141
142 if (seat->focus == wnd) {
143 nwnd = ds_display_prev_window(wnd);
144 if (nwnd == NULL)
145 nwnd = ds_display_last_window(wnd->display);
146 if (nwnd == wnd)
147 nwnd = NULL;
148
149 ds_seat_set_focus(seat, nwnd);
150 }
151
152 if (seat->popup == wnd)
153 ds_seat_set_popup(seat, NULL);
154}
155
156/** Switch focus to another window.
157 *
158 * @param seat Seat
159 * @param wnd Window to evacuate focus from
160 */
161void ds_seat_switch_focus(ds_seat_t *seat)
162{
163 ds_window_t *nwnd;
164
165 if (seat->focus != NULL)
166 nwnd = ds_display_prev_window(seat->focus);
167 else
168 nwnd = NULL;
169
170 if (nwnd == NULL)
171 nwnd = ds_display_last_window(seat->display);
172
173 if (nwnd != NULL)
174 ds_seat_set_focus(seat, nwnd);
175}
176
177/** Post keyboard event to the seat's focused window.
178 *
179 * @param seat Seat
180 * @param event Event
181 *
182 * @return EOK on success or an error code
183 */
184errno_t ds_seat_post_kbd_event(ds_seat_t *seat, kbd_event_t *event)
185{
186 ds_window_t *dwindow;
187 bool alt_or_shift;
188
189 alt_or_shift = event->mods & (KM_SHIFT | KM_ALT);
190 if (event->type == KEY_PRESS && alt_or_shift && event->key == KC_TAB) {
191 /* On Alt-Tab or Shift-Tab, switch focus to next window */
192 ds_seat_switch_focus(seat);
193 return EOK;
194 }
195
196 dwindow = seat->popup;
197 if (dwindow == NULL)
198 dwindow = seat->focus;
199
200 if (dwindow == NULL)
201 return EOK;
202
203 return ds_window_post_kbd_event(dwindow, event);
204}
205
206/** Get current cursor used by seat.
207 *
208 * @param wmcurs WM curor
209 * @param ccurs Client cursor
210 * @return
211 */
212static ds_cursor_t *ds_seat_compute_cursor(ds_cursor_t *wmcurs, ds_cursor_t *ccurs)
213{
214 if (wmcurs != NULL)
215 return wmcurs;
216
217 return ccurs;
218}
219
220/** Get current cursor used by seat.
221 *
222 * @param seat Seat
223 * @return Current cursor
224 */
225static ds_cursor_t *ds_seat_get_cursor(ds_seat_t *seat)
226{
227 return ds_seat_compute_cursor(seat->wm_cursor, seat->client_cursor);
228}
229
230/** Set client cursor.
231 *
232 * Set cursor selected by client. This may update the actual cursor
233 * if WM is not overriding the cursor.
234 *
235 * @param seat Seat
236 * @param cursor Client cursor
237 */
238static void ds_seat_set_client_cursor(ds_seat_t *seat, ds_cursor_t *cursor)
239{
240 ds_cursor_t *old_cursor;
241 ds_cursor_t *new_cursor;
242 gfx_rect_t old_rect;
243
244 old_cursor = ds_seat_get_cursor(seat);
245 new_cursor = ds_seat_compute_cursor(seat->wm_cursor, cursor);
246
247 if (new_cursor != old_cursor) {
248 ds_seat_get_pointer_rect(seat, &old_rect);
249 seat->client_cursor = cursor;
250 ds_seat_repaint_pointer(seat, &old_rect);
251 } else {
252 seat->client_cursor = cursor;
253 }
254}
255
256/** Set WM cursor.
257 *
258 * Set cursor override for window management.
259 *
260 * @param seat Seat
261 * @param cursor WM cursor override or @c NULL not to override the cursor
262 */
263void ds_seat_set_wm_cursor(ds_seat_t *seat, ds_cursor_t *cursor)
264{
265 ds_cursor_t *old_cursor;
266 ds_cursor_t *new_cursor;
267 gfx_rect_t old_rect;
268
269 old_cursor = ds_seat_get_cursor(seat);
270 new_cursor = ds_seat_compute_cursor(cursor, seat->client_cursor);
271
272 if (new_cursor != old_cursor) {
273 ds_seat_get_pointer_rect(seat, &old_rect);
274 seat->wm_cursor = cursor;
275 ds_seat_repaint_pointer(seat, &old_rect);
276 } else {
277 seat->wm_cursor = cursor;
278 }
279}
280
281/** Get rectangle covered by pointer.
282 *
283 * @param seat Seat
284 * @param rect Place to store rectangle
285 */
286void ds_seat_get_pointer_rect(ds_seat_t *seat, gfx_rect_t *rect)
287{
288 ds_cursor_t *cursor;
289
290 cursor = ds_seat_get_cursor(seat);
291 ds_cursor_get_rect(cursor, &seat->pntpos, rect);
292}
293
294/** Repaint seat pointer
295 *
296 * Repaint the pointer after it has moved or changed. This is done by
297 * repainting the area of the display previously (@a old_rect) and currently
298 * covered by the pointer.
299 *
300 * @param seat Seat
301 * @param old_rect Rectangle previously covered by pointer
302 *
303 * @return EOK on success or an error code
304 */
305static errno_t ds_seat_repaint_pointer(ds_seat_t *seat, gfx_rect_t *old_rect)
306{
307 gfx_rect_t new_rect;
308 gfx_rect_t envelope;
309 errno_t rc;
310
311 ds_seat_get_pointer_rect(seat, &new_rect);
312
313 if (gfx_rect_is_incident(old_rect, &new_rect)) {
314 /* Rectangles do not intersect. Repaint them separately. */
315 rc = ds_display_paint(seat->display, &new_rect);
316 if (rc != EOK)
317 return rc;
318
319 rc = ds_display_paint(seat->display, old_rect);
320 if (rc != EOK)
321 return rc;
322 } else {
323 /*
324 * Rectangles intersect. As an optimization, repaint them
325 * in a single operation.
326 */
327 gfx_rect_envelope(old_rect, &new_rect, &envelope);
328
329 rc = ds_display_paint(seat->display, &envelope);
330 if (rc != EOK)
331 return rc;
332 }
333
334 return EOK;
335}
336
337/** Post pointing device event to the seat
338 *
339 * Update pointer position and generate position event.
340 *
341 * @param seat Seat
342 * @param event Event
343 *
344 * @return EOK on success or an error code
345 */
346errno_t ds_seat_post_ptd_event(ds_seat_t *seat, ptd_event_t *event)
347{
348 ds_display_t *disp = seat->display;
349 gfx_coord2_t npos;
350 gfx_rect_t old_rect;
351 ds_window_t *wnd;
352 pos_event_t pevent;
353 errno_t rc;
354
355 wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
356
357 /* Focus window on button press */
358 if (event->type == PTD_PRESS && event->btn_num == 1) {
359 if (wnd != NULL && (wnd->flags & wndf_popup) == 0) {
360 ds_seat_set_focus(seat, wnd);
361 }
362 }
363
364 if (event->type == PTD_PRESS || event->type == PTD_RELEASE) {
365 pevent.pos_id = 0;
366 pevent.type = (event->type == PTD_PRESS) ?
367 POS_PRESS : POS_RELEASE;
368 pevent.btn_num = event->btn_num;
369 pevent.hpos = seat->pntpos.x;
370 pevent.vpos = seat->pntpos.y;
371
372 rc = ds_seat_post_pos_event(seat, &pevent);
373 if (rc != EOK)
374 return rc;
375 }
376
377 if (event->type == PTD_MOVE) {
378 gfx_coord2_add(&seat->pntpos, &event->dmove, &npos);
379 gfx_coord2_clip(&npos, &disp->rect, &npos);
380
381 ds_seat_get_pointer_rect(seat, &old_rect);
382 seat->pntpos = npos;
383
384 pevent.pos_id = 0;
385 pevent.type = POS_UPDATE;
386 pevent.btn_num = 0;
387 pevent.hpos = seat->pntpos.x;
388 pevent.vpos = seat->pntpos.y;
389
390 rc = ds_seat_post_pos_event(seat, &pevent);
391 if (rc != EOK)
392 return rc;
393
394 ds_seat_repaint_pointer(seat, &old_rect);
395 }
396
397 if (event->type == PTD_ABS_MOVE) {
398 /*
399 * Project input device area onto display area. Technically
400 * we probably want to project onto the area of a particular
401 * display device. The tricky part is figuring out which
402 * display device the input device is associated with.
403 */
404 gfx_coord2_project(&event->apos, &event->abounds,
405 &disp->rect, &npos);
406
407 gfx_coord2_clip(&npos, &disp->rect, &npos);
408
409 ds_seat_get_pointer_rect(seat, &old_rect);
410 seat->pntpos = npos;
411
412 pevent.pos_id = 0;
413 pevent.type = POS_UPDATE;
414 pevent.btn_num = 0;
415 pevent.hpos = seat->pntpos.x;
416 pevent.vpos = seat->pntpos.y;
417
418 rc = ds_seat_post_pos_event(seat, &pevent);
419 if (rc != EOK)
420 return rc;
421
422 ds_seat_repaint_pointer(seat, &old_rect);
423 }
424
425 return EOK;
426}
427
428/** Post position event to seat.
429 *
430 * Deliver event to relevant windows.
431 *
432 * @param seat Seat
433 * @param event Position event
434 */
435errno_t ds_seat_post_pos_event(ds_seat_t *seat, pos_event_t *event)
436{
437 ds_window_t *wnd;
438 errno_t rc;
439
440 wnd = ds_display_window_by_pos(seat->display, &seat->pntpos);
441
442 /* Click outside popup window */
443 if (event->type == POS_PRESS && wnd != seat->popup) {
444 /* Close popup window */
445 ds_seat_set_popup(seat, NULL);
446 }
447
448 /* Deliver event to popup window. */
449 if (seat->popup != NULL) {
450 rc = ds_window_post_pos_event(seat->popup, event);
451 if (rc != EOK)
452 return rc;
453 }
454
455 if (seat->focus != wnd && seat->focus != NULL) {
456 rc = ds_window_post_pos_event(seat->focus, event);
457 if (rc != EOK)
458 return rc;
459
460 /* Only deliver release events to the focused window */
461 if (event->type == POS_RELEASE)
462 return EOK;
463 }
464
465 if (wnd != NULL) {
466 /* Moving over a window */
467 ds_seat_set_client_cursor(seat, wnd->cursor);
468
469 /*
470 * Only deliver event if we didn't already deliver it
471 * to the same window above.
472 */
473 if (wnd != seat->popup) {
474 rc = ds_window_post_pos_event(wnd, event);
475 if (rc != EOK)
476 return rc;
477 }
478 } else {
479 /* Not over a window */
480 ds_seat_set_client_cursor(seat, seat->display->cursor[dcurs_arrow]);
481 }
482
483 return EOK;
484}
485
486/** Paint seat pointer.
487 *
488 * @param seat Seat whose pointer to paint
489 * @param rect Clipping rectangle
490 */
491errno_t ds_seat_paint_pointer(ds_seat_t *seat, gfx_rect_t *rect)
492{
493 ds_cursor_t *cursor;
494
495 cursor = ds_seat_get_cursor(seat);
496 return ds_cursor_paint(cursor, &seat->pntpos, rect);
497}
498
499/** @}
500 */
Note: See TracBrowser for help on using the repository browser.