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

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since cdd6fc9 was 3fd38b2, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Size taskbar based on display size, fix text mode

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