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

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

Pause before resuming UI, if needed

To be able to see output of commands executed from Navigator, we need
to pause before repainting the UI. This should not be needed, however,
for binaries that do not leave anything on the screen (e.g. (G)UI
applications). We use a simple heuristic - if the cursor is in the top
left corner of the screen, we don't pause.

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