source: mainline/uspace/lib/ui/src/ui.c@ 5afc1aa

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

Pass input device ID via display specification argument

This allows launcher to start applications in the correct seat,
meaning the correct seat's focus will be changed to the newly
created window.

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