source: mainline/uspace/lib/ui/src/ui.c@ 899bdfd

Last change on this file since 899bdfd was 899bdfd, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 10 months ago

Terminal scrolling and resizing support

  • Property mode set to 100644
File size: 13.9 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 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 *awnd;
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 /* XXX Should repaint all windows */
442 awnd = ui_window_get_active(ui);
443 if (awnd == NULL)
444 return EOK;
445
446 rc = ui_wdecor_paint(awnd->wdecor);
447 if (rc != EOK)
448 return rc;
449
450 return ui_window_paint(awnd);
451}
452
453/** Free up console for other users.
454 *
455 * Release console resources for another application (that the current
456 * task is starting). After the other application finishes, resume
457 * operation with ui_resume(). No calls to UI must happen inbetween
458 * and no events must be processed (i.e. the calling function must not
459 * return control to UI.
460 *
461 * @param ui UI
462 * @return EOK on success or an error code
463 */
464errno_t ui_suspend(ui_t *ui)
465{
466 errno_t rc;
467
468 assert(!ui->suspended);
469
470 if (ui->cgc == NULL) {
471 ui->suspended = true;
472 return EOK;
473 }
474
475 (void) console_set_caption(ui->console, "");
476 rc = console_gc_suspend(ui->cgc);
477 if (rc != EOK)
478 return rc;
479
480 ui->suspended = true;
481 return EOK;
482}
483
484/** Resume suspended UI.
485 *
486 * Reclaim console resources (after child application has finished running)
487 * and restore UI operation previously suspended by calling ui_suspend().
488 *
489 * @param ui UI
490 * @return EOK on success or an error code
491 */
492errno_t ui_resume(ui_t *ui)
493{
494 errno_t rc;
495 ui_window_t *awnd;
496 sysarg_t col;
497 sysarg_t row;
498 cons_event_t ev;
499
500 assert(ui->suspended);
501
502 if (ui->cgc == NULL) {
503 ui->suspended = false;
504 return EOK;
505 }
506
507 rc = console_get_pos(ui->console, &col, &row);
508 if (rc != EOK)
509 return rc;
510
511 /*
512 * Here's a little heuristic to help determine if we need
513 * to pause before returning to the UI. If we are in the
514 * top-left corner, chances are the screen is empty and
515 * there is no need to pause.
516 */
517 if (col != 0 || row != 0) {
518 printf("Press any key or button to continue...\n");
519
520 while (true) {
521 rc = console_get_event(ui->console, &ev);
522 if (rc != EOK)
523 return EIO;
524
525 if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS)
526 break;
527
528 if (ev.type == CEV_POS && ev.ev.pos.type == POS_PRESS)
529 break;
530 }
531 }
532
533 rc = console_gc_resume(ui->cgc);
534 if (rc != EOK)
535 return rc;
536
537 ui->suspended = false;
538
539 awnd = ui_window_get_active(ui);
540 if (awnd != NULL)
541 (void) console_set_caption(ui->console, awnd->wdecor->caption);
542
543 rc = gfx_cursor_set_visible(console_gc_get_ctx(ui->cgc), false);
544 if (rc != EOK)
545 return rc;
546
547 return EOK;
548}
549
550/** Determine if UI is suspended.
551 *
552 * @param ui UI
553 * @return @c true iff UI is suspended
554 */
555bool ui_is_suspended(ui_t *ui)
556{
557 return ui->suspended;
558}
559
560/** Lock UI.
561 *
562 * Block UI from calling window callbacks. @c ui_lock() and @c ui_unlock()
563 * must be used when accessing UI resources from a fibril (as opposed to
564 * from a window callback).
565 *
566 * @param ui UI
567 */
568void ui_lock(ui_t *ui)
569{
570 fibril_mutex_lock(&ui->lock);
571}
572
573/** Unlock UI.
574 *
575 * Allow UI to call window callbacks. @c ui_lock() and @c ui_unlock()
576 * must be used when accessing window resources from a fibril (as opposed to
577 * from a window callback).
578 *
579 * @param ui UI
580 */
581void ui_unlock(ui_t *ui)
582{
583 fibril_mutex_unlock(&ui->lock);
584}
585
586/** Terminate user interface.
587 *
588 * Calling this function causes the user interface to terminate
589 * (i.e. exit from ui_run()). This would be typically called from
590 * an event handler.
591 *
592 * @param ui User interface
593 */
594void ui_quit(ui_t *ui)
595{
596 ui->quit = true;
597}
598
599/** Determine if we are running in text mode.
600 *
601 * @param ui User interface
602 * @return @c true iff we are running in text mode
603 */
604bool ui_is_textmode(ui_t *ui)
605{
606 /*
607 * XXX Currently console is always text and display is always
608 * graphics, but this need not always be true.
609 */
610 return (ui->console != NULL);
611}
612
613/** Determine if we are emulating windows.
614 *
615 * @param ui User interface
616 * @return @c true iff we are running in text mode
617 */
618bool ui_is_fullscreen(ui_t *ui)
619{
620 return (ui->display == NULL);
621}
622
623/** Get UI screen rectangle.
624 *
625 * @param ui User interface
626 * @param rect Place to store bounding rectangle
627 */
628errno_t ui_get_rect(ui_t *ui, gfx_rect_t *rect)
629{
630 display_info_t info;
631 sysarg_t cols, rows;
632 errno_t rc;
633
634 if (ui->display != NULL) {
635 rc = display_get_info(ui->display, &info);
636 if (rc != EOK)
637 return rc;
638
639 *rect = info.rect;
640 } else if (ui->console != NULL) {
641 rc = console_get_size(ui->console, &cols, &rows);
642 if (rc != EOK)
643 return rc;
644
645 rect->p0.x = 0;
646 rect->p0.y = 0;
647 rect->p1.x = cols;
648 rect->p1.y = rows;
649 } else {
650 return ENOTSUP;
651 }
652
653 return EOK;
654}
655
656/** Get clickmatic from UI.
657 *
658 * @pararm ui UI
659 * @return Clickmatic
660 */
661ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
662{
663 return ui->clickmatic;
664}
665
666/** @}
667 */
Note: See TracBrowser for help on using the repository browser.