Ignore:
File:
1 edited

Legend:

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

    rd55ab823 r26edcd6  
    11/*
    2  * Copyright (c) 2020 Jiri Svoboda
     2 * Copyright (c) 2026 Jiri Svoboda
    33 * All rights reserved.
    44 *
     
    3434 */
    3535
     36#include <adt/list.h>
     37#include <ctype.h>
    3638#include <display.h>
    3739#include <errno.h>
    3840#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>
    3947#include <stdlib.h>
     48#include <str.h>
    4049#include <task.h>
     50#include <types/common.h>
     51#include <ui/clickmatic.h>
    4152#include <ui/ui.h>
     53#include <ui/wdecor.h>
     54#include <ui/window.h>
     55#include "../private/wdecor.h"
     56#include "../private/window.h"
    4257#include "../private/ui.h"
    4358
     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
    44147/** Create new user interface.
    45148 *
    46149 * @param ospec Output specification or @c UI_DISPLAY_DEFAULT to use
    47  *              the default output
     150 *              the default display service, UI_CONSOLE_DEFAULT to use
     151 *              the default console service, UI_DISPLAY_NULL to use
     152 *              dummy output.
    48153 * @param rui Place to store pointer to new UI
    49154 * @return EOK on success or an error code
     
    53158        errno_t rc;
    54159        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;
    55167        ui_t *ui;
    56168
    57         rc = display_open(ospec, &display);
     169        rc = ui_ospec_parse(ospec, &ws, &osvc, &idev_id);
    58170        if (rc != EOK)
    59171                return rc;
    60172
    61         rc = ui_create_disp(display, &ui);
     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);
    62265        if (rc != EOK) {
    63                 display_close(display);
    64                 return rc;
    65         }
    66 
    67         ui->display = display;
    68         ui->myoutput = true;
     266                free(ui);
     267                return rc;
     268        }
     269
     270        ui->console = console;
     271        list_initialize(&ui->windows);
     272        fibril_mutex_initialize(&ui->lock);
    69273        *rui = ui;
    70274        return EOK;
     
    80284{
    81285        ui_t *ui;
     286        errno_t rc;
    82287
    83288        ui = calloc(1, sizeof(ui_t));
     
    85290                return ENOMEM;
    86291
     292        rc = ui_clickmatic_create(ui, &ui->clickmatic);
     293        if (rc != EOK) {
     294                free(ui);
     295                return rc;
     296        }
     297
    87298        ui->display = disp;
     299        list_initialize(&ui->windows);
     300        fibril_mutex_initialize(&ui->lock);
    88301        *rui = ui;
    89302        return EOK;
     
    99312                return;
    100313
    101         if (ui->myoutput)
    102                 display_close(ui->display);
     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
    103325        free(ui);
     326}
     327
     328/** Resize UI after screen size change.
     329 *
     330 * @param ui UI
     331 * @param rect New screen rectangle.
     332 */
     333static 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
     353static 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        }
    104413}
    105414
     
    113422void ui_run(ui_t *ui)
    114423{
    115         task_retval(0);
    116 
    117         while (!ui->quit)
    118                 fibril_usleep(100000);
     424        cons_event_t event;
     425        usec_t timeout;
     426        errno_t rc;
     427
     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 */
     458errno_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 */
     517errno_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 */
     545errno_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 */
     608bool 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 */
     621void 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 */
     636void ui_unlock(ui_t *ui)
     637{
     638        fibril_mutex_unlock(&ui->lock);
     639        if (ui->display != NULL)
     640                display_unlock(ui->display);
    119641}
    120642
     
    132654}
    133655
     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 */
     661bool 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 */
     675bool 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 */
     685errno_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 */
     718ui_clickmatic_t *ui_get_clickmatic(ui_t *ui)
     719{
     720        return ui->clickmatic;
     721}
     722
    134723/** @}
    135724 */
Note: See TracChangeset for help on using the changeset viewer.