Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/lib/ui/src/ui.c

    r26edcd6 rd55ab823  
    11/*
    2  * Copyright (c) 2026 Jiri Svoboda
     2 * Copyright (c) 2020 Jiri Svoboda
    33 * All rights reserved.
    44 *
     
    3434 */
    3535
    36 #include <adt/list.h>
    37 #include <ctype.h>
    3836#include <display.h>
    3937#include <errno.h>
    4038#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>
    4739#include <stdlib.h>
    48 #include <str.h>
    4940#include <task.h>
    50 #include <types/common.h>
    51 #include <ui/clickmatic.h>
    5241#include <ui/ui.h>
    53 #include <ui/wdecor.h>
    54 #include <ui/window.h>
    55 #include "../private/wdecor.h"
    56 #include "../private/window.h"
    5742#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  */
    72 static 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;
    141 error:
    142         free(*osvc);
    143         *osvc = NULL;
    144         return rc;
    145 }
    14643
    14744/** Create new user interface.
    14845 *
    14946 * @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.
     47 *              the default output
    15348 * @param rui Place to store pointer to new UI
    15449 * @return EOK on success or an error code
     
    15853        errno_t rc;
    15954        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;
    16755        ui_t *ui;
    16856
    169         rc = ui_ospec_parse(ospec, &ws, &osvc, &idev_id);
     57        rc = display_open(ospec, &display);
    17058        if (rc != EOK)
    17159                return rc;
    17260
    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 
    192 disp_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 
    234 cons_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  */
    255 errno_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);
     61        rc = ui_create_disp(display, &ui);
    26562        if (rc != EOK) {
    266                 free(ui);
     63                display_close(display);
    26764                return rc;
    26865        }
    26966
    270         ui->console = console;
    271         list_initialize(&ui->windows);
    272         fibril_mutex_initialize(&ui->lock);
     67        ui->display = display;
     68        ui->myoutput = true;
    27369        *rui = ui;
    27470        return EOK;
     
    28480{
    28581        ui_t *ui;
    286         errno_t rc;
    28782
    28883        ui = calloc(1, sizeof(ui_t));
     
    29085                return ENOMEM;
    29186
    292         rc = ui_clickmatic_create(ui, &ui->clickmatic);
    293         if (rc != EOK) {
    294                 free(ui);
    295                 return rc;
    296         }
    297 
    29887        ui->display = disp;
    299         list_initialize(&ui->windows);
    300         fibril_mutex_initialize(&ui->lock);
    30188        *rui = ui;
    30289        return EOK;
     
    31299                return;
    313100
    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 
     101        if (ui->myoutput)
     102                display_close(ui->display);
    325103        free(ui);
    326 }
    327 
    328 /** Resize UI after screen size change.
    329  *
    330  * @param ui UI
    331  * @param rect New screen rectangle.
    332  */
    333 static void ui_resize(ui_t *ui, gfx_rect_t *rect)
    334 {
    335         ui_window_t *wnd;
    336 
    337         ui->rect = *rect;
    338 
    339         /* Reposition/resize windows */
    340         wnd = ui_window_first(ui);
    341         while (wnd != NULL) {
    342                 ui_window_update_placement(wnd);
    343                 wnd = ui_window_next(wnd);
    344         }
    345 
    346         /*
    347          * XXX Resizing cleared console GC so we need to repaint the
    348          * background.
    349          */
    350         (void)ui_paint(ui);
    351 }
    352 
    353 static void ui_cons_event_process(ui_t *ui, cons_event_t *event)
    354 {
    355         ui_window_t *awnd;
    356         ui_evclaim_t claim;
    357         pos_event_t pos;
    358         sysarg_t cols, rows;
    359         gfx_rect_t rect;
    360         errno_t rc;
    361 
    362         awnd = ui_window_get_active(ui);
    363         if (awnd == NULL)
    364                 return;
    365 
    366         switch (event->type) {
    367         case CEV_KEY:
    368                 ui_lock(ui);
    369                 ui_window_send_kbd(awnd, &event->ev.key);
    370                 ui_unlock(ui);
    371                 break;
    372         case CEV_POS:
    373                 pos = event->ev.pos;
    374                 /* Translate event to window-relative coordinates */
    375                 pos.hpos -= awnd->dpos.x;
    376                 pos.vpos -= awnd->dpos.y;
    377 
    378                 claim = ui_wdecor_pos_event(awnd->wdecor, &pos);
    379                 /* Note: If event is claimed, awnd might not be valid anymore */
    380                 if (claim == ui_unclaimed) {
    381                         ui_lock(ui);
    382                         ui_window_send_pos(awnd, &pos);
    383                         ui_unlock(ui);
    384                 }
    385 
    386                 break;
    387         case CEV_RESIZE:
    388                 rc = console_gc_resize(ui->cgc);
    389                 if (rc != EOK) {
    390                         /* XXX No good way to recover. */
    391                         console_done(ui->console);
    392                         exit(1);
    393                 }
    394 
    395                 rc = console_get_size(ui->console, &cols, &rows);
    396                 if (rc != EOK) {
    397                         /* XXX No good way to recover. */
    398                         console_done(ui->console);
    399                         exit(1);
    400                 }
    401 
    402                 ui_lock(ui);
    403 
    404                 rect.p0.x = 0;
    405                 rect.p0.y = 0;
    406                 rect.p1.x = cols;
    407                 rect.p1.y = rows;
    408 
    409                 ui_resize(ui, &rect);
    410                 ui_unlock(ui);
    411                 break;
    412         }
    413104}
    414105
     
    422113void ui_run(ui_t *ui)
    423114{
    424         cons_event_t event;
    425         usec_t timeout;
    426         errno_t rc;
     115        task_retval(0);
    427116
    428         /* Only return command prompt if we are running in a separate window */
    429         if (ui->display != NULL)
    430                 task_retval(0);
    431 
    432         while (!ui->quit) {
    433                 if (ui->console != NULL) {
    434                         timeout = 100000;
    435                         rc = console_get_event_timeout(ui->console,
    436                             &event, &timeout);
    437 
    438                         /* Do we actually have an event? */
    439                         if (rc == EOK) {
    440                                 ui_cons_event_process(ui, &event);
    441                         } else if (rc != ETIMEOUT) {
    442                                 /* Error, quit */
    443                                 break;
    444                         }
    445                 } else {
    446                         fibril_usleep(100000);
    447                 }
    448         }
    449 }
    450 
    451 /** Repaint UI (only used in fullscreen mode).
    452  *
    453  * This is used when an area is exposed in fullscreen mode.
    454  *
    455  * @param ui UI
    456  * @return @c EOK on success or an error code
    457  */
    458 errno_t ui_paint(ui_t *ui)
    459 {
    460         errno_t rc;
    461         gfx_context_t *gc;
    462         ui_window_t *wnd;
    463         gfx_color_t *color = NULL;
    464 
    465         /* In case of null output */
    466         if (ui->cgc == NULL)
    467                 return EOK;
    468 
    469         gc = console_gc_get_ctx(ui->cgc);
    470 
    471         rc = gfx_color_new_ega(0x11, &color);
    472         if (rc != EOK)
    473                 return rc;
    474 
    475         rc = gfx_set_color(gc, color);
    476         if (rc != EOK) {
    477                 gfx_color_delete(color);
    478                 return rc;
    479         }
    480 
    481         rc = gfx_fill_rect(gc, &ui->rect);
    482         if (rc != EOK) {
    483                 gfx_color_delete(color);
    484                 return rc;
    485         }
    486 
    487         gfx_color_delete(color);
    488 
    489         /* Repaint all windows */
    490         wnd = ui_window_first(ui);
    491         while (wnd != NULL) {
    492                 rc = ui_wdecor_paint(wnd->wdecor);
    493                 if (rc != EOK)
    494                         return rc;
    495 
    496                 rc = ui_window_paint(wnd);
    497                 if (rc != EOK)
    498                         return rc;
    499 
    500                 wnd = ui_window_next(wnd);
    501         }
    502 
    503         return EOK;
    504 }
    505 
    506 /** Free up console for other users.
    507  *
    508  * Release console resources for another application (that the current
    509  * task is starting). After the other application finishes, resume
    510  * operation with ui_resume(). No calls to UI must happen inbetween
    511  * and no events must be processed (i.e. the calling function must not
    512  * return control to UI.
    513  *
    514  * @param ui UI
    515  * @return EOK on success or an error code
    516  */
    517 errno_t ui_suspend(ui_t *ui)
    518 {
    519         errno_t rc;
    520 
    521         assert(!ui->suspended);
    522 
    523         if (ui->cgc == NULL) {
    524                 ui->suspended = true;
    525                 return EOK;
    526         }
    527 
    528         (void) console_set_caption(ui->console, "");
    529         rc = console_gc_suspend(ui->cgc);
    530         if (rc != EOK)
    531                 return rc;
    532 
    533         ui->suspended = true;
    534         return EOK;
    535 }
    536 
    537 /** Resume suspended UI.
    538  *
    539  * Reclaim console resources (after child application has finished running)
    540  * and restore UI operation previously suspended by calling ui_suspend().
    541  *
    542  * @param ui UI
    543  * @return EOK on success or an error code
    544  */
    545 errno_t ui_resume(ui_t *ui)
    546 {
    547         errno_t rc;
    548         ui_window_t *awnd;
    549         sysarg_t col;
    550         sysarg_t row;
    551         cons_event_t ev;
    552 
    553         assert(ui->suspended);
    554 
    555         if (ui->cgc == NULL) {
    556                 ui->suspended = false;
    557                 return EOK;
    558         }
    559 
    560         rc = console_get_pos(ui->console, &col, &row);
    561         if (rc != EOK)
    562                 return rc;
    563 
    564         /*
    565          * Here's a little heuristic to help determine if we need
    566          * to pause before returning to the UI. If we are in the
    567          * top-left corner, chances are the screen is empty and
    568          * there is no need to pause.
    569          */
    570         if (col != 0 || row != 0) {
    571                 printf("Press any key or button to continue...\n");
    572 
    573                 while (true) {
    574                         rc = console_get_event(ui->console, &ev);
    575                         if (rc != EOK)
    576                                 return EIO;
    577 
    578                         if (ev.type == CEV_KEY && ev.ev.key.type == KEY_PRESS)
    579                                 break;
    580 
    581                         if (ev.type == CEV_POS && ev.ev.pos.type == POS_PRESS)
    582                                 break;
    583                 }
    584         }
    585 
    586         rc = console_gc_resume(ui->cgc);
    587         if (rc != EOK)
    588                 return rc;
    589 
    590         ui->suspended = false;
    591 
    592         awnd = ui_window_get_active(ui);
    593         if (awnd != NULL)
    594                 (void) console_set_caption(ui->console, awnd->wdecor->caption);
    595 
    596         rc = gfx_cursor_set_visible(console_gc_get_ctx(ui->cgc), false);
    597         if (rc != EOK)
    598                 return rc;
    599 
    600         return EOK;
    601 }
    602 
    603 /** Determine if UI is suspended.
    604  *
    605  * @param ui UI
    606  * @return @c true iff UI is suspended
    607  */
    608 bool ui_is_suspended(ui_t *ui)
    609 {
    610         return ui->suspended;
    611 }
    612 
    613 /** Lock UI.
    614  *
    615  * Block UI from calling window callbacks. @c ui_lock() and @c ui_unlock()
    616  * must be used when accessing UI resources from a fibril (as opposed to
    617  * from a window callback).
    618  *
    619  * @param ui UI
    620  */
    621 void ui_lock(ui_t *ui)
    622 {
    623         if (ui->display != NULL)
    624                 display_lock(ui->display);
    625         fibril_mutex_lock(&ui->lock);
    626 }
    627 
    628 /** Unlock UI.
    629  *
    630  * Allow UI to call window callbacks. @c ui_lock() and @c ui_unlock()
    631  * must be used when accessing window resources from a fibril (as opposed to
    632  * from a window callback).
    633  *
    634  * @param ui UI
    635  */
    636 void ui_unlock(ui_t *ui)
    637 {
    638         fibril_mutex_unlock(&ui->lock);
    639         if (ui->display != NULL)
    640                 display_unlock(ui->display);
     117        while (!ui->quit)
     118                fibril_usleep(100000);
    641119}
    642120
     
    654132}
    655133
    656 /** Determine if we are running in text mode.
    657  *
    658  * @param ui User interface
    659  * @return @c true iff we are running in text mode
    660  */
    661 bool ui_is_textmode(ui_t *ui)
    662 {
    663         /*
    664          * XXX Currently console is always text and display is always
    665          * graphics, but this need not always be true.
    666          */
    667         return (ui->console != NULL);
    668 }
    669 
    670 /** Determine if we are emulating windows.
    671  *
    672  * @param ui User interface
    673  * @return @c true iff we are running in text mode
    674  */
    675 bool ui_is_fullscreen(ui_t *ui)
    676 {
    677         return (ui->display == NULL);
    678 }
    679 
    680 /** Get UI screen rectangle.
    681  *
    682  * @param ui User interface
    683  * @param rect Place to store bounding rectangle
    684  */
    685 errno_t ui_get_rect(ui_t *ui, gfx_rect_t *rect)
    686 {
    687         display_info_t info;
    688         sysarg_t cols, rows;
    689         errno_t rc;
    690 
    691         if (ui->display != NULL) {
    692                 rc = display_get_info(ui->display, &info);
    693                 if (rc != EOK)
    694                         return rc;
    695 
    696                 *rect = info.rect;
    697         } else if (ui->console != NULL) {
    698                 rc = console_get_size(ui->console, &cols, &rows);
    699                 if (rc != EOK)
    700                         return rc;
    701 
    702                 rect->p0.x = 0;
    703                 rect->p0.y = 0;
    704                 rect->p1.x = cols;
    705                 rect->p1.y = rows;
    706         } else {
    707                 return ENOTSUP;
    708         }
    709 
    710         return EOK;
    711 }
    712 
    713 /** Get clickmatic from UI.
    714  *
    715  * @pararm ui UI
    716  * @return Clickmatic
    717  */
    718 ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
    719 {
    720         return ui->clickmatic;
    721 }
    722 
    723134/** @}
    724135 */
Note: See TracChangeset for help on using the changeset viewer.