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
RevLine 
[f7a90df]1/*
[8279aab]2 * Copyright (c) 2025 Jiri Svoboda
[f7a90df]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
[252d03c]36#include <adt/list.h>
[77ffa01]37#include <ctype.h>
[f7a90df]38#include <display.h>
39#include <errno.h>
[d284ce9]40#include <fibril.h>
[8965860c]41#include <fibril_synch.h>
[1ebcb791]42#include <gfx/color.h>
[c632c96]43#include <gfx/cursor.h>
[1ebcb791]44#include <gfx/render.h>
[77ffa01]45#include <io/console.h>
[87822ce]46#include <stdbool.h>
[f7a90df]47#include <stdlib.h>
[77ffa01]48#include <str.h>
[d55ab823]49#include <task.h>
[b1f0a14]50#include <types/common.h>
[8965860c]51#include <ui/clickmatic.h>
[f7a90df]52#include <ui/ui.h>
[77ffa01]53#include <ui/wdecor.h>
[252d03c]54#include <ui/window.h>
[b48e680f]55#include "../private/wdecor.h"
[77ffa01]56#include "../private/window.h"
[f7a90df]57#include "../private/ui.h"
58
[77ffa01]59/** Parse output specification.
60 *
61 * Output specification has the form <proto>@<service> where proto is
[3d10a2f]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).
[77ffa01]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
[b1f0a14]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
[77ffa01]71 */
[b1f0a14]72static errno_t ui_ospec_parse(const char *ospec, ui_winsys_t *ws,
73 char **osvc, sysarg_t *ridev_id)
[77ffa01]74{
75 const char *cp;
[b1f0a14]76 const char *qm;
77 const char *endptr;
78 uint64_t idev_id;
79 errno_t rc;
80
81 *ridev_id = 0;
[77ffa01]82
83 cp = ospec;
84 while (isalpha(*cp))
85 ++cp;
86
[b1f0a14]87 /* Window system / protocol */
[77ffa01]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;
[3d10a2f]93 } else if (str_lcmp(ospec, "null@", str_length("null@")) == 0) {
94 *ws = ui_ws_null;
[552b69f]95 } else if (str_lcmp(ospec, "@", str_length("@")) == 0) {
96 *ws = ui_ws_any;
[77ffa01]97 } else {
98 *ws = ui_ws_unknown;
99 }
100
[b1f0a14]101 ++cp;
[77ffa01]102 } else {
103 *ws = ui_ws_display;
104 }
[b1f0a14]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;
[77ffa01]145}
146
[f7a90df]147/** Create new user interface.
148 *
149 * @param ospec Output specification or @c UI_DISPLAY_DEFAULT to use
[3d10a2f]150 * the default display service, UI_CONSOLE_DEFAULT to use
151 * the default console service, UI_DISPLAY_NULL to use
152 * dummy output.
[f7a90df]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;
[77ffa01]160 console_ctrl_t *console;
[252d03c]161 console_gc_t *cgc;
[77ffa01]162 ui_winsys_t ws;
[b1f0a14]163 char *osvc;
[1ebcb791]164 sysarg_t cols;
165 sysarg_t rows;
[b1f0a14]166 sysarg_t idev_id;
[f7a90df]167 ui_t *ui;
168
[b1f0a14]169 rc = ui_ospec_parse(ospec, &ws, &osvc, &idev_id);
170 if (rc != EOK)
171 return rc;
[77ffa01]172
[552b69f]173 if (ws == ui_ws_display || ws == ui_ws_any) {
[b1f0a14]174 rc = display_open((str_cmp(osvc, "") != 0) ? osvc :
175 DISPLAY_DEFAULT, &display);
[77ffa01]176 if (rc != EOK)
[552b69f]177 goto disp_fail;
[77ffa01]178
179 rc = ui_create_disp(display, &ui);
180 if (rc != EOK) {
181 display_close(display);
[552b69f]182 goto disp_fail;
[77ffa01]183 }
[552b69f]184
[b1f0a14]185 free(osvc);
[552b69f]186 ui->myoutput = true;
[b1f0a14]187 ui->idev_id = idev_id;
[552b69f]188 *rui = ui;
189 return EOK;
190 }
191
192disp_fail:
193 if (ws == ui_ws_console || ws == ui_ws_any) {
[77ffa01]194 console = console_init(stdin, stdout);
195 if (console == NULL)
[552b69f]196 goto cons_fail;
[f7a90df]197
[1ebcb791]198 rc = console_get_size(console, &cols, &rows);
199 if (rc != EOK) {
200 console_done(console);
[552b69f]201 goto cons_fail;
[1ebcb791]202 }
203
[bb14312]204 console_cursor_visibility(console, false);
205
[77ffa01]206 /* ws == ui_ws_console */
207 rc = ui_create_cons(console, &ui);
208 if (rc != EOK) {
209 console_done(console);
[552b69f]210 goto cons_fail;
[77ffa01]211 }
[252d03c]212
213 rc = console_gc_create(console, NULL, &cgc);
214 if (rc != EOK) {
215 ui_destroy(ui);
216 console_done(console);
[552b69f]217 goto cons_fail;
[252d03c]218 }
219
[b1f0a14]220 free(osvc);
221
[252d03c]222 ui->cgc = cgc;
[1ebcb791]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);
[552b69f]229 ui->myoutput = true;
230 *rui = ui;
231 return EOK;
232 }
233
234cons_fail:
235 if (ws == ui_ws_null) {
[b1f0a14]236 free(osvc);
[3d10a2f]237 rc = ui_create_disp(NULL, &ui);
238 if (rc != EOK)
239 return rc;
[552b69f]240
241 ui->myoutput = true;
242 *rui = ui;
243 return EOK;
[f7a90df]244 }
245
[b1f0a14]246 free(osvc);
[552b69f]247 return EINVAL;
[f7a90df]248}
249
[77ffa01]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;
[8965860c]258 errno_t rc;
[77ffa01]259
260 ui = calloc(1, sizeof(ui_t));
261 if (ui == NULL)
262 return ENOMEM;
263
[8965860c]264 rc = ui_clickmatic_create(ui, &ui->clickmatic);
265 if (rc != EOK) {
266 free(ui);
267 return rc;
268 }
269
[77ffa01]270 ui->console = console;
[252d03c]271 list_initialize(&ui->windows);
[8965860c]272 fibril_mutex_initialize(&ui->lock);
[77ffa01]273 *rui = ui;
274 return EOK;
275}
276
[f7a90df]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;
[8965860c]286 errno_t rc;
[f7a90df]287
288 ui = calloc(1, sizeof(ui_t));
289 if (ui == NULL)
290 return ENOMEM;
291
[8965860c]292 rc = ui_clickmatic_create(ui, &ui->clickmatic);
293 if (rc != EOK) {
294 free(ui);
295 return rc;
296 }
297
[f7a90df]298 ui->display = disp;
[252d03c]299 list_initialize(&ui->windows);
[8965860c]300 fibril_mutex_initialize(&ui->lock);
[f7a90df]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
[77ffa01]314 if (ui->myoutput) {
[252d03c]315 if (ui->cgc != NULL)
316 console_gc_delete(ui->cgc);
[bb14312]317 if (ui->console != NULL) {
318 console_cursor_visibility(ui->console, true);
[77ffa01]319 console_done(ui->console);
[bb14312]320 }
[77ffa01]321 if (ui->display != NULL)
322 display_close(ui->display);
323 }
324
[f7a90df]325 free(ui);
326}
327
[77ffa01]328static void ui_cons_event_process(ui_t *ui, cons_event_t *event)
329{
[252d03c]330 ui_window_t *awnd;
331 ui_evclaim_t claim;
[3c3657c]332 pos_event_t pos;
[252d03c]333
334 awnd = ui_window_get_active(ui);
335 if (awnd == NULL)
[77ffa01]336 return;
337
338 switch (event->type) {
339 case CEV_KEY:
[983052c]340 ui_lock(ui);
[252d03c]341 ui_window_send_kbd(awnd, &event->ev.key);
[983052c]342 ui_unlock(ui);
[77ffa01]343 break;
344 case CEV_POS:
[3c3657c]345 pos = event->ev.pos;
[8ce56a6]346 /* Translate event to window-relative coordinates */
[3c3657c]347 pos.hpos -= awnd->dpos.x;
348 pos.vpos -= awnd->dpos.y;
349
350 claim = ui_wdecor_pos_event(awnd->wdecor, &pos);
[252d03c]351 /* Note: If event is claimed, awnd might not be valid anymore */
[983052c]352 if (claim == ui_unclaimed) {
353 ui_lock(ui);
[3c3657c]354 ui_window_send_pos(awnd, &pos);
[983052c]355 ui_unlock(ui);
356 }
[3c3657c]357
[899bdfd]358 break;
359 case CEV_RESIZE:
360 ui_lock(ui);
361 ui_window_send_resize(awnd);
362 ui_unlock(ui);
[77ffa01]363 break;
364 }
365}
366
[d284ce9]367/** Execute user interface.
368 *
[d55ab823]369 * Return task exit code of zero and block unitl the application starts
370 * the termination process by calling ui_quit(@a ui).
[d284ce9]371 *
372 * @param ui User interface
373 */
374void ui_run(ui_t *ui)
375{
[77ffa01]376 cons_event_t event;
377 usec_t timeout;
[87822ce]378 errno_t rc;
[d55ab823]379
[77ffa01]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;
[87822ce]387 rc = console_get_event_timeout(ui->console,
[77ffa01]388 &event, &timeout);
[87822ce]389
390 /* Do we actually have an event? */
391 if (rc == EOK) {
[77ffa01]392 ui_cons_event_process(ui, &event);
[87822ce]393 } else if (rc != ETIMEOUT) {
394 /* Error, quit */
395 break;
396 }
[77ffa01]397 } else {
398 fibril_usleep(100000);
399 }
400 }
[d284ce9]401}
402
[252d03c]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;
[1ebcb791]413 gfx_context_t *gc;
[3e41cc4]414 ui_window_t *wnd;
[1ebcb791]415 gfx_color_t *color = NULL;
416
[6d172f6]417 /* In case of null output */
418 if (ui->cgc == NULL)
419 return EOK;
420
[1ebcb791]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);
[252d03c]440
[3e41cc4]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;
[252d03c]447
[3e41cc4]448 rc = ui_window_paint(wnd);
449 if (rc != EOK)
450 return rc;
[252d03c]451
[3e41cc4]452 wnd = ui_window_next(wnd);
453 }
454
455 return EOK;
[252d03c]456}
457
[c632c96]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{
[983052c]471 errno_t rc;
472
473 assert(!ui->suspended);
474
475 if (ui->cgc == NULL) {
476 ui->suspended = true;
[c632c96]477 return EOK;
[983052c]478 }
[c632c96]479
[b48e680f]480 (void) console_set_caption(ui->console, "");
[983052c]481 rc = console_gc_suspend(ui->cgc);
482 if (rc != EOK)
483 return rc;
484
485 ui->suspended = true;
486 return EOK;
[c632c96]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;
[b48e680f]500 ui_window_t *awnd;
[e3e64f6]501 sysarg_t col;
502 sysarg_t row;
503 cons_event_t ev;
[c632c96]504
[983052c]505 assert(ui->suspended);
506
507 if (ui->cgc == NULL) {
508 ui->suspended = false;
[c632c96]509 return EOK;
[983052c]510 }
[c632c96]511
[e3e64f6]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
[c632c96]538 rc = console_gc_resume(ui->cgc);
539 if (rc != EOK)
540 return rc;
541
[983052c]542 ui->suspended = false;
543
[b48e680f]544 awnd = ui_window_get_active(ui);
545 if (awnd != NULL)
546 (void) console_set_caption(ui->console, awnd->wdecor->caption);
547
[983052c]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;
[c632c96]563}
564
[8965860c]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{
[8279aab]575 if (ui->display != NULL)
576 display_lock(ui->display);
[8965860c]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);
[8279aab]591 if (ui->display != NULL)
592 display_unlock(ui->display);
[8965860c]593}
594
[d284ce9]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
[9c7dc8e]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
[252d03c]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
[3fd38b2]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
[8965860c]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
[f7a90df]675/** @}
676 */
Note: See TracBrowser for help on using the repository browser.