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
Line 
1/*
2 * Copyright (c) 2023 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 }
360}
361
362/** Execute user interface.
363 *
364 * Return task exit code of zero and block unitl the application starts
365 * the termination process by calling ui_quit(@a ui).
366 *
367 * @param ui User interface
368 */
369void ui_run(ui_t *ui)
370{
371 cons_event_t event;
372 usec_t timeout;
373 errno_t rc;
374
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;
382 rc = console_get_event_timeout(ui->console,
383 &event, &timeout);
384
385 /* Do we actually have an event? */
386 if (rc == EOK) {
387 ui_cons_event_process(ui, &event);
388 } else if (rc != ETIMEOUT) {
389 /* Error, quit */
390 break;
391 }
392 } else {
393 fibril_usleep(100000);
394 }
395 }
396}
397
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;
408 gfx_context_t *gc;
409 ui_window_t *awnd;
410 gfx_color_t *color = NULL;
411
412 /* In case of null output */
413 if (ui->cgc == NULL)
414 return EOK;
415
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);
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
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{
461 errno_t rc;
462
463 assert(!ui->suspended);
464
465 if (ui->cgc == NULL) {
466 ui->suspended = true;
467 return EOK;
468 }
469
470 (void) console_set_caption(ui->console, "");
471 rc = console_gc_suspend(ui->cgc);
472 if (rc != EOK)
473 return rc;
474
475 ui->suspended = true;
476 return EOK;
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;
490 ui_window_t *awnd;
491 sysarg_t col;
492 sysarg_t row;
493 cons_event_t ev;
494
495 assert(ui->suspended);
496
497 if (ui->cgc == NULL) {
498 ui->suspended = false;
499 return EOK;
500 }
501
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
528 rc = console_gc_resume(ui->cgc);
529 if (rc != EOK)
530 return rc;
531
532 ui->suspended = false;
533
534 awnd = ui_window_get_active(ui);
535 if (awnd != NULL)
536 (void) console_set_caption(ui->console, awnd->wdecor->caption);
537
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;
553}
554
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
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
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
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
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
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
661/** @}
662 */
Note: See TracBrowser for help on using the repository browser.