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

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

Task bar should not crash when starting in terminal

Firstly, we need to make sure we do not paint anything while
UI is suspended. Also, we were missing calls to ui_lock/unlock()
while delivering kbd/pos events from console.

  • Property mode set to 100644
File size: 13.8 KB
RevLine 
[f7a90df]1/*
[b1f0a14]2 * Copyright (c) 2023 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
[77ffa01]358 break;
359 }
360}
361
[d284ce9]362/** Execute user interface.
363 *
[d55ab823]364 * Return task exit code of zero and block unitl the application starts
365 * the termination process by calling ui_quit(@a ui).
[d284ce9]366 *
367 * @param ui User interface
368 */
369void ui_run(ui_t *ui)
370{
[77ffa01]371 cons_event_t event;
372 usec_t timeout;
[87822ce]373 errno_t rc;
[d55ab823]374
[77ffa01]375 /* Only return command prompt if we are running in a separate window */
376 if (ui->display != NULL)
377 task_retval(0);
378
379 while (!ui->quit) {
380 if (ui->console != NULL) {
381 timeout = 100000;
[87822ce]382 rc = console_get_event_timeout(ui->console,
[77ffa01]383 &event, &timeout);
[87822ce]384
385 /* Do we actually have an event? */
386 if (rc == EOK) {
[77ffa01]387 ui_cons_event_process(ui, &event);
[87822ce]388 } else if (rc != ETIMEOUT) {
389 /* Error, quit */
390 break;
391 }
[77ffa01]392 } else {
393 fibril_usleep(100000);
394 }
395 }
[d284ce9]396}
397
[252d03c]398/** Repaint UI (only used in fullscreen mode).
399 *
400 * This is used when an area is exposed in fullscreen mode.
401 *
402 * @param ui UI
403 * @return @c EOK on success or an error code
404 */
405errno_t ui_paint(ui_t *ui)
406{
407 errno_t rc;
[1ebcb791]408 gfx_context_t *gc;
[252d03c]409 ui_window_t *awnd;
[1ebcb791]410 gfx_color_t *color = NULL;
411
[6d172f6]412 /* In case of null output */
413 if (ui->cgc == NULL)
414 return EOK;
415
[1ebcb791]416 gc = console_gc_get_ctx(ui->cgc);
417
418 rc = gfx_color_new_ega(0x11, &color);
419 if (rc != EOK)
420 return rc;
421
422 rc = gfx_set_color(gc, color);
423 if (rc != EOK) {
424 gfx_color_delete(color);
425 return rc;
426 }
427
428 rc = gfx_fill_rect(gc, &ui->rect);
429 if (rc != EOK) {
430 gfx_color_delete(color);
431 return rc;
432 }
433
434 gfx_color_delete(color);
[252d03c]435
436 /* XXX Should repaint all windows */
437 awnd = ui_window_get_active(ui);
438 if (awnd == NULL)
439 return EOK;
440
441 rc = ui_wdecor_paint(awnd->wdecor);
442 if (rc != EOK)
443 return rc;
444
445 return ui_window_paint(awnd);
446}
447
[c632c96]448/** Free up console for other users.
449 *
450 * Release console resources for another application (that the current
451 * task is starting). After the other application finishes, resume
452 * operation with ui_resume(). No calls to UI must happen inbetween
453 * and no events must be processed (i.e. the calling function must not
454 * return control to UI.
455 *
456 * @param ui UI
457 * @return EOK on success or an error code
458 */
459errno_t ui_suspend(ui_t *ui)
460{
[983052c]461 errno_t rc;
462
463 assert(!ui->suspended);
464
465 if (ui->cgc == NULL) {
466 ui->suspended = true;
[c632c96]467 return EOK;
[983052c]468 }
[c632c96]469
[b48e680f]470 (void) console_set_caption(ui->console, "");
[983052c]471 rc = console_gc_suspend(ui->cgc);
472 if (rc != EOK)
473 return rc;
474
475 ui->suspended = true;
476 return EOK;
[c632c96]477}
478
479/** Resume suspended UI.
480 *
481 * Reclaim console resources (after child application has finished running)
482 * and restore UI operation previously suspended by calling ui_suspend().
483 *
484 * @param ui UI
485 * @return EOK on success or an error code
486 */
487errno_t ui_resume(ui_t *ui)
488{
489 errno_t rc;
[b48e680f]490 ui_window_t *awnd;
[e3e64f6]491 sysarg_t col;
492 sysarg_t row;
493 cons_event_t ev;
[c632c96]494
[983052c]495 assert(ui->suspended);
496
497 if (ui->cgc == NULL) {
498 ui->suspended = false;
[c632c96]499 return EOK;
[983052c]500 }
[c632c96]501
[e3e64f6]502 rc = console_get_pos(ui->console, &col, &row);
503 if (rc != EOK)
504 return rc;
505
506 /*
507 * Here's a little heuristic to help determine if we need
508 * to pause before returning to the UI. If we are in the
509 * top-left corner, chances are the screen is empty and
510 * there is no need to pause.
511 */
512 if (col != 0 || row != 0) {
513 printf("Press any key or button to continue...\n");
514
515 while (true) {
516 rc = console_get_event(ui->console, &ev);
517 if (rc != EOK)
518 return EIO;
519
520 if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS)
521 break;
522
523 if (ev.type == CEV_POS && ev.ev.pos.type == POS_PRESS)
524 break;
525 }
526 }
527
[c632c96]528 rc = console_gc_resume(ui->cgc);
529 if (rc != EOK)
530 return rc;
531
[983052c]532 ui->suspended = false;
533
[b48e680f]534 awnd = ui_window_get_active(ui);
535 if (awnd != NULL)
536 (void) console_set_caption(ui->console, awnd->wdecor->caption);
537
[983052c]538 rc = gfx_cursor_set_visible(console_gc_get_ctx(ui->cgc), false);
539 if (rc != EOK)
540 return rc;
541
542 return EOK;
543}
544
545/** Determine if UI is suspended.
546 *
547 * @param ui UI
548 * @return @c true iff UI is suspended
549 */
550bool ui_is_suspended(ui_t *ui)
551{
552 return ui->suspended;
[c632c96]553}
554
[8965860c]555/** Lock UI.
556 *
557 * Block UI from calling window callbacks. @c ui_lock() and @c ui_unlock()
558 * must be used when accessing UI resources from a fibril (as opposed to
559 * from a window callback).
560 *
561 * @param ui UI
562 */
563void ui_lock(ui_t *ui)
564{
565 fibril_mutex_lock(&ui->lock);
566}
567
568/** Unlock UI.
569 *
570 * Allow UI to call window callbacks. @c ui_lock() and @c ui_unlock()
571 * must be used when accessing window resources from a fibril (as opposed to
572 * from a window callback).
573 *
574 * @param ui UI
575 */
576void ui_unlock(ui_t *ui)
577{
578 fibril_mutex_unlock(&ui->lock);
579}
580
[d284ce9]581/** Terminate user interface.
582 *
583 * Calling this function causes the user interface to terminate
584 * (i.e. exit from ui_run()). This would be typically called from
585 * an event handler.
586 *
587 * @param ui User interface
588 */
589void ui_quit(ui_t *ui)
590{
591 ui->quit = true;
592}
593
[9c7dc8e]594/** Determine if we are running in text mode.
595 *
596 * @param ui User interface
597 * @return @c true iff we are running in text mode
598 */
599bool ui_is_textmode(ui_t *ui)
600{
601 /*
602 * XXX Currently console is always text and display is always
603 * graphics, but this need not always be true.
604 */
605 return (ui->console != NULL);
606}
607
[252d03c]608/** Determine if we are emulating windows.
609 *
610 * @param ui User interface
611 * @return @c true iff we are running in text mode
612 */
613bool ui_is_fullscreen(ui_t *ui)
614{
615 return (ui->display == NULL);
616}
617
[3fd38b2]618/** Get UI screen rectangle.
619 *
620 * @param ui User interface
621 * @param rect Place to store bounding rectangle
622 */
623errno_t ui_get_rect(ui_t *ui, gfx_rect_t *rect)
624{
625 display_info_t info;
626 sysarg_t cols, rows;
627 errno_t rc;
628
629 if (ui->display != NULL) {
630 rc = display_get_info(ui->display, &info);
631 if (rc != EOK)
632 return rc;
633
634 *rect = info.rect;
635 } else if (ui->console != NULL) {
636 rc = console_get_size(ui->console, &cols, &rows);
637 if (rc != EOK)
638 return rc;
639
640 rect->p0.x = 0;
641 rect->p0.y = 0;
642 rect->p1.x = cols;
643 rect->p1.y = rows;
644 } else {
645 return ENOTSUP;
646 }
647
648 return EOK;
649}
650
[8965860c]651/** Get clickmatic from UI.
652 *
653 * @pararm ui UI
654 * @return Clickmatic
655 */
656ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
657{
658 return ui->clickmatic;
659}
660
[f7a90df]661/** @}
662 */
Note: See TracBrowser for help on using the repository browser.