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

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

Clickmatic

A class that periodically generates when held, after initial delay.
This is quite similar to the typematic feature found in PC keyboards.
We use it to automatically scroll when scrollbar button or through
is held.

  • Property mode set to 100644
File size: 11.7 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 clickmatic from UI.
527 *
528 * @pararm ui UI
529 * @return Clickmatic
530 */
531ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
532{
533 return ui->clickmatic;
534}
535
536/** @}
537 */
Note: See TracBrowser for help on using the repository browser.