source: mainline/uspace/lib/ui/src/ui.c@ adfa8b6

Last change on this file since adfa8b6 was 3e41cc4, checked in by Jiri Svoboda <jiri@…>, 2 months ago

Repaint all windows in fullscreen mode in ui_paint().

After error dialog is closed in Navigator, need to also repaint main window,
not just progress window.

  • Property mode set to 100644
File size: 14.0 KB
Line 
1/*
2 * Copyright (c) 2025 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 libui
30 * @{
31 */
32/**
33 * @file User interface
34 */
35
36#include <adt/list.h>
37#include <ctype.h>
38#include <display.h>
39#include <errno.h>
40#include <fibril.h>
41#include <fibril_synch.h>
42#include <gfx/color.h>
43#include <gfx/cursor.h>
44#include <gfx/render.h>
45#include <io/console.h>
46#include <stdbool.h>
47#include <stdlib.h>
48#include <str.h>
49#include <task.h>
50#include <types/common.h>
51#include <ui/clickmatic.h>
52#include <ui/ui.h>
53#include <ui/wdecor.h>
54#include <ui/window.h>
55#include "../private/wdecor.h"
56#include "../private/window.h"
57#include "../private/ui.h"
58
59/** Parse output specification.
60 *
61 * Output specification has the form <proto>@<service> where proto is
62 * eiher 'disp' for display service, 'cons' for console, 'null'
63 * for dummy output. Service is a location ID service name (e.g. hid/display).
64 *
65 * @param ospec Output specification
66 * @param ws Place to store window system type (protocol)
67 * @param osvc Place to store pointer to output service name
68 * @param ridev_id Place to store input device ID
69 * @return EOK on success, EINVAL if syntax is invalid, ENOMEM if out of
70 * memory
71 */
72static errno_t ui_ospec_parse(const char *ospec, ui_winsys_t *ws,
73 char **osvc, sysarg_t *ridev_id)
74{
75 const char *cp;
76 const char *qm;
77 const char *endptr;
78 uint64_t idev_id;
79 errno_t rc;
80
81 *ridev_id = 0;
82
83 cp = ospec;
84 while (isalpha(*cp))
85 ++cp;
86
87 /* Window system / protocol */
88 if (*cp == '@') {
89 if (str_lcmp(ospec, "disp@", str_length("disp@")) == 0) {
90 *ws = ui_ws_display;
91 } else if (str_lcmp(ospec, "cons@", str_length("cons@")) == 0) {
92 *ws = ui_ws_console;
93 } else if (str_lcmp(ospec, "null@", str_length("null@")) == 0) {
94 *ws = ui_ws_null;
95 } else if (str_lcmp(ospec, "@", str_length("@")) == 0) {
96 *ws = ui_ws_any;
97 } else {
98 *ws = ui_ws_unknown;
99 }
100
101 ++cp;
102 } else {
103 *ws = ui_ws_display;
104 }
105
106 /* Output service is the part before question mark */
107 qm = str_chr(cp, '?');
108 if (qm != NULL) {
109 *osvc = str_ndup(cp, qm - cp);
110 } else {
111 /* No question mark */
112 *osvc = str_dup(cp);
113 }
114
115 if (*osvc == NULL)
116 return ENOMEM;
117
118 if (qm != NULL) {
119 /* The part after the question mark */
120 cp = qm + 1;
121
122 /* Input device ID parameter */
123 if (str_lcmp(cp, "idev=", str_length("idev=")) == 0) {
124 cp += str_length("idev=");
125
126 rc = str_uint64_t(cp, &endptr, 10, false, &idev_id);
127 if (rc != EOK)
128 goto error;
129
130 *ridev_id = idev_id;
131 cp = endptr;
132 }
133 }
134
135 if (*cp != '\0') {
136 rc = EINVAL;
137 goto error;
138 }
139
140 return EOK;
141error:
142 free(*osvc);
143 *osvc = NULL;
144 return rc;
145}
146
147/** Create new user interface.
148 *
149 * @param ospec Output specification or @c UI_DISPLAY_DEFAULT to use
150 * the default display service, UI_CONSOLE_DEFAULT to use
151 * the default console service, UI_DISPLAY_NULL to use
152 * dummy output.
153 * @param rui Place to store pointer to new UI
154 * @return EOK on success or an error code
155 */
156errno_t ui_create(const char *ospec, ui_t **rui)
157{
158 errno_t rc;
159 display_t *display;
160 console_ctrl_t *console;
161 console_gc_t *cgc;
162 ui_winsys_t ws;
163 char *osvc;
164 sysarg_t cols;
165 sysarg_t rows;
166 sysarg_t idev_id;
167 ui_t *ui;
168
169 rc = ui_ospec_parse(ospec, &ws, &osvc, &idev_id);
170 if (rc != EOK)
171 return rc;
172
173 if (ws == ui_ws_display || ws == ui_ws_any) {
174 rc = display_open((str_cmp(osvc, "") != 0) ? osvc :
175 DISPLAY_DEFAULT, &display);
176 if (rc != EOK)
177 goto disp_fail;
178
179 rc = ui_create_disp(display, &ui);
180 if (rc != EOK) {
181 display_close(display);
182 goto disp_fail;
183 }
184
185 free(osvc);
186 ui->myoutput = true;
187 ui->idev_id = idev_id;
188 *rui = ui;
189 return EOK;
190 }
191
192disp_fail:
193 if (ws == ui_ws_console || ws == ui_ws_any) {
194 console = console_init(stdin, stdout);
195 if (console == NULL)
196 goto cons_fail;
197
198 rc = console_get_size(console, &cols, &rows);
199 if (rc != EOK) {
200 console_done(console);
201 goto cons_fail;
202 }
203
204 console_cursor_visibility(console, false);
205
206 /* ws == ui_ws_console */
207 rc = ui_create_cons(console, &ui);
208 if (rc != EOK) {
209 console_done(console);
210 goto cons_fail;
211 }
212
213 rc = console_gc_create(console, NULL, &cgc);
214 if (rc != EOK) {
215 ui_destroy(ui);
216 console_done(console);
217 goto cons_fail;
218 }
219
220 free(osvc);
221
222 ui->cgc = cgc;
223 ui->rect.p0.x = 0;
224 ui->rect.p0.y = 0;
225 ui->rect.p1.x = cols;
226 ui->rect.p1.y = rows;
227
228 (void) ui_paint(ui);
229 ui->myoutput = true;
230 *rui = ui;
231 return EOK;
232 }
233
234cons_fail:
235 if (ws == ui_ws_null) {
236 free(osvc);
237 rc = ui_create_disp(NULL, &ui);
238 if (rc != EOK)
239 return rc;
240
241 ui->myoutput = true;
242 *rui = ui;
243 return EOK;
244 }
245
246 free(osvc);
247 return EINVAL;
248}
249
250/** Create new user interface using console service.
251 *
252 * @param rui Place to store pointer to new UI
253 * @return EOK on success or an error code
254 */
255errno_t ui_create_cons(console_ctrl_t *console, ui_t **rui)
256{
257 ui_t *ui;
258 errno_t rc;
259
260 ui = calloc(1, sizeof(ui_t));
261 if (ui == NULL)
262 return ENOMEM;
263
264 rc = ui_clickmatic_create(ui, &ui->clickmatic);
265 if (rc != EOK) {
266 free(ui);
267 return rc;
268 }
269
270 ui->console = console;
271 list_initialize(&ui->windows);
272 fibril_mutex_initialize(&ui->lock);
273 *rui = ui;
274 return EOK;
275}
276
277/** Create new user interface using display service.
278 *
279 * @param disp Display
280 * @param rui Place to store pointer to new UI
281 * @return EOK on success or an error code
282 */
283errno_t ui_create_disp(display_t *disp, ui_t **rui)
284{
285 ui_t *ui;
286 errno_t rc;
287
288 ui = calloc(1, sizeof(ui_t));
289 if (ui == NULL)
290 return ENOMEM;
291
292 rc = ui_clickmatic_create(ui, &ui->clickmatic);
293 if (rc != EOK) {
294 free(ui);
295 return rc;
296 }
297
298 ui->display = disp;
299 list_initialize(&ui->windows);
300 fibril_mutex_initialize(&ui->lock);
301 *rui = ui;
302 return EOK;
303}
304
305/** Destroy user interface.
306 *
307 * @param ui User interface or @c NULL
308 */
309void ui_destroy(ui_t *ui)
310{
311 if (ui == NULL)
312 return;
313
314 if (ui->myoutput) {
315 if (ui->cgc != NULL)
316 console_gc_delete(ui->cgc);
317 if (ui->console != NULL) {
318 console_cursor_visibility(ui->console, true);
319 console_done(ui->console);
320 }
321 if (ui->display != NULL)
322 display_close(ui->display);
323 }
324
325 free(ui);
326}
327
328static void ui_cons_event_process(ui_t *ui, cons_event_t *event)
329{
330 ui_window_t *awnd;
331 ui_evclaim_t claim;
332 pos_event_t pos;
333
334 awnd = ui_window_get_active(ui);
335 if (awnd == NULL)
336 return;
337
338 switch (event->type) {
339 case CEV_KEY:
340 ui_lock(ui);
341 ui_window_send_kbd(awnd, &event->ev.key);
342 ui_unlock(ui);
343 break;
344 case CEV_POS:
345 pos = event->ev.pos;
346 /* Translate event to window-relative coordinates */
347 pos.hpos -= awnd->dpos.x;
348 pos.vpos -= awnd->dpos.y;
349
350 claim = ui_wdecor_pos_event(awnd->wdecor, &pos);
351 /* Note: If event is claimed, awnd might not be valid anymore */
352 if (claim == ui_unclaimed) {
353 ui_lock(ui);
354 ui_window_send_pos(awnd, &pos);
355 ui_unlock(ui);
356 }
357
358 break;
359 case CEV_RESIZE:
360 ui_lock(ui);
361 ui_window_send_resize(awnd);
362 ui_unlock(ui);
363 break;
364 }
365}
366
367/** Execute user interface.
368 *
369 * Return task exit code of zero and block unitl the application starts
370 * the termination process by calling ui_quit(@a ui).
371 *
372 * @param ui User interface
373 */
374void ui_run(ui_t *ui)
375{
376 cons_event_t event;
377 usec_t timeout;
378 errno_t rc;
379
380 /* Only return command prompt if we are running in a separate window */
381 if (ui->display != NULL)
382 task_retval(0);
383
384 while (!ui->quit) {
385 if (ui->console != NULL) {
386 timeout = 100000;
387 rc = console_get_event_timeout(ui->console,
388 &event, &timeout);
389
390 /* Do we actually have an event? */
391 if (rc == EOK) {
392 ui_cons_event_process(ui, &event);
393 } else if (rc != ETIMEOUT) {
394 /* Error, quit */
395 break;
396 }
397 } else {
398 fibril_usleep(100000);
399 }
400 }
401}
402
403/** Repaint UI (only used in fullscreen mode).
404 *
405 * This is used when an area is exposed in fullscreen mode.
406 *
407 * @param ui UI
408 * @return @c EOK on success or an error code
409 */
410errno_t ui_paint(ui_t *ui)
411{
412 errno_t rc;
413 gfx_context_t *gc;
414 ui_window_t *wnd;
415 gfx_color_t *color = NULL;
416
417 /* In case of null output */
418 if (ui->cgc == NULL)
419 return EOK;
420
421 gc = console_gc_get_ctx(ui->cgc);
422
423 rc = gfx_color_new_ega(0x11, &color);
424 if (rc != EOK)
425 return rc;
426
427 rc = gfx_set_color(gc, color);
428 if (rc != EOK) {
429 gfx_color_delete(color);
430 return rc;
431 }
432
433 rc = gfx_fill_rect(gc, &ui->rect);
434 if (rc != EOK) {
435 gfx_color_delete(color);
436 return rc;
437 }
438
439 gfx_color_delete(color);
440
441 /* Repaint all windows */
442 wnd = ui_window_first(ui);
443 while (wnd != NULL) {
444 rc = ui_wdecor_paint(wnd->wdecor);
445 if (rc != EOK)
446 return rc;
447
448 rc = ui_window_paint(wnd);
449 if (rc != EOK)
450 return rc;
451
452 wnd = ui_window_next(wnd);
453 }
454
455 return EOK;
456}
457
458/** Free up console for other users.
459 *
460 * Release console resources for another application (that the current
461 * task is starting). After the other application finishes, resume
462 * operation with ui_resume(). No calls to UI must happen inbetween
463 * and no events must be processed (i.e. the calling function must not
464 * return control to UI.
465 *
466 * @param ui UI
467 * @return EOK on success or an error code
468 */
469errno_t ui_suspend(ui_t *ui)
470{
471 errno_t rc;
472
473 assert(!ui->suspended);
474
475 if (ui->cgc == NULL) {
476 ui->suspended = true;
477 return EOK;
478 }
479
480 (void) console_set_caption(ui->console, "");
481 rc = console_gc_suspend(ui->cgc);
482 if (rc != EOK)
483 return rc;
484
485 ui->suspended = true;
486 return EOK;
487}
488
489/** Resume suspended UI.
490 *
491 * Reclaim console resources (after child application has finished running)
492 * and restore UI operation previously suspended by calling ui_suspend().
493 *
494 * @param ui UI
495 * @return EOK on success or an error code
496 */
497errno_t ui_resume(ui_t *ui)
498{
499 errno_t rc;
500 ui_window_t *awnd;
501 sysarg_t col;
502 sysarg_t row;
503 cons_event_t ev;
504
505 assert(ui->suspended);
506
507 if (ui->cgc == NULL) {
508 ui->suspended = false;
509 return EOK;
510 }
511
512 rc = console_get_pos(ui->console, &col, &row);
513 if (rc != EOK)
514 return rc;
515
516 /*
517 * Here's a little heuristic to help determine if we need
518 * to pause before returning to the UI. If we are in the
519 * top-left corner, chances are the screen is empty and
520 * there is no need to pause.
521 */
522 if (col != 0 || row != 0) {
523 printf("Press any key or button to continue...\n");
524
525 while (true) {
526 rc = console_get_event(ui->console, &ev);
527 if (rc != EOK)
528 return EIO;
529
530 if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS)
531 break;
532
533 if (ev.type == CEV_POS && ev.ev.pos.type == POS_PRESS)
534 break;
535 }
536 }
537
538 rc = console_gc_resume(ui->cgc);
539 if (rc != EOK)
540 return rc;
541
542 ui->suspended = false;
543
544 awnd = ui_window_get_active(ui);
545 if (awnd != NULL)
546 (void) console_set_caption(ui->console, awnd->wdecor->caption);
547
548 rc = gfx_cursor_set_visible(console_gc_get_ctx(ui->cgc), false);
549 if (rc != EOK)
550 return rc;
551
552 return EOK;
553}
554
555/** Determine if UI is suspended.
556 *
557 * @param ui UI
558 * @return @c true iff UI is suspended
559 */
560bool ui_is_suspended(ui_t *ui)
561{
562 return ui->suspended;
563}
564
565/** Lock UI.
566 *
567 * Block UI from calling window callbacks. @c ui_lock() and @c ui_unlock()
568 * must be used when accessing UI resources from a fibril (as opposed to
569 * from a window callback).
570 *
571 * @param ui UI
572 */
573void ui_lock(ui_t *ui)
574{
575 if (ui->display != NULL)
576 display_lock(ui->display);
577 fibril_mutex_lock(&ui->lock);
578}
579
580/** Unlock UI.
581 *
582 * Allow UI to call window callbacks. @c ui_lock() and @c ui_unlock()
583 * must be used when accessing window resources from a fibril (as opposed to
584 * from a window callback).
585 *
586 * @param ui UI
587 */
588void ui_unlock(ui_t *ui)
589{
590 fibril_mutex_unlock(&ui->lock);
591 if (ui->display != NULL)
592 display_unlock(ui->display);
593}
594
595/** Terminate user interface.
596 *
597 * Calling this function causes the user interface to terminate
598 * (i.e. exit from ui_run()). This would be typically called from
599 * an event handler.
600 *
601 * @param ui User interface
602 */
603void ui_quit(ui_t *ui)
604{
605 ui->quit = true;
606}
607
608/** Determine if we are running in text mode.
609 *
610 * @param ui User interface
611 * @return @c true iff we are running in text mode
612 */
613bool ui_is_textmode(ui_t *ui)
614{
615 /*
616 * XXX Currently console is always text and display is always
617 * graphics, but this need not always be true.
618 */
619 return (ui->console != NULL);
620}
621
622/** Determine if we are emulating windows.
623 *
624 * @param ui User interface
625 * @return @c true iff we are running in text mode
626 */
627bool ui_is_fullscreen(ui_t *ui)
628{
629 return (ui->display == NULL);
630}
631
632/** Get UI screen rectangle.
633 *
634 * @param ui User interface
635 * @param rect Place to store bounding rectangle
636 */
637errno_t ui_get_rect(ui_t *ui, gfx_rect_t *rect)
638{
639 display_info_t info;
640 sysarg_t cols, rows;
641 errno_t rc;
642
643 if (ui->display != NULL) {
644 rc = display_get_info(ui->display, &info);
645 if (rc != EOK)
646 return rc;
647
648 *rect = info.rect;
649 } else if (ui->console != NULL) {
650 rc = console_get_size(ui->console, &cols, &rows);
651 if (rc != EOK)
652 return rc;
653
654 rect->p0.x = 0;
655 rect->p0.y = 0;
656 rect->p1.x = cols;
657 rect->p1.y = rows;
658 } else {
659 return ENOTSUP;
660 }
661
662 return EOK;
663}
664
665/** Get clickmatic from UI.
666 *
667 * @pararm ui UI
668 * @return Clickmatic
669 */
670ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
671{
672 return ui->clickmatic;
673}
674
675/** @}
676 */
Note: See TracBrowser for help on using the repository browser.