From d537694a987bbb01e780bd5abe9412722fc38faa Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Mon, 27 Jul 2015 20:15:34 +0200 Subject: terminal: drop unfinished code This drops the libsystemd-terminal and systemd-consoled code for various reasons: * It's been sitting there unfinished for over a year now and won't get finished any time soon. * Since its initial creation, several parts need significant rework: The input handling should be replaced with the now commonly used libinput, the drm accessors should coordinate the handling of mode-object hotplugging (including split connectors) with other DRM users, and the internal library users should be converted to sd-device and friends. * There is still significant kernel work required before sd-console is really useful. This includes, but is not limited to, simpledrm and drmlog. * The authority daemon is needed before all this code can be used for real. And this will definitely take a lot more time to get done as no-one else is currently working on this, but me. * kdbus maintenance has taken up way more time than I thought and it has much higher priority. I don't see me spending much time on the terminal code in the near future. If anyone intends to hack on this, please feel free to contact me. I'll gladly help you out with any issues. Once kdbus and authorityd are finished (whenever that will be..) I'll definitely pick this up again. But until then, lets reduce compile times and maintenance efforts on this code and drop it for now. --- .gitignore | 7 - Makefile.am | 140 - autogen.sh | 3 - configure.ac | 22 - src/console/Makefile | 1 - src/console/consoled-display.c | 81 - src/console/consoled-manager.c | 284 -- src/console/consoled-session.c | 279 -- src/console/consoled-terminal.c | 358 --- src/console/consoled-workspace.c | 167 -- src/console/consoled.c | 66 - src/console/consoled.h | 164 -- src/libsystemd-terminal/.gitignore | 1 - src/libsystemd-terminal/evcat.c | 488 ---- src/libsystemd-terminal/grdev-drm.c | 3092 -------------------- src/libsystemd-terminal/grdev-internal.h | 251 -- src/libsystemd-terminal/grdev.c | 1359 --------- src/libsystemd-terminal/grdev.h | 199 -- src/libsystemd-terminal/idev-evdev.c | 859 ------ src/libsystemd-terminal/idev-internal.h | 188 -- src/libsystemd-terminal/idev-keyboard.c | 1159 -------- src/libsystemd-terminal/idev.c | 799 ----- src/libsystemd-terminal/idev.h | 221 -- src/libsystemd-terminal/modeset.c | 482 ---- src/libsystemd-terminal/subterm.c | 981 ------- src/libsystemd-terminal/sysview-internal.h | 144 - src/libsystemd-terminal/sysview.c | 1554 ---------- src/libsystemd-terminal/sysview.h | 162 -- src/libsystemd-terminal/term-charset.c | 488 ---- src/libsystemd-terminal/term-internal.h | 650 ----- src/libsystemd-terminal/term-page.c | 2091 -------------- src/libsystemd-terminal/term-parser.c | 1702 ----------- src/libsystemd-terminal/term-screen.c | 4333 ---------------------------- src/libsystemd-terminal/term-wcwidth.c | 312 -- src/libsystemd-terminal/term.h | 183 -- src/libsystemd-terminal/test-term-page.c | 459 --- src/libsystemd-terminal/test-term-parser.c | 141 - src/libsystemd-terminal/test-unifont.c | 125 - src/libsystemd-terminal/unifont-def.h | 137 - src/libsystemd-terminal/unifont.c | 238 -- src/libsystemd-terminal/unifont.h | 54 - tools/compile-unifont.py | 119 - units/user/.gitignore | 1 - units/user/systemd-consoled.service.in | 15 - 44 files changed, 24559 deletions(-) delete mode 120000 src/console/Makefile delete mode 100644 src/console/consoled-display.c delete mode 100644 src/console/consoled-manager.c delete mode 100644 src/console/consoled-session.c delete mode 100644 src/console/consoled-terminal.c delete mode 100644 src/console/consoled-workspace.c delete mode 100644 src/console/consoled.c delete mode 100644 src/console/consoled.h delete mode 100644 src/libsystemd-terminal/.gitignore delete mode 100644 src/libsystemd-terminal/evcat.c delete mode 100644 src/libsystemd-terminal/grdev-drm.c delete mode 100644 src/libsystemd-terminal/grdev-internal.h delete mode 100644 src/libsystemd-terminal/grdev.c delete mode 100644 src/libsystemd-terminal/grdev.h delete mode 100644 src/libsystemd-terminal/idev-evdev.c delete mode 100644 src/libsystemd-terminal/idev-internal.h delete mode 100644 src/libsystemd-terminal/idev-keyboard.c delete mode 100644 src/libsystemd-terminal/idev.c delete mode 100644 src/libsystemd-terminal/idev.h delete mode 100644 src/libsystemd-terminal/modeset.c delete mode 100644 src/libsystemd-terminal/subterm.c delete mode 100644 src/libsystemd-terminal/sysview-internal.h delete mode 100644 src/libsystemd-terminal/sysview.c delete mode 100644 src/libsystemd-terminal/sysview.h delete mode 100644 src/libsystemd-terminal/term-charset.c delete mode 100644 src/libsystemd-terminal/term-internal.h delete mode 100644 src/libsystemd-terminal/term-page.c delete mode 100644 src/libsystemd-terminal/term-parser.c delete mode 100644 src/libsystemd-terminal/term-screen.c delete mode 100644 src/libsystemd-terminal/term-wcwidth.c delete mode 100644 src/libsystemd-terminal/term.h delete mode 100644 src/libsystemd-terminal/test-term-page.c delete mode 100644 src/libsystemd-terminal/test-term-parser.c delete mode 100644 src/libsystemd-terminal/test-unifont.c delete mode 100644 src/libsystemd-terminal/unifont-def.h delete mode 100644 src/libsystemd-terminal/unifont.c delete mode 100644 src/libsystemd-terminal/unifont.h delete mode 100755 tools/compile-unifont.py delete mode 100644 units/user/systemd-consoled.service.in diff --git a/.gitignore b/.gitignore index 7659e7a645..10622d4dfa 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,6 @@ /systemd-cgls /systemd-cgroups-agent /systemd-cgtop -/systemd-consoled /systemd-coredump /systemd-cryptsetup /systemd-cryptsetup-generator @@ -76,7 +75,6 @@ /systemd-detect-virt /systemd-efi-boot-generator /systemd-escape -/systemd-evcat /systemd-export /systemd-firstboot /systemd-fsck @@ -102,7 +100,6 @@ /systemd-machine-id-commit /systemd-machine-id-setup /systemd-machined -/systemd-modeset /systemd-modules-load /systemd-networkd /systemd-networkd-wait-online @@ -124,7 +121,6 @@ /systemd-sleep /systemd-socket-proxyd /systemd-stdio-bridge -/systemd-subterm /systemd-sysctl /systemd-system-update-generator /systemd-sysusers @@ -258,15 +254,12 @@ /test-strv /test-strxcpyx /test-tables -/test-term-page -/test-term-parser /test-terminal-util /test-time /test-tmpfiles /test-udev /test-uid-range /test-unaligned -/test-unifont /test-unit-file /test-unit-name /test-utf8 diff --git a/Makefile.am b/Makefile.am index d21982285f..0edb1d88e7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -237,7 +237,6 @@ AM_CPPFLAGS = \ -I $(top_srcdir)/src/libsystemd/sd-hwdb \ -I $(top_srcdir)/src/libsystemd/sd-device \ -I $(top_srcdir)/src/libsystemd-network \ - -I $(top_srcdir)/src/libsystemd-terminal \ $(OUR_CPPFLAGS) AM_CFLAGS = $(OUR_CFLAGS) @@ -3307,145 +3306,6 @@ tests += \ manual_tests += \ test-pppoe -# ------------------------------------------------------------------------------ -if ENABLE_TERMINAL -noinst_LTLIBRARIES += \ - libsystemd-terminal.la - -rootlibexec_PROGRAMS += \ - systemd-consoled - -noinst_PROGRAMS += \ - systemd-evcat \ - systemd-modeset \ - systemd-subterm - -pkgdata_DATA = \ - src/libsystemd-terminal/unifont-glyph-array.bin - -nodist_userunit_DATA += \ - units/user/systemd-consoled.service - -USER_DEFAULT_TARGET_WANTS += \ - systemd-consoled.service - -tests += \ - test-term-page \ - test-term-parser \ - test-unifont -endif - -EXTRA_DIST += \ - units/user/systemd-consoled.service.in - -libsystemd_terminal_la_CFLAGS = \ - $(AM_CFLAGS) \ - $(TERMINAL_CFLAGS) - -libsystemd_terminal_la_SOURCES = \ - src/libsystemd-terminal/grdev.h \ - src/libsystemd-terminal/grdev-internal.h \ - src/libsystemd-terminal/grdev.c \ - src/libsystemd-terminal/grdev-drm.c \ - src/libsystemd-terminal/idev.h \ - src/libsystemd-terminal/idev-internal.h \ - src/libsystemd-terminal/idev.c \ - src/libsystemd-terminal/idev-evdev.c \ - src/libsystemd-terminal/idev-keyboard.c \ - src/libsystemd-terminal/sysview.h \ - src/libsystemd-terminal/sysview-internal.h \ - src/libsystemd-terminal/sysview.c \ - src/libsystemd-terminal/term.h \ - src/libsystemd-terminal/term-internal.h \ - src/libsystemd-terminal/term-charset.c \ - src/libsystemd-terminal/term-page.c \ - src/libsystemd-terminal/term-parser.c \ - src/libsystemd-terminal/term-screen.c \ - src/libsystemd-terminal/term-wcwidth.c \ - src/libsystemd-terminal/unifont.h \ - src/libsystemd-terminal/unifont-def.h \ - src/libsystemd-terminal/unifont.c - -libsystemd_terminal_la_LIBADD = \ - libshared.la \ - $(TERMINAL_LIBS) - -systemd_consoled_CFLAGS = \ - $(AM_CFLAGS) \ - $(TERMINAL_CFLAGS) - -systemd_consoled_SOURCES = \ - src/console/consoled.h \ - src/console/consoled.c \ - src/console/consoled-display.c \ - src/console/consoled-manager.c \ - src/console/consoled-session.c \ - src/console/consoled-terminal.c \ - src/console/consoled-workspace.c - -systemd_consoled_LDADD = \ - libsystemd-terminal.la \ - libshared.la \ - $(TERMINAL_LIBS) - -systemd_evcat_CFLAGS = \ - $(AM_CFLAGS) \ - $(TERMINAL_CFLAGS) - -systemd_evcat_SOURCES = \ - src/libsystemd-terminal/evcat.c - -systemd_evcat_LDADD = \ - libsystemd-terminal.la \ - libshared.la \ - $(TERMINAL_LIBS) - -systemd_modeset_CFLAGS = \ - $(AM_CFLAGS) \ - $(TERMINAL_CFLAGS) - -systemd_modeset_SOURCES = \ - src/libsystemd-terminal/modeset.c - -systemd_modeset_LDADD = \ - libsystemd-terminal.la \ - libshared.la \ - $(TERMINAL_LIBS) - -systemd_subterm_SOURCES = \ - src/libsystemd-terminal/subterm.c - -systemd_subterm_LDADD = \ - libsystemd-terminal.la \ - libshared.la - -test_term_page_SOURCES = \ - src/libsystemd-terminal/test-term-page.c - -test_term_page_LDADD = \ - libsystemd-terminal.la \ - libshared.la - -test_term_parser_SOURCES = \ - src/libsystemd-terminal/test-term-parser.c - -test_term_parser_LDADD = \ - libsystemd-terminal.la \ - libshared.la - -test_unifont_SOURCES = \ - src/libsystemd-terminal/test-unifont.c - -test_unifont_LDADD = \ - libsystemd-terminal.la \ - libshared.la - -src/libsystemd-terminal/unifont-glyph-array.bin: tools/compile-unifont.py $(UNIFONT) - $(AM_V_GEN)$(PYTHON) $< <$(UNIFONT) >$@ - -EXTRA_DIST += \ - tools/compile-unifont.py - # ------------------------------------------------------------------------------ include_HEADERS += \ src/libudev/libudev.h diff --git a/autogen.sh b/autogen.sh index 2d4acdfef1..607a9682dd 100755 --- a/autogen.sh +++ b/autogen.sh @@ -57,9 +57,6 @@ cd $oldpwd if [ "x$1" = "xc" ]; then $topdir/configure CFLAGS='-g -O0 -ftrapv' --enable-compat-libs --enable-kdbus $args make clean -elif [ "x$1" = "xt" ]; then - $topdir/configure CFLAGS='-g -O0 -ftrapv' --enable-compat-libs --enable-kdbus --enable-terminal $args - make clean elif [ "x$1" = "xg" ]; then $topdir/configure CFLAGS='-g -Og -ftrapv' --enable-compat-libs --enable-kdbus $args make clean diff --git a/configure.ac b/configure.ac index 2fddf29f36..7b92eb25d4 100644 --- a/configure.ac +++ b/configure.ac @@ -1170,27 +1170,6 @@ AS_IF([test "x$enable_gnuefi" != "xno"], [ ]) AM_CONDITIONAL(HAVE_GNUEFI, [test "x$have_gnuefi" = xyes]) -# ------------------------------------------------------------------------------ -AC_ARG_WITH(unifont, - AS_HELP_STRING([--with-unifont=PATH], - [Path to unifont.hex]), - [UNIFONT="$withval"], - [UNIFONT="/usr/share/unifont/unifont.hex"]) -AC_SUBST(UNIFONT) - -have_terminal=no -have_unifont=no -AC_ARG_ENABLE(terminal, AS_HELP_STRING([--enable-terminal], [enable terminal support])) -if test "x$enable_terminal" = "xyes"; then - PKG_CHECK_MODULES([TERMINAL], [ libevdev >= 1.2 xkbcommon >= 0.5 libdrm >= 2.4], [have_terminal=yes]) - AC_CHECK_FILE($UNIFONT, [have_unifont=yes]) - AS_IF([test "x$have_terminal" != xyes -o "x$have_unifont" != "xyes" -a "x$enable_terminal" = xyes], - [AC_MSG_ERROR([*** terminal support requested but required dependencies not available])], - [test "x$have_terminal" = xyes -a "x$have_unifont" = "xyes"], - [AC_DEFINE(ENABLE_TERMINAL, 1, [Define if terminal support is to be enabled])]) -fi -AM_CONDITIONAL(ENABLE_TERMINAL, [test "x$have_terminal" = "xyes" -a "x$have_unifont" = "xyes"]) - # ------------------------------------------------------------------------------ have_kdbus=no AC_ARG_ENABLE(kdbus, AS_HELP_STRING([--disable-kdbus], [do not connect to kdbus by default])) @@ -1547,7 +1526,6 @@ AC_MSG_RESULT([ dbus: ${have_dbus} nss-myhostname: ${have_myhostname} hwdb: ${enable_hwdb} - terminal: ${have_terminal} kdbus: ${have_kdbus} Python: ${have_python} man pages: ${have_manpages} diff --git a/src/console/Makefile b/src/console/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/console/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/console/consoled-display.c b/src/console/consoled-display.c deleted file mode 100644 index 569c011dc0..0000000000 --- a/src/console/consoled-display.c +++ /dev/null @@ -1,81 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include "consoled.h" -#include "grdev.h" -#include "list.h" -#include "macro.h" -#include "util.h" - -int display_new(Display **out, Session *s, grdev_display *display) { - _cleanup_(display_freep) Display *d = NULL; - - assert(out); - assert(s); - assert(display); - - d = new0(Display, 1); - if (!d) - return -ENOMEM; - - d->session = s; - d->grdev = display; - d->width = grdev_display_get_width(display); - d->height = grdev_display_get_height(display); - LIST_PREPEND(displays_by_session, d->session->display_list, d); - - grdev_display_enable(display); - - *out = d; - d = NULL; - return 0; -} - -Display *display_free(Display *d) { - if (!d) - return NULL; - - LIST_REMOVE(displays_by_session, d->session->display_list, d); - free(d); - - return NULL; -} - -void display_refresh(Display *d) { - assert(d); - - d->width = grdev_display_get_width(d->grdev); - d->height = grdev_display_get_height(d->grdev); -} - -void display_render(Display *d, Workspace *w) { - const grdev_display_target *target; - - assert(d); - assert(w); - - GRDEV_DISPLAY_FOREACH_TARGET(d->grdev, target) { - if (workspace_draw(w, target)) - grdev_display_flip_target(d->grdev, target); - } -} diff --git a/src/console/consoled-manager.c b/src/console/consoled-manager.c deleted file mode 100644 index 20424eb267..0000000000 --- a/src/console/consoled-manager.c +++ /dev/null @@ -1,284 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "log.h" -#include "signal-util.h" -#include "util.h" -#include "consoled.h" -#include "idev.h" -#include "grdev.h" -#include "sysview.h" -#include "unifont.h" - -int manager_new(Manager **out) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(out); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - r = sd_event_set_watchdog(m->event, true); - if (r < 0) - return r; - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGQUIT, SIGINT, SIGWINCH, SIGCHLD, -1); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGQUIT, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sd_bus_open_system(&m->sysbus); - if (r < 0) - return r; - - r = sd_bus_attach_event(m->sysbus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return r; - - r = unifont_new(&m->uf); - if (r < 0) - return r; - - r = sysview_context_new(&m->sysview, - SYSVIEW_CONTEXT_SCAN_LOGIND | - SYSVIEW_CONTEXT_SCAN_EVDEV | - SYSVIEW_CONTEXT_SCAN_DRM, - m->event, - m->sysbus, - NULL); - if (r < 0) - return r; - - r = grdev_context_new(&m->grdev, m->event, m->sysbus); - if (r < 0) - return r; - - r = idev_context_new(&m->idev, m->event, m->sysbus); - if (r < 0) - return r; - - *out = m; - m = NULL; - return 0; -} - -Manager *manager_free(Manager *m) { - if (!m) - return NULL; - - assert(!m->workspace_list); - - m->idev = idev_context_unref(m->idev); - m->grdev = grdev_context_unref(m->grdev); - m->sysview = sysview_context_free(m->sysview); - m->uf = unifont_unref(m->uf); - m->sysbus = sd_bus_unref(m->sysbus); - m->event = sd_event_unref(m->event); - free(m); - - return NULL; -} - -static int manager_sysview_session_filter(Manager *m, sysview_event *event) { - const char *sid = event->session_filter.id; - _cleanup_free_ char *desktop = NULL; - int r; - - assert(sid); - - r = sd_session_get_desktop(sid, &desktop); - if (r < 0) - return 0; - - return streq(desktop, "systemd-console"); -} - -static int manager_sysview_session_add(Manager *m, sysview_event *event) { - sysview_session *session = event->session_add.session; - Session *s; - int r; - - r = sysview_session_take_control(session); - if (r < 0) - return log_error_errno(r, "Cannot request session control on '%s': %m", - sysview_session_get_name(session)); - - r = session_new(&s, m, session); - if (r < 0) { - log_error_errno(r, "Cannot create session on '%s': %m", - sysview_session_get_name(session)); - sysview_session_release_control(session); - return r; - } - - sysview_session_set_userdata(session, s); - - return 0; -} - -static int manager_sysview_session_remove(Manager *m, sysview_event *event) { - sysview_session *session = event->session_remove.session; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_free(s); - - return 0; -} - -static int manager_sysview_session_attach(Manager *m, sysview_event *event) { - sysview_session *session = event->session_attach.session; - sysview_device *device = event->session_attach.device; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_add_device(s, device); - - return 0; -} - -static int manager_sysview_session_detach(Manager *m, sysview_event *event) { - sysview_session *session = event->session_detach.session; - sysview_device *device = event->session_detach.device; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_remove_device(s, device); - - return 0; -} - -static int manager_sysview_session_refresh(Manager *m, sysview_event *event) { - sysview_session *session = event->session_refresh.session; - sysview_device *device = event->session_refresh.device; - struct udev_device *ud = event->session_refresh.ud; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - session_refresh_device(s, device, ud); - - return 0; -} - -static int manager_sysview_session_control(Manager *m, sysview_event *event) { - sysview_session *session = event->session_control.session; - int error = event->session_control.error; - Session *s; - - s = sysview_session_get_userdata(session); - if (!s) - return 0; - - if (error < 0) { - log_error_errno(error, "Cannot take session control on '%s': %m", - sysview_session_get_name(session)); - session_free(s); - sysview_session_set_userdata(session, NULL); - return error; - } - - return 0; -} - -static int manager_sysview_fn(sysview_context *sysview, void *userdata, sysview_event *event) { - Manager *m = userdata; - int r; - - assert(m); - - switch (event->type) { - case SYSVIEW_EVENT_SESSION_FILTER: - r = manager_sysview_session_filter(m, event); - break; - case SYSVIEW_EVENT_SESSION_ADD: - r = manager_sysview_session_add(m, event); - break; - case SYSVIEW_EVENT_SESSION_REMOVE: - r = manager_sysview_session_remove(m, event); - break; - case SYSVIEW_EVENT_SESSION_ATTACH: - r = manager_sysview_session_attach(m, event); - break; - case SYSVIEW_EVENT_SESSION_DETACH: - r = manager_sysview_session_detach(m, event); - break; - case SYSVIEW_EVENT_SESSION_REFRESH: - r = manager_sysview_session_refresh(m, event); - break; - case SYSVIEW_EVENT_SESSION_CONTROL: - r = manager_sysview_session_control(m, event); - break; - default: - r = 0; - break; - } - - return r; -} - -int manager_run(Manager *m) { - int r; - - assert(m); - - r = sysview_context_start(m->sysview, manager_sysview_fn, m); - if (r < 0) - return r; - - r = sd_event_loop(m->event); - - sysview_context_stop(m->sysview); - return r; -} diff --git a/src/console/consoled-session.c b/src/console/consoled-session.c deleted file mode 100644 index 264a4d009a..0000000000 --- a/src/console/consoled-session.c +++ /dev/null @@ -1,279 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include "consoled.h" -#include "grdev.h" -#include "idev.h" -#include "list.h" -#include "macro.h" -#include "sd-event.h" -#include "sysview.h" -#include "util.h" - -static bool session_feed_keyboard(Session *s, idev_data *data) { - idev_data_keyboard *kdata = &data->keyboard; - - if (!data->resync && kdata->value == 1 && kdata->n_syms == 1) { - uint32_t nr; - sysview_seat *seat; - - /* handle VT-switch requests */ - nr = 0; - - switch (kdata->keysyms[0]) { - case XKB_KEY_F1 ... XKB_KEY_F12: - if (IDEV_KBDMATCH(kdata, - IDEV_KBDMOD_CTRL | IDEV_KBDMOD_ALT, - kdata->keysyms[0])) - nr = kdata->keysyms[0] - XKB_KEY_F1 + 1; - break; - case XKB_KEY_XF86Switch_VT_1 ... XKB_KEY_XF86Switch_VT_12: - nr = kdata->keysyms[0] - XKB_KEY_XF86Switch_VT_1 + 1; - break; - } - - if (nr != 0) { - seat = sysview_session_get_seat(s->sysview); - sysview_seat_switch_to(seat, nr); - return true; - } - } - - return false; -} - -static bool session_feed(Session *s, idev_data *data) { - switch (data->type) { - case IDEV_DATA_KEYBOARD: - return session_feed_keyboard(s, data); - default: - return false; - } -} - -static int session_idev_fn(idev_session *idev, void *userdata, idev_event *event) { - Session *s = userdata; - - switch (event->type) { - case IDEV_EVENT_DEVICE_ADD: - idev_device_enable(event->device_add.device); - break; - case IDEV_EVENT_DEVICE_REMOVE: - idev_device_disable(event->device_remove.device); - break; - case IDEV_EVENT_DEVICE_DATA: - if (!session_feed(s, &event->device_data.data)) - workspace_feed(s->active_ws, &event->device_data.data); - break; - } - - return 0; -} - -static void session_grdev_fn(grdev_session *grdev, void *userdata, grdev_event *event) { - grdev_display *display; - Session *s = userdata; - Display *d; - int r; - - switch (event->type) { - case GRDEV_EVENT_DISPLAY_ADD: - display = event->display_add.display; - - r = display_new(&d, s, display); - if (r < 0) { - log_error_errno(r, "Cannot create display '%s' on '%s': %m", - grdev_display_get_name(display), sysview_session_get_name(s->sysview)); - break; - } - - grdev_display_set_userdata(display, d); - workspace_refresh(s->active_ws); - break; - case GRDEV_EVENT_DISPLAY_REMOVE: - display = event->display_remove.display; - d = grdev_display_get_userdata(display); - if (!d) - break; - - display_free(d); - workspace_refresh(s->active_ws); - break; - case GRDEV_EVENT_DISPLAY_CHANGE: - display = event->display_remove.display; - d = grdev_display_get_userdata(display); - if (!d) - break; - - display_refresh(d); - workspace_refresh(s->active_ws); - break; - case GRDEV_EVENT_DISPLAY_FRAME: - display = event->display_remove.display; - d = grdev_display_get_userdata(display); - if (!d) - break; - - session_dirty(s); - break; - } -} - -static int session_redraw_fn(sd_event_source *src, void *userdata) { - Session *s = userdata; - Display *d; - - LIST_FOREACH(displays_by_session, d, s->display_list) - display_render(d, s->active_ws); - - grdev_session_commit(s->grdev); - - return 0; -} - -int session_new(Session **out, Manager *m, sysview_session *session) { - _cleanup_(session_freep) Session *s = NULL; - int r; - - assert(out); - assert(m); - assert(session); - - s = new0(Session, 1); - if (!s) - return -ENOMEM; - - s->manager = m; - s->sysview = session; - - r = grdev_session_new(&s->grdev, - m->grdev, - GRDEV_SESSION_MANAGED, - sysview_session_get_name(session), - session_grdev_fn, - s); - if (r < 0) - return r; - - r = idev_session_new(&s->idev, - m->idev, - IDEV_SESSION_MANAGED, - sysview_session_get_name(session), - session_idev_fn, - s); - if (r < 0) - return r; - - r = workspace_new(&s->my_ws, m); - if (r < 0) - return r; - - s->active_ws = workspace_attach(s->my_ws, s); - - r = sd_event_add_defer(m->event, &s->redraw_src, session_redraw_fn, s); - if (r < 0) - return r; - - grdev_session_enable(s->grdev); - idev_session_enable(s->idev); - - *out = s; - s = NULL; - return 0; -} - -Session *session_free(Session *s) { - if (!s) - return NULL; - - assert(!s->display_list); - - sd_event_source_unref(s->redraw_src); - - workspace_detach(s->active_ws, s); - workspace_unref(s->my_ws); - - idev_session_free(s->idev); - grdev_session_free(s->grdev); - free(s); - - return NULL; -} - -void session_dirty(Session *s) { - int r; - - assert(s); - - r = sd_event_source_set_enabled(s->redraw_src, SD_EVENT_ONESHOT); - if (r < 0) - log_error_errno(r, "Cannot enable redraw-source: %m"); -} - -void session_add_device(Session *s, sysview_device *device) { - unsigned int type; - - assert(s); - assert(device); - - type = sysview_device_get_type(device); - switch (type) { - case SYSVIEW_DEVICE_DRM: - grdev_session_add_drm(s->grdev, sysview_device_get_ud(device)); - break; - case SYSVIEW_DEVICE_EVDEV: - idev_session_add_evdev(s->idev, sysview_device_get_ud(device)); - break; - } -} - -void session_remove_device(Session *s, sysview_device *device) { - unsigned int type; - - assert(s); - assert(device); - - type = sysview_device_get_type(device); - switch (type) { - case SYSVIEW_DEVICE_DRM: - grdev_session_remove_drm(s->grdev, sysview_device_get_ud(device)); - break; - case SYSVIEW_DEVICE_EVDEV: - idev_session_remove_evdev(s->idev, sysview_device_get_ud(device)); - break; - } -} - -void session_refresh_device(Session *s, sysview_device *device, struct udev_device *ud) { - unsigned int type; - - assert(s); - assert(device); - - type = sysview_device_get_type(device); - switch (type) { - case SYSVIEW_DEVICE_DRM: - grdev_session_hotplug_drm(s->grdev, sysview_device_get_ud(device)); - break; - } -} diff --git a/src/console/consoled-terminal.c b/src/console/consoled-terminal.c deleted file mode 100644 index 03447d1b92..0000000000 --- a/src/console/consoled-terminal.c +++ /dev/null @@ -1,358 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include "consoled.h" -#include "list.h" -#include "macro.h" -#include "util.h" - -static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) { - Terminal *t = userdata; - int r; - - if (t->pty) { - r = pty_write(t->pty, buf, size); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) { - Terminal *t = userdata; - int r; - - switch (event) { - case PTY_CHILD: - log_debug("PTY child exited"); - t->pty = pty_unref(t->pty); - break; - case PTY_DATA: - r = term_screen_feed_text(t->screen, ptr, size); - if (r < 0) - log_error_errno(r, "Cannot update screen state: %m"); - - workspace_dirty(t->workspace); - break; - } - - return 0; -} - -int terminal_new(Terminal **out, Workspace *w) { - _cleanup_(terminal_freep) Terminal *t = NULL; - int r; - - assert(w); - - t = new0(Terminal, 1); - if (!t) - return -ENOMEM; - - t->workspace = w; - LIST_PREPEND(terminals_by_workspace, w->terminal_list, t); - - r = term_parser_new(&t->parser, true); - if (r < 0) - return r; - - r = term_screen_new(&t->screen, terminal_write_fn, t, NULL, NULL); - if (r < 0) - return r; - - r = term_screen_set_answerback(t->screen, "systemd-console"); - if (r < 0) - return r; - - if (out) - *out = t; - t = NULL; - return 0; -} - -Terminal *terminal_free(Terminal *t) { - if (!t) - return NULL; - - assert(t->workspace); - - if (t->pty) { - (void) pty_signal(t->pty, SIGHUP); - pty_close(t->pty); - pty_unref(t->pty); - } - term_screen_unref(t->screen); - term_parser_free(t->parser); - LIST_REMOVE(terminals_by_workspace, t->workspace->terminal_list, t); - free(t); - - return NULL; -} - -void terminal_resize(Terminal *t) { - uint32_t width, height, fw, fh; - int r; - - assert(t); - - width = t->workspace->width; - height = t->workspace->height; - fw = unifont_get_width(t->workspace->manager->uf); - fh = unifont_get_height(t->workspace->manager->uf); - - width = (fw > 0) ? width / fw : 0; - height = (fh > 0) ? height / fh : 0; - - if (t->pty) { - r = pty_resize(t->pty, width, height); - if (r < 0) - log_error_errno(r, "Cannot resize pty: %m"); - } - - r = term_screen_resize(t->screen, width, height); - if (r < 0) - log_error_errno(r, "Cannot resize screen: %m"); -} - -void terminal_run(Terminal *t) { - pid_t pid; - - assert(t); - - if (t->pty) - return; - - pid = pty_fork(&t->pty, - t->workspace->manager->event, - terminal_pty_fn, - t, - term_screen_get_width(t->screen), - term_screen_get_height(t->screen)); - if (pid < 0) { - log_error_errno(pid, "Cannot fork PTY: %m"); - return; - } else if (pid == 0) { - /* child */ - - char **argv = (char*[]){ - (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL, - NULL - }; - - setenv("TERM", "xterm-256color", 1); - setenv("COLORTERM", "systemd-console", 1); - - execve(argv[0], argv, environ); - log_error_errno(errno, "Cannot exec %s (%d): %m", argv[0], -errno); - _exit(1); - } -} - -static void terminal_feed_keyboard(Terminal *t, idev_data *data) { - idev_data_keyboard *kdata = &data->keyboard; - int r; - - if (!data->resync && (kdata->value == 1 || kdata->value == 2)) { - assert_cc(TERM_KBDMOD_CNT == (int)IDEV_KBDMOD_CNT); - assert_cc(TERM_KBDMOD_IDX_SHIFT == (int)IDEV_KBDMOD_IDX_SHIFT && - TERM_KBDMOD_IDX_CTRL == (int)IDEV_KBDMOD_IDX_CTRL && - TERM_KBDMOD_IDX_ALT == (int)IDEV_KBDMOD_IDX_ALT && - TERM_KBDMOD_IDX_LINUX == (int)IDEV_KBDMOD_IDX_LINUX && - TERM_KBDMOD_IDX_CAPS == (int)IDEV_KBDMOD_IDX_CAPS); - - r = term_screen_feed_keyboard(t->screen, - kdata->keysyms, - kdata->n_syms, - kdata->ascii, - kdata->codepoints, - kdata->mods); - if (r < 0) - log_error_errno(r, "Cannot feed keyboard data to screen: %m"); - } -} - -void terminal_feed(Terminal *t, idev_data *data) { - switch (data->type) { - case IDEV_DATA_KEYBOARD: - terminal_feed_keyboard(t, data); - break; - } -} - -static void terminal_fill(uint8_t *dst, - uint32_t width, - uint32_t height, - uint32_t stride, - uint32_t value) { - uint32_t i, j, *px; - - for (j = 0; j < height; ++j) { - px = (uint32_t*)dst; - - for (i = 0; i < width; ++i) - *px++ = value; - - dst += stride; - } -} - -static void terminal_blend(uint8_t *dst, - uint32_t width, - uint32_t height, - uint32_t dst_stride, - const uint8_t *src, - uint32_t src_stride, - uint32_t fg, - uint32_t bg) { - uint32_t i, j, *px; - - for (j = 0; j < height; ++j) { - px = (uint32_t*)dst; - - for (i = 0; i < width; ++i) { - if (!src || src[i / 8] & (1 << (7 - i % 8))) - *px = fg; - else - *px = bg; - - ++px; - } - - src += src_stride; - dst += dst_stride; - } -} - -typedef struct { - const grdev_display_target *target; - unifont *uf; - uint32_t cell_width; - uint32_t cell_height; - bool dirty; -} TerminalDrawContext; - -static int terminal_draw_cell(term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width) { - TerminalDrawContext *ctx = userdata; - const grdev_display_target *target = ctx->target; - grdev_fb *fb = target->back; - uint32_t xpos, ypos, width, height; - uint32_t fg, bg; - unifont_glyph g; - uint8_t *dst; - int r; - - if (n_ch > 0) { - r = unifont_lookup(ctx->uf, &g, *ch); - if (r < 0) - r = unifont_lookup(ctx->uf, &g, 0xfffd); - if (r < 0) - unifont_fallback(&g); - } - - xpos = x * ctx->cell_width; - ypos = y * ctx->cell_height; - - if (xpos >= fb->width || ypos >= fb->height) - return 0; - - width = MIN(fb->width - xpos, ctx->cell_width * ch_width); - height = MIN(fb->height - ypos, ctx->cell_height); - - term_attr_to_argb32(attr, &fg, &bg, NULL); - - ctx->dirty = true; - - dst = fb->maps[0]; - dst += fb->strides[0] * ypos + sizeof(uint32_t) * xpos; - - if (n_ch < 1) { - terminal_fill(dst, - width, - height, - fb->strides[0], - bg); - } else { - if (width > g.width) - terminal_fill(dst + sizeof(uint32_t) * g.width, - width - g.width, - height, - fb->strides[0], - bg); - if (height > g.height) - terminal_fill(dst + fb->strides[0] * g.height, - width, - height - g.height, - fb->strides[0], - bg); - - terminal_blend(dst, - width, - height, - fb->strides[0], - g.data, - g.stride, - fg, - bg); - } - - return 0; -} - -bool terminal_draw(Terminal *t, const grdev_display_target *target) { - TerminalDrawContext ctx = { }; - uint64_t age; - - assert(t); - assert(target); - - /* start up terminal on first frame */ - terminal_run(t); - - ctx.target = target; - ctx.uf = t->workspace->manager->uf; - ctx.cell_width = unifont_get_width(ctx.uf); - ctx.cell_height = unifont_get_height(ctx.uf); - ctx.dirty = false; - - if (target->front) { - /* if the frontbuffer is new enough, no reason to redraw */ - age = term_screen_get_age(t->screen); - if (age != 0 && age <= target->front->data.u64) - return false; - } else { - /* force flip if no frontbuffer is set, yet */ - ctx.dirty = true; - } - - term_screen_draw(t->screen, terminal_draw_cell, &ctx, &target->back->data.u64); - - return ctx.dirty; -} diff --git a/src/console/consoled-workspace.c b/src/console/consoled-workspace.c deleted file mode 100644 index 5e9e5c7c49..0000000000 --- a/src/console/consoled-workspace.c +++ /dev/null @@ -1,167 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include "consoled.h" -#include "grdev.h" -#include "idev.h" -#include "list.h" -#include "macro.h" -#include "util.h" - -int workspace_new(Workspace **out, Manager *m) { - _cleanup_(workspace_unrefp) Workspace *w = NULL; - int r; - - assert(out); - - w = new0(Workspace, 1); - if (!w) - return -ENOMEM; - - w->ref = 1; - w->manager = m; - LIST_PREPEND(workspaces_by_manager, m->workspace_list, w); - - r = terminal_new(&w->current, w); - if (r < 0) - return r; - - *out = w; - w = NULL; - return 0; -} - -static void workspace_cleanup(Workspace *w) { - Terminal *t; - - assert(w); - assert(w->ref == 0); - assert(w->manager); - assert(!w->session_list); - - w->current = NULL; - while ((t = w->terminal_list)) - terminal_free(t); - - LIST_REMOVE(workspaces_by_manager, w->manager->workspace_list, w); - free(w); -} - -Workspace *workspace_ref(Workspace *w) { - assert(w); - - ++w->ref; - return w; -} - -Workspace *workspace_unref(Workspace *w) { - if (!w) - return NULL; - - assert(w->ref > 0); - - if (--w->ref == 0) - workspace_cleanup(w); - - return NULL; -} - -Workspace *workspace_attach(Workspace *w, Session *s) { - assert(w); - assert(s); - - LIST_PREPEND(sessions_by_workspace, w->session_list, s); - workspace_refresh(w); - return workspace_ref(w); -} - -Workspace *workspace_detach(Workspace *w, Session *s) { - assert(w); - assert(s); - assert(s->active_ws == w); - - LIST_REMOVE(sessions_by_workspace, w->session_list, s); - workspace_refresh(w); - return workspace_unref(w); -} - -void workspace_refresh(Workspace *w) { - uint32_t width, height; - Terminal *t; - Session *s; - Display *d; - - assert(w); - - width = 0; - height = 0; - - /* find out minimum dimension of all attached displays */ - LIST_FOREACH(sessions_by_workspace, s, w->session_list) { - LIST_FOREACH(displays_by_session, d, s->display_list) { - assert(d->width > 0 && d->height > 0); - - if (width == 0 || d->width < width) - width = d->width; - if (height == 0 || d->height < height) - height = d->height; - } - } - - /* either both are zero, or none is zero */ - assert(!(!width ^ !height)); - - /* update terminal-sizes if dimensions changed */ - if (w->width != width || w->height != height) { - w->width = width; - w->height = height; - - LIST_FOREACH(terminals_by_workspace, t, w->terminal_list) - terminal_resize(t); - - workspace_dirty(w); - } -} - -void workspace_dirty(Workspace *w) { - Session *s; - - assert(w); - - LIST_FOREACH(sessions_by_workspace, s, w->session_list) - session_dirty(s); -} - -void workspace_feed(Workspace *w, idev_data *data) { - assert(w); - assert(data); - - terminal_feed(w->current, data); -} - -bool workspace_draw(Workspace *w, const grdev_display_target *target) { - assert(w); - assert(target); - - return terminal_draw(w->current, target); -} diff --git a/src/console/consoled.c b/src/console/consoled.c deleted file mode 100644 index 9f69e8983f..0000000000 --- a/src/console/consoled.c +++ /dev/null @@ -1,66 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include "sd-daemon.h" -#include "log.h" -#include "signal-util.h" -#include "consoled.h" - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto out; - } - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Could not create manager: %m"); - goto out; - } - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = manager_run(m); - if (r < 0) { - log_error_errno(r, "Cannot run manager: %m"); - goto out; - } - -out: - sd_notify(false, - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/console/consoled.h b/src/console/consoled.h deleted file mode 100644 index f85c1a0791..0000000000 --- a/src/console/consoled.h +++ /dev/null @@ -1,164 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include "grdev.h" -#include "idev.h" -#include "list.h" -#include "macro.h" -#include "pty.h" -#include "sd-bus.h" -#include "sd-event.h" -#include "sysview.h" -#include "term.h" -#include "unifont.h" - -typedef struct Manager Manager; -typedef struct Session Session; -typedef struct Display Display; -typedef struct Workspace Workspace; -typedef struct Terminal Terminal; - -/* - * Terminals - */ - -struct Terminal { - Workspace *workspace; - LIST_FIELDS(Terminal, terminals_by_workspace); - - term_utf8 utf8; - term_parser *parser; - term_screen *screen; - Pty *pty; -}; - -int terminal_new(Terminal **out, Workspace *w); -Terminal *terminal_free(Terminal *t); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Terminal*, terminal_free); - -void terminal_resize(Terminal *t); -void terminal_run(Terminal *t); -void terminal_feed(Terminal *t, idev_data *data); -bool terminal_draw(Terminal *t, const grdev_display_target *target); - -/* - * Workspaces - */ - -struct Workspace { - unsigned long ref; - Manager *manager; - LIST_FIELDS(Workspace, workspaces_by_manager); - - LIST_HEAD(Terminal, terminal_list); - Terminal *current; - - LIST_HEAD(Session, session_list); - uint32_t width; - uint32_t height; -}; - -int workspace_new(Workspace **out, Manager *m); -Workspace *workspace_ref(Workspace *w); -Workspace *workspace_unref(Workspace *w); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Workspace*, workspace_unref); - -Workspace *workspace_attach(Workspace *w, Session *s); -Workspace *workspace_detach(Workspace *w, Session *s); -void workspace_refresh(Workspace *w); - -void workspace_dirty(Workspace *w); -void workspace_feed(Workspace *w, idev_data *data); -bool workspace_draw(Workspace *w, const grdev_display_target *target); - -/* - * Displays - */ - -struct Display { - Session *session; - LIST_FIELDS(Display, displays_by_session); - grdev_display *grdev; - uint32_t width; - uint32_t height; -}; - -int display_new(Display **out, Session *s, grdev_display *grdev); -Display *display_free(Display *d); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Display*, display_free); - -void display_refresh(Display *d); -void display_render(Display *d, Workspace *w); - -/* - * Sessions - */ - -struct Session { - Manager *manager; - sysview_session *sysview; - grdev_session *grdev; - idev_session *idev; - - LIST_FIELDS(Session, sessions_by_workspace); - Workspace *my_ws; - Workspace *active_ws; - - LIST_HEAD(Display, display_list); - sd_event_source *redraw_src; -}; - -int session_new(Session **out, Manager *m, sysview_session *session); -Session *session_free(Session *s); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Session*, session_free); - -void session_dirty(Session *s); - -void session_add_device(Session *s, sysview_device *device); -void session_remove_device(Session *s, sysview_device *device); -void session_refresh_device(Session *s, sysview_device *device, struct udev_device *ud); - -/* - * Managers - */ - -struct Manager { - sd_event *event; - sd_bus *sysbus; - unifont *uf; - sysview_context *sysview; - grdev_context *grdev; - idev_context *idev; - LIST_HEAD(Workspace, workspace_list); -}; - -int manager_new(Manager **out); -Manager *manager_free(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -int manager_run(Manager *m); diff --git a/src/libsystemd-terminal/.gitignore b/src/libsystemd-terminal/.gitignore deleted file mode 100644 index 7de83bd3e9..0000000000 --- a/src/libsystemd-terminal/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/unifont-glyph-array.bin diff --git a/src/libsystemd-terminal/evcat.c b/src/libsystemd-terminal/evcat.c deleted file mode 100644 index 2aeefc2e16..0000000000 --- a/src/libsystemd-terminal/evcat.c +++ /dev/null @@ -1,488 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Event Catenation - * The evcat tool catenates input events of all requested devices and prints - * them to standard-output. It's only meant for debugging of input-related - * problems. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "build.h" -#include "event-util.h" -#include "macro.h" -#include "signal-util.h" -#include "util.h" -#include "idev.h" -#include "sysview.h" -#include "term-internal.h" - -typedef struct Evcat Evcat; - -struct Evcat { - char *session; - char *seat; - sd_event *event; - sd_bus *bus; - sysview_context *sysview; - idev_context *idev; - idev_session *idev_session; - - bool managed : 1; -}; - -static Evcat *evcat_free(Evcat *e) { - if (!e) - return NULL; - - e->idev_session = idev_session_free(e->idev_session); - e->idev = idev_context_unref(e->idev); - e->sysview = sysview_context_free(e->sysview); - e->bus = sd_bus_unref(e->bus); - e->event = sd_event_unref(e->event); - free(e->seat); - free(e->session); - free(e); - - tcflush(0, TCIOFLUSH); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free); - -static bool is_managed(const char *session) { - unsigned int vtnr; - struct stat st; - long mode; - int r; - - /* Using logind's Controller API is highly fragile if there is already - * a session controller running. If it is registered as controller - * itself, TakeControl will simply fail. But if its a legacy controller - * that does not use logind's controller API, we must never register - * our own controller. Otherwise, we really mess up the VT. Therefore, - * only run in managed mode if there's no-one else. */ - - if (geteuid() == 0) - return false; - - if (!isatty(1)) - return false; - - if (!session) - return false; - - r = sd_session_get_vt(session, &vtnr); - if (r < 0 || vtnr < 1 || vtnr > 63) - return false; - - mode = 0; - r = ioctl(1, KDGETMODE, &mode); - if (r < 0 || mode != KD_TEXT) - return false; - - r = fstat(1, &st); - if (r < 0 || minor(st.st_rdev) != vtnr) - return false; - - return true; -} - -static int evcat_new(Evcat **out) { - _cleanup_(evcat_freep) Evcat *e = NULL; - int r; - - assert(out); - - e = new0(Evcat, 1); - if (!e) - return log_oom(); - - r = sd_pid_get_session(getpid(), &e->session); - if (r < 0) - return log_error_errno(r, "Cannot retrieve logind session: %m"); - - r = sd_session_get_seat(e->session, &e->seat); - if (r < 0) - return log_error_errno(r, "Cannot retrieve seat of logind session: %m"); - - e->managed = is_managed(e->session); - - r = sd_event_default(&e->event); - if (r < 0) - return r; - - r = sd_bus_open_system(&e->bus); - if (r < 0) - return r; - - r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return r; - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1); - if (r < 0) - return r; - - r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sysview_context_new(&e->sysview, - SYSVIEW_CONTEXT_SCAN_LOGIND | - SYSVIEW_CONTEXT_SCAN_EVDEV, - e->event, - e->bus, - NULL); - if (r < 0) - return r; - - r = idev_context_new(&e->idev, e->event, e->bus); - if (r < 0) - return r; - - *out = e; - e = NULL; - return 0; -} - -static void kdata_print(idev_data *data) { - idev_data_keyboard *k = &data->keyboard; - char buf[128]; - uint32_t i, c; - int cwidth; - - /* Key-press state: UP/DOWN/REPEAT */ - printf(" %-6s", k->value == 0 ? "UP" : - k->value == 1 ? "DOWN" : - "REPEAT"); - - /* Resync state */ - printf(" | %-6s", data->resync ? "RESYNC" : ""); - - /* Keycode that triggered the event */ - printf(" | %5u", (unsigned)k->keycode); - - /* Well-known name of the keycode */ - printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : ""); - - /* Well-known modifiers */ - printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); - printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); - printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); - printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); - printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); - - /* Consumed modifiers */ - printf(" | %-5s", (k->consumed_mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); - printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); - printf(" %-3s", (k->consumed_mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); - printf(" %-5s", (k->consumed_mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); - printf(" %-4s", (k->consumed_mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); - - /* Resolved symbols */ - printf(" |"); - for (i = 0; i < k->n_syms; ++i) { - buf[0] = 0; - xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf)); - - if (is_locale_utf8()) { - c = k->codepoints[i]; - if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) { - /* "%4lc" doesn't work well, so hard-code it */ - cwidth = mk_wcwidth(c); - while (cwidth++ < 2) - printf(" "); - - printf(" '%lc':", (wchar_t)c); - } else { - printf(" "); - } - } - - printf(" XKB_KEY_%-30s", buf); - } - - printf("\n"); -} - -static bool kdata_is_exit(idev_data *data) { - idev_data_keyboard *k = &data->keyboard; - - if (k->value != 1) - return false; - if (k->n_syms != 1) - return false; - - return k->codepoints[0] == 'q'; -} - -static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) { - Evcat *e = userdata; - - switch (ev->type) { - case IDEV_EVENT_DEVICE_ADD: - idev_device_enable(ev->device_add.device); - break; - case IDEV_EVENT_DEVICE_REMOVE: - idev_device_disable(ev->device_remove.device); - break; - case IDEV_EVENT_DEVICE_DATA: - switch (ev->device_data.data.type) { - case IDEV_DATA_KEYBOARD: - if (kdata_is_exit(&ev->device_data.data)) - sd_event_exit(e->event, 0); - else - kdata_print(&ev->device_data.data); - - break; - } - - break; - } - - return 0; -} - -static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { - unsigned int flags, type; - Evcat *e = userdata; - sysview_device *d; - const char *name; - int r; - - switch (ev->type) { - case SYSVIEW_EVENT_SESSION_FILTER: - if (streq_ptr(e->session, ev->session_filter.id)) - return 1; - - break; - case SYSVIEW_EVENT_SESSION_ADD: - assert(!e->idev_session); - - name = sysview_session_get_name(ev->session_add.session); - flags = 0; - - if (e->managed) - flags |= IDEV_SESSION_MANAGED; - - r = idev_session_new(&e->idev_session, - e->idev, - flags, - name, - evcat_idev_fn, - e); - if (r < 0) - return log_error_errno(r, "Cannot create idev session: %m"); - - if (e->managed) { - r = sysview_session_take_control(ev->session_add.session); - if (r < 0) - return log_error_errno(r, "Cannot request session control: %m"); - } - - idev_session_enable(e->idev_session); - - break; - case SYSVIEW_EVENT_SESSION_REMOVE: - idev_session_disable(e->idev_session); - e->idev_session = idev_session_free(e->idev_session); - if (sd_event_get_exit_code(e->event, &r) == -ENODATA) - sd_event_exit(e->event, 0); - break; - case SYSVIEW_EVENT_SESSION_ATTACH: - d = ev->session_attach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_EVDEV) { - r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d)); - if (r < 0) - return log_error_errno(r, "Cannot add evdev device to idev: %m"); - } - - break; - case SYSVIEW_EVENT_SESSION_DETACH: - d = ev->session_detach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_EVDEV) { - r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d)); - if (r < 0) - return log_error_errno(r, "Cannot remove evdev device from idev: %m"); - } - - break; - case SYSVIEW_EVENT_SESSION_CONTROL: - r = ev->session_control.error; - if (r < 0) - return log_error_errno(r, "Cannot acquire session control: %m"); - - r = ioctl(1, KDSKBMODE, K_UNICODE); - if (r < 0) - return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m"); - - r = ioctl(1, KDSETMODE, KD_TEXT); - if (r < 0) - return log_error_errno(errno, "Cannot set KD_TEXT on stdout: %m"); - - printf("\n"); - - break; - } - - return 0; -} - -static int evcat_run(Evcat *e) { - struct termios in_attr, saved_attr; - int r; - - assert(e); - - if (!e->managed && geteuid() > 0) - log_warning("You run in unmanaged mode without being root. This is likely to produce no output.."); - - printf("evcat - Read and catenate events from selected input devices\n" - " Running on seat '%s' in user-session '%s'\n" - " Exit by pressing ^C or 'q'\n\n", - e->seat ? : "seat0", e->session ? : ""); - - r = sysview_context_start(e->sysview, evcat_sysview_fn, e); - if (r < 0) - goto out; - - r = tcgetattr(0, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - saved_attr = in_attr; - in_attr.c_lflag &= ~ECHO; - - r = tcsetattr(0, TCSANOW, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - r = sd_event_loop(e->event); - tcsetattr(0, TCSANOW, &saved_attr); - printf("exiting..\n"); - -out: - sysview_context_stop(e->sysview); - return r; -} - -static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Read and catenate events from selected input devices.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {}, - }; - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - case 'h': - help(); - return 0; - - case ARG_VERSION: - puts(PACKAGE_STRING); - puts(SYSTEMD_FEATURES); - return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind) { - log_error("Too many arguments"); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_(evcat_freep) Evcat *e = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - setlocale(LC_ALL, ""); - if (!is_locale_utf8()) - log_warning("Locale is not set to UTF-8. Codepoints will not be printed!"); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = evcat_new(&e); - if (r < 0) - goto finish; - - r = evcat_run(e); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c deleted file mode 100644 index 10c13e348a..0000000000 --- a/src/libsystemd-terminal/grdev-drm.c +++ /dev/null @@ -1,3092 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Yuck! DRM headers need system headers included first.. but we have to - * include it before util/missing.h to avoid redefining ioctl bits */ -#include -#include -#include - -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "macro.h" -#include "util.h" -#include "bus-util.h" -#include "grdev.h" -#include "grdev-internal.h" - -#define GRDRM_MAX_TRIES (16) - -typedef struct grdrm_object grdrm_object; -typedef struct grdrm_plane grdrm_plane; -typedef struct grdrm_connector grdrm_connector; -typedef struct grdrm_encoder grdrm_encoder; -typedef struct grdrm_crtc grdrm_crtc; - -typedef struct grdrm_fb grdrm_fb; -typedef struct grdrm_pipe grdrm_pipe; -typedef struct grdrm_card grdrm_card; -typedef struct unmanaged_card unmanaged_card; -typedef struct managed_card managed_card; - -/* - * Objects - */ - -enum { - GRDRM_TYPE_CRTC, - GRDRM_TYPE_ENCODER, - GRDRM_TYPE_CONNECTOR, - GRDRM_TYPE_PLANE, - GRDRM_TYPE_CNT -}; - -struct grdrm_object { - grdrm_card *card; - uint32_t id; - uint32_t index; - unsigned int type; - void (*free_fn) (grdrm_object *object); - - bool present : 1; - bool assigned : 1; -}; - -struct grdrm_plane { - grdrm_object object; - - struct { - uint32_t used_crtc; - uint32_t used_fb; - uint32_t gamma_size; - - uint32_t n_crtcs; - uint32_t max_crtcs; - uint32_t *crtcs; - uint32_t n_formats; - uint32_t max_formats; - uint32_t *formats; - } kern; -}; - -struct grdrm_connector { - grdrm_object object; - - struct { - uint32_t type; - uint32_t type_id; - uint32_t used_encoder; - uint32_t connection; - uint32_t mm_width; - uint32_t mm_height; - uint32_t subpixel; - - uint32_t n_encoders; - uint32_t max_encoders; - uint32_t *encoders; - uint32_t n_modes; - uint32_t max_modes; - struct drm_mode_modeinfo *modes; - uint32_t n_props; - uint32_t max_props; - uint32_t *prop_ids; - uint64_t *prop_values; - } kern; -}; - -struct grdrm_encoder { - grdrm_object object; - - struct { - uint32_t type; - uint32_t used_crtc; - - uint32_t n_crtcs; - uint32_t max_crtcs; - uint32_t *crtcs; - uint32_t n_clones; - uint32_t max_clones; - uint32_t *clones; - } kern; -}; - -struct grdrm_crtc { - grdrm_object object; - - struct { - uint32_t used_fb; - uint32_t fb_offset_x; - uint32_t fb_offset_y; - uint32_t gamma_size; - - uint32_t n_used_connectors; - uint32_t max_used_connectors; - uint32_t *used_connectors; - - bool mode_set; - struct drm_mode_modeinfo mode; - } kern; - - struct { - bool set; - uint32_t fb; - uint32_t fb_x; - uint32_t fb_y; - uint32_t gamma; - - uint32_t n_connectors; - uint32_t *connectors; - - bool mode_set; - struct drm_mode_modeinfo mode; - } old; - - struct { - struct drm_mode_modeinfo mode; - uint32_t n_connectors; - uint32_t max_connectors; - uint32_t *connectors; - } set; - - grdrm_pipe *pipe; - - bool applied : 1; -}; - -#define GRDRM_OBJECT_INIT(_card, _id, _index, _type, _free_fn) ((grdrm_object){ \ - .card = (_card), \ - .id = (_id), \ - .index = (_index), \ - .type = (_type), \ - .free_fn = (_free_fn), \ - }) - -grdrm_object *grdrm_find_object(grdrm_card *card, uint32_t id); -int grdrm_object_add(grdrm_object *object); -grdrm_object *grdrm_object_free(grdrm_object *object); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdrm_object*, grdrm_object_free); - -int grdrm_plane_new(grdrm_plane **out, grdrm_card *card, uint32_t id, uint32_t index); -int grdrm_connector_new(grdrm_connector **out, grdrm_card *card, uint32_t id, uint32_t index); -int grdrm_encoder_new(grdrm_encoder **out, grdrm_card *card, uint32_t id, uint32_t index); -int grdrm_crtc_new(grdrm_crtc **out, grdrm_card *card, uint32_t id, uint32_t index); - -#define plane_from_object(_obj) container_of((_obj), grdrm_plane, object) -#define connector_from_object(_obj) container_of((_obj), grdrm_connector, object) -#define encoder_from_object(_obj) container_of((_obj), grdrm_encoder, object) -#define crtc_from_object(_obj) container_of((_obj), grdrm_crtc, object) - -/* - * Framebuffers - */ - -struct grdrm_fb { - grdev_fb base; - grdrm_card *card; - uint32_t id; - uint32_t handles[4]; - uint32_t offsets[4]; - uint32_t sizes[4]; - uint32_t flipid; -}; - -static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_modeinfo *mode); -grdrm_fb *grdrm_fb_free(grdrm_fb *fb); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdrm_fb*, grdrm_fb_free); - -#define fb_from_base(_fb) container_of((_fb), grdrm_fb, base) - -/* - * Pipes - */ - -struct grdrm_pipe { - grdev_pipe base; - grdrm_crtc *crtc; - uint32_t counter; -}; - -#define grdrm_pipe_from_base(_e) container_of((_e), grdrm_pipe, base) - -#define GRDRM_PIPE_NAME_MAX (GRDRM_CARD_NAME_MAX + 1 + DECIMAL_STR_MAX(uint32_t)) - -static const grdev_pipe_vtable grdrm_pipe_vtable; - -static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_modeinfo *mode, size_t n_fbs); - -/* - * Cards - */ - -struct grdrm_card { - grdev_card base; - - int fd; - sd_event_source *fd_src; - - uint32_t n_crtcs; - uint32_t n_encoders; - uint32_t n_connectors; - uint32_t n_planes; - uint32_t max_ids; - Hashmap *object_map; - - bool async_hotplug : 1; - bool hotplug : 1; - bool running : 1; - bool ready : 1; - bool cap_dumb : 1; - bool cap_monotonic : 1; -}; - -struct unmanaged_card { - grdrm_card card; - char *devnode; -}; - -struct managed_card { - grdrm_card card; - dev_t devnum; - - sd_bus_slot *slot_pause_device; - sd_bus_slot *slot_resume_device; - sd_bus_slot *slot_take_device; - - bool requested : 1; /* TakeDevice() was sent */ - bool acquired : 1; /* TakeDevice() was successful */ - bool master : 1; /* we are DRM-Master */ -}; - -#define grdrm_card_from_base(_e) container_of((_e), grdrm_card, base) -#define unmanaged_card_from_base(_e) \ - container_of(grdrm_card_from_base(_e), unmanaged_card, card) -#define managed_card_from_base(_e) \ - container_of(grdrm_card_from_base(_e), managed_card, card) - -#define GRDRM_CARD_INIT(_vtable, _session) ((grdrm_card){ \ - .base = GRDEV_CARD_INIT((_vtable), (_session)), \ - .fd = -1, \ - .max_ids = 32, \ - }) - -#define GRDRM_CARD_NAME_MAX (6 + DECIMAL_STR_MAX(unsigned) * 2) - -static const grdev_card_vtable unmanaged_card_vtable; -static const grdev_card_vtable managed_card_vtable; - -static int grdrm_card_open(grdrm_card *card, int dev_fd); -static void grdrm_card_close(grdrm_card *card); -static bool grdrm_card_async(grdrm_card *card, int r); - -/* - * The page-flip event of the kernel provides 64bit of arbitrary user-data. As - * drivers tend to drop events on intermediate deep mode-sets or because we - * might receive events during session activation, we try to avoid allocaing - * dynamic data on those events. Instead, we safe the CRTC id plus a 32bit - * counter in there. This way, we only get 32bit counters, not 64bit, but that - * should be more than enough. On the bright side, we no longer care whether we - * lose events. No memory leaks will occur. - * Modern DRM drivers might be fixed to no longer leak events, but we want to - * be safe. And associating dynamically allocated data with those events is - * kinda ugly, anyway. - */ - -static uint64_t grdrm_encode_vblank_data(uint32_t id, uint32_t counter) { - return id | ((uint64_t)counter << 32); -} - -static void grdrm_decode_vblank_data(uint64_t data, uint32_t *out_id, uint32_t *out_counter) { - if (out_id) - *out_id = data & 0xffffffffU; - if (out_counter) - *out_counter = (data >> 32) & 0xffffffffU; -} - -static bool grdrm_modes_compatible(const struct drm_mode_modeinfo *a, const struct drm_mode_modeinfo *b) { - assert(a); - assert(b); - - /* Test whether both modes are compatible according to our internal - * assumptions on modes. This comparison is highly dependent on how - * we treat modes in grdrm. If we export mode details, we need to - * make this comparison much stricter. */ - - if (a->hdisplay != b->hdisplay) - return false; - if (a->vdisplay != b->vdisplay) - return false; - if (a->vrefresh != b->vrefresh) - return false; - - return true; -} - -/* - * Objects - */ - -grdrm_object *grdrm_find_object(grdrm_card *card, uint32_t id) { - assert_return(card, NULL); - - return id > 0 ? hashmap_get(card->object_map, UINT32_TO_PTR(id)) : NULL; -} - -int grdrm_object_add(grdrm_object *object) { - int r; - - assert(object); - assert(object->card); - assert(object->id > 0); - assert(IN_SET(object->type, GRDRM_TYPE_CRTC, GRDRM_TYPE_ENCODER, GRDRM_TYPE_CONNECTOR, GRDRM_TYPE_PLANE)); - assert(object->free_fn); - - if (object->index >= 32) - log_debug("grdrm: %s: object index exceeds 32bit masks: type=%u, index=%" PRIu32, - object->card->base.name, object->type, object->index); - - r = hashmap_put(object->card->object_map, UINT32_TO_PTR(object->id), object); - if (r < 0) - return r; - - return 0; -} - -grdrm_object *grdrm_object_free(grdrm_object *object) { - if (!object) - return NULL; - - assert(object->card); - assert(object->id > 0); - assert(IN_SET(object->type, GRDRM_TYPE_CRTC, GRDRM_TYPE_ENCODER, GRDRM_TYPE_CONNECTOR, GRDRM_TYPE_PLANE)); - assert(object->free_fn); - - hashmap_remove_value(object->card->object_map, UINT32_TO_PTR(object->id), object); - - object->free_fn(object); - return NULL; -} - -/* - * Planes - */ - -static void plane_free(grdrm_object *object) { - grdrm_plane *plane = plane_from_object(object); - - free(plane->kern.formats); - free(plane->kern.crtcs); - free(plane); -} - -int grdrm_plane_new(grdrm_plane **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_plane *plane; - int r; - - assert(card); - - plane = new0(grdrm_plane, 1); - if (!plane) - return -ENOMEM; - - object = &plane->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_PLANE, plane_free); - - plane->kern.max_crtcs = 32; - plane->kern.crtcs = new0(uint32_t, plane->kern.max_crtcs); - if (!plane->kern.crtcs) - return -ENOMEM; - - plane->kern.max_formats = 32; - plane->kern.formats = new0(uint32_t, plane->kern.max_formats); - if (!plane->kern.formats) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = plane; - object = NULL; - return 0; -} - -static int grdrm_plane_resync(grdrm_plane *plane) { - grdrm_card *card = plane->object.card; - size_t tries; - int r; - - assert(plane); - - for (tries = 0; tries < GRDRM_MAX_TRIES; ++tries) { - struct drm_mode_get_plane res; - grdrm_object *object; - bool resized = false; - Iterator iter; - - zero(res); - res.plane_id = plane->object.id; - res.format_type_ptr = PTR_TO_UINT64(plane->kern.formats); - res.count_format_types = plane->kern.max_formats; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETPLANE, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: plane %u removed during resync", - card->base.name, plane->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve plane %u: %m", - card->base.name, plane->object.id); - } - - return r; - } - - plane->kern.n_crtcs = 0; - memzero(plane->kern.crtcs, sizeof(uint32_t) * plane->kern.max_crtcs); - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CRTC || object->index >= 32) - continue; - if (!(res.possible_crtcs & (1 << object->index))) - continue; - if (plane->kern.n_crtcs >= 32) { - log_debug("grdrm: %s: possible_crtcs of plane %" PRIu32 " exceeds 32bit mask", - card->base.name, plane->object.id); - continue; - } - - plane->kern.crtcs[plane->kern.n_crtcs++] = object->id; - } - - if (res.count_format_types > plane->kern.max_formats) { - uint32_t max, *t; - - max = ALIGN_POWER2(res.count_format_types); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive plane resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - t = realloc(plane->kern.formats, sizeof(*t) * max); - if (!t) - return -ENOMEM; - - plane->kern.formats = t; - plane->kern.max_formats = max; - resized = true; - } - - if (resized) - continue; - - plane->kern.n_formats = res.count_format_types; - plane->kern.used_crtc = res.crtc_id; - plane->kern.used_fb = res.fb_id; - plane->kern.gamma_size = res.gamma_size; - - break; - } - - if (tries >= GRDRM_MAX_TRIES) { - log_debug("grdrm: %s: plane %u not settled for retrieval", card->base.name, plane->object.id); - return -EFAULT; - } - - return 0; -} - -/* - * Connectors - */ - -static void connector_free(grdrm_object *object) { - grdrm_connector *connector = connector_from_object(object); - - free(connector->kern.prop_values); - free(connector->kern.prop_ids); - free(connector->kern.modes); - free(connector->kern.encoders); - free(connector); -} - -int grdrm_connector_new(grdrm_connector **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_connector *connector; - int r; - - assert(card); - - connector = new0(grdrm_connector, 1); - if (!connector) - return -ENOMEM; - - object = &connector->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_CONNECTOR, connector_free); - - connector->kern.max_encoders = 32; - connector->kern.encoders = new0(uint32_t, connector->kern.max_encoders); - if (!connector->kern.encoders) - return -ENOMEM; - - connector->kern.max_modes = 32; - connector->kern.modes = new0(struct drm_mode_modeinfo, connector->kern.max_modes); - if (!connector->kern.modes) - return -ENOMEM; - - connector->kern.max_props = 32; - connector->kern.prop_ids = new0(uint32_t, connector->kern.max_props); - connector->kern.prop_values = new0(uint64_t, connector->kern.max_props); - if (!connector->kern.prop_ids || !connector->kern.prop_values) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = connector; - object = NULL; - return 0; -} - -static int grdrm_connector_resync(grdrm_connector *connector) { - grdrm_card *card = connector->object.card; - size_t tries; - int r; - - assert(connector); - - for (tries = 0; tries < GRDRM_MAX_TRIES; ++tries) { - struct drm_mode_get_connector res; - bool resized = false; - uint32_t max; - - zero(res); - res.connector_id = connector->object.id; - res.encoders_ptr = PTR_TO_UINT64(connector->kern.encoders); - res.props_ptr = PTR_TO_UINT64(connector->kern.prop_ids); - res.prop_values_ptr = PTR_TO_UINT64(connector->kern.prop_values); - res.count_encoders = connector->kern.max_encoders; - res.count_props = connector->kern.max_props; - - /* The kernel reads modes from the EDID information only if we - * pass count_modes==0. This is a legacy hack for libdrm (which - * called every ioctl twice). Now we have to adopt.. *sigh*. - * If we never received an hotplug event, there's no reason to - * sync modes. EDID reads are heavy, so skip that if not - * required. */ - if (card->hotplug) { - if (tries > 0) { - res.modes_ptr = PTR_TO_UINT64(connector->kern.modes); - res.count_modes = connector->kern.max_modes; - } else { - resized = true; - } - } - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETCONNECTOR, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: connector %u removed during resync", - card->base.name, connector->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve connector %u: %m", - card->base.name, connector->object.id); - } - - return r; - } - - if (res.count_encoders > connector->kern.max_encoders) { - uint32_t *t; - - max = ALIGN_POWER2(res.count_encoders); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive connector resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - t = realloc(connector->kern.encoders, sizeof(*t) * max); - if (!t) - return -ENOMEM; - - connector->kern.encoders = t; - connector->kern.max_encoders = max; - resized = true; - } - - if (res.count_modes > connector->kern.max_modes) { - struct drm_mode_modeinfo *t; - - max = ALIGN_POWER2(res.count_modes); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive connector resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - t = realloc(connector->kern.modes, sizeof(*t) * max); - if (!t) - return -ENOMEM; - - connector->kern.modes = t; - connector->kern.max_modes = max; - resized = true; - } - - if (res.count_props > connector->kern.max_props) { - uint32_t *tids; - uint64_t *tvals; - - max = ALIGN_POWER2(res.count_props); - if (!max || max > UINT16_MAX) { - log_debug("grdrm: %s: excessive connector resource limit: %" PRIu32, card->base.name, max); - return -ERANGE; - } - - tids = realloc(connector->kern.prop_ids, sizeof(*tids) * max); - if (!tids) - return -ENOMEM; - connector->kern.prop_ids = tids; - - tvals = realloc(connector->kern.prop_values, sizeof(*tvals) * max); - if (!tvals) - return -ENOMEM; - connector->kern.prop_values = tvals; - - connector->kern.max_props = max; - resized = true; - } - - if (resized) - continue; - - connector->kern.n_encoders = res.count_encoders; - connector->kern.n_props = res.count_props; - connector->kern.type = res.connector_type; - connector->kern.type_id = res.connector_type_id; - connector->kern.used_encoder = res.encoder_id; - connector->kern.connection = res.connection; - connector->kern.mm_width = res.mm_width; - connector->kern.mm_height = res.mm_height; - connector->kern.subpixel = res.subpixel; - if (res.modes_ptr == PTR_TO_UINT64(connector->kern.modes)) - connector->kern.n_modes = res.count_modes; - - break; - } - - if (tries >= GRDRM_MAX_TRIES) { - log_debug("grdrm: %s: connector %u not settled for retrieval", card->base.name, connector->object.id); - return -EFAULT; - } - - return 0; -} - -/* - * Encoders - */ - -static void encoder_free(grdrm_object *object) { - grdrm_encoder *encoder = encoder_from_object(object); - - free(encoder->kern.clones); - free(encoder->kern.crtcs); - free(encoder); -} - -int grdrm_encoder_new(grdrm_encoder **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_encoder *encoder; - int r; - - assert(card); - - encoder = new0(grdrm_encoder, 1); - if (!encoder) - return -ENOMEM; - - object = &encoder->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_ENCODER, encoder_free); - - encoder->kern.max_crtcs = 32; - encoder->kern.crtcs = new0(uint32_t, encoder->kern.max_crtcs); - if (!encoder->kern.crtcs) - return -ENOMEM; - - encoder->kern.max_clones = 32; - encoder->kern.clones = new0(uint32_t, encoder->kern.max_clones); - if (!encoder->kern.clones) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = encoder; - object = NULL; - return 0; -} - -static int grdrm_encoder_resync(grdrm_encoder *encoder) { - grdrm_card *card = encoder->object.card; - struct drm_mode_get_encoder res; - grdrm_object *object; - Iterator iter; - int r; - - assert(encoder); - - zero(res); - res.encoder_id = encoder->object.id; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETENCODER, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: encoder %u removed during resync", - card->base.name, encoder->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve encoder %u: %m", - card->base.name, encoder->object.id); - } - - return r; - } - - encoder->kern.type = res.encoder_type; - encoder->kern.used_crtc = res.crtc_id; - - encoder->kern.n_crtcs = 0; - memzero(encoder->kern.crtcs, sizeof(uint32_t) * encoder->kern.max_crtcs); - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CRTC || object->index >= 32) - continue; - if (!(res.possible_crtcs & (1 << object->index))) - continue; - if (encoder->kern.n_crtcs >= 32) { - log_debug("grdrm: %s: possible_crtcs exceeds 32bit mask", card->base.name); - continue; - } - - encoder->kern.crtcs[encoder->kern.n_crtcs++] = object->id; - } - - encoder->kern.n_clones = 0; - memzero(encoder->kern.clones, sizeof(uint32_t) * encoder->kern.max_clones); - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_ENCODER || object->index >= 32) - continue; - if (!(res.possible_clones & (1 << object->index))) - continue; - if (encoder->kern.n_clones >= 32) { - log_debug("grdrm: %s: possible_encoders exceeds 32bit mask", card->base.name); - continue; - } - - encoder->kern.clones[encoder->kern.n_clones++] = object->id; - } - - return 0; -} - -/* - * Crtcs - */ - -static void crtc_free(grdrm_object *object) { - grdrm_crtc *crtc = crtc_from_object(object); - - if (crtc->pipe) - grdev_pipe_free(&crtc->pipe->base); - free(crtc->set.connectors); - free(crtc->old.connectors); - free(crtc->kern.used_connectors); - free(crtc); -} - -int grdrm_crtc_new(grdrm_crtc **out, grdrm_card *card, uint32_t id, uint32_t index) { - _cleanup_(grdrm_object_freep) grdrm_object *object = NULL; - grdrm_crtc *crtc; - int r; - - assert(card); - - crtc = new0(grdrm_crtc, 1); - if (!crtc) - return -ENOMEM; - - object = &crtc->object; - *object = GRDRM_OBJECT_INIT(card, id, index, GRDRM_TYPE_CRTC, crtc_free); - - crtc->kern.max_used_connectors = 32; - crtc->kern.used_connectors = new0(uint32_t, crtc->kern.max_used_connectors); - if (!crtc->kern.used_connectors) - return -ENOMEM; - - crtc->old.connectors = new0(uint32_t, crtc->kern.max_used_connectors); - if (!crtc->old.connectors) - return -ENOMEM; - - r = grdrm_object_add(object); - if (r < 0) - return r; - - if (out) - *out = crtc; - object = NULL; - return 0; -} - -static int grdrm_crtc_resync(grdrm_crtc *crtc) { - grdrm_card *card = crtc->object.card; - struct drm_mode_crtc res = { .crtc_id = crtc->object.id }; - int r; - - assert(crtc); - - /* make sure we can cache any combination later */ - if (card->n_connectors > crtc->kern.max_used_connectors) { - uint32_t max, *t; - - max = ALIGN_POWER2(card->n_connectors); - if (!max) - return -ENOMEM; - - t = realloc_multiply(crtc->kern.used_connectors, sizeof(*t), max); - if (!t) - return -ENOMEM; - - crtc->kern.used_connectors = t; - crtc->kern.max_used_connectors = max; - - if (!crtc->old.set) { - crtc->old.connectors = calloc(sizeof(*t), max); - if (!crtc->old.connectors) - return -ENOMEM; - } - } - - /* GETCRTC doesn't return connectors. We have to read all - * encoder-state and deduce the setup ourselves.. */ - crtc->kern.n_used_connectors = 0; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETCRTC, &res); - if (r < 0) { - r = -errno; - if (r == -ENOENT) { - card->async_hotplug = true; - r = 0; - log_debug("grdrm: %s: crtc %u removed during resync", - card->base.name, crtc->object.id); - } else { - log_debug_errno(errno, "grdrm: %s: cannot retrieve crtc %u: %m", - card->base.name, crtc->object.id); - } - - return r; - } - - crtc->kern.used_fb = res.fb_id; - crtc->kern.fb_offset_x = res.x; - crtc->kern.fb_offset_y = res.y; - crtc->kern.gamma_size = res.gamma_size; - crtc->kern.mode_set = res.mode_valid; - crtc->kern.mode = res.mode; - - return 0; -} - -static void grdrm_crtc_assign(grdrm_crtc *crtc, grdrm_connector *connector) { - uint32_t n_connectors; - int r; - - assert(crtc); - assert(!crtc->object.assigned); - assert(!connector || !connector->object.assigned); - - /* always mark both as assigned; even if assignments cannot be set */ - crtc->object.assigned = true; - if (connector) - connector->object.assigned = true; - - /* we will support hw clone mode in the future */ - n_connectors = connector ? 1 : 0; - - /* bail out if configuration is preserved */ - if (crtc->set.n_connectors == n_connectors && - (n_connectors == 0 || crtc->set.connectors[0] == connector->object.id)) - return; - - crtc->applied = false; - crtc->set.n_connectors = 0; - - if (n_connectors > crtc->set.max_connectors) { - uint32_t max, *t; - - max = ALIGN_POWER2(n_connectors); - if (!max) { - r = -ENOMEM; - goto error; - } - - t = realloc(crtc->set.connectors, sizeof(*t) * max); - if (!t) { - r = -ENOMEM; - goto error; - } - - crtc->set.connectors = t; - crtc->set.max_connectors = max; - } - - if (connector) { - struct drm_mode_modeinfo *m, *pref = NULL; - uint32_t i; - - for (i = 0; i < connector->kern.n_modes; ++i) { - m = &connector->kern.modes[i]; - - /* ignore 3D modes by default */ - if (m->flags & DRM_MODE_FLAG_3D_MASK) - continue; - - if (!pref) { - pref = m; - continue; - } - - /* use PREFERRED over non-PREFERRED */ - if ((pref->type & DRM_MODE_TYPE_PREFERRED) && - !(m->type & DRM_MODE_TYPE_PREFERRED)) - continue; - - /* use DRIVER over non-PREFERRED|DRIVER */ - if ((pref->type & DRM_MODE_TYPE_DRIVER) && - !(m->type & (DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED))) - continue; - - /* always prefer higher resolution */ - if (pref->hdisplay > m->hdisplay || - (pref->hdisplay == m->hdisplay && pref->vdisplay > m->vdisplay)) - continue; - - pref = m; - } - - if (pref) { - crtc->set.mode = *pref; - crtc->set.n_connectors = 1; - crtc->set.connectors[0] = connector->object.id; - log_debug("grdrm: %s: assigned connector %" PRIu32 " to crtc %" PRIu32 " with mode %s", - crtc->object.card->base.name, connector->object.id, crtc->object.id, pref->name); - } else { - log_debug("grdrm: %s: connector %" PRIu32 " to be assigned but has no valid mode", - crtc->object.card->base.name, connector->object.id); - } - } - - return; - -error: - log_debug("grdrm: %s: cannot assign crtc %" PRIu32 ": %s", - crtc->object.card->base.name, crtc->object.id, strerror(-r)); -} - -static void grdrm_crtc_expose(grdrm_crtc *crtc) { - grdrm_pipe *pipe; - grdrm_fb *fb; - size_t i; - int r; - - assert(crtc); - assert(crtc->object.assigned); - - if (crtc->set.n_connectors < 1) { - if (crtc->pipe) - grdev_pipe_free(&crtc->pipe->base); - crtc->pipe = NULL; - return; - } - - pipe = crtc->pipe; - if (pipe) { - if (pipe->base.width != crtc->set.mode.hdisplay || - pipe->base.height != crtc->set.mode.vdisplay || - pipe->base.vrefresh != crtc->set.mode.vrefresh) { - grdev_pipe_free(&pipe->base); - crtc->pipe = NULL; - pipe = NULL; - } - } - - if (crtc->pipe) { - pipe->base.front = NULL; - pipe->base.back = NULL; - for (i = 0; i < pipe->base.max_fbs; ++i) { - fb = fb_from_base(pipe->base.fbs[i]); - if (fb->id == crtc->kern.used_fb) - pipe->base.front = &fb->base; - else if (!fb->flipid) - pipe->base.back = &fb->base; - } - } else { - r = grdrm_pipe_new(&pipe, crtc, &crtc->set.mode, 2); - if (r < 0) { - log_debug("grdrm: %s: cannot create pipe for crtc %" PRIu32 ": %s", - crtc->object.card->base.name, crtc->object.id, strerror(-r)); - return; - } - - for (i = 0; i < pipe->base.max_fbs; ++i) { - r = grdrm_fb_new(&fb, crtc->object.card, &crtc->set.mode); - if (r < 0) { - log_debug("grdrm: %s: cannot allocate framebuffer for crtc %" PRIu32 ": %s", - crtc->object.card->base.name, crtc->object.id, strerror(-r)); - grdev_pipe_free(&pipe->base); - return; - } - - pipe->base.fbs[i] = &fb->base; - } - - pipe->base.front = NULL; - pipe->base.back = pipe->base.fbs[0]; - crtc->pipe = pipe; - } - - grdev_pipe_ready(&crtc->pipe->base, true); -} - -static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb *basefb) { - struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - grdrm_pipe *pipe = crtc->pipe; - grdrm_fb *fb; - int r; - - assert(crtc); - assert(basefb); - assert(pipe); - - fb = fb_from_base(basefb); - - set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->set.connectors); - set_crtc.count_connectors = crtc->set.n_connectors; - set_crtc.fb_id = fb->id; - set_crtc.x = 0; - set_crtc.y = 0; - set_crtc.mode_valid = 1; - set_crtc.mode = crtc->set.mode; - - r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot set crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - grdrm_card_async(card, r); - return; - } - - if (!crtc->applied) { - log_debug("grdrm: %s: crtc %" PRIu32 " applied via deep modeset", - card->base.name, crtc->object.id); - crtc->applied = true; - } - - pipe->base.back = NULL; - pipe->base.front = &fb->base; - fb->flipid = 0; - ++pipe->counter; - pipe->base.flipping = false; - pipe->base.flip = false; - - /* We cannot schedule dummy page-flips on pipes, hence, the - * application would have to schedule their own frame-timers. - * To avoid duplicating that everywhere, we schedule our own - * timer and raise a fake FRAME event when it fires. */ - grdev_pipe_schedule(&pipe->base, 1); -} - -static int grdrm_crtc_commit_flip(grdrm_crtc *crtc, grdev_fb *basefb) { - struct drm_mode_crtc_page_flip page_flip = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - grdrm_pipe *pipe = crtc->pipe; - grdrm_fb *fb; - uint32_t cnt; - int r; - - assert(crtc); - assert(basefb); - assert(pipe); - - if (!crtc->applied) { - if (!grdrm_modes_compatible(&crtc->kern.mode, &crtc->set.mode)) - return 0; - - /* TODO: Theoretically, we should be able to page-flip to our - * framebuffer here. We didn't perform any deep modeset, but the - * DRM driver is really supposed to reject our page-flip in case - * the FB is not compatible. We then properly fall back to a - * deep modeset. - * As it turns out, drivers don't to this. Therefore, we need to - * perform a full modeset on enter now. We might avoid this in - * the future with fixed drivers.. */ - - return 0; - } - - fb = fb_from_base(basefb); - - cnt = ++pipe->counter ? : ++pipe->counter; - page_flip.fb_id = fb->id; - page_flip.flags = DRM_MODE_PAGE_FLIP_EVENT; - page_flip.user_data = grdrm_encode_vblank_data(crtc->object.id, cnt); - - r = ioctl(card->fd, DRM_IOCTL_MODE_PAGE_FLIP, &page_flip); - if (r < 0) { - r = -errno; - /* Avoid excessive logging on EINVAL; it is currently not - * possible to see whether cards support page-flipping, so - * avoid logging on each frame. */ - if (r != -EINVAL) - log_debug_errno(errno, "grdrm: %s: cannot schedule page-flip on crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - if (grdrm_card_async(card, r)) - return r; - - return 0; - } - - if (!crtc->applied) { - log_debug("grdrm: %s: crtc %" PRIu32 " applied via page flip", - card->base.name, crtc->object.id); - crtc->applied = true; - } - - pipe->base.flipping = true; - pipe->base.flip = false; - pipe->counter = cnt; - fb->flipid = cnt; - pipe->base.back = NULL; - - /* Raise fake FRAME event if it takes longer than 2 - * frames to receive the pageflip event. We assume the - * queue ran over or some other error happened. */ - grdev_pipe_schedule(&pipe->base, 2); - - return 1; -} - -static void grdrm_crtc_commit(grdrm_crtc *crtc) { - struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - grdrm_pipe *pipe; - grdev_fb *fb; - int r; - - assert(crtc); - assert(crtc->object.assigned); - - pipe = crtc->pipe; - if (!pipe) { - /* If a crtc is not assigned any connector, we want any - * previous setup to be cleared, so make sure the CRTC is - * disabled. Otherwise, there might be content on the CRTC - * while we run, which is not what we want. - * If you want to avoid modesets on specific CRTCs, you should - * still keep their assignment, but never enable the resulting - * pipe. This way, we wouldn't touch it at all. */ - if (!crtc->applied) { - crtc->applied = true; - r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot shutdown crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - grdrm_card_async(card, r); - return; - } - - log_debug("grdrm: %s: crtc %" PRIu32 " applied via shutdown", - card->base.name, crtc->object.id); - } - - return; - } - - /* we always fully ignore disabled pipes */ - if (!pipe->base.enabled) - return; - - assert(crtc->set.n_connectors > 0); - - if (pipe->base.flip) - fb = pipe->base.back; - else if (!crtc->applied) - fb = pipe->base.front; - else - return; - - if (!fb) - return; - - r = grdrm_crtc_commit_flip(crtc, fb); - if (r == 0) { - /* in case we couldn't page-flip, perform deep modeset */ - grdrm_crtc_commit_deep(crtc, fb); - } -} - -static void grdrm_crtc_restore(grdrm_crtc *crtc) { - struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id }; - grdrm_card *card = crtc->object.card; - int r; - - if (!crtc->old.set) - return; - - set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->old.connectors); - set_crtc.count_connectors = crtc->old.n_connectors; - set_crtc.fb_id = crtc->old.fb; - set_crtc.x = crtc->old.fb_x; - set_crtc.y = crtc->old.fb_y; - set_crtc.gamma_size = crtc->old.gamma; - set_crtc.mode_valid = crtc->old.mode_set; - set_crtc.mode = crtc->old.mode; - - r = ioctl(card->fd, DRM_IOCTL_MODE_SETCRTC, &set_crtc); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot restore crtc %" PRIu32 ": %m", - card->base.name, crtc->object.id); - - grdrm_card_async(card, r); - return; - } - - if (crtc->pipe) { - ++crtc->pipe->counter; - crtc->pipe->base.front = NULL; - crtc->pipe->base.flipping = false; - } - - log_debug("grdrm: %s: crtc %" PRIu32 " restored", card->base.name, crtc->object.id); -} - -static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct drm_event_vblank *event) { - bool flipped = false; - grdrm_pipe *pipe; - size_t i; - - assert(crtc); - assert(event); - - pipe = crtc->pipe; - if (!pipe) - return; - - /* We got a page-flip event. To be safe, we reset all FBs on the same - * pipe that have smaller flipids than the flip we got as we know they - * are executed in order. We need to do this to guarantee - * queue-overflows or other missed events don't cause starvation. - * Furthermore, if we find the exact FB this event is for, *and* this - * is the most recent event, we mark it as front FB and raise a - * frame event. */ - - for (i = 0; i < pipe->base.max_fbs; ++i) { - grdrm_fb *fb; - - if (!pipe->base.fbs[i]) - continue; - - fb = fb_from_base(pipe->base.fbs[i]); - if (counter != 0 && counter == pipe->counter && fb->flipid == counter) { - pipe->base.front = &fb->base; - fb->flipid = 0; - flipped = true; - } else if (counter - fb->flipid < UINT16_MAX) { - fb->flipid = 0; - } - } - - if (flipped) { - crtc->pipe->base.flipping = false; - grdev_pipe_frame(&pipe->base); - } -} - -/* - * Framebuffers - */ - -static int grdrm_fb_new(grdrm_fb **out, grdrm_card *card, const struct drm_mode_modeinfo *mode) { - _cleanup_(grdrm_fb_freep) grdrm_fb *fb = NULL; - struct drm_mode_create_dumb create_dumb = { }; - struct drm_mode_map_dumb map_dumb = { }; - struct drm_mode_fb_cmd2 add_fb = { }; - unsigned int i; - int r; - - assert_return(out, -EINVAL); - assert_return(card, -EINVAL); - - fb = new0(grdrm_fb, 1); - if (!fb) - return -ENOMEM; - - /* TODO: we should choose a compatible format of the previous CRTC - * setting to allow page-flip to it. Only choose fallback if the - * previous setting was crap (non xrgb32'ish). */ - - fb->card = card; - fb->base.format = DRM_FORMAT_XRGB8888; - fb->base.width = mode->hdisplay; - fb->base.height = mode->vdisplay; - - for (i = 0; i < ELEMENTSOF(fb->base.maps); ++i) - fb->base.maps[i] = MAP_FAILED; - - create_dumb.width = fb->base.width; - create_dumb.height = fb->base.height; - create_dumb.bpp = 32; - - r = ioctl(card->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); - if (r < 0) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot create dumb buffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - fb->handles[0] = create_dumb.handle; - fb->base.strides[0] = create_dumb.pitch; - fb->sizes[0] = create_dumb.size; - - map_dumb.handle = fb->handles[0]; - - r = ioctl(card->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); - if (r < 0) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot map dumb buffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - fb->base.maps[0] = mmap(0, fb->sizes[0], PROT_WRITE, MAP_SHARED, card->fd, map_dumb.offset); - if (fb->base.maps[0] == MAP_FAILED) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot memory-map dumb buffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - memzero(fb->base.maps[0], fb->sizes[0]); - - add_fb.width = fb->base.width; - add_fb.height = fb->base.height; - add_fb.pixel_format = fb->base.format; - add_fb.flags = 0; - memcpy(add_fb.handles, fb->handles, sizeof(fb->handles)); - memcpy(add_fb.pitches, fb->base.strides, sizeof(fb->base.strides)); - memcpy(add_fb.offsets, fb->offsets, sizeof(fb->offsets)); - - r = ioctl(card->fd, DRM_IOCTL_MODE_ADDFB2, &add_fb); - if (r < 0) { - r = negative_errno(); - log_debug_errno(errno, "grdrm: %s: cannot add framebuffer %" PRIu32 "x%" PRIu32": %m", - card->base.name, fb->base.width, fb->base.height); - return r; - } - - fb->id = add_fb.fb_id; - - *out = fb; - fb = NULL; - return 0; -} - -grdrm_fb *grdrm_fb_free(grdrm_fb *fb) { - unsigned int i; - int r; - - if (!fb) - return NULL; - - assert(fb->card); - - if (fb->base.free_fn) - fb->base.free_fn(fb->base.data.ptr); - - if (fb->id > 0 && fb->card->fd >= 0) { - r = ioctl(fb->card->fd, DRM_IOCTL_MODE_RMFB, fb->id); - if (r < 0) - log_debug_errno(errno, "grdrm: %s: cannot delete framebuffer %" PRIu32 ": %m", - fb->card->base.name, fb->id); - } - - for (i = 0; i < ELEMENTSOF(fb->handles); ++i) { - struct drm_mode_destroy_dumb destroy_dumb = { }; - - if (fb->base.maps[i] != MAP_FAILED) - munmap(fb->base.maps[i], fb->sizes[i]); - - if (fb->handles[i] > 0 && fb->card->fd >= 0) { - destroy_dumb.handle = fb->handles[i]; - r = ioctl(fb->card->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); - if (r < 0) - log_debug_errno(errno, "grdrm: %s: cannot destroy dumb-buffer %" PRIu32 ": %m", - fb->card->base.name, fb->handles[i]); - } - } - - free(fb); - - return NULL; -} - -/* - * Pipes - */ - -static void grdrm_pipe_name(char *out, grdrm_crtc *crtc) { - /* @out must be at least of size GRDRM_PIPE_NAME_MAX */ - sprintf(out, "%s/%" PRIu32, crtc->object.card->base.name, crtc->object.id); -} - -static int grdrm_pipe_new(grdrm_pipe **out, grdrm_crtc *crtc, struct drm_mode_modeinfo *mode, size_t n_fbs) { - _cleanup_(grdev_pipe_freep) grdev_pipe *basepipe = NULL; - grdrm_card *card = crtc->object.card; - char name[GRDRM_PIPE_NAME_MAX]; - grdrm_pipe *pipe; - int r; - - assert_return(crtc, -EINVAL); - assert_return(grdev_is_drm_card(&card->base), -EINVAL); - - pipe = new0(grdrm_pipe, 1); - if (!pipe) - return -ENOMEM; - - basepipe = &pipe->base; - pipe->base = GRDEV_PIPE_INIT(&grdrm_pipe_vtable, &card->base); - pipe->crtc = crtc; - pipe->base.width = mode->hdisplay; - pipe->base.height = mode->vdisplay; - pipe->base.vrefresh = mode->vrefresh ? : 25; - - grdrm_pipe_name(name, crtc); - r = grdev_pipe_add(&pipe->base, name, n_fbs); - if (r < 0) - return r; - - if (out) - *out = pipe; - basepipe = NULL; - return 0; -} - -static void grdrm_pipe_free(grdev_pipe *basepipe) { - grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe); - size_t i; - - assert(pipe->crtc); - - for (i = 0; i < pipe->base.max_fbs; ++i) - if (pipe->base.fbs[i]) - grdrm_fb_free(fb_from_base(pipe->base.fbs[i])); - - free(pipe); -} - -static grdev_fb *grdrm_pipe_target(grdev_pipe *basepipe) { - grdrm_fb *fb; - size_t i; - - if (!basepipe->back) { - for (i = 0; i < basepipe->max_fbs; ++i) { - if (!basepipe->fbs[i]) - continue; - - fb = fb_from_base(basepipe->fbs[i]); - if (&fb->base == basepipe->front) - continue; - if (basepipe->flipping && fb->flipid) - continue; - - basepipe->back = &fb->base; - break; - } - } - - return basepipe->back; -} - -static void grdrm_pipe_enable(grdev_pipe *basepipe) { - grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe); - - pipe->crtc->applied = false; -} - -static void grdrm_pipe_disable(grdev_pipe *basepipe) { - grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe); - - pipe->crtc->applied = false; -} - -static const grdev_pipe_vtable grdrm_pipe_vtable = { - .free = grdrm_pipe_free, - .target = grdrm_pipe_target, - .enable = grdrm_pipe_enable, - .disable = grdrm_pipe_disable, -}; - -/* - * Cards - */ - -static void grdrm_name(char *out, dev_t devnum) { - /* @out must be at least of size GRDRM_CARD_NAME_MAX */ - sprintf(out, "drm/%u:%u", major(devnum), minor(devnum)); -} - -static void grdrm_card_print(grdrm_card *card) { - grdrm_object *object; - grdrm_crtc *crtc; - grdrm_encoder *encoder; - grdrm_connector *connector; - grdrm_plane *plane; - Iterator iter; - uint32_t i; - char *p, *buf; - - log_debug("grdrm: %s: state dump", card->base.name); - - log_debug(" crtcs:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - - if (crtc->kern.mode_set) - log_debug(" mode: %dx%d", crtc->kern.mode.hdisplay, crtc->kern.mode.vdisplay); - else - log_debug(" mode: "); - } - - log_debug(" encoders:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_ENCODER) - continue; - - encoder = encoder_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - - if (encoder->kern.used_crtc) - log_debug(" crtc: %u", encoder->kern.used_crtc); - else - log_debug(" crtc: "); - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * encoder->kern.n_crtcs + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < encoder->kern.n_crtcs; ++i) - p += sprintf(p, " %" PRIu32, encoder->kern.crtcs[i]); - - log_debug(" possible crtcs:%s", buf); - free(buf); - } - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * encoder->kern.n_clones + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < encoder->kern.n_clones; ++i) - p += sprintf(p, " %" PRIu32, encoder->kern.clones[i]); - - log_debug(" possible clones:%s", buf); - free(buf); - } - } - - log_debug(" connectors:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_CONNECTOR) - continue; - - connector = connector_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - log_debug(" type: %" PRIu32 "-%" PRIu32 " connection: %" PRIu32 " subpixel: %" PRIu32 " extents: %" PRIu32 "x%" PRIu32, - connector->kern.type, connector->kern.type_id, connector->kern.connection, connector->kern.subpixel, - connector->kern.mm_width, connector->kern.mm_height); - - if (connector->kern.used_encoder) - log_debug(" encoder: %" PRIu32, connector->kern.used_encoder); - else - log_debug(" encoder: "); - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * connector->kern.n_encoders + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < connector->kern.n_encoders; ++i) - p += sprintf(p, " %" PRIu32, connector->kern.encoders[i]); - - log_debug(" possible encoders:%s", buf); - free(buf); - } - - for (i = 0; i < connector->kern.n_modes; ++i) { - struct drm_mode_modeinfo *mode = &connector->kern.modes[i]; - log_debug(" mode: %" PRIu32 "x%" PRIu32, mode->hdisplay, mode->vdisplay); - } - } - - log_debug(" planes:"); - HASHMAP_FOREACH(object, card->object_map, iter) { - if (object->type != GRDRM_TYPE_PLANE) - continue; - - plane = plane_from_object(object); - log_debug(" (id: %u index: %d)", object->id, object->index); - log_debug(" gamma-size: %" PRIu32, plane->kern.gamma_size); - - if (plane->kern.used_crtc) - log_debug(" crtc: %" PRIu32, plane->kern.used_crtc); - else - log_debug(" crtc: "); - - buf = malloc((DECIMAL_STR_MAX(uint32_t) + 1) * plane->kern.n_crtcs + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < plane->kern.n_crtcs; ++i) - p += sprintf(p, " %" PRIu32, plane->kern.crtcs[i]); - - log_debug(" possible crtcs:%s", buf); - free(buf); - } - - buf = malloc((DECIMAL_STR_MAX(unsigned int) + 3) * plane->kern.n_formats + 1); - if (buf) { - buf[0] = 0; - p = buf; - - for (i = 0; i < plane->kern.n_formats; ++i) - p += sprintf(p, " 0x%x", (unsigned int)plane->kern.formats[i]); - - log_debug(" possible formats:%s", buf); - free(buf); - } - } -} - -static int grdrm_card_resync(grdrm_card *card) { - _cleanup_free_ uint32_t *crtc_ids = NULL, *encoder_ids = NULL, *connector_ids = NULL, *plane_ids = NULL; - uint32_t allocated = 0; - grdrm_object *object; - Iterator iter; - size_t tries; - int r; - - assert(card); - - card->async_hotplug = false; - allocated = 0; - - /* mark existing objects for possible removal */ - HASHMAP_FOREACH(object, card->object_map, iter) - object->present = false; - - for (tries = 0; tries < GRDRM_MAX_TRIES; ++tries) { - struct drm_mode_get_plane_res pres; - struct drm_mode_card_res res; - uint32_t i, max; - - if (allocated < card->max_ids) { - free(crtc_ids); - free(encoder_ids); - free(connector_ids); - free(plane_ids); - crtc_ids = new0(uint32_t, card->max_ids); - encoder_ids = new0(uint32_t, card->max_ids); - connector_ids = new0(uint32_t, card->max_ids); - plane_ids = new0(uint32_t, card->max_ids); - - if (!crtc_ids || !encoder_ids || !connector_ids || !plane_ids) - return -ENOMEM; - - allocated = card->max_ids; - } - - zero(res); - res.crtc_id_ptr = PTR_TO_UINT64(crtc_ids); - res.connector_id_ptr = PTR_TO_UINT64(connector_ids); - res.encoder_id_ptr = PTR_TO_UINT64(encoder_ids); - res.count_crtcs = allocated; - res.count_encoders = allocated; - res.count_connectors = allocated; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETRESOURCES, &res); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot retrieve drm resources: %m", - card->base.name); - return r; - } - - zero(pres); - pres.plane_id_ptr = PTR_TO_UINT64(plane_ids); - pres.count_planes = allocated; - - r = ioctl(card->fd, DRM_IOCTL_MODE_GETPLANERESOURCES, &pres); - if (r < 0) { - r = -errno; - log_debug_errno(errno, "grdrm: %s: cannot retrieve drm plane-resources: %m", - card->base.name); - return r; - } - - max = MAX(MAX(res.count_crtcs, res.count_encoders), - MAX(res.count_connectors, pres.count_planes)); - if (max > allocated) { - uint32_t n; - - n = ALIGN_POWER2(max); - if (!n || n > UINT16_MAX) { - log_debug("grdrm: %s: excessive DRM resource limit: %" PRIu32, - card->base.name, max); - return -ERANGE; - } - - /* retry with resized buffers */ - card->max_ids = n; - continue; - } - - /* mark available objects as present */ - - for (i = 0; i < res.count_crtcs; ++i) { - object = grdrm_find_object(card, crtc_ids[i]); - if (object && object->type == GRDRM_TYPE_CRTC) { - object->present = true; - object->index = i; - crtc_ids[i] = 0; - } - } - - for (i = 0; i < res.count_encoders; ++i) { - object = grdrm_find_object(card, encoder_ids[i]); - if (object && object->type == GRDRM_TYPE_ENCODER) { - object->present = true; - object->index = i; - encoder_ids[i] = 0; - } - } - - for (i = 0; i < res.count_connectors; ++i) { - object = grdrm_find_object(card, connector_ids[i]); - if (object && object->type == GRDRM_TYPE_CONNECTOR) { - object->present = true; - object->index = i; - connector_ids[i] = 0; - } - } - - for (i = 0; i < pres.count_planes; ++i) { - object = grdrm_find_object(card, plane_ids[i]); - if (object && object->type == GRDRM_TYPE_PLANE) { - object->present = true; - object->index = i; - plane_ids[i] = 0; - } - } - - /* drop removed objects */ - - HASHMAP_FOREACH(object, card->object_map, iter) - if (!object->present) - grdrm_object_free(object); - - /* add new objects */ - - card->n_crtcs = res.count_crtcs; - for (i = 0; i < res.count_crtcs; ++i) { - if (crtc_ids[i] < 1) - continue; - - r = grdrm_crtc_new(NULL, card, crtc_ids[i], i); - if (r < 0) - return r; - } - - card->n_encoders = res.count_encoders; - for (i = 0; i < res.count_encoders; ++i) { - if (encoder_ids[i] < 1) - continue; - - r = grdrm_encoder_new(NULL, card, encoder_ids[i], i); - if (r < 0) - return r; - } - - card->n_connectors = res.count_connectors; - for (i = 0; i < res.count_connectors; ++i) { - if (connector_ids[i] < 1) - continue; - - r = grdrm_connector_new(NULL, card, connector_ids[i], i); - if (r < 0) - return r; - } - - card->n_planes = pres.count_planes; - for (i = 0; i < pres.count_planes; ++i) { - if (plane_ids[i] < 1) - continue; - - r = grdrm_plane_new(NULL, card, plane_ids[i], i); - if (r < 0) - return r; - } - - /* re-sync objects after object_map is synced */ - - HASHMAP_FOREACH(object, card->object_map, iter) { - switch (object->type) { - case GRDRM_TYPE_CRTC: - r = grdrm_crtc_resync(crtc_from_object(object)); - break; - case GRDRM_TYPE_ENCODER: - r = grdrm_encoder_resync(encoder_from_object(object)); - break; - case GRDRM_TYPE_CONNECTOR: - r = grdrm_connector_resync(connector_from_object(object)); - break; - case GRDRM_TYPE_PLANE: - r = grdrm_plane_resync(plane_from_object(object)); - break; - default: - assert_not_reached("grdrm: invalid object type"); - r = 0; - } - - if (r < 0) - return r; - - if (card->async_hotplug) - break; - } - - /* if modeset objects change during sync, start over */ - if (card->async_hotplug) { - card->async_hotplug = false; - continue; - } - - /* cache crtc/connector relationship */ - HASHMAP_FOREACH(object, card->object_map, iter) { - grdrm_connector *connector; - grdrm_encoder *encoder; - grdrm_crtc *crtc; - - if (object->type != GRDRM_TYPE_CONNECTOR) - continue; - - connector = connector_from_object(object); - if (connector->kern.connection != 1 || connector->kern.used_encoder < 1) - continue; - - object = grdrm_find_object(card, connector->kern.used_encoder); - if (!object || object->type != GRDRM_TYPE_ENCODER) - continue; - - encoder = encoder_from_object(object); - if (encoder->kern.used_crtc < 1) - continue; - - object = grdrm_find_object(card, encoder->kern.used_crtc); - if (!object || object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - assert(crtc->kern.n_used_connectors < crtc->kern.max_used_connectors); - crtc->kern.used_connectors[crtc->kern.n_used_connectors++] = connector->object.id; - } - - /* cache old crtc settings for later restore */ - HASHMAP_FOREACH(object, card->object_map, iter) { - grdrm_crtc *crtc; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - - /* Save data if it is the first time we refresh the CRTC. This data can - * be used optionally to restore any previous configuration. For - * instance, it allows us to restore VT configurations after we close - * our session again. */ - if (!crtc->old.set) { - crtc->old.fb = crtc->kern.used_fb; - crtc->old.fb_x = crtc->kern.fb_offset_x; - crtc->old.fb_y = crtc->kern.fb_offset_y; - crtc->old.gamma = crtc->kern.gamma_size; - crtc->old.n_connectors = crtc->kern.n_used_connectors; - if (crtc->old.n_connectors) - memcpy(crtc->old.connectors, crtc->kern.used_connectors, sizeof(uint32_t) * crtc->old.n_connectors); - crtc->old.mode_set = crtc->kern.mode_set; - crtc->old.mode = crtc->kern.mode; - crtc->old.set = true; - } - } - - /* everything synced */ - break; - } - - if (tries >= GRDRM_MAX_TRIES) { - /* - * Ugh! We were unable to sync the DRM card state due to heavy - * hotplugging. This should never happen, so print a debug - * message and bail out. The next uevent will trigger - * this again. - */ - - log_debug("grdrm: %s: hotplug-storm when syncing card", card->base.name); - return -EFAULT; - } - - return 0; -} - -static bool card_configure_crtc(grdrm_crtc *crtc, grdrm_connector *connector) { - grdrm_card *card = crtc->object.card; - grdrm_encoder *encoder; - grdrm_object *object; - uint32_t i, j; - - if (crtc->object.assigned || connector->object.assigned) - return false; - if (connector->kern.connection != 1) - return false; - - for (i = 0; i < connector->kern.n_encoders; ++i) { - object = grdrm_find_object(card, connector->kern.encoders[i]); - if (!object || object->type != GRDRM_TYPE_ENCODER) - continue; - - encoder = encoder_from_object(object); - for (j = 0; j < encoder->kern.n_crtcs; ++j) { - if (encoder->kern.crtcs[j] == crtc->object.id) { - grdrm_crtc_assign(crtc, connector); - return true; - } - } - } - - return false; -} - -static void grdrm_card_configure(grdrm_card *card) { - /* - * Modeset Configuration - * This is where we update our modeset configuration and assign - * connectors to CRTCs. This means, each connector that we want to - * enable needs a CRTC, disabled (or unavailable) connectors are left - * alone in the dark. Once all CRTCs are assigned, the remaining CRTCs - * are disabled. - * Sounds trivial, but there're several caveats: - * - * * Multiple connectors can be driven by the same CRTC. This is - * known as 'hardware clone mode'. Advantage over software clone - * mode is that only a single CRTC is needed to drive multiple - * displays. However, few hardware supports this and it's a huge - * headache to configure on dynamic demands. Therefore, we only - * support it if configured statically beforehand. - * - * * CRTCs are not created equal. Some might be much more powerful - * than others, including more advanced plane support. So far, our - * CRTC selection is random. You need to supply static - * configuration if you want special setups. So far, there is no - * proper way to do advanced CRTC selection on dynamic demands. It - * is not really clear which demands require what CRTC, so, like - * everyone else, we do random CRTC selection unless explicitly - * states otherwise. - * - * * Each Connector has a list of possible encoders that can drive - * it, and each encoder has a list of possible CRTCs. If this graph - * is a tree, assignment is trivial. However, if not, we cannot - * reliably decide on configurations beforehand. The encoder is - * always selected by the kernel, so we have to actually set a mode - * to know which encoder is used. There is no way to ask the kernel - * whether a given configuration is possible. This will change with - * atomic-modesetting, but until then, we keep our configurations - * simple and assume they work all just fine. If one fails - * unexpectedly, we print a warning and disable it. - * - * Configuring a card consists of several steps: - * - * 1) First of all, we apply any user-configuration. If a user wants - * a fixed configuration, we apply it and preserve it. - * So far, we don't support user configuration files, so this step - * is skipped. - * - * 2) Secondly, we need to apply any quirks from hwdb. Some hardware - * might only support limited configurations or require special - * CRTC/Connector mappings. We read this from hwdb and apply it, if - * present. - * So far, we don't support this as there is no known quirk, so - * this step is skipped. - * - * 3) As deep modesets are expensive, we try to avoid them if - * possible. Therefore, we read the current configuration from the - * kernel and try to preserve it, if compatible with our demands. - * If not, we break it and reassign it in a following step. - * - * 4) The main step involves configuring all remaining objects. By - * default, all available connectors are enabled, except for those - * disabled by user-configuration. We lookup a suitable CRTC for - * each connector and assign them. As there might be more - * connectors than CRTCs, we apply some ordering so users can - * select which connectors are more important right now. - * So far, we only apply the default ordering, more might be added - * in the future. - */ - - grdrm_object *object; - grdrm_crtc *crtc; - Iterator i, j; - - /* clear assignments */ - HASHMAP_FOREACH(object, card->object_map, i) - object->assigned = false; - - /* preserve existing configurations */ - HASHMAP_FOREACH(object, card->object_map, i) { - if (object->type != GRDRM_TYPE_CRTC || object->assigned) - continue; - - crtc = crtc_from_object(object); - - if (crtc->applied) { - /* If our mode is set, preserve it. If no connector is - * set, modeset either failed or the pipe is unused. In - * both cases, leave it alone. It might be tried again - * below in case there're remaining connectors. - * Otherwise, try restoring the assignments. If they - * are no longer valid, leave the pipe untouched. */ - - if (crtc->set.n_connectors < 1) - continue; - - assert(crtc->set.n_connectors == 1); - - object = grdrm_find_object(card, crtc->set.connectors[0]); - if (!object || object->type != GRDRM_TYPE_CONNECTOR) - continue; - - card_configure_crtc(crtc, connector_from_object(object)); - } else if (crtc->kern.mode_set && crtc->kern.n_used_connectors != 1) { - /* If our mode is not set on the pipe, we know the kern - * information is valid. Try keeping it. If it's not - * possible, leave the pipe untouched for later - * assignements. */ - - object = grdrm_find_object(card, crtc->kern.used_connectors[0]); - if (!object || object->type != GRDRM_TYPE_CONNECTOR) - continue; - - card_configure_crtc(crtc, connector_from_object(object)); - } - } - - /* assign remaining objects */ - HASHMAP_FOREACH(object, card->object_map, i) { - if (object->type != GRDRM_TYPE_CRTC || object->assigned) - continue; - - crtc = crtc_from_object(object); - - HASHMAP_FOREACH(object, card->object_map, j) { - if (object->type != GRDRM_TYPE_CONNECTOR) - continue; - - if (card_configure_crtc(crtc, connector_from_object(object))) - break; - } - - if (!crtc->object.assigned) - grdrm_crtc_assign(crtc, NULL); - } - - /* expose configuration */ - HASHMAP_FOREACH(object, card->object_map, i) { - if (object->type != GRDRM_TYPE_CRTC) - continue; - - grdrm_crtc_expose(crtc_from_object(object)); - } -} - -static void grdrm_card_hotplug(grdrm_card *card) { - int r; - - assert(card); - - if (!card->running) - return; - - log_debug("grdrm: %s/%s: reconfigure card", card->base.session->name, card->base.name); - - card->ready = false; - r = grdrm_card_resync(card); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot re-sync card: %m", - card->base.session->name, card->base.name); - return; - } - - grdev_session_pin(card->base.session); - - /* debug statement to print card information */ - if (0) - grdrm_card_print(card); - - grdrm_card_configure(card); - card->ready = true; - card->hotplug = false; - - grdev_session_unpin(card->base.session); -} - -static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - grdrm_card *card = userdata; - struct drm_event_vblank *vblank; - struct drm_event *event; - uint32_t id, counter; - grdrm_object *object; - char buf[4096]; - size_t len; - ssize_t l; - - if (revents & (EPOLLHUP | EPOLLERR)) { - /* Immediately close device on HUP; no need to flush pending - * data.. there're no events we care about here. */ - log_debug("grdrm: %s/%s: HUP", card->base.session->name, card->base.name); - grdrm_card_close(card); - return 0; - } - - if (revents & (EPOLLIN)) { - l = read(card->fd, buf, sizeof(buf)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - log_debug_errno(errno, "grdrm: %s/%s: read error: %m", - card->base.session->name, card->base.name); - grdrm_card_close(card); - return 0; - } - - for (len = l; len > 0; len -= event->length) { - event = (void*)buf; - - if (len < sizeof(*event) || len < event->length) { - log_debug("grdrm: %s/%s: truncated event", - card->base.session->name, card->base.name); - break; - } - - switch (event->type) { - case DRM_EVENT_FLIP_COMPLETE: - vblank = (void*)event; - if (event->length < sizeof(*vblank)) { - log_debug("grdrm: %s/%s: truncated vblank event", - card->base.session->name, card->base.name); - break; - } - - grdrm_decode_vblank_data(vblank->user_data, &id, &counter); - object = grdrm_find_object(card, id); - if (!object || object->type != GRDRM_TYPE_CRTC) - break; - - grdrm_crtc_flip_complete(crtc_from_object(object), counter, vblank); - break; - } - } - } - - return 0; -} - -static int grdrm_card_add(grdrm_card *card, const char *name) { - assert(card); - assert(card->fd < 0); - - card->object_map = hashmap_new(&trivial_hash_ops); - if (!card->object_map) - return -ENOMEM; - - return grdev_card_add(&card->base, name); -} - -static void grdrm_card_destroy(grdrm_card *card) { - assert(card); - assert(!card->running); - assert(card->fd < 0); - assert(hashmap_size(card->object_map) == 0); - - hashmap_free(card->object_map); -} - -static void grdrm_card_commit(grdev_card *basecard) { - grdrm_card *card = grdrm_card_from_base(basecard); - grdrm_object *object; - Iterator iter; - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (!card->ready) - break; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - grdrm_crtc_commit(crtc_from_object(object)); - } -} - -static void grdrm_card_restore(grdev_card *basecard) { - grdrm_card *card = grdrm_card_from_base(basecard); - grdrm_object *object; - Iterator iter; - - HASHMAP_FOREACH(object, card->object_map, iter) { - if (!card->ready) - break; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - grdrm_crtc_restore(crtc_from_object(object)); - } -} - -static void grdrm_card_enable(grdrm_card *card) { - assert(card); - - if (card->fd < 0 || card->running) - return; - - /* ignore cards without DUMB_BUFFER capability */ - if (!card->cap_dumb) - return; - - assert(card->fd_src); - - log_debug("grdrm: %s/%s: enable", card->base.session->name, card->base.name); - - card->running = true; - sd_event_source_set_enabled(card->fd_src, SD_EVENT_ON); - grdrm_card_hotplug(card); -} - -static void grdrm_card_disable(grdrm_card *card) { - grdrm_object *object; - Iterator iter; - - assert(card); - - if (card->fd < 0 || !card->running) - return; - - assert(card->fd_src); - - log_debug("grdrm: %s/%s: disable", card->base.session->name, card->base.name); - - card->running = false; - card->ready = false; - sd_event_source_set_enabled(card->fd_src, SD_EVENT_OFF); - - /* stop all pipes */ - HASHMAP_FOREACH(object, card->object_map, iter) { - grdrm_crtc *crtc; - - if (object->type != GRDRM_TYPE_CRTC) - continue; - - crtc = crtc_from_object(object); - crtc->applied = false; - if (crtc->pipe) - grdev_pipe_ready(&crtc->pipe->base, false); - } -} - -static int grdrm_card_open(grdrm_card *card, int dev_fd) { - _cleanup_(grdev_session_unpinp) grdev_session *pin = NULL; - _cleanup_close_ int fd = dev_fd; - struct drm_get_cap cap; - int r, flags; - - assert(card); - assert(dev_fd >= 0); - assert(card->fd != dev_fd); - - pin = grdev_session_pin(card->base.session); - grdrm_card_close(card); - - log_debug("grdrm: %s/%s: open", card->base.session->name, card->base.name); - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = fd_cloexec(fd, true); - if (r < 0) - return r; - - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -errno; - if ((flags & O_ACCMODE) != O_RDWR) - return -EACCES; - - r = sd_event_add_io(card->base.session->context->event, - &card->fd_src, - fd, - EPOLLHUP | EPOLLERR | EPOLLIN, - grdrm_card_io_fn, - card); - if (r < 0) - return r; - - sd_event_source_set_enabled(card->fd_src, SD_EVENT_OFF); - - card->hotplug = true; - card->fd = fd; - fd = -1; - - /* cache DUMB_BUFFER capability */ - cap.capability = DRM_CAP_DUMB_BUFFER; - cap.value = 0; - r = ioctl(card->fd, DRM_IOCTL_GET_CAP, &cap); - card->cap_dumb = r >= 0 && cap.value; - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot retrieve DUMB_BUFFER capability: %m", - card->base.session->name, card->base.name); - else if (!card->cap_dumb) - log_debug("grdrm: %s/%s: DUMB_BUFFER capability not supported", - card->base.session->name, card->base.name); - - /* cache TIMESTAMP_MONOTONIC capability */ - cap.capability = DRM_CAP_TIMESTAMP_MONOTONIC; - cap.value = 0; - r = ioctl(card->fd, DRM_IOCTL_GET_CAP, &cap); - card->cap_monotonic = r >= 0 && cap.value; - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot retrieve TIMESTAMP_MONOTONIC capability: %m", - card->base.session->name, card->base.name); - else if (!card->cap_monotonic) - log_debug("grdrm: %s/%s: TIMESTAMP_MONOTONIC is disabled globally, fix this NOW!", - card->base.session->name, card->base.name); - - return 0; -} - -static void grdrm_card_close(grdrm_card *card) { - grdrm_object *object; - - if (card->fd < 0) - return; - - log_debug("grdrm: %s/%s: close", card->base.session->name, card->base.name); - - grdrm_card_disable(card); - - card->fd_src = sd_event_source_unref(card->fd_src); - card->fd = safe_close(card->fd); - - grdev_session_pin(card->base.session); - while ((object = hashmap_first(card->object_map))) - grdrm_object_free(object); - grdev_session_unpin(card->base.session); -} - -static bool grdrm_card_async(grdrm_card *card, int r) { - switch (r) { - case -EACCES: - /* If we get EACCES on runtime DRM calls, we lost DRM-Master - * (or we did something terribly wrong). Immediately disable - * the card, so we stop all pipes and wait to be activated - * again. */ - grdrm_card_disable(card); - break; - case -ENOENT: - /* DRM objects can be hotplugged at any time. If an object is - * removed that we use, we remember that state so a following - * call can test for this. - * Note that we also get a uevent as followup, this will resync - * the whole device. */ - card->async_hotplug = true; - break; - } - - return !card->ready; -} - -/* - * Unmanaged Cards - * The unmanaged DRM card opens the device node for a given DRM device - * directly (/dev/dri/cardX) and thus needs sufficient privileges. It opens - * the device only if we really require it and releases it as soon as we're - * disabled or closed. - * The unmanaged element can be used in all situations where you have direct - * access to DRM device nodes. Unlike managed DRM elements, it can be used - * outside of user sessions and in emergency situations where logind is not - * available. - */ - -static void unmanaged_card_enable(grdev_card *basecard) { - unmanaged_card *cu = unmanaged_card_from_base(basecard); - int r, fd; - - if (cu->card.fd < 0) { - /* try open on activation if it failed during allocation */ - fd = open(cu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - /* not fatal; simply ignore the device */ - log_debug_errno(errno, "grdrm: %s/%s: cannot open node %s: %m", - basecard->session->name, basecard->name, cu->devnode); - return; - } - - /* we might already be DRM-Master by open(); that's fine */ - - r = grdrm_card_open(&cu->card, fd); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - basecard->session->name, basecard->name); - return; - } - } - - r = ioctl(cu->card.fd, DRM_IOCTL_SET_MASTER, 0); - if (r < 0) { - log_debug_errno(errno, "grdrm: %s/%s: cannot acquire DRM-Master: %m", - basecard->session->name, basecard->name); - return; - } - - grdrm_card_enable(&cu->card); -} - -static void unmanaged_card_disable(grdev_card *basecard) { - unmanaged_card *cu = unmanaged_card_from_base(basecard); - - grdrm_card_disable(&cu->card); -} - -static int unmanaged_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) { - _cleanup_(grdev_card_freep) grdev_card *basecard = NULL; - char name[GRDRM_CARD_NAME_MAX]; - unmanaged_card *cu; - const char *devnode; - dev_t devnum; - int r, fd; - - assert_return(session, -EINVAL); - assert_return(ud, -EINVAL); - - devnode = udev_device_get_devnode(ud); - devnum = udev_device_get_devnum(ud); - if (!devnode || devnum == 0) - return -ENODEV; - - grdrm_name(name, devnum); - - cu = new0(unmanaged_card, 1); - if (!cu) - return -ENOMEM; - - basecard = &cu->card.base; - cu->card = GRDRM_CARD_INIT(&unmanaged_card_vtable, session); - - cu->devnode = strdup(devnode); - if (!cu->devnode) - return -ENOMEM; - - r = grdrm_card_add(&cu->card, name); - if (r < 0) - return r; - - /* try to open but ignore errors */ - fd = open(cu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - /* not fatal; allow uaccess based control on activation */ - log_debug_errno(errno, "grdrm: %s/%s: cannot open node %s: %m", - basecard->session->name, basecard->name, cu->devnode); - } else { - /* We might get DRM-Master implicitly on open(); drop it immediately - * so we acquire it only once we're actually enabled. We don't - * really care whether this call fails or not, but let's log any - * weird errors, anyway. */ - r = ioctl(fd, DRM_IOCTL_DROP_MASTER, 0); - if (r < 0 && errno != EACCES && errno != EINVAL) - log_debug_errno(errno, "grdrm: %s/%s: cannot drop DRM-Master: %m", - basecard->session->name, basecard->name); - - r = grdrm_card_open(&cu->card, fd); - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - basecard->session->name, basecard->name); - } - - if (out) - *out = basecard; - basecard = NULL; - return 0; -} - -static void unmanaged_card_free(grdev_card *basecard) { - unmanaged_card *cu = unmanaged_card_from_base(basecard); - - assert(!basecard->enabled); - - grdrm_card_close(&cu->card); - grdrm_card_destroy(&cu->card); - free(cu->devnode); - free(cu); -} - -static const grdev_card_vtable unmanaged_card_vtable = { - .free = unmanaged_card_free, - .enable = unmanaged_card_enable, - .disable = unmanaged_card_disable, - .commit = grdrm_card_commit, - .restore = grdrm_card_restore, -}; - -/* - * Managed Cards - * The managed DRM card uses systemd-logind to acquire DRM devices. This - * means, we do not open the device node /dev/dri/cardX directly. Instead, - * logind passes us a file-descriptor whenever our session is activated. Thus, - * we don't need access to the device node directly. - * Furthermore, whenever the session is put asleep, logind revokes the - * file-descriptor so we loose access to the device. - * Managed DRM cards should be preferred over unmanaged DRM cards whenever - * you run inside a user session with exclusive device access. - */ - -static void managed_card_enable(grdev_card *card) { - managed_card *cm = managed_card_from_base(card); - - /* If the device is manually re-enabled, we try to resume our card - * management. Note that we have no control over DRM-Master and the fd, - * so we have to take over the state from the last logind event. */ - - if (cm->master) - grdrm_card_enable(&cm->card); -} - -static void managed_card_disable(grdev_card *card) { - managed_card *cm = managed_card_from_base(card); - - /* If the device is manually disabled, we keep the FD but put our card - * management asleep. This way, we can wake up at any time, but don't - * touch the device while asleep. */ - - grdrm_card_disable(&cm->card); -} - -static int managed_card_pause_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - managed_card *cm = userdata; - grdev_session *session = cm->card.base.session; - uint32_t major, minor; - const char *mode; - int r; - - /* - * We get PauseDevice() signals from logind whenever a device we - * requested was, or is about to be, paused. Arguments are major/minor - * number of the device and the mode of the operation. - * In case the event is not about our device, we ignore it. Otherwise, - * we treat it as asynchronous DRM-DROP-MASTER. Note that we might have - * already handled an EACCES error from a modeset ioctl, in which case - * we already disabled the device. - * - * @mode can be one of the following: - * "pause": The device is about to be paused. We must react - * immediately and respond with PauseDeviceComplete(). Once - * we replied, logind will pause the device. Note that - * logind might apply any kind of timeout and force pause - * the device if we don't respond in a timely manner. In - * this case, we will receive a second PauseDevice event - * with @mode set to "force" (or similar). - * "force": The device was disabled forecfully by logind. DRM-Master - * was already dropped. This is just an asynchronous - * notification so we can put the device asleep (in case - * we didn't already notice the dropped DRM-Master). - * "gone": This is like "force" but is sent if the device was - * paused due to a device-removal event. - * - * We always handle PauseDevice signals as "force" as we properly - * support asynchronously dropping DRM-Master, anyway. But in case - * logind sent mode "pause", we also call PauseDeviceComplete() to - * immediately acknowledge the request. - */ - - r = sd_bus_message_read(signal, "uus", &major, &minor, &mode); - if (r < 0) { - log_debug("grdrm: %s/%s: erroneous PauseDevice signal", - session->name, cm->card.base.name); - return 0; - } - - /* not our device? */ - if (makedev(major, minor) != cm->devnum) - return 0; - - cm->master = false; - grdrm_card_disable(&cm->card); - - if (streq(mode, "pause")) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - - /* - * Sending PauseDeviceComplete() is racy if logind triggers the - * timeout. That is, if we take too long and logind pauses the - * device by sending a forced PauseDevice, our - * PauseDeviceComplete call will be stray. That's fine, though. - * logind ignores such stray calls. Only if logind also sent a - * further PauseDevice() signal, it might match our call - * incorrectly to the newer PauseDevice(). That's fine, too, as - * we handle that event asynchronously, anyway. Therefore, - * whatever happens, we're fine. Yay! - */ - - r = sd_bus_message_new_method_call(session->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "PauseDeviceComplete"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major, minor); - if (r >= 0) - r = sd_bus_send(session->context->sysbus, m, NULL); - } - - if (r < 0) - log_debug_errno(r, "grdrm: %s/%s: cannot send PauseDeviceComplete: %m", - session->name, cm->card.base.name); - } - - return 0; -} - -static int managed_card_resume_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - managed_card *cm = userdata; - grdev_session *session = cm->card.base.session; - uint32_t major, minor; - int r, fd; - - /* - * We get ResumeDevice signals whenever logind resumed a previously - * paused device. The arguments contain the major/minor number of the - * related device and a new file-descriptor for the freshly opened - * device-node. - * If the signal is not about our device, we simply ignore it. - * Otherwise, we immediately resume the device. Note that we drop the - * new file-descriptor as we already have one from TakeDevice(). logind - * preserves the file-context across pause/resume for DRM but only - * drops/acquires DRM-Master accordingly. This way, our context (like - * DRM-FBs and BOs) is preserved. - */ - - r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd); - if (r < 0) { - log_debug("grdrm: %s/%s: erroneous ResumeDevice signal", - session->name, cm->card.base.name); - return 0; - } - - /* not our device? */ - if (makedev(major, minor) != cm->devnum) - return 0; - - if (cm->card.fd < 0) { - /* This shouldn't happen. We should already own an FD from - * TakeDevice(). However, let's be safe and use this FD in case - * we really don't have one. There is no harm in doing this - * and our code works fine this way. */ - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "grdrm: %s/%s: cannot duplicate fd: %m", - session->name, cm->card.base.name); - return 0; - } - - r = grdrm_card_open(&cm->card, fd); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - session->name, cm->card.base.name); - return 0; - } - } - - cm->master = true; - if (cm->card.base.enabled) - grdrm_card_enable(&cm->card); - - return 0; -} - -static int managed_card_setup_bus(managed_card *cm) { - grdev_session *session = cm->card.base.session; - _cleanup_free_ char *match = NULL; - int r; - - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='PauseDevice'," - "path='", session->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(session->context->sysbus, - &cm->slot_pause_device, - match, - managed_card_pause_device_fn, - cm); - if (r < 0) - return r; - - free(match); - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='ResumeDevice'," - "path='", session->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(session->context->sysbus, - &cm->slot_resume_device, - match, - managed_card_resume_device_fn, - cm); - if (r < 0) - return r; - - return 0; -} - -static int managed_card_take_device_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - managed_card *cm = userdata; - grdev_session *session = cm->card.base.session; - int r, paused, fd; - - cm->slot_take_device = sd_bus_slot_unref(cm->slot_take_device); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("grdrm: %s/%s: TakeDevice failed: %s: %s", - session->name, cm->card.base.name, error->name, error->message); - return 0; - } - - cm->acquired = true; - - r = sd_bus_message_read(reply, "hb", &fd, &paused); - if (r < 0) { - log_debug("grdrm: %s/%s: erroneous TakeDevice reply", - session->name, cm->card.base.name); - return 0; - } - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "grdrm: %s/%s: cannot duplicate fd: %m", - session->name, cm->card.base.name); - return 0; - } - - r = grdrm_card_open(&cm->card, fd); - if (r < 0) { - log_debug_errno(r, "grdrm: %s/%s: cannot open: %m", - session->name, cm->card.base.name); - return 0; - } - - if (!paused && cm->card.base.enabled) - grdrm_card_enable(&cm->card); - - return 0; -} - -static void managed_card_take_device(managed_card *cm) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - grdev_session *session = cm->card.base.session; - int r; - - r = sd_bus_message_new_method_call(session->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "TakeDevice"); - if (r < 0) - goto error; - - r = sd_bus_message_append(m, "uu", major(cm->devnum), minor(cm->devnum)); - if (r < 0) - goto error; - - r = sd_bus_call_async(session->context->sysbus, - &cm->slot_take_device, - m, - managed_card_take_device_fn, - cm, - 0); - if (r < 0) - goto error; - - cm->requested = true; - return; - -error: - log_debug_errno(r, "grdrm: %s/%s: cannot send TakeDevice request: %m", - session->name, cm->card.base.name); -} - -static void managed_card_release_device(managed_card *cm) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - grdev_session *session = cm->card.base.session; - int r; - - /* - * If TakeDevice() is pending or was successful, make sure to - * release the device again. We don't care for return-values, - * so send it without waiting or callbacks. - * If a failed TakeDevice() is pending, but someone else took - * the device on the same bus-connection, we might incorrectly - * release their device. This is an unlikely race, though. - * Furthermore, you really shouldn't have two users of the - * controller-API on the same session, on the same devices, *AND* on - * the same bus-connection. So we don't care for that race.. - */ - - grdrm_card_close(&cm->card); - cm->requested = false; - - if (!cm->acquired && !cm->slot_take_device) - return; - - cm->slot_take_device = sd_bus_slot_unref(cm->slot_take_device); - cm->acquired = false; - - r = sd_bus_message_new_method_call(session->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "ReleaseDevice"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major(cm->devnum), minor(cm->devnum)); - if (r >= 0) - r = sd_bus_send(session->context->sysbus, m, NULL); - } - - if (r < 0 && r != -ENOTCONN) - log_debug_errno(r, "grdrm: %s/%s: cannot send ReleaseDevice: %m", - session->name, cm->card.base.name); -} - -static int managed_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) { - _cleanup_(grdev_card_freep) grdev_card *basecard = NULL; - char name[GRDRM_CARD_NAME_MAX]; - managed_card *cm; - dev_t devnum; - int r; - - assert_return(session, -EINVAL); - assert_return(session->managed, -EINVAL); - assert_return(session->context->sysbus, -EINVAL); - assert_return(ud, -EINVAL); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return -ENODEV; - - grdrm_name(name, devnum); - - cm = new0(managed_card, 1); - if (!cm) - return -ENOMEM; - - basecard = &cm->card.base; - cm->card = GRDRM_CARD_INIT(&managed_card_vtable, session); - cm->devnum = devnum; - - r = managed_card_setup_bus(cm); - if (r < 0) - return r; - - r = grdrm_card_add(&cm->card, name); - if (r < 0) - return r; - - managed_card_take_device(cm); - - if (out) - *out = basecard; - basecard = NULL; - return 0; -} - -static void managed_card_free(grdev_card *basecard) { - managed_card *cm = managed_card_from_base(basecard); - - assert(!basecard->enabled); - - managed_card_release_device(cm); - cm->slot_resume_device = sd_bus_slot_unref(cm->slot_resume_device); - cm->slot_pause_device = sd_bus_slot_unref(cm->slot_pause_device); - grdrm_card_destroy(&cm->card); - free(cm); -} - -static const grdev_card_vtable managed_card_vtable = { - .free = managed_card_free, - .enable = managed_card_enable, - .disable = managed_card_disable, - .commit = grdrm_card_commit, - .restore = grdrm_card_restore, -}; - -/* - * Generic Constructor - * Instead of relying on the caller to choose between managed and unmanaged - * DRM devices, the grdev_drm_new() constructor does that for you (by - * looking at session->managed). - */ - -bool grdev_is_drm_card(grdev_card *basecard) { - return basecard && (basecard->vtable == &unmanaged_card_vtable || - basecard->vtable == &managed_card_vtable); -} - -grdev_card *grdev_find_drm_card(grdev_session *session, dev_t devnum) { - char name[GRDRM_CARD_NAME_MAX]; - - assert_return(session, NULL); - assert_return(devnum != 0, NULL); - - grdrm_name(name, devnum); - return grdev_find_card(session, name); -} - -int grdev_drm_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud) { - assert_return(session, -EINVAL); - assert_return(ud, -EINVAL); - - return session->managed ? managed_card_new(out, session, ud) : unmanaged_card_new(out, session, ud); -} - -void grdev_drm_card_hotplug(grdev_card *basecard, struct udev_device *ud) { - const char *p, *action; - grdrm_card *card; - dev_t devnum; - - assert(basecard); - assert(grdev_is_drm_card(basecard)); - assert(ud); - - card = grdrm_card_from_base(basecard); - - action = udev_device_get_action(ud); - if (!action || streq(action, "add") || streq(action, "remove")) { - /* If we get add/remove events on DRM nodes without devnum, we - * got hotplugged DRM objects so refresh the device. */ - devnum = udev_device_get_devnum(ud); - if (devnum == 0) { - card->hotplug = true; - grdrm_card_hotplug(card); - } - } else if (streq_ptr(action, "change")) { - /* A change event with HOTPLUG=1 is sent whenever a connector - * changed state. Refresh the device to update our state. */ - p = udev_device_get_property_value(ud, "HOTPLUG"); - if (streq_ptr(p, "1")) { - card->hotplug = true; - grdrm_card_hotplug(card); - } - } -} diff --git a/src/libsystemd-terminal/grdev-internal.h b/src/libsystemd-terminal/grdev-internal.h deleted file mode 100644 index 46d65f0248..0000000000 --- a/src/libsystemd-terminal/grdev-internal.h +++ /dev/null @@ -1,251 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "list.h" -#include "util.h" -#include "grdev.h" - -typedef struct grdev_tile grdev_tile; -typedef struct grdev_display_cache grdev_display_cache; - -typedef struct grdev_pipe_vtable grdev_pipe_vtable; -typedef struct grdev_pipe grdev_pipe; -typedef struct grdev_card_vtable grdev_card_vtable; -typedef struct grdev_card grdev_card; - -/* - * DRM cards - */ - -bool grdev_is_drm_card(grdev_card *card); -grdev_card *grdev_find_drm_card(grdev_session *session, dev_t devnum); -int grdev_drm_card_new(grdev_card **out, grdev_session *session, struct udev_device *ud); -void grdev_drm_card_hotplug(grdev_card *card, struct udev_device *ud); - -/* - * Displays - */ - -enum { - GRDEV_TILE_LEAF, - GRDEV_TILE_NODE, - GRDEV_TILE_CNT -}; - -struct grdev_tile { - LIST_FIELDS(grdev_tile, children_by_node); - grdev_tile *parent; - grdev_display *display; - - uint32_t x; - uint32_t y; - unsigned int rotate; - unsigned int flip; - uint32_t cache_w; - uint32_t cache_h; - - unsigned int type; - - union { - struct { - grdev_pipe *pipe; - } leaf; - - struct { - size_t n_children; - LIST_HEAD(grdev_tile, child_list); - } node; - }; -}; - -int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe); -int grdev_tile_new_node(grdev_tile **out); -grdev_tile *grdev_tile_free(grdev_tile *tile); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_tile*, grdev_tile_free); - -struct grdev_display { - grdev_session *session; - char *name; - void *userdata; - - size_t n_leafs; - grdev_tile *tile; - - size_t n_pipes; - size_t max_pipes; - - uint32_t width; - uint32_t height; - - struct grdev_display_cache { - grdev_pipe *pipe; - grdev_display_target target; - - bool incomplete : 1; - } *pipes; - - bool enabled : 1; - bool public : 1; - bool modified : 1; - bool framed : 1; -}; - -grdev_display *grdev_find_display(grdev_session *session, const char *name); - -int grdev_display_new(grdev_display **out, grdev_session *session, const char *name); -grdev_display *grdev_display_free(grdev_display *display); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_display*, grdev_display_free); - -/* - * Pipes - */ - -struct grdev_pipe_vtable { - void (*free) (grdev_pipe *pipe); - void (*enable) (grdev_pipe *pipe); - void (*disable) (grdev_pipe *pipe); - grdev_fb *(*target) (grdev_pipe *pipe); -}; - -struct grdev_pipe { - const grdev_pipe_vtable *vtable; - grdev_card *card; - char *name; - - grdev_tile *tile; - grdev_display_cache *cache; - sd_event_source *vsync_src; - - uint32_t width; - uint32_t height; - uint32_t vrefresh; - - size_t max_fbs; - grdev_fb *front; - grdev_fb *back; - grdev_fb **fbs; - - bool enabled : 1; - bool running : 1; - bool flip : 1; - bool flipping : 1; -}; - -#define GRDEV_PIPE_INIT(_vtable, _card) ((grdev_pipe){ \ - .vtable = (_vtable), \ - .card = (_card), \ - }) - -grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name); - -int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs); -grdev_pipe *grdev_pipe_free(grdev_pipe *pipe); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_pipe*, grdev_pipe_free); - -void grdev_pipe_ready(grdev_pipe *pipe, bool running); -void grdev_pipe_frame(grdev_pipe *pipe); -void grdev_pipe_schedule(grdev_pipe *pipe, uint64_t frames); - -/* - * Cards - */ - -struct grdev_card_vtable { - void (*free) (grdev_card *card); - void (*enable) (grdev_card *card); - void (*disable) (grdev_card *card); - void (*commit) (grdev_card *card); - void (*restore) (grdev_card *card); -}; - -struct grdev_card { - const grdev_card_vtable *vtable; - grdev_session *session; - char *name; - - Hashmap *pipe_map; - - bool enabled : 1; - bool modified : 1; -}; - -#define GRDEV_CARD_INIT(_vtable, _session) ((grdev_card){ \ - .vtable = (_vtable), \ - .session = (_session), \ - }) - -grdev_card *grdev_find_card(grdev_session *session, const char *name); - -int grdev_card_add(grdev_card *card, const char *name); -grdev_card *grdev_card_free(grdev_card *card); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_card*, grdev_card_free); - -/* - * Sessions - */ - -struct grdev_session { - grdev_context *context; - char *name; - char *path; - grdev_event_fn event_fn; - void *userdata; - - unsigned long n_pins; - - Hashmap *card_map; - Hashmap *display_map; - - bool custom : 1; - bool managed : 1; - bool enabled : 1; - bool modified : 1; -}; - -grdev_session *grdev_session_pin(grdev_session *session); -grdev_session *grdev_session_unpin(grdev_session *session); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_session*, grdev_session_unpin); - -/* - * Contexts - */ - -struct grdev_context { - unsigned long ref; - sd_event *event; - sd_bus *sysbus; - - Hashmap *session_map; -}; diff --git a/src/libsystemd-terminal/grdev.c b/src/libsystemd-terminal/grdev.c deleted file mode 100644 index 71f0bd31e7..0000000000 --- a/src/libsystemd-terminal/grdev.c +++ /dev/null @@ -1,1359 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "login-util.h" -#include "macro.h" -#include "util.h" -#include "grdev.h" -#include "grdev-internal.h" - -static void pipe_enable(grdev_pipe *pipe); -static void pipe_disable(grdev_pipe *pipe); -static void card_modified(grdev_card *card); -static void session_frame(grdev_session *session, grdev_display *display); - -/* - * Displays - */ - -static inline grdev_tile *tile_leftmost(grdev_tile *tile) { - if (!tile) - return NULL; - - while (tile->type == GRDEV_TILE_NODE && tile->node.child_list) - tile = tile->node.child_list; - - return tile; -} - -#define TILE_FOREACH(_root, _i) \ - for (_i = tile_leftmost(_root); _i; _i = tile_leftmost(_i->children_by_node_next) ? : _i->parent) - -#define TILE_FOREACH_SAFE(_root, _i, _next) \ - for (_i = tile_leftmost(_root); _i && ((_next = tile_leftmost(_i->children_by_node_next) ? : _i->parent), true); _i = _next) - -static void tile_link(grdev_tile *tile, grdev_tile *parent) { - grdev_display *display; - grdev_tile *t; - - assert(tile); - assert(!tile->parent); - assert(!tile->display); - assert(parent); - assert(parent->type == GRDEV_TILE_NODE); - - display = parent->display; - - assert(!display || !display->enabled); - - ++parent->node.n_children; - LIST_PREPEND(children_by_node, parent->node.child_list, tile); - tile->parent = parent; - - if (display) { - display->modified = true; - TILE_FOREACH(tile, t) { - t->display = display; - if (t->type == GRDEV_TILE_LEAF) { - ++display->n_leafs; - if (display->enabled) - pipe_enable(t->leaf.pipe); - } - } - } -} - -static void tile_unlink(grdev_tile *tile) { - grdev_tile *parent, *t; - grdev_display *display; - - assert(tile); - - display = tile->display; - parent = tile->parent; - if (!parent) { - assert(!display); - return; - } - - assert(parent->type == GRDEV_TILE_NODE); - assert(parent->display == display); - assert(parent->node.n_children > 0); - - --parent->node.n_children; - LIST_REMOVE(children_by_node, parent->node.child_list, tile); - tile->parent = NULL; - - if (display) { - display->modified = true; - TILE_FOREACH(tile, t) { - t->display = NULL; - if (t->type == GRDEV_TILE_LEAF) { - --display->n_leafs; - t->leaf.pipe->cache = NULL; - pipe_disable(t->leaf.pipe); - } - } - } - - /* Tile trees are driven by leafs. Internal nodes have no owner, thus, - * we must take care to not leave them around. Therefore, whenever we - * unlink any part of a tree, we also destroy the parent, in case it's - * now stale. - * Parents are stale if they have no children and either have no display - * or if they are intermediate nodes (i.e, they have a parent). - * This means, you can easily create trees, but you can never partially - * move or destruct them so far. They're always reduced to minimal form - * if you cut them. This might change later, but so far we didn't need - * partial destruction or the ability to move whole trees. */ - - if (parent->node.n_children < 1 && (parent->parent || !parent->display)) - grdev_tile_free(parent); -} - -static int tile_new(grdev_tile **out) { - _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL; - - assert(out); - - tile = new0(grdev_tile, 1); - if (!tile) - return -ENOMEM; - - tile->type = (unsigned)-1; - - *out = tile; - tile = NULL; - return 0; -} - -int grdev_tile_new_leaf(grdev_tile **out, grdev_pipe *pipe) { - _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(pipe, -EINVAL); - assert_return(!pipe->tile, -EINVAL); - - r = tile_new(&tile); - if (r < 0) - return r; - - tile->type = GRDEV_TILE_LEAF; - tile->leaf.pipe = pipe; - - if (out) - *out = tile; - tile = NULL; - return 0; -} - -int grdev_tile_new_node(grdev_tile **out) { - _cleanup_(grdev_tile_freep) grdev_tile *tile = NULL; - int r; - - assert_return(out, -EINVAL); - - r = tile_new(&tile); - if (r < 0) - return r; - - tile->type = GRDEV_TILE_NODE; - - *out = tile; - tile = NULL; - return 0; -} - -grdev_tile *grdev_tile_free(grdev_tile *tile) { - if (!tile) - return NULL; - - tile_unlink(tile); - - switch (tile->type) { - case GRDEV_TILE_LEAF: - assert(!tile->parent); - assert(!tile->display); - assert(tile->leaf.pipe); - - break; - case GRDEV_TILE_NODE: - assert(!tile->parent); - assert(!tile->display); - assert(tile->node.n_children == 0); - - break; - } - - free(tile); - - return NULL; -} - -grdev_display *grdev_find_display(grdev_session *session, const char *name) { - assert_return(session, NULL); - assert_return(name, NULL); - - return hashmap_get(session->display_map, name); -} - -int grdev_display_new(grdev_display **out, grdev_session *session, const char *name) { - _cleanup_(grdev_display_freep) grdev_display *display = NULL; - int r; - - assert(session); - assert(name); - - display = new0(grdev_display, 1); - if (!display) - return -ENOMEM; - - display->session = session; - - display->name = strdup(name); - if (!display->name) - return -ENOMEM; - - r = grdev_tile_new_node(&display->tile); - if (r < 0) - return r; - - display->tile->display = display; - - r = hashmap_put(session->display_map, display->name, display); - if (r < 0) - return r; - - if (out) - *out = display; - display = NULL; - return 0; -} - -grdev_display *grdev_display_free(grdev_display *display) { - if (!display) - return NULL; - - assert(!display->public); - assert(!display->enabled); - assert(!display->modified); - assert(display->n_leafs == 0); - assert(display->n_pipes == 0); - - if (display->name) - hashmap_remove_value(display->session->display_map, display->name, display); - - if (display->tile) { - display->tile->display = NULL; - grdev_tile_free(display->tile); - } - - free(display->pipes); - free(display->name); - free(display); - - return NULL; -} - -void grdev_display_set_userdata(grdev_display *display, void *userdata) { - assert(display); - - display->userdata = userdata; -} - -void *grdev_display_get_userdata(grdev_display *display) { - assert_return(display, NULL); - - return display->userdata; -} - -const char *grdev_display_get_name(grdev_display *display) { - assert_return(display, NULL); - - return display->name; -} - -uint32_t grdev_display_get_width(grdev_display *display) { - assert_return(display, 0); - - return display->width; -} - -uint32_t grdev_display_get_height(grdev_display *display) { - assert_return(display, 0); - - return display->height; -} - -bool grdev_display_is_enabled(grdev_display *display) { - return display && display->enabled; -} - -void grdev_display_enable(grdev_display *display) { - grdev_tile *t; - - assert(display); - - if (!display->enabled) { - display->enabled = true; - TILE_FOREACH(display->tile, t) - if (t->type == GRDEV_TILE_LEAF) - pipe_enable(t->leaf.pipe); - } -} - -void grdev_display_disable(grdev_display *display) { - grdev_tile *t; - - assert(display); - - if (display->enabled) { - display->enabled = false; - TILE_FOREACH(display->tile, t) - if (t->type == GRDEV_TILE_LEAF) - pipe_disable(t->leaf.pipe); - } -} - -const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev) { - grdev_display_cache *cache; - size_t idx; - - assert_return(display, NULL); - assert_return(!display->modified, NULL); - assert_return(display->enabled, NULL); - - if (prev) { - cache = container_of(prev, grdev_display_cache, target); - - assert(cache->pipe); - assert(cache->pipe->tile->display == display); - assert(display->pipes >= cache); - - idx = cache - display->pipes + 1; - } else { - idx = 0; - } - - for (cache = display->pipes + idx; idx < display->n_pipes; ++idx, ++cache) { - grdev_display_target *target; - grdev_pipe *pipe; - grdev_fb *fb; - - pipe = cache->pipe; - target = &cache->target; - - if (!pipe->running || !pipe->enabled) - continue; - - /* find suitable back-buffer */ - if (!pipe->back) { - if (!pipe->vtable->target) - continue; - if (!(fb = pipe->vtable->target(pipe))) - continue; - - assert(fb == pipe->back); - } - - target->front = pipe->front; - target->back = pipe->back; - - return target; - } - - return NULL; -} - -void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target) { - grdev_display_cache *cache; - - assert(display); - assert(!display->modified); - assert(display->enabled); - assert(target); - - cache = container_of(target, grdev_display_cache, target); - - assert(cache->pipe); - assert(cache->pipe->tile->display == display); - - cache->pipe->flip = true; -} - -static void display_cache_apply(grdev_display_cache *c, grdev_tile *l) { - uint32_t x, y, width, height; - grdev_display_target *t; - - assert(c); - assert(l); - assert(l->cache_w >= c->target.width + c->target.x); - assert(l->cache_h >= c->target.height + c->target.y); - - t = &c->target; - - /* rotate child */ - - t->rotate = (t->rotate + l->rotate) & 0x3; - - x = t->x; - y = t->y; - width = t->width; - height = t->height; - - switch (l->rotate) { - case GRDEV_ROTATE_0: - break; - case GRDEV_ROTATE_90: - t->x = l->cache_h - (height + y); - t->y = x; - t->width = height; - t->height = width; - break; - case GRDEV_ROTATE_180: - t->x = l->cache_w - (width + x); - t->y = l->cache_h - (height + y); - break; - case GRDEV_ROTATE_270: - t->x = y; - t->y = l->cache_w - (width + x); - t->width = height; - t->height = width; - break; - } - - /* flip child */ - - t->flip ^= l->flip; - - if (l->flip & GRDEV_FLIP_HORIZONTAL) - t->x = l->cache_w - (t->width + t->x); - if (l->flip & GRDEV_FLIP_VERTICAL) - t->y = l->cache_h - (t->height + t->y); - - /* move child */ - - t->x += l->x; - t->y += l->y; -} - -static void display_cache_targets(grdev_display *display) { - grdev_display_cache *c; - grdev_tile *tile; - - assert(display); - - /* depth-first with children before parent */ - for (tile = tile_leftmost(display->tile); - tile; - tile = tile_leftmost(tile->children_by_node_next) ? : tile->parent) { - if (tile->type == GRDEV_TILE_LEAF) { - grdev_pipe *p; - - /* We're at a leaf and no parent has been cached, yet. - * Copy the pipe information into the target cache and - * update our global pipe-caches if required. */ - - assert(tile->leaf.pipe); - assert(display->n_pipes + 1 <= display->max_pipes); - - p = tile->leaf.pipe; - c = &display->pipes[display->n_pipes++]; - - zero(*c); - c->pipe = p; - c->pipe->cache = c; - c->target.width = p->width; - c->target.height = p->height; - tile->cache_w = p->width; - tile->cache_h = p->height; - - /* all new tiles are incomplete due to geometry changes */ - c->incomplete = true; - - display_cache_apply(c, tile); - } else { - grdev_tile *child, *l; - - /* We're now at a node with all its children already - * computed (depth-first, child before parent). We - * first need to know the size of our tile, then we - * recurse into all leafs and update their cache. */ - - tile->cache_w = 0; - tile->cache_h = 0; - - LIST_FOREACH(children_by_node, child, tile->node.child_list) { - if (child->x + child->cache_w > tile->cache_w) - tile->cache_w = child->x + child->cache_w; - if (child->y + child->cache_h > tile->cache_h) - tile->cache_h = child->y + child->cache_h; - } - - assert(tile->cache_w > 0); - assert(tile->cache_h > 0); - - TILE_FOREACH(tile, l) - if (l->type == GRDEV_TILE_LEAF) - display_cache_apply(l->leaf.pipe->cache, tile); - } - } -} - -static bool display_cache(grdev_display *display) { - grdev_tile *tile; - size_t n; - void *t; - int r; - - assert(display); - - if (!display->modified) - return false; - - display->modified = false; - display->framed = false; - display->n_pipes = 0; - display->width = 0; - display->height = 0; - - if (display->n_leafs < 1) - return false; - - TILE_FOREACH(display->tile, tile) - if (tile->type == GRDEV_TILE_LEAF) - tile->leaf.pipe->cache = NULL; - - if (display->n_leafs > display->max_pipes) { - n = ALIGN_POWER2(display->n_leafs); - if (!n) { - r = -ENOMEM; - goto out; - } - - t = realloc_multiply(display->pipes, sizeof(*display->pipes), n); - if (!t) { - r = -ENOMEM; - goto out; - } - - display->pipes = t; - display->max_pipes = n; - } - - display_cache_targets(display); - display->width = display->tile->cache_w; - display->height = display->tile->cache_h; - - r = 0; - -out: - if (r < 0) - log_debug_errno(r, "grdev: %s/%s: cannot cache pipes: %m", - display->session->name, display->name); - return true; -} - -/* - * Pipes - */ - -grdev_pipe *grdev_find_pipe(grdev_card *card, const char *name) { - assert_return(card, NULL); - assert_return(name, NULL); - - return hashmap_get(card->pipe_map, name); -} - -static int pipe_vsync_fn(sd_event_source *src, uint64_t usec, void *userdata) { - grdev_pipe *pipe = userdata; - - grdev_pipe_frame(pipe); - return 0; -} - -int grdev_pipe_add(grdev_pipe *pipe, const char *name, size_t n_fbs) { - int r; - - assert_return(pipe, -EINVAL); - assert_return(pipe->vtable, -EINVAL); - assert_return(pipe->vtable->free, -EINVAL); - assert_return(pipe->card, -EINVAL); - assert_return(pipe->card->session, -EINVAL); - assert_return(!pipe->cache, -EINVAL); - assert_return(pipe->width > 0, -EINVAL); - assert_return(pipe->height > 0, -EINVAL); - assert_return(pipe->vrefresh > 0, -EINVAL); - assert_return(!pipe->enabled, -EINVAL); - assert_return(!pipe->running, -EINVAL); - assert_return(name, -EINVAL); - - pipe->name = strdup(name); - if (!pipe->name) - return -ENOMEM; - - if (n_fbs > 0) { - pipe->fbs = new0(grdev_fb*, n_fbs); - if (!pipe->fbs) - return -ENOMEM; - - pipe->max_fbs = n_fbs; - } - - r = grdev_tile_new_leaf(&pipe->tile, pipe); - if (r < 0) - return r; - - r = sd_event_add_time(pipe->card->session->context->event, - &pipe->vsync_src, - CLOCK_MONOTONIC, - 0, - 10 * USEC_PER_MSEC, - pipe_vsync_fn, - pipe); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_OFF); - if (r < 0) - return r; - - r = hashmap_put(pipe->card->pipe_map, pipe->name, pipe); - if (r < 0) - return r; - - card_modified(pipe->card); - return 0; -} - -grdev_pipe *grdev_pipe_free(grdev_pipe *pipe) { - grdev_pipe tmp; - - if (!pipe) - return NULL; - - assert(pipe->card); - assert(pipe->vtable); - assert(pipe->vtable->free); - - if (pipe->name) - hashmap_remove_value(pipe->card->pipe_map, pipe->name, pipe); - if (pipe->tile) - tile_unlink(pipe->tile); - - assert(!pipe->cache); - - tmp = *pipe; - pipe->vtable->free(pipe); - - sd_event_source_unref(tmp.vsync_src); - grdev_tile_free(tmp.tile); - card_modified(tmp.card); - free(tmp.fbs); - free(tmp.name); - - return NULL; -} - -static void pipe_enable(grdev_pipe *pipe) { - assert(pipe); - - if (!pipe->enabled) { - pipe->enabled = true; - if (pipe->vtable->enable) - pipe->vtable->enable(pipe); - } -} - -static void pipe_disable(grdev_pipe *pipe) { - assert(pipe); - - if (pipe->enabled) { - pipe->enabled = false; - if (pipe->vtable->disable) - pipe->vtable->disable(pipe); - } -} - -void grdev_pipe_ready(grdev_pipe *pipe, bool running) { - assert(pipe); - - /* grdev_pipe_ready() is used by backends to notify about pipe state - * changed. If a pipe is ready, it can be fully used by us (available, - * enabled and accessible). Backends can disable pipes at any time - * (like for async revocation), but can only enable them from parent - * context. Otherwise, we might call user-callbacks recursively. */ - - if (pipe->running == running) - return; - - pipe->running = running; - - /* runtime events for unused pipes are not interesting */ - if (pipe->cache && pipe->enabled) { - grdev_display *display = pipe->tile->display; - - assert(display); - - if (running) - session_frame(display->session, display); - else - pipe->cache->incomplete = true; - } -} - -void grdev_pipe_frame(grdev_pipe *pipe) { - grdev_display *display; - - assert(pipe); - - /* if pipe is unused, ignore any frame events */ - if (!pipe->cache || !pipe->enabled) - return; - - display = pipe->tile->display; - assert(display); - - grdev_pipe_schedule(pipe, 0); - session_frame(display->session, display); -} - -void grdev_pipe_schedule(grdev_pipe *pipe, uint64_t frames) { - int r; - uint64_t ts; - - if (!frames) { - sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_OFF); - return; - } - - r = sd_event_now(pipe->card->session->context->event, CLOCK_MONOTONIC, &ts); - if (r < 0) - goto error; - - ts += frames * USEC_PER_MSEC * 1000ULL / pipe->vrefresh; - - r = sd_event_source_set_time(pipe->vsync_src, ts); - if (r < 0) - goto error; - - r = sd_event_source_set_enabled(pipe->vsync_src, SD_EVENT_ONESHOT); - if (r < 0) - goto error; - - return; - -error: - log_debug_errno(r, "grdev: %s/%s/%s: cannot schedule vsync timer: %m", - pipe->card->session->name, pipe->card->name, pipe->name); -} - -/* - * Cards - */ - -grdev_card *grdev_find_card(grdev_session *session, const char *name) { - assert_return(session, NULL); - assert_return(name, NULL); - - return hashmap_get(session->card_map, name); -} - -int grdev_card_add(grdev_card *card, const char *name) { - int r; - - assert_return(card, -EINVAL); - assert_return(card->vtable, -EINVAL); - assert_return(card->vtable->free, -EINVAL); - assert_return(card->session, -EINVAL); - assert_return(name, -EINVAL); - - card->name = strdup(name); - if (!card->name) - return -ENOMEM; - - card->pipe_map = hashmap_new(&string_hash_ops); - if (!card->pipe_map) - return -ENOMEM; - - r = hashmap_put(card->session->card_map, card->name, card); - if (r < 0) - return r; - - return 0; -} - -grdev_card *grdev_card_free(grdev_card *card) { - grdev_card tmp; - - if (!card) - return NULL; - - assert(!card->enabled); - assert(card->vtable); - assert(card->vtable->free); - - if (card->name) - hashmap_remove_value(card->session->card_map, card->name, card); - - tmp = *card; - card->vtable->free(card); - - assert(hashmap_size(tmp.pipe_map) == 0); - - hashmap_free(tmp.pipe_map); - free(tmp.name); - - return NULL; -} - -static void card_modified(grdev_card *card) { - assert(card); - assert(card->session->n_pins > 0); - - card->modified = true; -} - -static void grdev_card_enable(grdev_card *card) { - assert(card); - - if (!card->enabled) { - card->enabled = true; - if (card->vtable->enable) - card->vtable->enable(card); - } -} - -static void grdev_card_disable(grdev_card *card) { - assert(card); - - if (card->enabled) { - card->enabled = false; - if (card->vtable->disable) - card->vtable->disable(card); - } -} - -/* - * Sessions - */ - -static void session_raise(grdev_session *session, grdev_event *event) { - session->event_fn(session, session->userdata, event); -} - -static void session_raise_display_add(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_ADD, - .display_add = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_raise_display_remove(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_REMOVE, - .display_remove = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_raise_display_change(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_CHANGE, - .display_change = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_raise_display_frame(grdev_session *session, grdev_display *display) { - grdev_event event = { - .type = GRDEV_EVENT_DISPLAY_FRAME, - .display_frame = { - .display = display, - }, - }; - - session_raise(session, &event); -} - -static void session_add_card(grdev_session *session, grdev_card *card) { - assert(session); - assert(card); - - log_debug("grdev: %s: add card '%s'", session->name, card->name); - - /* Cards are not exposed to users, but managed internally. Cards are - * enabled if the session is enabled, and will track that state. The - * backend can probe the card at any time, but only if enabled. It - * will then add pipes according to hardware state. - * That is, the card may create pipes as soon as we enable it here. */ - - if (session->enabled) - grdev_card_enable(card); -} - -static void session_remove_card(grdev_session *session, grdev_card *card) { - assert(session); - assert(card); - - log_debug("grdev: %s: remove card '%s'", session->name, card->name); - - /* As cards are not exposed, it can never be accessed by outside - * users and we can simply remove it. Disabling the card does not - * necessarily drop all pipes of the card. This is usually deferred - * to card destruction (as pipes are cached as long as FDs remain - * open). Therefore, the card destruction might cause pipes, and thus - * visible displays, to be removed. */ - - grdev_card_disable(card); - grdev_card_free(card); -} - -static void session_add_display(grdev_session *session, grdev_display *display) { - assert(session); - assert(display); - assert(!display->enabled); - - log_debug("grdev: %s: add display '%s'", session->name, display->name); - - /* Displays are the main entity for public API users. We create them - * independent of card backends and they wrap any underlying display - * architecture. Displays are public at all times, thus, may be entered - * by outside users at any time. */ - - display->public = true; - session_raise_display_add(session, display); -} - -static void session_remove_display(grdev_session *session, grdev_display *display) { - assert(session); - assert(display); - - log_debug("grdev: %s: remove display '%s'", session->name, display->name); - - /* Displays are public, so we have to be careful when removing them. - * We first tell users about their removal, disable them and then drop - * them. We now, after the notification, no external access will - * happen. Therefore, we can release the tiles afterwards safely. */ - - if (display->public) { - display->public = false; - session_raise_display_remove(session, display); - } - - grdev_display_disable(display); - grdev_display_free(display); -} - -static void session_change_display(grdev_session *session, grdev_display *display) { - bool changed; - - assert(session); - assert(display); - - changed = display_cache(display); - - if (display->n_leafs == 0) { - session_remove_display(session, display); - } else if (!display->public) { - session_add_display(session, display); - session_frame(session, display); - } else if (changed) { - session_raise_display_change(session, display); - session_frame(session, display); - } else if (display->framed) { - session_frame(session, display); - } -} - -static void session_frame(grdev_session *session, grdev_display *display) { - assert(session); - assert(display); - - display->framed = false; - - if (!display->enabled || !session->enabled) - return; - - if (session->n_pins > 0) - display->framed = true; - else - session_raise_display_frame(session, display); -} - -int grdev_session_new(grdev_session **out, - grdev_context *context, - unsigned int flags, - const char *name, - grdev_event_fn event_fn, - void *userdata) { - _cleanup_(grdev_session_freep) grdev_session *session = NULL; - int r; - - assert(out); - assert(context); - assert(name); - assert(event_fn); - assert_return(session_id_valid(name) == !(flags & GRDEV_SESSION_CUSTOM), -EINVAL); - assert_return(!(flags & GRDEV_SESSION_CUSTOM) || !(flags & GRDEV_SESSION_MANAGED), -EINVAL); - assert_return(!(flags & GRDEV_SESSION_MANAGED) || context->sysbus, -EINVAL); - - session = new0(grdev_session, 1); - if (!session) - return -ENOMEM; - - session->context = grdev_context_ref(context); - session->custom = flags & GRDEV_SESSION_CUSTOM; - session->managed = flags & GRDEV_SESSION_MANAGED; - session->event_fn = event_fn; - session->userdata = userdata; - - session->name = strdup(name); - if (!session->name) - return -ENOMEM; - - if (session->managed) { - r = sd_bus_path_encode("/org/freedesktop/login1/session", - session->name, &session->path); - if (r < 0) - return r; - } - - session->card_map = hashmap_new(&string_hash_ops); - if (!session->card_map) - return -ENOMEM; - - session->display_map = hashmap_new(&string_hash_ops); - if (!session->display_map) - return -ENOMEM; - - r = hashmap_put(context->session_map, session->name, session); - if (r < 0) - return r; - - *out = session; - session = NULL; - return 0; -} - -grdev_session *grdev_session_free(grdev_session *session) { - grdev_card *card; - - if (!session) - return NULL; - - grdev_session_disable(session); - - while ((card = hashmap_first(session->card_map))) - session_remove_card(session, card); - - assert(hashmap_size(session->display_map) == 0); - - if (session->name) - hashmap_remove_value(session->context->session_map, session->name, session); - - hashmap_free(session->display_map); - hashmap_free(session->card_map); - session->context = grdev_context_unref(session->context); - free(session->path); - free(session->name); - free(session); - - return NULL; -} - -bool grdev_session_is_enabled(grdev_session *session) { - return session && session->enabled; -} - -void grdev_session_enable(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (!session->enabled) { - session->enabled = true; - HASHMAP_FOREACH(card, session->card_map, iter) - grdev_card_enable(card); - } -} - -void grdev_session_disable(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (session->enabled) { - session->enabled = false; - HASHMAP_FOREACH(card, session->card_map, iter) - grdev_card_disable(card); - } -} - -void grdev_session_commit(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (!session->enabled) - return; - - HASHMAP_FOREACH(card, session->card_map, iter) - if (card->vtable->commit) - card->vtable->commit(card); -} - -void grdev_session_restore(grdev_session *session) { - grdev_card *card; - Iterator iter; - - assert(session); - - if (!session->enabled) - return; - - HASHMAP_FOREACH(card, session->card_map, iter) - if (card->vtable->restore) - card->vtable->restore(card); -} - -void grdev_session_add_drm(grdev_session *session, struct udev_device *ud) { - grdev_card *card; - dev_t devnum; - int r; - - assert(session); - assert(ud); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return grdev_session_hotplug_drm(session, ud); - - card = grdev_find_drm_card(session, devnum); - if (card) - return; - - r = grdev_drm_card_new(&card, session, ud); - if (r < 0) { - log_debug_errno(r, "grdev: %s: cannot add DRM device for %s: %m", - session->name, udev_device_get_syspath(ud)); - return; - } - - session_add_card(session, card); -} - -void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud) { - grdev_card *card; - dev_t devnum; - - assert(session); - assert(ud); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return grdev_session_hotplug_drm(session, ud); - - card = grdev_find_drm_card(session, devnum); - if (!card) - return; - - session_remove_card(session, card); -} - -void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud) { - grdev_card *card = NULL; - struct udev_device *p; - dev_t devnum; - - assert(session); - assert(ud); - - for (p = ud; p; p = udev_device_get_parent_with_subsystem_devtype(p, "drm", NULL)) { - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - continue; - - card = grdev_find_drm_card(session, devnum); - if (card) - break; - } - - if (!card) - return; - - grdev_drm_card_hotplug(card, ud); -} - -static void session_configure(grdev_session *session) { - grdev_display *display; - grdev_tile *tile; - grdev_card *card; - grdev_pipe *pipe; - Iterator i, j; - int r; - - assert(session); - - /* - * Whenever backends add or remove pipes, we set session->modified and - * require them to pin the session while modifying it. On release, we - * reconfigure the device and re-assign displays to all modified pipes. - * - * So far, we configure each pipe as a separate display. We do not - * support user-configuration, nor have we gotten any reports from - * users with multi-pipe monitors (4k on DP-1.2 MST and so on). Until - * we get reports, we keep the logic to a minimum. - */ - - /* create new displays for all unconfigured pipes */ - HASHMAP_FOREACH(card, session->card_map, i) { - if (!card->modified) - continue; - - card->modified = false; - - HASHMAP_FOREACH(pipe, card->pipe_map, j) { - tile = pipe->tile; - if (tile->display) - continue; - - assert(!tile->parent); - - display = grdev_find_display(session, pipe->name); - if (display && display->tile) { - log_debug("grdev: %s/%s: occupied display for pipe %s", - session->name, card->name, pipe->name); - continue; - } else if (!display) { - r = grdev_display_new(&display, session, pipe->name); - if (r < 0) { - log_debug_errno(r, "grdev: %s/%s: cannot create display for pipe %s: %m", - session->name, card->name, pipe->name); - continue; - } - } - - tile_link(pipe->tile, display->tile); - } - } - - /* update displays */ - HASHMAP_FOREACH(display, session->display_map, i) - session_change_display(session, display); -} - -grdev_session *grdev_session_pin(grdev_session *session) { - assert(session); - - ++session->n_pins; - return session; -} - -grdev_session *grdev_session_unpin(grdev_session *session) { - if (!session) - return NULL; - - assert(session->n_pins > 0); - - if (--session->n_pins == 0) - session_configure(session); - - return NULL; -} - -/* - * Contexts - */ - -int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus) { - _cleanup_(grdev_context_unrefp) grdev_context *context = NULL; - - assert_return(out, -EINVAL); - assert_return(event, -EINVAL); - - context = new0(grdev_context, 1); - if (!context) - return -ENOMEM; - - context->ref = 1; - context->event = sd_event_ref(event); - - if (sysbus) - context->sysbus = sd_bus_ref(sysbus); - - context->session_map = hashmap_new(&string_hash_ops); - if (!context->session_map) - return -ENOMEM; - - *out = context; - context = NULL; - return 0; -} - -static void context_cleanup(grdev_context *context) { - assert(hashmap_size(context->session_map) == 0); - - hashmap_free(context->session_map); - context->sysbus = sd_bus_unref(context->sysbus); - context->event = sd_event_unref(context->event); - free(context); -} - -grdev_context *grdev_context_ref(grdev_context *context) { - assert_return(context, NULL); - assert_return(context->ref > 0, NULL); - - ++context->ref; - return context; -} - -grdev_context *grdev_context_unref(grdev_context *context) { - if (!context) - return NULL; - - assert_return(context->ref > 0, NULL); - - if (--context->ref == 0) - context_cleanup(context); - - return NULL; -} diff --git a/src/libsystemd-terminal/grdev.h b/src/libsystemd-terminal/grdev.h deleted file mode 100644 index 110d24e6d5..0000000000 --- a/src/libsystemd-terminal/grdev.h +++ /dev/null @@ -1,199 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Graphics Devices - * The grdev layer provides generic access to graphics devices. The device - * types are hidden in the implementation and exported in a generic way. The - * grdev_session object forms the base layer. It loads, configures and prepares - * any graphics devices associated with that session. Each session is totally - * independent of other sessions and can be controlled separately. - * The target devices on a session are called display. A display always - * corresponds to a real display regardless how many pipes are needed to drive - * that display. That is, an exported display might internally be created out - * of arbitrary combinations of target pipes. However, this is meant as - * implementation detail and API users must never assume details below the - * display-level. That is, a display is the most low-level object exported. - * Therefore, pipe-configuration and any low-level modesetting is hidden from - * the public API. It is provided by the implementation, and it is the - * implementation that decides how pipes are driven. - * - * The API users are free to ignore specific displays or combine them to create - * larger screens. This often requires user-configuration so is dictated by - * policy. The underlying pipe-configuration might be affected by these - * high-level policies, but is never directly controlled by those. That means, - * depending on the displays you use, it might affect how underlying resources - * are assigned. However, users can never directly apply policies to the pipes, - * but only to displays. In case specific hardware needs quirks on the pipe - * level, we support that via hwdb, not via public user configuration. - * - * Right now, displays are limited to rgb32 memory-mapped framebuffers on the - * primary plane. However, the grdev implementation can be easily extended to - * allow more powerful access (including hardware-acceleration for 2D and 3D - * compositing). So far, this wasn't needed so it is not exposed. - */ - -#pragma once - -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "util.h" - -typedef struct grdev_fb grdev_fb; -typedef struct grdev_display_target grdev_display_target; -typedef struct grdev_display grdev_display; - -typedef struct grdev_event grdev_event; -typedef struct grdev_session grdev_session; -typedef struct grdev_context grdev_context; - -enum { - /* clockwise rotation; we treat this is abelian group Z4 with ADD */ - GRDEV_ROTATE_0 = 0, - GRDEV_ROTATE_90 = 1, - GRDEV_ROTATE_180 = 2, - GRDEV_ROTATE_270 = 3, -}; - -enum { - /* flip states; we treat this as abelian group V4 with XOR */ - GRDEV_FLIP_NONE = 0x0, - GRDEV_FLIP_HORIZONTAL = 0x1, - GRDEV_FLIP_VERTICAL = 0x2, -}; - -/* - * Displays - */ - -struct grdev_fb { - uint32_t width; - uint32_t height; - uint32_t format; - int32_t strides[4]; - void *maps[4]; - - union { - void *ptr; - uint64_t u64; - } data; - - void (*free_fn) (void *ptr); -}; - -struct grdev_display_target { - uint32_t x; - uint32_t y; - uint32_t width; - uint32_t height; - unsigned int rotate; - unsigned int flip; - grdev_fb *front; - grdev_fb *back; -}; - -void grdev_display_set_userdata(grdev_display *display, void *userdata); -void *grdev_display_get_userdata(grdev_display *display); - -const char *grdev_display_get_name(grdev_display *display); -uint32_t grdev_display_get_width(grdev_display *display); -uint32_t grdev_display_get_height(grdev_display *display); - -bool grdev_display_is_enabled(grdev_display *display); -void grdev_display_enable(grdev_display *display); -void grdev_display_disable(grdev_display *display); - -const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev); -void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target); - -#define GRDEV_DISPLAY_FOREACH_TARGET(_display, _t) \ - for ((_t) = grdev_display_next_target((_display), NULL); \ - (_t); \ - (_t) = grdev_display_next_target((_display), (_t))) - -/* - * Events - */ - -enum { - GRDEV_EVENT_DISPLAY_ADD, - GRDEV_EVENT_DISPLAY_REMOVE, - GRDEV_EVENT_DISPLAY_CHANGE, - GRDEV_EVENT_DISPLAY_FRAME, -}; - -typedef void (*grdev_event_fn) (grdev_session *session, void *userdata, grdev_event *ev); - -struct grdev_event { - unsigned int type; - union { - struct { - grdev_display *display; - } display_add, display_remove, display_change; - - struct { - grdev_display *display; - } display_frame; - }; -}; - -/* - * Sessions - */ - -enum { - GRDEV_SESSION_CUSTOM = (1 << 0), - GRDEV_SESSION_MANAGED = (1 << 1), -}; - -int grdev_session_new(grdev_session **out, - grdev_context *context, - unsigned int flags, - const char *name, - grdev_event_fn event_fn, - void *userdata); -grdev_session *grdev_session_free(grdev_session *session); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_session*, grdev_session_free); - -bool grdev_session_is_enabled(grdev_session *session); -void grdev_session_enable(grdev_session *session); -void grdev_session_disable(grdev_session *session); - -void grdev_session_commit(grdev_session *session); -void grdev_session_restore(grdev_session *session); - -void grdev_session_add_drm(grdev_session *session, struct udev_device *ud); -void grdev_session_remove_drm(grdev_session *session, struct udev_device *ud); -void grdev_session_hotplug_drm(grdev_session *session, struct udev_device *ud); - -/* - * Contexts - */ - -int grdev_context_new(grdev_context **out, sd_event *event, sd_bus *sysbus); -grdev_context *grdev_context_ref(grdev_context *context); -grdev_context *grdev_context_unref(grdev_context *context); - -DEFINE_TRIVIAL_CLEANUP_FUNC(grdev_context*, grdev_context_unref); diff --git a/src/libsystemd-terminal/idev-evdev.c b/src/libsystemd-terminal/idev-evdev.c deleted file mode 100644 index f1a18b91d3..0000000000 --- a/src/libsystemd-terminal/idev-evdev.c +++ /dev/null @@ -1,859 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "macro.h" -#include "util.h" -#include "bus-util.h" -#include "idev.h" -#include "idev-internal.h" - -typedef struct idev_evdev idev_evdev; -typedef struct unmanaged_evdev unmanaged_evdev; -typedef struct managed_evdev managed_evdev; - -struct idev_evdev { - idev_element element; - struct libevdev *evdev; - int fd; - sd_event_source *fd_src; - sd_event_source *idle_src; - - bool unsync : 1; /* not in-sync with kernel */ - bool resync : 1; /* re-syncing with kernel */ - bool running : 1; -}; - -struct unmanaged_evdev { - idev_evdev evdev; - char *devnode; -}; - -struct managed_evdev { - idev_evdev evdev; - dev_t devnum; - sd_bus_slot *slot_take_device; - - bool requested : 1; /* TakeDevice() was sent */ - bool acquired : 1; /* TakeDevice() was successful */ -}; - -#define idev_evdev_from_element(_e) container_of((_e), idev_evdev, element) -#define unmanaged_evdev_from_element(_e) \ - container_of(idev_evdev_from_element(_e), unmanaged_evdev, evdev) -#define managed_evdev_from_element(_e) \ - container_of(idev_evdev_from_element(_e), managed_evdev, evdev) - -#define IDEV_EVDEV_INIT(_vtable, _session) ((idev_evdev){ \ - .element = IDEV_ELEMENT_INIT((_vtable), (_session)), \ - .fd = -1, \ - }) - -#define IDEV_EVDEV_NAME_MAX (8 + DECIMAL_STR_MAX(unsigned) * 2) - -static const idev_element_vtable unmanaged_evdev_vtable; -static const idev_element_vtable managed_evdev_vtable; - -static int idev_evdev_resume(idev_evdev *evdev, int dev_fd); -static void idev_evdev_pause(idev_evdev *evdev, bool release); - -/* - * Virtual Evdev Element - * The virtual evdev element is the base class of all other evdev elements. It - * uses libevdev to access the kernel evdev API. It supports asynchronous - * access revocation, re-syncing if events got dropped and more. - * This element cannot be used by itself. There must be a wrapper around it - * which opens a file-descriptor and passes it to the virtual evdev element. - */ - -static void idev_evdev_name(char *out, dev_t devnum) { - /* @out must be at least of size IDEV_EVDEV_NAME_MAX */ - sprintf(out, "evdev/%u:%u", major(devnum), minor(devnum)); -} - -static int idev_evdev_feed_resync(idev_evdev *evdev) { - idev_data data = { - .type = IDEV_DATA_RESYNC, - .resync = evdev->resync, - }; - - return idev_element_feed(&evdev->element, &data); -} - -static int idev_evdev_feed_evdev(idev_evdev *evdev, struct input_event *event) { - idev_data data = { - .type = IDEV_DATA_EVDEV, - .resync = evdev->resync, - .evdev = { - .event = *event, - }, - }; - - return idev_element_feed(&evdev->element, &data); -} - -static void idev_evdev_hup(idev_evdev *evdev) { - /* - * On HUP, we close the current fd via idev_evdev_pause(). This drops - * the event-sources from the main-loop and effectively puts the - * element asleep. If the HUP is part of a hotplug-event, a following - * udev-notification will destroy the element. Otherwise, the HUP is - * either result of access-revokation or a serious error. - * For unmanaged devices, we should never receive HUP (except for - * unplug-events). But if we do, something went seriously wrong and we - * shouldn't try to be clever. - * Instead, we simply stay asleep and wait for the device to be - * disabled and then re-enabled (or closed and re-opened). This will - * re-open the device node and restart the device. - * For managed devices, a HUP usually means our device-access was - * revoked. In that case, we simply put the device asleep and wait for - * logind to notify us once the device is alive again. logind also - * passes us a new fd. Hence, we don't have to re-enable the device. - * - * Long story short: The only thing we have to do here, is close() the - * file-descriptor and remove it from the main-loop. Everything else is - * handled via additional events we receive. - */ - - idev_evdev_pause(evdev, true); -} - -static int idev_evdev_io(idev_evdev *evdev) { - idev_element *e = &evdev->element; - struct input_event ev; - unsigned int flags; - int r, error = 0; - - /* - * Read input-events via libevdev until the input-queue is drained. In - * case we're disabled, don't do anything. The input-queue might - * overflow, but we don't care as we have to resync after wake-up, - * anyway. - * TODO: libevdev should give us a hint how many events to read. We - * really want to avoid starvation, so we shouldn't read forever in - * case we cannot keep up with the kernel. - * TODO: Make sure libevdev always reports SYN_DROPPED to us, regardless - * whether any event was synced afterwards. - */ - - flags = LIBEVDEV_READ_FLAG_NORMAL; - while (e->enabled) { - if (evdev->unsync) { - /* immediately resync, even if in sync right now */ - evdev->unsync = false; - evdev->resync = false; - flags = LIBEVDEV_READ_FLAG_NORMAL; - r = libevdev_next_event(evdev->evdev, flags | LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); - if (r < 0 && r != -EAGAIN) { - r = 0; - goto error; - } else if (r != LIBEVDEV_READ_STATUS_SYNC) { - log_debug("idev-evdev: %s/%s: cannot force resync: %d", - e->session->name, e->name, r); - } - } else { - r = libevdev_next_event(evdev->evdev, flags, &ev); - } - - if (evdev->resync && r == -EAGAIN) { - /* end of re-sync */ - evdev->resync = false; - flags = LIBEVDEV_READ_FLAG_NORMAL; - } else if (r == -EAGAIN) { - /* no data available */ - break; - } else if (r < 0) { - /* read error */ - goto error; - } else if (r == LIBEVDEV_READ_STATUS_SYNC) { - if (evdev->resync) { - /* sync-event */ - r = idev_evdev_feed_evdev(evdev, &ev); - if (r != 0) { - error = r; - break; - } - } else { - /* start of sync */ - evdev->resync = true; - flags = LIBEVDEV_READ_FLAG_SYNC; - r = idev_evdev_feed_resync(evdev); - if (r != 0) { - error = r; - break; - } - } - } else { - /* normal event */ - r = idev_evdev_feed_evdev(evdev, &ev); - if (r != 0) { - error = r; - break; - } - } - } - - if (error < 0) - log_debug_errno(error, "idev-evdev: %s/%s: error on data event: %m", - e->session->name, e->name); - return error; - -error: - idev_evdev_hup(evdev); - return 0; /* idev_evdev_hup() handles the error so discard it */ -} - -static int idev_evdev_event_fn(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - idev_evdev *evdev = userdata; - - /* fetch data as long as EPOLLIN is signalled */ - if (revents & EPOLLIN) - return idev_evdev_io(evdev); - - if (revents & (EPOLLHUP | EPOLLERR)) - idev_evdev_hup(evdev); - - return 0; -} - -static int idev_evdev_idle_fn(sd_event_source *s, void *userdata) { - idev_evdev *evdev = userdata; - - /* - * The idle-event is raised whenever we have to re-sync the libevdev - * state from the kernel. We simply call into idev_evdev_io() which - * flushes the state and re-syncs it if @unsync is set. - * State has to be synced whenever our view of the kernel device is - * out of date. This is the case when we open the device, if the - * kernel's receive buffer overflows, or on other exceptional - * situations. Events during re-syncs must be forwarded to the upper - * layers so they can update their view of the device. However, such - * events must only be handled passively, as they might be out-of-order - * and/or re-ordered. Therefore, we mark them as 'sync' events. - */ - - if (!evdev->unsync) - return 0; - - return idev_evdev_io(evdev); -} - -static void idev_evdev_destroy(idev_evdev *evdev) { - assert(evdev); - assert(evdev->fd < 0); - - libevdev_free(evdev->evdev); - evdev->evdev = NULL; -} - -static void idev_evdev_enable(idev_evdev *evdev) { - assert(evdev); - assert(evdev->fd_src); - assert(evdev->idle_src); - - if (evdev->running) - return; - if (evdev->fd < 0 || evdev->element.n_open < 1 || !evdev->element.enabled) - return; - - evdev->running = true; - sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_ON); - sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_ONESHOT); -} - -static void idev_evdev_disable(idev_evdev *evdev) { - assert(evdev); - assert(evdev->fd_src); - assert(evdev->idle_src); - - if (!evdev->running) - return; - - evdev->running = false; - idev_evdev_feed_resync(evdev); - sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_OFF); - sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_OFF); -} - -static int idev_evdev_resume(idev_evdev *evdev, int dev_fd) { - idev_element *e = &evdev->element; - _cleanup_close_ int fd = dev_fd; - int r, flags; - - if (fd < 0 || evdev->fd == fd) { - fd = -1; - idev_evdev_enable(evdev); - return 0; - } - - idev_evdev_pause(evdev, true); - log_debug("idev-evdev: %s/%s: resume", e->session->name, e->name); - - r = fd_nonblock(fd, true); - if (r < 0) - return r; - - r = fd_cloexec(fd, true); - if (r < 0) - return r; - - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -errno; - - flags &= O_ACCMODE; - if (flags == O_WRONLY) - return -EACCES; - - evdev->element.readable = true; - evdev->element.writable = !(flags & O_RDONLY); - - /* - * TODO: We *MUST* re-sync the device so we get a delta of the changed - * state while we didn't read events from the device. This works just - * fine with libevdev_change_fd(), however, libevdev_new_from_fd() (or - * libevdev_set_fd()) don't pass us events for the initial device - * state. So even if we force a re-sync, we will not get the delta for - * the initial device state. - * We really need to fix libevdev to support that! - */ - if (evdev->evdev) - r = libevdev_change_fd(evdev->evdev, fd); - else - r = libevdev_new_from_fd(fd, &evdev->evdev); - - if (r < 0) - return r; - - r = sd_event_add_io(e->session->context->event, - &evdev->fd_src, - fd, - EPOLLHUP | EPOLLERR | EPOLLIN, - idev_evdev_event_fn, - evdev); - if (r < 0) - return r; - - r = sd_event_add_defer(e->session->context->event, - &evdev->idle_src, - idev_evdev_idle_fn, - evdev); - if (r < 0) { - evdev->fd_src = sd_event_source_unref(evdev->fd_src); - return r; - } - - sd_event_source_set_enabled(evdev->fd_src, SD_EVENT_OFF); - sd_event_source_set_enabled(evdev->idle_src, SD_EVENT_OFF); - - evdev->unsync = true; - evdev->fd = fd; - fd = -1; - - idev_evdev_enable(evdev); - return 0; -} - -static void idev_evdev_pause(idev_evdev *evdev, bool release) { - idev_element *e = &evdev->element; - - if (evdev->fd < 0) - return; - - log_debug("idev-evdev: %s/%s: pause", e->session->name, e->name); - - idev_evdev_disable(evdev); - if (release) { - evdev->idle_src = sd_event_source_unref(evdev->idle_src); - evdev->fd_src = sd_event_source_unref(evdev->fd_src); - evdev->fd = safe_close(evdev->fd); - } -} - -/* - * Unmanaged Evdev Element - * The unmanaged evdev element opens the evdev node for a given input device - * directly (/dev/input/eventX) and thus needs sufficient privileges. It opens - * the device only if we really require it and releases it as soon as we're - * disabled or closed. - * The unmanaged element can be used in all situations where you have direct - * access to input device nodes. Unlike managed evdev elements, it can be used - * outside of user sessions and in emergency situations where logind is not - * available. - */ - -static void unmanaged_evdev_resume(idev_element *e) { - unmanaged_evdev *eu = unmanaged_evdev_from_element(e); - int r, fd; - - /* - * Unmanaged devices can be acquired on-demand. Therefore, don't - * acquire it unless someone opened the device *and* we're enabled. - */ - if (e->n_open < 1 || !e->enabled) - return; - - fd = eu->evdev.fd; - if (fd < 0) { - fd = open(eu->devnode, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - if (errno != EACCES && errno != EPERM) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot open node %s: %m", - e->session->name, e->name, eu->devnode); - return; - } - - fd = open(eu->devnode, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); - if (fd < 0) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot open node %s: %m", - e->session->name, e->name, eu->devnode); - return; - } - - e->readable = true; - e->writable = false; - } else { - e->readable = true; - e->writable = true; - } - } - - r = idev_evdev_resume(&eu->evdev, fd); - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot resume: %m", - e->session->name, e->name); -} - -static void unmanaged_evdev_pause(idev_element *e) { - unmanaged_evdev *eu = unmanaged_evdev_from_element(e); - - /* - * Release the device if the device is disabled or there is no-one who - * opened it. This guarantees we stay only available if we're opened - * *and* enabled. - */ - - idev_evdev_pause(&eu->evdev, true); -} - -static int unmanaged_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { - _cleanup_(idev_element_freep) idev_element *e = NULL; - char name[IDEV_EVDEV_NAME_MAX]; - unmanaged_evdev *eu; - const char *devnode; - dev_t devnum; - int r; - - assert_return(s, -EINVAL); - assert_return(ud, -EINVAL); - - devnode = udev_device_get_devnode(ud); - devnum = udev_device_get_devnum(ud); - if (!devnode || devnum == 0) - return -ENODEV; - - idev_evdev_name(name, devnum); - - eu = new0(unmanaged_evdev, 1); - if (!eu) - return -ENOMEM; - - e = &eu->evdev.element; - eu->evdev = IDEV_EVDEV_INIT(&unmanaged_evdev_vtable, s); - - eu->devnode = strdup(devnode); - if (!eu->devnode) - return -ENOMEM; - - r = idev_element_add(e, name); - if (r < 0) - return r; - - if (out) - *out = e; - e = NULL; - return 0; -} - -static void unmanaged_evdev_free(idev_element *e) { - unmanaged_evdev *eu = unmanaged_evdev_from_element(e); - - idev_evdev_destroy(&eu->evdev); - free(eu->devnode); - free(eu); -} - -static const idev_element_vtable unmanaged_evdev_vtable = { - .free = unmanaged_evdev_free, - .enable = unmanaged_evdev_resume, - .disable = unmanaged_evdev_pause, - .open = unmanaged_evdev_resume, - .close = unmanaged_evdev_pause, -}; - -/* - * Managed Evdev Element - * The managed evdev element uses systemd-logind to acquire evdev devices. This - * means, we do not open the device node /dev/input/eventX directly. Instead, - * logind passes us a file-descriptor whenever our session is activated. Thus, - * we don't need access to the device node directly. - * Furthermore, whenever the session is put asleep, logind revokes the - * file-descriptor so we loose access to the device. - * Managed evdev elements should be preferred over unmanaged elements whenever - * you run inside a user session with exclusive device access. - */ - -static int managed_evdev_take_device_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - managed_evdev *em = userdata; - idev_element *e = &em->evdev.element; - idev_session *s = e->session; - int r, paused, fd; - - em->slot_take_device = sd_bus_slot_unref(em->slot_take_device); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("idev-evdev: %s/%s: TakeDevice failed: %s: %s", - s->name, e->name, error->name, error->message); - return 0; - } - - em->acquired = true; - - r = sd_bus_message_read(reply, "hb", &fd, &paused); - if (r < 0) { - log_debug("idev-evdev: %s/%s: erroneous TakeDevice reply", s->name, e->name); - return 0; - } - - /* If the device is paused, ignore it; we will get the next fd via - * ResumeDevice signals. */ - if (paused) - return 0; - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot duplicate evdev fd: %m", s->name, e->name); - return 0; - } - - r = idev_evdev_resume(&em->evdev, fd); - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot resume: %m", - s->name, e->name); - - return 0; -} - -static void managed_evdev_enable(idev_element *e) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - idev_context *c = s->context; - int r; - - /* - * Acquiring managed devices is heavy, so do it only once we're - * enabled *and* opened by someone. - */ - if (e->n_open < 1 || !e->enabled) - return; - - /* bail out if already pending */ - if (em->requested) - return; - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - s->path, - "org.freedesktop.login1.Session", - "TakeDevice"); - if (r < 0) - goto error; - - r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); - if (r < 0) - goto error; - - r = sd_bus_call_async(c->sysbus, - &em->slot_take_device, - m, - managed_evdev_take_device_fn, - em, - 0); - if (r < 0) - goto error; - - em->requested = true; - return; - -error: - log_debug_errno(r, "idev-evdev: %s/%s: cannot send TakeDevice request: %m", - s->name, e->name); -} - -static void managed_evdev_disable(idev_element *e) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - idev_context *c = s->context; - int r; - - /* - * Releasing managed devices is heavy. Once acquired, we get - * notifications for sleep/wake-up events, so there's no reason to - * release it if disabled but opened. However, if a device is closed, - * we release it immediately as we don't care for sleep/wake-up events - * then (even if we're actually enabled). - */ - - idev_evdev_pause(&em->evdev, false); - - if (e->n_open > 0 || !em->requested) - return; - - /* - * If TakeDevice() is pending or was successful, make sure to - * release the device again. We don't care for return-values, - * so send it without waiting or callbacks. - * If a failed TakeDevice() is pending, but someone else took - * the device on the same bus-connection, we might incorrectly - * release their device. This is an unlikely race, though. - * Furthermore, you really shouldn't have two users of the - * controller-API on the same session, on the same devices, *AND* on - * the same bus-connection. So we don't care for that race.. - */ - - idev_evdev_pause(&em->evdev, true); - em->requested = false; - - if (!em->acquired && !em->slot_take_device) - return; - - em->slot_take_device = sd_bus_slot_unref(em->slot_take_device); - em->acquired = false; - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - s->path, - "org.freedesktop.login1.Session", - "ReleaseDevice"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); - if (r >= 0) - r = sd_bus_send(c->sysbus, m, NULL); - } - - if (r < 0 && r != -ENOTCONN) - log_debug_errno(r, "idev-evdev: %s/%s: cannot send ReleaseDevice: %m", - s->name, e->name); -} - -static void managed_evdev_resume(idev_element *e, int fd) { - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - int r; - - /* - * We get ResumeDevice signals whenever logind resumed a previously - * paused device. The arguments contain the major/minor number of the - * related device and a new file-descriptor for the freshly opened - * device-node. We take the file-descriptor and immediately resume the - * device. - */ - - fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (fd < 0) { - log_debug_errno(errno, "idev-evdev: %s/%s: cannot duplicate evdev fd: %m", - s->name, e->name); - return; - } - - r = idev_evdev_resume(&em->evdev, fd); - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot resume: %m", - s->name, e->name); - - return; -} - -static void managed_evdev_pause(idev_element *e, const char *mode) { - managed_evdev *em = managed_evdev_from_element(e); - idev_session *s = e->session; - idev_context *c = s->context; - int r; - - /* - * We get PauseDevice() signals from logind whenever a device we - * requested was, or is about to be, paused. Arguments are major/minor - * number of the device and the mode of the operation. - * We treat it as asynchronous access-revocation (as if we got HUP on - * the device fd). Note that we might have already treated the HUP - * event via EPOLLHUP, whichever comes first. - * - * @mode can be one of the following: - * "pause": The device is about to be paused. We must react - * immediately and respond with PauseDeviceComplete(). Once - * we replied, logind will pause the device. Note that - * logind might apply any kind of timeout and force pause - * the device if we don't respond in a timely manner. In - * this case, we will receive a second PauseDevice event - * with @mode set to "force" (or similar). - * "force": The device was disabled forecfully by logind. Access is - * already revoked. This is just an asynchronous - * notification so we can put the device asleep (in case - * we didn't already notice the access revocation). - * "gone": This is like "force" but is sent if the device was - * paused due to a device-removal event. - * - * We always handle PauseDevice signals as "force" as we properly - * support asynchronous access revocation, anyway. But in case logind - * sent mode "pause", we also call PauseDeviceComplete() to immediately - * acknowledge the request. - */ - - idev_evdev_pause(&em->evdev, true); - - if (streq(mode, "pause")) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - - /* - * Sending PauseDeviceComplete() is racy if logind triggers the - * timeout. That is, if we take too long and logind pauses the - * device by sending a forced PauseDevice, our - * PauseDeviceComplete call will be stray. That's fine, though. - * logind ignores such stray calls. Only if logind also sent a - * further PauseDevice() signal, it might match our call - * incorrectly to the newer PauseDevice(). That's fine, too, as - * we handle that event asynchronously, anyway. Therefore, - * whatever happens, we're fine. Yay! - */ - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - s->path, - "org.freedesktop.login1.Session", - "PauseDeviceComplete"); - if (r >= 0) { - r = sd_bus_message_append(m, "uu", major(em->devnum), minor(em->devnum)); - if (r >= 0) - r = sd_bus_send(c->sysbus, m, NULL); - } - - if (r < 0) - log_debug_errno(r, "idev-evdev: %s/%s: cannot send PauseDeviceComplete: %m", - s->name, e->name); - } -} - -static int managed_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { - _cleanup_(idev_element_freep) idev_element *e = NULL; - char name[IDEV_EVDEV_NAME_MAX]; - managed_evdev *em; - dev_t devnum; - int r; - - assert_return(s, -EINVAL); - assert_return(s->managed, -EINVAL); - assert_return(s->context->sysbus, -EINVAL); - assert_return(ud, -EINVAL); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return -ENODEV; - - idev_evdev_name(name, devnum); - - em = new0(managed_evdev, 1); - if (!em) - return -ENOMEM; - - e = &em->evdev.element; - em->evdev = IDEV_EVDEV_INIT(&managed_evdev_vtable, s); - em->devnum = devnum; - - r = idev_element_add(e, name); - if (r < 0) - return r; - - if (out) - *out = e; - e = NULL; - return 0; -} - -static void managed_evdev_free(idev_element *e) { - managed_evdev *em = managed_evdev_from_element(e); - - idev_evdev_destroy(&em->evdev); - free(em); -} - -static const idev_element_vtable managed_evdev_vtable = { - .free = managed_evdev_free, - .enable = managed_evdev_enable, - .disable = managed_evdev_disable, - .open = managed_evdev_enable, - .close = managed_evdev_disable, - .resume = managed_evdev_resume, - .pause = managed_evdev_pause, -}; - -/* - * Generic Constructor - * Instead of relying on the caller to choose between managed and unmanaged - * evdev devices, the idev_evdev_new() constructor does that for you (by - * looking at s->managed). - */ - -bool idev_is_evdev(idev_element *e) { - return e && (e->vtable == &unmanaged_evdev_vtable || - e->vtable == &managed_evdev_vtable); -} - -idev_element *idev_find_evdev(idev_session *s, dev_t devnum) { - char name[IDEV_EVDEV_NAME_MAX]; - - assert_return(s, NULL); - assert_return(devnum != 0, NULL); - - idev_evdev_name(name, devnum); - return idev_find_element(s, name); -} - -int idev_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud) { - assert_return(s, -EINVAL); - assert_return(ud, -EINVAL); - - return s->managed ? managed_evdev_new(out, s, ud) : unmanaged_evdev_new(out, s, ud); -} diff --git a/src/libsystemd-terminal/idev-internal.h b/src/libsystemd-terminal/idev-internal.h deleted file mode 100644 index a02a16c408..0000000000 --- a/src/libsystemd-terminal/idev-internal.h +++ /dev/null @@ -1,188 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "list.h" -#include "util.h" -#include "idev.h" - -typedef struct idev_link idev_link; -typedef struct idev_device_vtable idev_device_vtable; -typedef struct idev_element idev_element; -typedef struct idev_element_vtable idev_element_vtable; - -/* - * Evdev Elements - */ - -bool idev_is_evdev(idev_element *e); -idev_element *idev_find_evdev(idev_session *s, dev_t devnum); -int idev_evdev_new(idev_element **out, idev_session *s, struct udev_device *ud); - -/* - * Keyboard Devices - */ - -bool idev_is_keyboard(idev_device *d); -idev_device *idev_find_keyboard(idev_session *s, const char *name); -int idev_keyboard_new(idev_device **out, idev_session *s, const char *name); - -/* - * Element Links - */ - -struct idev_link { - /* element-to-device connection */ - LIST_FIELDS(idev_link, links_by_element); - idev_element *element; - - /* device-to-element connection */ - LIST_FIELDS(idev_link, links_by_device); - idev_device *device; -}; - -/* - * Devices - */ - -struct idev_device_vtable { - void (*free) (idev_device *d); - void (*attach) (idev_device *d, idev_link *l); - void (*detach) (idev_device *d, idev_link *l); - int (*feed) (idev_device *d, idev_data *data); -}; - -struct idev_device { - const idev_device_vtable *vtable; - idev_session *session; - char *name; - - LIST_HEAD(idev_link, links); - - bool public : 1; - bool enabled : 1; -}; - -#define IDEV_DEVICE_INIT(_vtable, _session) ((idev_device){ \ - .vtable = (_vtable), \ - .session = (_session), \ - }) - -idev_device *idev_find_device(idev_session *s, const char *name); - -int idev_device_add(idev_device *d, const char *name); -idev_device *idev_device_free(idev_device *d); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_device*, idev_device_free); - -int idev_device_feed(idev_device *d, idev_data *data); -void idev_device_feedback(idev_device *d, idev_data *data); - -/* - * Elements - */ - -struct idev_element_vtable { - void (*free) (idev_element *e); - void (*enable) (idev_element *e); - void (*disable) (idev_element *e); - void (*open) (idev_element *e); - void (*close) (idev_element *e); - void (*resume) (idev_element *e, int fd); - void (*pause) (idev_element *e, const char *mode); - void (*feedback) (idev_element *e, idev_data *data); -}; - -struct idev_element { - const idev_element_vtable *vtable; - idev_session *session; - unsigned long n_open; - char *name; - - LIST_HEAD(idev_link, links); - - bool enabled : 1; - bool readable : 1; - bool writable : 1; -}; - -#define IDEV_ELEMENT_INIT(_vtable, _session) ((idev_element){ \ - .vtable = (_vtable), \ - .session = (_session), \ - }) - -idev_element *idev_find_element(idev_session *s, const char *name); - -int idev_element_add(idev_element *e, const char *name); -idev_element *idev_element_free(idev_element *e); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_element*, idev_element_free); - -int idev_element_feed(idev_element *e, idev_data *data); -void idev_element_feedback(idev_element *e, idev_data *data); - -/* - * Sessions - */ - -struct idev_session { - idev_context *context; - char *name; - char *path; - sd_bus_slot *slot_resume_device; - sd_bus_slot *slot_pause_device; - - Hashmap *element_map; - Hashmap *device_map; - - idev_event_fn event_fn; - void *userdata; - - bool custom : 1; - bool managed : 1; - bool enabled : 1; -}; - -idev_session *idev_find_session(idev_context *c, const char *name); -int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data); - -/* - * Contexts - */ - -struct idev_context { - unsigned long ref; - sd_event *event; - sd_bus *sysbus; - - Hashmap *session_map; - Hashmap *data_map; -}; diff --git a/src/libsystemd-terminal/idev-keyboard.c b/src/libsystemd-terminal/idev-keyboard.c deleted file mode 100644 index 93f49e9458..0000000000 --- a/src/libsystemd-terminal/idev-keyboard.c +++ /dev/null @@ -1,1159 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "macro.h" -#include "util.h" -#include "bus-util.h" -#include "idev.h" -#include "idev-internal.h" -#include "term-internal.h" - -typedef struct kbdtbl kbdtbl; -typedef struct kbdmap kbdmap; -typedef struct kbdctx kbdctx; -typedef struct idev_keyboard idev_keyboard; - -struct kbdtbl { - unsigned long ref; - struct xkb_compose_table *xkb_compose_table; -}; - -struct kbdmap { - unsigned long ref; - struct xkb_keymap *xkb_keymap; - xkb_mod_index_t modmap[IDEV_KBDMOD_CNT]; - xkb_led_index_t ledmap[IDEV_KBDLED_CNT]; -}; - -struct kbdctx { - unsigned long ref; - idev_context *context; - struct xkb_context *xkb_context; - struct kbdmap *kbdmap; - struct kbdtbl *kbdtbl; - - sd_bus_slot *slot_locale_props_changed; - sd_bus_slot *slot_locale_get_all; - - char *locale_lang; - char *locale_x11_model; - char *locale_x11_layout; - char *locale_x11_variant; - char *locale_x11_options; - char *last_x11_model; - char *last_x11_layout; - char *last_x11_variant; - char *last_x11_options; -}; - -struct idev_keyboard { - idev_device device; - kbdctx *kbdctx; - kbdmap *kbdmap; - kbdtbl *kbdtbl; - - struct xkb_state *xkb_state; - struct xkb_compose_state *xkb_compose; - - usec_t repeat_delay; - usec_t repeat_rate; - sd_event_source *repeat_timer; - - uint32_t n_syms; - idev_data evdata; - idev_data repdata; - uint32_t *compose_res; - - bool repeating : 1; -}; - -#define keyboard_from_device(_d) container_of((_d), idev_keyboard, device) - -#define KBDCTX_KEY "keyboard.context" /* hashmap key for global kbdctx */ -#define KBDXKB_SHIFT (8) /* xkb shifts evdev key-codes by 8 */ -#define KBDKEY_UP (0) /* KEY UP event value */ -#define KBDKEY_DOWN (1) /* KEY DOWN event value */ -#define KBDKEY_REPEAT (2) /* KEY REPEAT event value */ - -static const idev_device_vtable keyboard_vtable; - -static int keyboard_update_kbdmap(idev_keyboard *k); -static int keyboard_update_kbdtbl(idev_keyboard *k); - -/* - * Keyboard Compose Tables - */ - -static kbdtbl *kbdtbl_ref(kbdtbl *kt) { - if (kt) { - assert_return(kt->ref > 0, NULL); - ++kt->ref; - } - - return kt; -} - -static kbdtbl *kbdtbl_unref(kbdtbl *kt) { - if (!kt) - return NULL; - - assert_return(kt->ref > 0, NULL); - - if (--kt->ref > 0) - return NULL; - - xkb_compose_table_unref(kt->xkb_compose_table); - free(kt); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(kbdtbl*, kbdtbl_unref); - -static int kbdtbl_new_from_locale(kbdtbl **out, kbdctx *kc, const char *locale) { - _cleanup_(kbdtbl_unrefp) kbdtbl *kt = NULL; - - assert_return(out, -EINVAL); - assert_return(locale, -EINVAL); - - kt = new0(kbdtbl, 1); - if (!kt) - return -ENOMEM; - - kt->ref = 1; - - kt->xkb_compose_table = xkb_compose_table_new_from_locale(kc->xkb_context, - locale, - XKB_COMPOSE_COMPILE_NO_FLAGS); - if (!kt->xkb_compose_table) - return errno > 0 ? -errno : -EFAULT; - - *out = kt; - kt = NULL; - return 0; -} - -/* - * Keyboard Keymaps - */ - -static const char * const kbdmap_modmap[IDEV_KBDMOD_CNT] = { - [IDEV_KBDMOD_IDX_SHIFT] = XKB_MOD_NAME_SHIFT, - [IDEV_KBDMOD_IDX_CTRL] = XKB_MOD_NAME_CTRL, - [IDEV_KBDMOD_IDX_ALT] = XKB_MOD_NAME_ALT, - [IDEV_KBDMOD_IDX_LINUX] = XKB_MOD_NAME_LOGO, - [IDEV_KBDMOD_IDX_CAPS] = XKB_MOD_NAME_CAPS, -}; - -static const char * const kbdmap_ledmap[IDEV_KBDLED_CNT] = { - [IDEV_KBDLED_IDX_NUM] = XKB_LED_NAME_NUM, - [IDEV_KBDLED_IDX_CAPS] = XKB_LED_NAME_CAPS, - [IDEV_KBDLED_IDX_SCROLL] = XKB_LED_NAME_SCROLL, -}; - -static kbdmap *kbdmap_ref(kbdmap *km) { - assert_return(km, NULL); - assert_return(km->ref > 0, NULL); - - ++km->ref; - return km; -} - -static kbdmap *kbdmap_unref(kbdmap *km) { - if (!km) - return NULL; - - assert_return(km->ref > 0, NULL); - - if (--km->ref > 0) - return NULL; - - xkb_keymap_unref(km->xkb_keymap); - free(km); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(kbdmap*, kbdmap_unref); - -static int kbdmap_new_from_names(kbdmap **out, - kbdctx *kc, - const char *model, - const char *layout, - const char *variant, - const char *options) { - _cleanup_(kbdmap_unrefp) kbdmap *km = NULL; - struct xkb_rule_names rmlvo = { }; - unsigned int i; - - assert_return(out, -EINVAL); - - km = new0(kbdmap, 1); - if (!km) - return -ENOMEM; - - km->ref = 1; - - rmlvo.rules = "evdev"; - rmlvo.model = model; - rmlvo.layout = layout; - rmlvo.variant = variant; - rmlvo.options = options; - - errno = 0; - km->xkb_keymap = xkb_keymap_new_from_names(kc->xkb_context, &rmlvo, 0); - if (!km->xkb_keymap) - return errno > 0 ? -errno : -EFAULT; - - for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { - const char *t = kbdmap_modmap[i]; - - if (t) - km->modmap[i] = xkb_keymap_mod_get_index(km->xkb_keymap, t); - else - km->modmap[i] = XKB_MOD_INVALID; - } - - for (i = 0; i < IDEV_KBDLED_CNT; ++i) { - const char *t = kbdmap_ledmap[i]; - - if (t) - km->ledmap[i] = xkb_keymap_led_get_index(km->xkb_keymap, t); - else - km->ledmap[i] = XKB_LED_INVALID; - } - - *out = km; - km = NULL; - return 0; -} - -/* - * Keyboard Context - */ - -static int kbdctx_refresh_compose_table(kbdctx *kc, const char *lang) { - kbdtbl *kt; - idev_session *s; - idev_device *d; - Iterator i, j; - int r; - - if (!lang) - lang = "C"; - - if (streq_ptr(kc->locale_lang, lang)) - return 0; - - r = free_and_strdup(&kc->locale_lang, lang); - if (r < 0) - return r; - - log_debug("idev-keyboard: new default compose table: [ %s ]", lang); - - r = kbdtbl_new_from_locale(&kt, kc, lang); - if (r < 0) { - /* TODO: We need to catch the case where no compose-file is - * available. xkb doesn't tell us so far.. so we must not treat - * it as a hard-failure but just continue. Preferably, we want - * xkb to tell us exactly whether compilation failed or whether - * there is no compose file available for this locale. */ - log_debug_errno(r, "idev-keyboard: cannot load compose-table for '%s': %m", - lang); - r = 0; - kt = NULL; - } - - kbdtbl_unref(kc->kbdtbl); - kc->kbdtbl = kt; - - HASHMAP_FOREACH(s, kc->context->session_map, i) - HASHMAP_FOREACH(d, s->device_map, j) - if (idev_is_keyboard(d)) - keyboard_update_kbdtbl(keyboard_from_device(d)); - - return 0; -} - -static void move_str(char **dest, char **src) { - free(*dest); - *dest = *src; - *src = NULL; -} - -static int kbdctx_refresh_keymap(kbdctx *kc) { - idev_session *s; - idev_device *d; - Iterator i, j; - kbdmap *km; - int r; - - if (kc->kbdmap && - streq_ptr(kc->locale_x11_model, kc->last_x11_model) && - streq_ptr(kc->locale_x11_layout, kc->last_x11_layout) && - streq_ptr(kc->locale_x11_variant, kc->last_x11_variant) && - streq_ptr(kc->locale_x11_options, kc->last_x11_options)) - return 0 ; - - move_str(&kc->last_x11_model, &kc->locale_x11_model); - move_str(&kc->last_x11_layout, &kc->locale_x11_layout); - move_str(&kc->last_x11_variant, &kc->locale_x11_variant); - move_str(&kc->last_x11_options, &kc->locale_x11_options); - - log_debug("idev-keyboard: new default keymap: [%s / %s / %s / %s]", - kc->last_x11_model, kc->last_x11_layout, kc->last_x11_variant, kc->last_x11_options); - - /* TODO: add a fallback keymap that's compiled-in */ - r = kbdmap_new_from_names(&km, kc, kc->last_x11_model, kc->last_x11_layout, - kc->last_x11_variant, kc->last_x11_options); - if (r < 0) - return log_debug_errno(r, "idev-keyboard: cannot create keymap from locale1: %m"); - - kbdmap_unref(kc->kbdmap); - kc->kbdmap = km; - - HASHMAP_FOREACH(s, kc->context->session_map, i) - HASHMAP_FOREACH(d, s->device_map, j) - if (idev_is_keyboard(d)) - keyboard_update_kbdmap(keyboard_from_device(d)); - - return 0; -} - -static int kbdctx_set_locale(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - kbdctx *kc = userdata; - const char *s, *ctype = NULL, *lang = NULL; - int r; - - r = sd_bus_message_enter_container(m, 'a', "s"); - if (r < 0) - goto error; - - while ((r = sd_bus_message_read(m, "s", &s)) > 0) { - if (!ctype) - ctype = startswith(s, "LC_CTYPE="); - if (!lang) - lang = startswith(s, "LANG="); - } - - if (r < 0) - goto error; - - r = sd_bus_message_exit_container(m); - if (r < 0) - goto error; - - kbdctx_refresh_compose_table(kc, ctype ? : lang); - r = 0; - -error: - if (r < 0) - log_debug_errno(r, "idev-keyboard: cannot parse locale property from locale1: %m"); - - return r; -} - -static const struct bus_properties_map kbdctx_locale_map[] = { - { "Locale", "as", kbdctx_set_locale, 0 }, - { "X11Model", "s", NULL, offsetof(kbdctx, locale_x11_model) }, - { "X11Layout", "s", NULL, offsetof(kbdctx, locale_x11_layout) }, - { "X11Variant", "s", NULL, offsetof(kbdctx, locale_x11_variant) }, - { "X11Options", "s", NULL, offsetof(kbdctx, locale_x11_options) }, - { }, -}; - -static int kbdctx_locale_get_all_fn(sd_bus_message *m, - void *userdata, - sd_bus_error *ret_err) { - kbdctx *kc = userdata; - int r; - - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - - if (sd_bus_message_is_method_error(m, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(m); - - log_debug("idev-keyboard: GetAll() on locale1 failed: %s: %s", - error->name, error->message); - return 0; - } - - r = bus_message_map_all_properties(m, kbdctx_locale_map, kc); - if (r < 0) { - log_debug("idev-keyboard: erroneous GetAll() reply from locale1"); - return 0; - } - - kbdctx_refresh_keymap(kc); - return 0; -} - -static int kbdctx_query_locale(kbdctx *kc) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - - r = sd_bus_message_new_method_call(kc->context->sysbus, - &m, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - "org.freedesktop.DBus.Properties", - "GetAll"); - if (r < 0) - goto error; - - r = sd_bus_message_append(m, "s", "org.freedesktop.locale1"); - if (r < 0) - goto error; - - r = sd_bus_call_async(kc->context->sysbus, - &kc->slot_locale_get_all, - m, - kbdctx_locale_get_all_fn, - kc, - 0); - if (r < 0) - goto error; - - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: cannot send GetAll to locale1: %m"); -} - -static int kbdctx_locale_props_changed_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_err) { - kbdctx *kc = userdata; - int r; - - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - - /* skip interface name */ - r = sd_bus_message_skip(signal, "s"); - if (r < 0) - goto error; - - r = bus_message_map_properties_changed(signal, kbdctx_locale_map, kc); - if (r < 0) - goto error; - - if (r > 0) { - r = kbdctx_query_locale(kc); - if (r < 0) - return r; - } - - kbdctx_refresh_keymap(kc); - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: cannot handle PropertiesChanged from locale1: %m"); -} - -static int kbdctx_setup_bus(kbdctx *kc) { - int r; - - r = sd_bus_add_match(kc->context->sysbus, - &kc->slot_locale_props_changed, - "type='signal'," - "sender='org.freedesktop.locale1'," - "interface='org.freedesktop.DBus.Properties'," - "member='PropertiesChanged'," - "path='/org/freedesktop/locale1'", - kbdctx_locale_props_changed_fn, - kc); - if (r < 0) - return log_debug_errno(r, "idev-keyboard: cannot setup locale1 link: %m"); - - return kbdctx_query_locale(kc); -} - -static void kbdctx_log_fn(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { - char buf[LINE_MAX]; - int sd_lvl; - - if (lvl >= XKB_LOG_LEVEL_DEBUG) - sd_lvl = LOG_DEBUG; - else if (lvl >= XKB_LOG_LEVEL_INFO) - sd_lvl = LOG_INFO; - else if (lvl >= XKB_LOG_LEVEL_WARNING) - sd_lvl = LOG_INFO; /* most XKB warnings really are informational */ - else - /* XKB_LOG_LEVEL_ERROR and worse */ - sd_lvl = LOG_ERR; - - snprintf(buf, sizeof(buf), "idev-xkb: %s", format); - log_internalv(sd_lvl, 0, __FILE__, __LINE__, __func__, buf, args); -} - -static kbdctx *kbdctx_ref(kbdctx *kc) { - assert_return(kc, NULL); - assert_return(kc->ref > 0, NULL); - - ++kc->ref; - return kc; -} - -static kbdctx *kbdctx_unref(kbdctx *kc) { - if (!kc) - return NULL; - - assert_return(kc->ref > 0, NULL); - - if (--kc->ref > 0) - return NULL; - - free(kc->last_x11_options); - free(kc->last_x11_variant); - free(kc->last_x11_layout); - free(kc->last_x11_model); - free(kc->locale_x11_options); - free(kc->locale_x11_variant); - free(kc->locale_x11_layout); - free(kc->locale_x11_model); - free(kc->locale_lang); - kc->slot_locale_get_all = sd_bus_slot_unref(kc->slot_locale_get_all); - kc->slot_locale_props_changed = sd_bus_slot_unref(kc->slot_locale_props_changed); - kc->kbdtbl = kbdtbl_unref(kc->kbdtbl); - kc->kbdmap = kbdmap_unref(kc->kbdmap); - xkb_context_unref(kc->xkb_context); - hashmap_remove_value(kc->context->data_map, KBDCTX_KEY, kc); - free(kc); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(kbdctx*, kbdctx_unref); - -static int kbdctx_new(kbdctx **out, idev_context *c) { - _cleanup_(kbdctx_unrefp) kbdctx *kc = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(c, -EINVAL); - - kc = new0(kbdctx, 1); - if (!kc) - return -ENOMEM; - - kc->ref = 1; - kc->context = c; - - errno = 0; - kc->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - if (!kc->xkb_context) - return errno > 0 ? -errno : -EFAULT; - - xkb_context_set_log_fn(kc->xkb_context, kbdctx_log_fn); - xkb_context_set_log_level(kc->xkb_context, XKB_LOG_LEVEL_DEBUG); - - r = kbdctx_refresh_keymap(kc); - if (r < 0) - return r; - - r = kbdctx_refresh_compose_table(kc, NULL); - if (r < 0) - return r; - - if (c->sysbus) { - r = kbdctx_setup_bus(kc); - if (r < 0) - return r; - } - - r = hashmap_put(c->data_map, KBDCTX_KEY, kc); - if (r < 0) - return r; - - *out = kc; - kc = NULL; - return 0; -} - -static int get_kbdctx(idev_context *c, kbdctx **out) { - kbdctx *kc; - - assert_return(c, -EINVAL); - assert_return(out, -EINVAL); - - kc = hashmap_get(c->data_map, KBDCTX_KEY); - if (kc) { - *out = kbdctx_ref(kc); - return 0; - } - - return kbdctx_new(out, c); -} - -/* - * Keyboard Devices - */ - -bool idev_is_keyboard(idev_device *d) { - return d && d->vtable == &keyboard_vtable; -} - -idev_device *idev_find_keyboard(idev_session *s, const char *name) { - char *kname; - - assert_return(s, NULL); - assert_return(name, NULL); - - kname = strjoina("keyboard/", name); - return hashmap_get(s->device_map, kname); -} - -static int keyboard_raise_data(idev_keyboard *k, idev_data *data) { - idev_device *d = &k->device; - int r; - - r = idev_session_raise_device_data(d->session, d, data); - if (r < 0) - log_debug_errno(r, "idev-keyboard: %s/%s: error while raising data event: %m", - d->session->name, d->name); - - return r; -} - -static int keyboard_resize_bufs(idev_keyboard *k, uint32_t n_syms) { - uint32_t *t; - - if (n_syms <= k->n_syms) - return 0; - - t = realloc(k->compose_res, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->compose_res = t; - - t = realloc(k->evdata.keyboard.keysyms, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->evdata.keyboard.keysyms = t; - - t = realloc(k->evdata.keyboard.codepoints, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->evdata.keyboard.codepoints = t; - - t = realloc(k->repdata.keyboard.keysyms, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->repdata.keyboard.keysyms = t; - - t = realloc(k->repdata.keyboard.codepoints, sizeof(*t) * n_syms); - if (!t) - return -ENOMEM; - k->repdata.keyboard.codepoints = t; - - k->n_syms = n_syms; - return 0; -} - -static unsigned int keyboard_read_compose(idev_keyboard *k, const xkb_keysym_t **out) { - _cleanup_free_ char *t = NULL; - term_utf8 u8 = { }; - char buf[256], *p; - size_t flen = 0; - int i, r; - - r = xkb_compose_state_get_utf8(k->xkb_compose, buf, sizeof(buf)); - if (r >= (int)sizeof(buf)) { - t = malloc(r + 1); - if (!t) - return 0; - - xkb_compose_state_get_utf8(k->xkb_compose, t, r + 1); - p = t; - } else { - p = buf; - } - - for (i = 0; i < r; ++i) { - uint32_t *ucs; - size_t len, j; - - len = term_utf8_decode(&u8, &ucs, p[i]); - if (len > 0) { - r = keyboard_resize_bufs(k, flen + len); - if (r < 0) - return 0; - - for (j = 0; j < len; ++j) - k->compose_res[flen++] = ucs[j]; - } - } - - *out = k->compose_res; - return flen; -} - -static void keyboard_arm(idev_keyboard *k, usec_t usecs) { - int r; - - if (usecs != 0) { - usecs += now(CLOCK_MONOTONIC); - r = sd_event_source_set_time(k->repeat_timer, usecs); - if (r >= 0) - sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_ONESHOT); - } else { - sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_OFF); - } -} - -static int keyboard_repeat_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) { - idev_keyboard *k = userdata; - - /* never feed REPEAT keys into COMPOSE */ - - keyboard_arm(k, k->repeat_rate); - return keyboard_raise_data(k, &k->repdata); -} - -int idev_keyboard_new(idev_device **out, idev_session *s, const char *name) { - _cleanup_(idev_device_freep) idev_device *d = NULL; - idev_keyboard *k; - char *kname; - int r; - - assert_return(out, -EINVAL); - assert_return(s, -EINVAL); - assert_return(name, -EINVAL); - - k = new0(idev_keyboard, 1); - if (!k) - return -ENOMEM; - - d = &k->device; - k->device = IDEV_DEVICE_INIT(&keyboard_vtable, s); - k->repeat_delay = 250 * USEC_PER_MSEC; - k->repeat_rate = 30 * USEC_PER_MSEC; - - /* TODO: add key-repeat configuration */ - - r = get_kbdctx(s->context, &k->kbdctx); - if (r < 0) - return r; - - r = keyboard_update_kbdmap(k); - if (r < 0) - return r; - - r = keyboard_update_kbdtbl(k); - if (r < 0) - return r; - - r = keyboard_resize_bufs(k, 8); - if (r < 0) - return r; - - r = sd_event_add_time(s->context->event, - &k->repeat_timer, - CLOCK_MONOTONIC, - 0, - 10 * USEC_PER_MSEC, - keyboard_repeat_timer_fn, - k); - if (r < 0) - return r; - - r = sd_event_source_set_enabled(k->repeat_timer, SD_EVENT_OFF); - if (r < 0) - return r; - - kname = strjoina("keyboard/", name); - r = idev_device_add(d, kname); - if (r < 0) - return r; - - if (out) - *out = d; - d = NULL; - return 0; -} - -static void keyboard_free(idev_device *d) { - idev_keyboard *k = keyboard_from_device(d); - - xkb_compose_state_unref(k->xkb_compose); - xkb_state_unref(k->xkb_state); - free(k->repdata.keyboard.codepoints); - free(k->repdata.keyboard.keysyms); - free(k->evdata.keyboard.codepoints); - free(k->evdata.keyboard.keysyms); - free(k->compose_res); - k->repeat_timer = sd_event_source_unref(k->repeat_timer); - k->kbdtbl = kbdtbl_unref(k->kbdtbl); - k->kbdmap = kbdmap_unref(k->kbdmap); - k->kbdctx = kbdctx_unref(k->kbdctx); - free(k); -} - -static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_syms, const uint32_t *syms) { - xkb_layout_index_t n_lo, lo; - xkb_level_index_t lv; - struct xkb_keymap *keymap; - const xkb_keysym_t *s; - int num; - - if (n_syms == 1 && syms[0] < 128 && syms[0] > 0) - return syms[0]; - - keymap = xkb_state_get_keymap(state); - n_lo = xkb_keymap_num_layouts_for_key(keymap, code + KBDXKB_SHIFT); - - for (lo = 0; lo < n_lo; ++lo) { - lv = xkb_state_key_get_level(state, code + KBDXKB_SHIFT, lo); - num = xkb_keymap_key_get_syms_by_level(keymap, code + KBDXKB_SHIFT, lo, lv, &s); - if (num == 1 && s[0] < 128 && s[0] > 0) - return s[0]; - } - - return -1; -} - -static int keyboard_fill(idev_keyboard *k, - idev_data *dst, - bool resync, - uint16_t code, - uint32_t value, - uint32_t n_syms, - const uint32_t *keysyms) { - idev_data_keyboard *kev; - uint32_t i; - int r; - - assert(dst == &k->evdata || dst == &k->repdata); - - r = keyboard_resize_bufs(k, n_syms); - if (r < 0) - return r; - - dst->type = IDEV_DATA_KEYBOARD; - dst->resync = resync; - kev = &dst->keyboard; - kev->ascii = guess_ascii(k->xkb_state, code, n_syms, keysyms); - kev->value = value; - kev->keycode = code; - kev->mods = 0; - kev->consumed_mods = 0; - kev->n_syms = n_syms; - memcpy(kev->keysyms, keysyms, sizeof(*keysyms) * n_syms); - - for (i = 0; i < n_syms; ++i) { - kev->codepoints[i] = xkb_keysym_to_utf32(keysyms[i]); - if (!kev->codepoints[i]) - kev->codepoints[i] = 0xffffffffUL; - } - - for (i = 0; i < IDEV_KBDMOD_CNT; ++i) { - if (k->kbdmap->modmap[i] == XKB_MOD_INVALID) - continue; - - r = xkb_state_mod_index_is_active(k->xkb_state, k->kbdmap->modmap[i], XKB_STATE_MODS_EFFECTIVE); - if (r > 0) - kev->mods |= 1 << i; - - r = xkb_state_mod_index_is_consumed(k->xkb_state, code + KBDXKB_SHIFT, k->kbdmap->modmap[i]); - if (r > 0) - kev->consumed_mods |= 1 << i; - } - - return 0; -} - -static void keyboard_repeat(idev_keyboard *k) { - idev_data *evdata = &k->evdata; - idev_data *repdata = &k->repdata; - idev_data_keyboard *evkbd = &evdata->keyboard; - idev_data_keyboard *repkbd = &repdata->keyboard; - const xkb_keysym_t *keysyms; - idev_device *d = &k->device; - bool repeats; - int r, num; - - if (evdata->resync) { - /* - * We received a re-sync event. During re-sync, any number of - * key-events may have been lost and sync-events may be - * re-ordered. Always disable key-repeat for those events. Any - * following event will trigger it again. - */ - - k->repeating = false; - keyboard_arm(k, 0); - return; - } - - repeats = xkb_keymap_key_repeats(k->kbdmap->xkb_keymap, evkbd->keycode + KBDXKB_SHIFT); - - if (k->repeating && repkbd->keycode == evkbd->keycode) { - /* - * We received an event for the key we currently repeat. If it - * was released, stop key-repeat. Otherwise, ignore the event. - */ - - if (evkbd->value == KBDKEY_UP) { - k->repeating = false; - keyboard_arm(k, 0); - } - } else if (evkbd->value == KBDKEY_DOWN && repeats) { - /* - * We received a key-down event for a key that repeats. The - * previous condition caught keys we already repeat, so we know - * this is a different key or no key-repeat is running. Start - * new key-repeat. - */ - - errno = 0; - num = xkb_state_key_get_syms(k->xkb_state, evkbd->keycode + KBDXKB_SHIFT, &keysyms); - if (num < 0) - r = errno > 0 ? errno : -EFAULT; - else - r = keyboard_fill(k, repdata, false, evkbd->keycode, KBDKEY_REPEAT, num, keysyms); - - if (r < 0) { - log_debug_errno(r, "idev-keyboard: %s/%s: cannot set key-repeat: %m", - d->session->name, d->name); - k->repeating = false; - keyboard_arm(k, 0); - } else { - k->repeating = true; - keyboard_arm(k, k->repeat_delay); - } - } else if (k->repeating && !repeats) { - /* - * We received an event for a key that does not repeat, but we - * currently repeat a previously received key. The new key is - * usually a modifier, but might be any kind of key. In this - * case, we continue repeating the old key, but update the - * symbols according to the new state. - */ - - errno = 0; - num = xkb_state_key_get_syms(k->xkb_state, repkbd->keycode + KBDXKB_SHIFT, &keysyms); - if (num < 0) - r = errno > 0 ? errno : -EFAULT; - else - r = keyboard_fill(k, repdata, false, repkbd->keycode, KBDKEY_REPEAT, num, keysyms); - - if (r < 0) { - log_debug_errno(r, "idev-keyboard: %s/%s: cannot update key-repeat: %m", - d->session->name, d->name); - k->repeating = false; - keyboard_arm(k, 0); - } - } -} - -static int keyboard_feed_evdev(idev_keyboard *k, idev_data *data) { - struct input_event *ev = &data->evdev.event; - enum xkb_state_component compch; - enum xkb_compose_status cstatus; - const xkb_keysym_t *keysyms; - idev_device *d = &k->device; - int num, r; - - if (ev->type != EV_KEY || ev->value > KBDKEY_DOWN) - return 0; - - /* TODO: We should audit xkb-actions, whether they need @resync as - * flag. Most actions should just be executed, however, there might - * be actions that depend on modifier-orders. Those should be - * suppressed. */ - - num = xkb_state_key_get_syms(k->xkb_state, ev->code + KBDXKB_SHIFT, &keysyms); - compch = xkb_state_update_key(k->xkb_state, ev->code + KBDXKB_SHIFT, ev->value); - - if (compch & XKB_STATE_LEDS) { - /* TODO: update LEDs */ - } - - if (num < 0) { - r = num; - goto error; - } - - if (k->xkb_compose && ev->value == KBDKEY_DOWN) { - if (num == 1 && !data->resync) { - xkb_compose_state_feed(k->xkb_compose, keysyms[0]); - cstatus = xkb_compose_state_get_status(k->xkb_compose); - } else { - cstatus = XKB_COMPOSE_CANCELLED; - } - - switch (cstatus) { - case XKB_COMPOSE_NOTHING: - /* keep produced keysyms and forward unchanged */ - break; - case XKB_COMPOSE_COMPOSING: - /* consumed by compose-state, drop keysym */ - keysyms = NULL; - num = 0; - break; - case XKB_COMPOSE_COMPOSED: - /* compose-state produced sth, replace keysym */ - num = keyboard_read_compose(k, &keysyms); - xkb_compose_state_reset(k->xkb_compose); - break; - case XKB_COMPOSE_CANCELLED: - /* canceled compose, reset, forward cancellation sym */ - xkb_compose_state_reset(k->xkb_compose); - break; - } - } else if (k->xkb_compose && - num == 1 && - keysyms[0] == XKB_KEY_Multi_key && - !data->resync && - ev->value == KBDKEY_UP) { - /* Reset compose state on Multi-Key UP events. This effectively - * requires you to hold the key during the whole sequence. I - * think it's pretty handy to avoid accidental - * Compose-sequences, but this may break Compose for disabled - * people. We really need to make this opional! (TODO) */ - xkb_compose_state_reset(k->xkb_compose); - } - - if (ev->value == KBDKEY_UP) { - /* never produce keysyms for UP */ - keysyms = NULL; - num = 0; - } - - r = keyboard_fill(k, &k->evdata, data->resync, ev->code, ev->value, num, keysyms); - if (r < 0) - goto error; - - keyboard_repeat(k); - return keyboard_raise_data(k, &k->evdata); - -error: - log_debug_errno(r, "idev-keyboard: %s/%s: cannot handle event: %m", - d->session->name, d->name); - k->repeating = false; - keyboard_arm(k, 0); - return 0; -} - -static int keyboard_feed(idev_device *d, idev_data *data) { - idev_keyboard *k = keyboard_from_device(d); - - switch (data->type) { - case IDEV_DATA_RESYNC: - /* - * If the underlying device is re-synced, key-events might be - * sent re-ordered. Thus, we don't know which key was pressed - * last. Key-repeat might get confused, hence, disable it - * during re-syncs. The first following event will enable it - * again. - */ - - k->repeating = false; - keyboard_arm(k, 0); - return 0; - case IDEV_DATA_EVDEV: - return keyboard_feed_evdev(k, data); - default: - return 0; - } -} - -static int keyboard_update_kbdmap(idev_keyboard *k) { - idev_device *d = &k->device; - struct xkb_state *state; - kbdmap *km; - int r; - - assert(k); - - km = k->kbdctx->kbdmap; - if (km == k->kbdmap) - return 0; - - errno = 0; - state = xkb_state_new(km->xkb_keymap); - if (!state) { - r = errno > 0 ? -errno : -EFAULT; - goto error; - } - - kbdmap_unref(k->kbdmap); - k->kbdmap = kbdmap_ref(km); - xkb_state_unref(k->xkb_state); - k->xkb_state = state; - - /* TODO: On state-change, we should trigger a resync so the whole - * event-state is flushed into the new xkb-state. libevdev currently - * does not support that, though. */ - - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: %s/%s: cannot adopt new keymap: %m", - d->session->name, d->name); -} - -static int keyboard_update_kbdtbl(idev_keyboard *k) { - idev_device *d = &k->device; - struct xkb_compose_state *compose = NULL; - kbdtbl *kt; - int r; - - assert(k); - - kt = k->kbdctx->kbdtbl; - if (kt == k->kbdtbl) - return 0; - - if (kt) { - errno = 0; - compose = xkb_compose_state_new(kt->xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); - if (!compose) { - r = errno > 0 ? -errno : -EFAULT; - goto error; - } - } - - kbdtbl_unref(k->kbdtbl); - k->kbdtbl = kbdtbl_ref(kt); - xkb_compose_state_unref(k->xkb_compose); - k->xkb_compose = compose; - - return 0; - -error: - return log_debug_errno(r, "idev-keyboard: %s/%s: cannot adopt new compose table: %m", - d->session->name, d->name); -} - -static const idev_device_vtable keyboard_vtable = { - .free = keyboard_free, - .feed = keyboard_feed, -}; diff --git a/src/libsystemd-terminal/idev.c b/src/libsystemd-terminal/idev.c deleted file mode 100644 index b92a393b69..0000000000 --- a/src/libsystemd-terminal/idev.c +++ /dev/null @@ -1,799 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "login-util.h" -#include "macro.h" -#include "util.h" -#include "idev.h" -#include "idev-internal.h" - -static void element_open(idev_element *e); -static void element_close(idev_element *e); - -/* - * Devices - */ - -idev_device *idev_find_device(idev_session *s, const char *name) { - assert_return(s, NULL); - assert_return(name, NULL); - - return hashmap_get(s->device_map, name); -} - -int idev_device_add(idev_device *d, const char *name) { - int r; - - assert_return(d, -EINVAL); - assert_return(d->vtable, -EINVAL); - assert_return(d->session, -EINVAL); - assert_return(name, -EINVAL); - - d->name = strdup(name); - if (!d->name) - return -ENOMEM; - - r = hashmap_put(d->session->device_map, d->name, d); - if (r < 0) - return r; - - return 0; -} - -idev_device *idev_device_free(idev_device *d) { - idev_device tmp; - - if (!d) - return NULL; - - assert(!d->enabled); - assert(!d->public); - assert(!d->links); - assert(d->vtable); - assert(d->vtable->free); - - if (d->name) - hashmap_remove_value(d->session->device_map, d->name, d); - - tmp = *d; - d->vtable->free(d); - - free(tmp.name); - - return NULL; -} - -int idev_device_feed(idev_device *d, idev_data *data) { - assert(d); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - if (d->vtable->feed) - return d->vtable->feed(d, data); - else - return 0; -} - -void idev_device_feedback(idev_device *d, idev_data *data) { - idev_link *l; - - assert(d); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - LIST_FOREACH(links_by_device, l, d->links) - idev_element_feedback(l->element, data); -} - -static void device_attach(idev_device *d, idev_link *l) { - assert(d); - assert(l); - - if (d->vtable->attach) - d->vtable->attach(d, l); - - if (d->enabled) - element_open(l->element); -} - -static void device_detach(idev_device *d, idev_link *l) { - assert(d); - assert(l); - - if (d->enabled) - element_close(l->element); - - if (d->vtable->detach) - d->vtable->detach(d, l); -} - -void idev_device_enable(idev_device *d) { - idev_link *l; - - assert(d); - - if (!d->enabled) { - d->enabled = true; - LIST_FOREACH(links_by_device, l, d->links) - element_open(l->element); - } -} - -void idev_device_disable(idev_device *d) { - idev_link *l; - - assert(d); - - if (d->enabled) { - d->enabled = false; - LIST_FOREACH(links_by_device, l, d->links) - element_close(l->element); - } -} - -/* - * Elements - */ - -idev_element *idev_find_element(idev_session *s, const char *name) { - assert_return(s, NULL); - assert_return(name, NULL); - - return hashmap_get(s->element_map, name); -} - -int idev_element_add(idev_element *e, const char *name) { - int r; - - assert_return(e, -EINVAL); - assert_return(e->vtable, -EINVAL); - assert_return(e->session, -EINVAL); - assert_return(name, -EINVAL); - - e->name = strdup(name); - if (!e->name) - return -ENOMEM; - - r = hashmap_put(e->session->element_map, e->name, e); - if (r < 0) - return r; - - return 0; -} - -idev_element *idev_element_free(idev_element *e) { - idev_element tmp; - - if (!e) - return NULL; - - assert(!e->enabled); - assert(!e->links); - assert(e->n_open == 0); - assert(e->vtable); - assert(e->vtable->free); - - if (e->name) - hashmap_remove_value(e->session->element_map, e->name, e); - - tmp = *e; - e->vtable->free(e); - - free(tmp.name); - - return NULL; -} - -int idev_element_feed(idev_element *e, idev_data *data) { - int r, error = 0; - idev_link *l; - - assert(e); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - LIST_FOREACH(links_by_element, l, e->links) { - r = idev_device_feed(l->device, data); - if (r != 0) - error = r; - } - - return error; -} - -void idev_element_feedback(idev_element *e, idev_data *data) { - assert(e); - assert(data); - assert(data->type < IDEV_DATA_CNT); - - if (e->vtable->feedback) - e->vtable->feedback(e, data); -} - -static void element_open(idev_element *e) { - assert(e); - - if (e->n_open++ == 0 && e->vtable->open) - e->vtable->open(e); -} - -static void element_close(idev_element *e) { - assert(e); - assert(e->n_open > 0); - - if (--e->n_open == 0 && e->vtable->close) - e->vtable->close(e); -} - -static void element_enable(idev_element *e) { - assert(e); - - if (!e->enabled) { - e->enabled = true; - if (e->vtable->enable) - e->vtable->enable(e); - } -} - -static void element_disable(idev_element *e) { - assert(e); - - if (e->enabled) { - e->enabled = false; - if (e->vtable->disable) - e->vtable->disable(e); - } -} - -static void element_resume(idev_element *e, int fd) { - assert(e); - assert(fd >= 0); - - if (e->vtable->resume) - e->vtable->resume(e, fd); -} - -static void element_pause(idev_element *e, const char *mode) { - assert(e); - assert(mode); - - if (e->vtable->pause) - e->vtable->pause(e, mode); -} - -/* - * Sessions - */ - -static int session_raise(idev_session *s, idev_event *ev) { - return s->event_fn(s, s->userdata, ev); -} - -static int session_raise_device_add(idev_session *s, idev_device *d) { - idev_event event = { - .type = IDEV_EVENT_DEVICE_ADD, - .device_add = { - .device = d, - }, - }; - - return session_raise(s, &event); -} - -static int session_raise_device_remove(idev_session *s, idev_device *d) { - idev_event event = { - .type = IDEV_EVENT_DEVICE_REMOVE, - .device_remove = { - .device = d, - }, - }; - - return session_raise(s, &event); -} - -int idev_session_raise_device_data(idev_session *s, idev_device *d, idev_data *data) { - idev_event event = { - .type = IDEV_EVENT_DEVICE_DATA, - .device_data = { - .device = d, - .data = *data, - }, - }; - - return session_raise(s, &event); -} - -static int session_add_device(idev_session *s, idev_device *d) { - int r; - - assert(s); - assert(d); - - log_debug("idev: %s: add device '%s'", s->name, d->name); - - d->public = true; - r = session_raise_device_add(s, d); - if (r != 0) { - d->public = false; - goto error; - } - - return 0; - -error: - if (r < 0) - log_debug_errno(r, "idev: %s: error while adding device '%s': %m", - s->name, d->name); - return r; -} - -static int session_remove_device(idev_session *s, idev_device *d) { - int r, error = 0; - - assert(s); - assert(d); - - log_debug("idev: %s: remove device '%s'", s->name, d->name); - - d->public = false; - r = session_raise_device_remove(s, d); - if (r != 0) - error = r; - - idev_device_disable(d); - - if (error < 0) - log_debug_errno(error, "idev: %s: error while removing device '%s': %m", - s->name, d->name); - idev_device_free(d); - return error; -} - -static int session_add_element(idev_session *s, idev_element *e) { - assert(s); - assert(e); - - log_debug("idev: %s: add element '%s'", s->name, e->name); - - if (s->enabled) - element_enable(e); - - return 0; -} - -static int session_remove_element(idev_session *s, idev_element *e) { - int r, error = 0; - idev_device *d; - idev_link *l; - - assert(s); - assert(e); - - log_debug("idev: %s: remove element '%s'", s->name, e->name); - - while ((l = e->links)) { - d = l->device; - LIST_REMOVE(links_by_device, d->links, l); - LIST_REMOVE(links_by_element, e->links, l); - device_detach(d, l); - - if (!d->links) { - r = session_remove_device(s, d); - if (r != 0) - error = r; - } - - l->device = NULL; - l->element = NULL; - free(l); - } - - element_disable(e); - - if (error < 0) - log_debug_errno(r, "idev: %s: error while removing element '%s': %m", - s->name, e->name); - idev_element_free(e); - return error; -} - -idev_session *idev_find_session(idev_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->session_map, name); -} - -static int session_resume_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - idev_session *s = userdata; - idev_element *e; - uint32_t major, minor; - int r, fd; - - r = sd_bus_message_read(signal, "uuh", &major, &minor, &fd); - if (r < 0) { - log_debug("idev: %s: erroneous ResumeDevice signal", s->name); - return 0; - } - - e = idev_find_evdev(s, makedev(major, minor)); - if (!e) - return 0; - - element_resume(e, fd); - return 0; -} - -static int session_pause_device_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - idev_session *s = userdata; - idev_element *e; - uint32_t major, minor; - const char *mode; - int r; - - r = sd_bus_message_read(signal, "uus", &major, &minor, &mode); - if (r < 0) { - log_debug("idev: %s: erroneous PauseDevice signal", s->name); - return 0; - } - - e = idev_find_evdev(s, makedev(major, minor)); - if (!e) - return 0; - - element_pause(e, mode); - return 0; -} - -static int session_setup_bus(idev_session *s) { - _cleanup_free_ char *match = NULL; - int r; - - if (!s->managed) - return 0; - - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='ResumeDevice'," - "path='", s->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(s->context->sysbus, - &s->slot_resume_device, - match, - session_resume_device_fn, - s); - if (r < 0) - return r; - - free(match); - match = strjoin("type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Session'," - "member='PauseDevice'," - "path='", s->path, "'", - NULL); - if (!match) - return -ENOMEM; - - r = sd_bus_add_match(s->context->sysbus, - &s->slot_pause_device, - match, - session_pause_device_fn, - s); - if (r < 0) - return r; - - return 0; -} - -int idev_session_new(idev_session **out, - idev_context *c, - unsigned int flags, - const char *name, - idev_event_fn event_fn, - void *userdata) { - _cleanup_(idev_session_freep) idev_session *s = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(c, -EINVAL); - assert_return(name, -EINVAL); - assert_return(event_fn, -EINVAL); - assert_return((flags & IDEV_SESSION_CUSTOM) == !session_id_valid(name), -EINVAL); - assert_return(!(flags & IDEV_SESSION_CUSTOM) || !(flags & IDEV_SESSION_MANAGED), -EINVAL); - assert_return(!(flags & IDEV_SESSION_MANAGED) || c->sysbus, -EINVAL); - - s = new0(idev_session, 1); - if (!s) - return -ENOMEM; - - s->context = idev_context_ref(c); - s->custom = flags & IDEV_SESSION_CUSTOM; - s->managed = flags & IDEV_SESSION_MANAGED; - s->event_fn = event_fn; - s->userdata = userdata; - - s->name = strdup(name); - if (!s->name) - return -ENOMEM; - - if (s->managed) { - r = sd_bus_path_encode("/org/freedesktop/login1/session", s->name, &s->path); - if (r < 0) - return r; - } - - s->element_map = hashmap_new(&string_hash_ops); - if (!s->element_map) - return -ENOMEM; - - s->device_map = hashmap_new(&string_hash_ops); - if (!s->device_map) - return -ENOMEM; - - r = session_setup_bus(s); - if (r < 0) - return r; - - r = hashmap_put(c->session_map, s->name, s); - if (r < 0) - return r; - - *out = s; - s = NULL; - return 0; -} - -idev_session *idev_session_free(idev_session *s) { - idev_element *e; - - if (!s) - return NULL; - - while ((e = hashmap_first(s->element_map))) - session_remove_element(s, e); - - assert(hashmap_size(s->device_map) == 0); - - if (s->name) - hashmap_remove_value(s->context->session_map, s->name, s); - - s->slot_pause_device = sd_bus_slot_unref(s->slot_pause_device); - s->slot_resume_device = sd_bus_slot_unref(s->slot_resume_device); - s->context = idev_context_unref(s->context); - hashmap_free(s->device_map); - hashmap_free(s->element_map); - free(s->path); - free(s->name); - free(s); - - return NULL; -} - -bool idev_session_is_enabled(idev_session *s) { - return s && s->enabled; -} - -void idev_session_enable(idev_session *s) { - idev_element *e; - Iterator i; - - assert(s); - - if (!s->enabled) { - s->enabled = true; - HASHMAP_FOREACH(e, s->element_map, i) - element_enable(e); - } -} - -void idev_session_disable(idev_session *s) { - idev_element *e; - Iterator i; - - assert(s); - - if (s->enabled) { - s->enabled = false; - HASHMAP_FOREACH(e, s->element_map, i) - element_disable(e); - } -} - -static int add_link(idev_element *e, idev_device *d) { - idev_link *l; - - assert(e); - assert(d); - - l = new0(idev_link, 1); - if (!l) - return -ENOMEM; - - l->element = e; - l->device = d; - LIST_PREPEND(links_by_element, e->links, l); - LIST_PREPEND(links_by_device, d->links, l); - device_attach(d, l); - - return 0; -} - -static int guess_type(struct udev_device *d) { - const char *id_key; - - id_key = udev_device_get_property_value(d, "ID_INPUT_KEY"); - if (streq_ptr(id_key, "1")) - return IDEV_DEVICE_KEYBOARD; - - return IDEV_DEVICE_CNT; -} - -int idev_session_add_evdev(idev_session *s, struct udev_device *ud) { - idev_element *e; - idev_device *d; - dev_t devnum; - int r, type; - - assert_return(s, -EINVAL); - assert_return(ud, -EINVAL); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return 0; - - e = idev_find_evdev(s, devnum); - if (e) - return 0; - - r = idev_evdev_new(&e, s, ud); - if (r < 0) - return r; - - r = session_add_element(s, e); - if (r != 0) - return r; - - type = guess_type(ud); - if (type < 0) - return type; - - switch (type) { - case IDEV_DEVICE_KEYBOARD: - d = idev_find_keyboard(s, e->name); - if (d) { - log_debug("idev: %s: keyboard for new evdev element '%s' already available", - s->name, e->name); - return 0; - } - - r = idev_keyboard_new(&d, s, e->name); - if (r < 0) - return r; - - r = add_link(e, d); - if (r < 0) { - idev_device_free(d); - return r; - } - - return session_add_device(s, d); - default: - /* unknown elements are silently ignored */ - return 0; - } -} - -int idev_session_remove_evdev(idev_session *s, struct udev_device *ud) { - idev_element *e; - dev_t devnum; - - assert(s); - assert(ud); - - devnum = udev_device_get_devnum(ud); - if (devnum == 0) - return 0; - - e = idev_find_evdev(s, devnum); - if (!e) - return 0; - - return session_remove_element(s, e); -} - -/* - * Contexts - */ - -int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus) { - _cleanup_(idev_context_unrefp) idev_context *c = NULL; - - assert_return(out, -EINVAL); - assert_return(event, -EINVAL); - - c = new0(idev_context, 1); - if (!c) - return -ENOMEM; - - c->ref = 1; - c->event = sd_event_ref(event); - - if (sysbus) - c->sysbus = sd_bus_ref(sysbus); - - c->session_map = hashmap_new(&string_hash_ops); - if (!c->session_map) - return -ENOMEM; - - c->data_map = hashmap_new(&string_hash_ops); - if (!c->data_map) - return -ENOMEM; - - *out = c; - c = NULL; - return 0; -} - -static void context_cleanup(idev_context *c) { - assert(hashmap_size(c->data_map) == 0); - assert(hashmap_size(c->session_map) == 0); - - hashmap_free(c->data_map); - hashmap_free(c->session_map); - c->sysbus = sd_bus_unref(c->sysbus); - c->event = sd_event_unref(c->event); - free(c); -} - -idev_context *idev_context_ref(idev_context *c) { - assert_return(c, NULL); - assert_return(c->ref > 0, NULL); - - ++c->ref; - return c; -} - -idev_context *idev_context_unref(idev_context *c) { - if (!c) - return NULL; - - assert_return(c->ref > 0, NULL); - - if (--c->ref == 0) - context_cleanup(c); - - return NULL; -} diff --git a/src/libsystemd-terminal/idev.h b/src/libsystemd-terminal/idev.h deleted file mode 100644 index 241677cbbe..0000000000 --- a/src/libsystemd-terminal/idev.h +++ /dev/null @@ -1,221 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * IDev - */ - -#pragma once - -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" - -typedef struct idev_data idev_data; -typedef struct idev_data_evdev idev_data_evdev; -typedef struct idev_data_keyboard idev_data_keyboard; - -typedef struct idev_event idev_event; -typedef struct idev_device idev_device; -typedef struct idev_session idev_session; -typedef struct idev_context idev_context; - -/* - * Types - */ - -enum { - IDEV_ELEMENT_EVDEV, - IDEV_ELEMENT_CNT -}; - -enum { - IDEV_DEVICE_KEYBOARD, - IDEV_DEVICE_CNT -}; - -/* - * Evdev Elements - */ - -struct idev_data_evdev { - struct input_event event; -}; - -/* - * Keyboard Devices - */ - -struct xkb_state; - -enum { - IDEV_KBDMOD_IDX_SHIFT, - IDEV_KBDMOD_IDX_CTRL, - IDEV_KBDMOD_IDX_ALT, - IDEV_KBDMOD_IDX_LINUX, - IDEV_KBDMOD_IDX_CAPS, - IDEV_KBDMOD_CNT, - - IDEV_KBDMOD_SHIFT = 1 << IDEV_KBDMOD_IDX_SHIFT, - IDEV_KBDMOD_CTRL = 1 << IDEV_KBDMOD_IDX_CTRL, - IDEV_KBDMOD_ALT = 1 << IDEV_KBDMOD_IDX_ALT, - IDEV_KBDMOD_LINUX = 1 << IDEV_KBDMOD_IDX_LINUX, - IDEV_KBDMOD_CAPS = 1 << IDEV_KBDMOD_IDX_CAPS, -}; - -enum { - IDEV_KBDLED_IDX_NUM, - IDEV_KBDLED_IDX_CAPS, - IDEV_KBDLED_IDX_SCROLL, - IDEV_KBDLED_CNT, - - IDEV_KBDLED_NUM = 1 << IDEV_KBDLED_IDX_NUM, - IDEV_KBDLED_CAPS = 1 << IDEV_KBDLED_IDX_CAPS, - IDEV_KBDLED_SCROLL = 1 << IDEV_KBDLED_IDX_SCROLL, -}; - -struct idev_data_keyboard { - struct xkb_state *xkb_state; - int8_t ascii; - uint8_t value; - uint16_t keycode; - uint32_t mods; - uint32_t consumed_mods; - uint32_t n_syms; - uint32_t *keysyms; - uint32_t *codepoints; -}; - -static inline bool idev_kbdmatch(idev_data_keyboard *kdata, - uint32_t mods, uint32_t n_syms, - const uint32_t *syms) { - const uint32_t significant = IDEV_KBDMOD_SHIFT | - IDEV_KBDMOD_CTRL | - IDEV_KBDMOD_ALT | - IDEV_KBDMOD_LINUX; - uint32_t real; - - if (n_syms != kdata->n_syms) - return false; - - real = kdata->mods & ~kdata->consumed_mods & significant; - if (real != mods) - return false; - - return !memcmp(syms, kdata->keysyms, n_syms * sizeof(*syms)); -} - -#define IDEV_KBDMATCH(_kdata, _mods, _sym) \ - idev_kbdmatch((_kdata), (_mods), 1, (const uint32_t[]){ (_sym) }) - -/* - * Data Packets - */ - -enum { - IDEV_DATA_RESYNC, - IDEV_DATA_EVDEV, - IDEV_DATA_KEYBOARD, - IDEV_DATA_CNT -}; - -struct idev_data { - unsigned int type; - bool resync : 1; - - union { - idev_data_evdev evdev; - idev_data_keyboard keyboard; - }; -}; - -/* - * Events - */ - -enum { - IDEV_EVENT_DEVICE_ADD, - IDEV_EVENT_DEVICE_REMOVE, - IDEV_EVENT_DEVICE_DATA, - IDEV_EVENT_CNT -}; - -struct idev_event { - unsigned int type; - union { - struct { - idev_device *device; - } device_add, device_remove; - - struct { - idev_device *device; - idev_data data; - } device_data; - }; -}; - -typedef int (*idev_event_fn) (idev_session *s, void *userdata, idev_event *ev); - -/* - * Devices - */ - -void idev_device_enable(idev_device *d); -void idev_device_disable(idev_device *d); - -/* - * Sessions - */ - -enum { - IDEV_SESSION_CUSTOM = (1 << 0), - IDEV_SESSION_MANAGED = (1 << 1), -}; - -int idev_session_new(idev_session **out, - idev_context *c, - unsigned int flags, - const char *name, - idev_event_fn event_fn, - void *userdata); -idev_session *idev_session_free(idev_session *s); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_session*, idev_session_free); - -bool idev_session_is_enabled(idev_session *s); -void idev_session_enable(idev_session *s); -void idev_session_disable(idev_session *s); - -int idev_session_add_evdev(idev_session *s, struct udev_device *ud); -int idev_session_remove_evdev(idev_session *s, struct udev_device *ud); - -/* - * Contexts - */ - -int idev_context_new(idev_context **out, sd_event *event, sd_bus *sysbus); -idev_context *idev_context_ref(idev_context *c); -idev_context *idev_context_unref(idev_context *c); - -DEFINE_TRIVIAL_CLEANUP_FUNC(idev_context*, idev_context_unref); diff --git a/src/libsystemd-terminal/modeset.c b/src/libsystemd-terminal/modeset.c deleted file mode 100644 index 790a244772..0000000000 --- a/src/libsystemd-terminal/modeset.c +++ /dev/null @@ -1,482 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Modeset Testing - * The modeset tool attaches to the session of the caller and shows a - * test-pattern on all displays of this session. It is meant as debugging tool - * for the grdev infrastructure. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "build.h" -#include "macro.h" -#include "random-util.h" -#include "signal-util.h" -#include "util.h" -#include "grdev.h" -#include "sysview.h" - -typedef struct Modeset Modeset; - -struct Modeset { - char *session; - char *seat; - sd_event *event; - sd_bus *bus; - sd_event_source *exit_src; - sysview_context *sysview; - grdev_context *grdev; - grdev_session *grdev_session; - - uint8_t r, g, b; - bool r_up, g_up, b_up; - - bool my_tty : 1; - bool managed : 1; -}; - -static int modeset_exit_fn(sd_event_source *source, void *userdata) { - Modeset *m = userdata; - - if (m->grdev_session) - grdev_session_restore(m->grdev_session); - - return 0; -} - -static Modeset *modeset_free(Modeset *m) { - if (!m) - return NULL; - - m->grdev_session = grdev_session_free(m->grdev_session); - m->grdev = grdev_context_unref(m->grdev); - m->sysview = sysview_context_free(m->sysview); - m->exit_src = sd_event_source_unref(m->exit_src); - m->bus = sd_bus_unref(m->bus); - m->event = sd_event_unref(m->event); - free(m->seat); - free(m->session); - free(m); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Modeset*, modeset_free); - -static bool is_my_tty(const char *session) { - unsigned int vtnr; - struct stat st; - long mode; - int r; - - /* Using logind's Controller API is highly fragile if there is already - * a session controller running. If it is registered as controller - * itself, TakeControl will simply fail. But if its a legacy controller - * that does not use logind's controller API, we must never register - * our own controller. Otherwise, we really mess up the VT. Therefore, - * only run in managed mode if there's no-one else. Furthermore, never - * try to access graphics devices if there's someone else. Unlike input - * devices, graphics devies cannot be shared easily. */ - - if (!isatty(1)) - return false; - - if (!session) - return false; - - r = sd_session_get_vt(session, &vtnr); - if (r < 0 || vtnr < 1 || vtnr > 63) - return false; - - mode = 0; - r = ioctl(1, KDGETMODE, &mode); - if (r < 0 || mode != KD_TEXT) - return false; - - r = fstat(1, &st); - if (r < 0 || minor(st.st_rdev) != vtnr) - return false; - - return true; -} - -static int modeset_new(Modeset **out) { - _cleanup_(modeset_freep) Modeset *m = NULL; - int r; - - assert(out); - - m = new0(Modeset, 1); - if (!m) - return log_oom(); - - r = sd_pid_get_session(getpid(), &m->session); - if (r < 0) - return log_error_errno(r, "Cannot retrieve logind session: %m"); - - r = sd_session_get_seat(m->session, &m->seat); - if (r < 0) - return log_error_errno(r, "Cannot retrieve seat of logind session: %m"); - - m->my_tty = is_my_tty(m->session); - m->managed = m->my_tty && geteuid() > 0; - - m->r = rand() % 0xff; - m->g = rand() % 0xff; - m->b = rand() % 0xff; - m->r_up = m->g_up = m->b_up = true; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - r = sd_bus_open_system(&m->bus); - if (r < 0) - return r; - - r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return r; - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_exit(m->event, &m->exit_src, modeset_exit_fn, m); - if (r < 0) - return r; - - /* schedule before sd-bus close */ - r = sd_event_source_set_priority(m->exit_src, -10); - if (r < 0) - return r; - - r = sysview_context_new(&m->sysview, - SYSVIEW_CONTEXT_SCAN_LOGIND | - SYSVIEW_CONTEXT_SCAN_DRM, - m->event, - m->bus, - NULL); - if (r < 0) - return r; - - r = grdev_context_new(&m->grdev, m->event, m->bus); - if (r < 0) - return r; - - *out = m; - m = NULL; - return 0; -} - -static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) { - uint8_t next; - - /* generate smoothly morphing colors */ - - next = cur + (*up ? 1 : -1) * (rand() % mod); - if ((*up && next < cur) || (!*up && next > cur)) { - *up = !*up; - next = cur; - } - - return next; -} - -static void modeset_draw(Modeset *m, const grdev_display_target *t) { - uint32_t j, k, *b; - uint8_t *l; - - assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888); - assert(!t->rotate); - assert(!t->flip); - - l = t->back->maps[0]; - for (j = 0; j < t->height; ++j) { - for (k = 0; k < t->width; ++k) { - b = (uint32_t*)l; - b[k] = (0xff << 24) | (m->r << 16) | (m->g << 8) | m->b; - } - - l += t->back->strides[0]; - } -} - -static void modeset_render(Modeset *m, grdev_display *d) { - const grdev_display_target *t; - - m->r = next_color(&m->r_up, m->r, 4); - m->g = next_color(&m->g_up, m->g, 3); - m->b = next_color(&m->b_up, m->b, 2); - - GRDEV_DISPLAY_FOREACH_TARGET(d, t) { - modeset_draw(m, t); - grdev_display_flip_target(d, t); - } - - grdev_session_commit(m->grdev_session); -} - -static void modeset_grdev_fn(grdev_session *session, void *userdata, grdev_event *ev) { - Modeset *m = userdata; - - switch (ev->type) { - case GRDEV_EVENT_DISPLAY_ADD: - grdev_display_enable(ev->display_add.display); - break; - case GRDEV_EVENT_DISPLAY_REMOVE: - break; - case GRDEV_EVENT_DISPLAY_CHANGE: - break; - case GRDEV_EVENT_DISPLAY_FRAME: - modeset_render(m, ev->display_frame.display); - break; - } -} - -static int modeset_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { - unsigned int flags, type; - Modeset *m = userdata; - sysview_device *d; - const char *name; - int r; - - switch (ev->type) { - case SYSVIEW_EVENT_SESSION_FILTER: - if (streq_ptr(m->session, ev->session_filter.id)) - return 1; - - break; - case SYSVIEW_EVENT_SESSION_ADD: - assert(!m->grdev_session); - - name = sysview_session_get_name(ev->session_add.session); - flags = 0; - - if (m->managed) - flags |= GRDEV_SESSION_MANAGED; - - r = grdev_session_new(&m->grdev_session, - m->grdev, - flags, - name, - modeset_grdev_fn, - m); - if (r < 0) - return log_error_errno(r, "Cannot create grdev session: %m"); - - if (m->managed) { - r = sysview_session_take_control(ev->session_add.session); - if (r < 0) - return log_error_errno(r, "Cannot request session control: %m"); - } - - grdev_session_enable(m->grdev_session); - - break; - case SYSVIEW_EVENT_SESSION_REMOVE: - if (!m->grdev_session) - return 0; - - grdev_session_restore(m->grdev_session); - grdev_session_disable(m->grdev_session); - m->grdev_session = grdev_session_free(m->grdev_session); - if (sd_event_get_exit_code(m->event, &r) == -ENODATA) - sd_event_exit(m->event, 0); - break; - case SYSVIEW_EVENT_SESSION_ATTACH: - d = ev->session_attach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_DRM) - grdev_session_add_drm(m->grdev_session, sysview_device_get_ud(d)); - - break; - case SYSVIEW_EVENT_SESSION_DETACH: - d = ev->session_detach.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_DRM) - grdev_session_remove_drm(m->grdev_session, sysview_device_get_ud(d)); - - break; - case SYSVIEW_EVENT_SESSION_REFRESH: - d = ev->session_refresh.device; - type = sysview_device_get_type(d); - if (type == SYSVIEW_DEVICE_DRM) - grdev_session_hotplug_drm(m->grdev_session, ev->session_refresh.ud); - - break; - case SYSVIEW_EVENT_SESSION_CONTROL: - r = ev->session_control.error; - if (r < 0) - return log_error_errno(r, "Cannot acquire session control: %m"); - - r = ioctl(1, KDSKBMODE, K_UNICODE); - if (r < 0) - return log_error_errno(errno, "Cannot set K_UNICODE on stdout: %m"); - - break; - } - - return 0; -} - -static int modeset_run(Modeset *m) { - struct termios in_attr, saved_attr; - int r; - - assert(m); - - if (!m->my_tty) { - log_warning("You need to run this program on a free VT"); - return -EACCES; - } - - if (!m->managed && geteuid() > 0) - log_warning("You run in unmanaged mode without being root. This is likely to fail.."); - - printf("modeset - Show test pattern on selected graphics devices\n" - " Running on seat '%s' in user-session '%s'\n" - " Exit by pressing ^C\n\n", - m->seat ? : "seat0", m->session ? : ""); - - r = sysview_context_start(m->sysview, modeset_sysview_fn, m); - if (r < 0) - goto out; - - r = tcgetattr(0, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - saved_attr = in_attr; - in_attr.c_lflag &= ~ECHO; - - r = tcsetattr(0, TCSANOW, &in_attr); - if (r < 0) { - r = -errno; - goto out; - } - - r = sd_event_loop(m->event); - tcsetattr(0, TCSANOW, &saved_attr); - printf("exiting..\n"); - -out: - sysview_context_stop(m->sysview); - return r; -} - -static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Show test pattern on all selected graphics devices.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {}, - }; - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - switch (c) { - case 'h': - help(); - return 0; - - case ARG_VERSION: - puts(PACKAGE_STRING); - puts(SYSTEMD_FEATURES); - return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind) { - log_error("Too many arguments"); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_(modeset_freep) Modeset *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - initialize_srand(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = modeset_new(&m); - if (r < 0) - goto finish; - - r = modeset_run(m); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/libsystemd-terminal/subterm.c b/src/libsystemd-terminal/subterm.c deleted file mode 100644 index 5f12540111..0000000000 --- a/src/libsystemd-terminal/subterm.c +++ /dev/null @@ -1,981 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Stacked Terminal-Emulator - * This is an interactive test of the term_screen implementation. It runs a - * fully-fletched terminal-emulator inside of a parent TTY. That is, instead of - * rendering the terminal as X11-window, it renders it as sub-window in the - * parent TTY. Think of this like what "GNU-screen" does. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "sd-event.h" -#include "macro.h" -#include "pty.h" -#include "ring.h" -#include "signal-util.h" -#include "utf8.h" -#include "util.h" -#include "term-internal.h" - -typedef struct Output Output; -typedef struct Terminal Terminal; - -struct Output { - int fd; - unsigned int width; - unsigned int height; - unsigned int in_width; - unsigned int in_height; - unsigned int cursor_x; - unsigned int cursor_y; - - char obuf[4096]; - size_t n_obuf; - - bool resized : 1; - bool in_menu : 1; -}; - -struct Terminal { - sd_event *event; - sd_event_source *frame_timer; - Output *output; - term_utf8 utf8; - term_parser *parser; - term_screen *screen; - - int in_fd; - int out_fd; - struct termios saved_in_attr; - struct termios saved_out_attr; - - Pty *pty; - Ring out_ring; - - bool is_scheduled : 1; - bool is_dirty : 1; - bool is_menu : 1; -}; - -/* - * Output Handling - */ - -#define BORDER_HORIZ "\xe2\x94\x81" -#define BORDER_VERT "\xe2\x94\x83" -#define BORDER_VERT_RIGHT "\xe2\x94\xa3" -#define BORDER_VERT_LEFT "\xe2\x94\xab" -#define BORDER_DOWN_RIGHT "\xe2\x94\x8f" -#define BORDER_DOWN_LEFT "\xe2\x94\x93" -#define BORDER_UP_RIGHT "\xe2\x94\x97" -#define BORDER_UP_LEFT "\xe2\x94\x9b" - -static int output_winch(Output *o) { - struct winsize wsz = { }; - int r; - - assert_return(o, -EINVAL); - - r = ioctl(o->fd, TIOCGWINSZ, &wsz); - if (r < 0) - return log_error_errno(errno, "error: cannot read window-size: %m"); - - if (wsz.ws_col != o->width || wsz.ws_row != o->height) { - o->width = wsz.ws_col; - o->height = wsz.ws_row; - o->in_width = MAX(o->width, 2U) - 2; - o->in_height = MAX(o->height, 6U) - 6; - o->resized = true; - } - - return 0; -} - -static int output_flush(Output *o) { - int r; - - if (o->n_obuf < 1) - return 0; - - r = loop_write(o->fd, o->obuf, o->n_obuf, false); - if (r < 0) - return log_error_errno(r, "error: cannot write to TTY: %m"); - - o->n_obuf = 0; - - return 0; -} - -static int output_write(Output *o, const void *buf, size_t size) { - ssize_t len; - int r; - - assert_return(o, -EINVAL); - assert_return(buf || size < 1, -EINVAL); - - if (size < 1) - return 0; - - if (o->n_obuf + size > o->n_obuf && o->n_obuf + size <= sizeof(o->obuf)) { - memcpy(o->obuf + o->n_obuf, buf, size); - o->n_obuf += size; - return 0; - } - - r = output_flush(o); - if (r < 0) - return r; - - len = loop_write(o->fd, buf, size, false); - if (len < 0) - return log_error_errno(len, "error: cannot write to TTY (%zd): %m", len); - - return 0; -} - -_printf_(3,0) -static int output_vnprintf(Output *o, size_t max, const char *format, va_list args) { - char buf[max]; - int r; - - assert_return(o, -EINVAL); - assert_return(format, -EINVAL); - assert_return(max <= 4096, -EINVAL); - - r = MIN(vsnprintf(buf, max, format, args), (int) max); - - return output_write(o, buf, r); -} - -_printf_(3,4) -static int output_nprintf(Output *o, size_t max, const char *format, ...) { - va_list args; - int r; - - va_start(args, format); - r = output_vnprintf(o, max, format, args); - va_end(args); - - return r; -} - -_printf_(2,0) -static int output_vprintf(Output *o, const char *format, va_list args) { - char buf[4096]; - int r; - - assert_return(o, -EINVAL); - assert_return(format, -EINVAL); - - r = vsnprintf(buf, sizeof(buf), format, args); - - assert_return(r < (ssize_t)sizeof(buf), -ENOBUFS); - - return output_write(o, buf, r); -} - -_printf_(2,3) -static int output_printf(Output *o, const char *format, ...) { - va_list args; - int r; - - va_start(args, format); - r = output_vprintf(o, format, args); - va_end(args); - - return r; -} - -static int output_move_to(Output *o, unsigned int x, unsigned int y) { - int r; - - assert_return(o, -EINVAL); - - /* force the \e[H code as o->cursor_x/y might be out-of-date */ - - r = output_printf(o, "\e[%u;%uH", y + 1, x + 1); - if (r < 0) - return r; - - o->cursor_x = x; - o->cursor_y = y; - return 0; -} - -static int output_print_line(Output *o, size_t len) { - const char line[] = - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ - BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ BORDER_HORIZ; - const size_t max = (sizeof(line) - 1) / (sizeof(BORDER_HORIZ) - 1); - size_t i; - int r = 0; - - assert_return(o, -EINVAL); - - for ( ; len > 0; len -= i) { - i = (len > max) ? max : len; - r = output_write(o, line, i * (sizeof(BORDER_HORIZ) - 1)); - if (r < 0) - break; - } - - return r; -} - -_printf_(2,3) -static int output_frame_printl(Output *o, const char *format, ...) { - va_list args; - int r; - - assert(o); - assert(format); - - /* out of frame? */ - if (o->cursor_y < 3 || o->cursor_y >= o->height - 3) - return 0; - - va_start(args, format); - r = output_vnprintf(o, o->width - 2, format, args); - va_end(args); - - if (r < 0) - return r; - - return output_move_to(o, 1, o->cursor_y + 1); -} - -static Output *output_free(Output *o) { - if (!o) - return NULL; - - /* re-enable cursor */ - output_printf(o, "\e[?25h"); - /* disable alternate screen buffer */ - output_printf(o, "\e[?1049l"); - output_flush(o); - - /* o->fd is owned by the caller */ - free(o); - - return NULL; -} - -static int output_new(Output **out, int fd) { - Output *o; - int r; - - assert_return(out, -EINVAL); - - o = new0(Output, 1); - if (!o) - return log_oom(); - - o->fd = fd; - - r = output_winch(o); - if (r < 0) - goto error; - - /* enable alternate screen buffer */ - r = output_printf(o, "\e[?1049h"); - if (r < 0) - goto error; - - /* always hide cursor */ - r = output_printf(o, "\e[?25l"); - if (r < 0) - goto error; - - r = output_flush(o); - if (r < 0) - goto error; - - *out = o; - return 0; - -error: - output_free(o); - return r; -} - -static void output_draw_frame(Output *o) { - unsigned int i; - - assert(o); - - /* print header-frame */ - - output_printf(o, BORDER_DOWN_RIGHT); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_DOWN_LEFT - "\r\n" - BORDER_VERT - "\e[2;%uH" /* cursor-position: 2/x */ - BORDER_VERT - "\r\n" - BORDER_VERT_RIGHT, - o->width); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_VERT_LEFT - "\r\n"); - - /* print body-frame */ - - for (i = 0; i < o->in_height; ++i) { - output_printf(o, BORDER_VERT - "\e[%u;%uH" /* cursor-position: 2/x */ - BORDER_VERT - "\r\n", - i + 4, o->width); - } - - /* print footer-frame */ - - output_printf(o, BORDER_VERT_RIGHT); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_VERT_LEFT - "\r\n" - BORDER_VERT - "\e[%u;%uH" /* cursor-position: 2/x */ - BORDER_VERT - "\r\n" - BORDER_UP_RIGHT, - o->height - 1, o->width); - output_print_line(o, o->width - 2); - output_printf(o, BORDER_UP_LEFT); - - /* print header/footer text */ - - output_printf(o, "\e[2;3H"); - output_nprintf(o, o->width - 4, "systemd - embedded terminal emulator"); - output_printf(o, "\e[%u;3H", o->height - 1); - output_nprintf(o, o->width - 4, "press ^C to enter menu"); -} - -static void output_draw_menu(Output *o) { - assert(o); - - output_frame_printl(o, "%s", ""); - output_frame_printl(o, " Menu: (the following keys are recognized)"); - output_frame_printl(o, " q: quit"); - output_frame_printl(o, " ^C: send ^C to the PTY"); -} - -static int output_draw_cell_fn(term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width) { - Output *o = userdata; - size_t k, ulen; - char utf8[4]; - - if (x >= o->in_width || y >= o->in_height) - return 0; - - if (x == 0 && y != 0) - output_printf(o, "\e[m\r\n" BORDER_VERT); - - switch (attr->fg.ccode) { - case TERM_CCODE_DEFAULT: - output_printf(o, "\e[39m"); - break; - case TERM_CCODE_256: - output_printf(o, "\e[38;5;%um", attr->fg.c256); - break; - case TERM_CCODE_RGB: - output_printf(o, "\e[38;2;%u;%u;%um", attr->fg.red, attr->fg.green, attr->fg.blue); - break; - case TERM_CCODE_BLACK ... TERM_CCODE_WHITE: - output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 30); - break; - case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE: - output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_LIGHT_BLACK + 90); - break; - } - - switch (attr->bg.ccode) { - case TERM_CCODE_DEFAULT: - output_printf(o, "\e[49m"); - break; - case TERM_CCODE_256: - output_printf(o, "\e[48;5;%um", attr->bg.c256); - break; - case TERM_CCODE_RGB: - output_printf(o, "\e[48;2;%u;%u;%um", attr->bg.red, attr->bg.green, attr->bg.blue); - break; - case TERM_CCODE_BLACK ... TERM_CCODE_WHITE: - output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_BLACK + 40); - break; - case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE: - output_printf(o, "\e[%um", attr->bg.ccode - TERM_CCODE_LIGHT_BLACK + 100); - break; - } - - output_printf(o, "\e[%u;%u;%u;%u;%u;%um", - attr->bold ? 1 : 22, - attr->italic ? 3 : 23, - attr->underline ? 4 : 24, - attr->inverse ? 7 : 27, - attr->blink ? 5 : 25, - attr->hidden ? 8 : 28); - - if (n_ch < 1) { - output_printf(o, " "); - } else { - for (k = 0; k < n_ch; ++k) { - ulen = utf8_encode_unichar(utf8, ch[k]); - output_write(o, utf8, ulen); - } - } - - return 0; -} - -static void output_draw_screen(Output *o, term_screen *s) { - assert(o); - assert(s); - - term_screen_draw(s, output_draw_cell_fn, o, NULL); - - output_printf(o, "\e[m"); -} - -static void output_draw(Output *o, bool menu, term_screen *screen) { - assert(o); - - /* - * This renders the contenst of the terminal. The layout contains a - * header, the main body and a footer. Around all areas we draw a - * border. It looks something like this: - * - * +----------------------------------------------------+ - * | Header | - * +----------------------------------------------------+ - * | | - * | | - * | | - * | Body | - * | | - * | | - * ~ ~ - * ~ ~ - * +----------------------------------------------------+ - * | Footer | - * +----------------------------------------------------+ - * - * The body is the part that grows vertically. - * - * We need at least 6 vertical lines to render the screen. This would - * leave 0 lines for the body. Therefore, we require 7 lines so there's - * at least one body line. Similarly, we need 2 horizontal cells for the - * frame, so we require 3. - * If the window is too small, we print an error message instead. - */ - - if (o->in_width < 1 || o->in_height < 1) { - output_printf(o, "\e[2J" /* erase-in-display: whole screen */ - "\e[H"); /* cursor-position: home */ - output_printf(o, "error: screen too small, need at least 3x7 cells"); - output_flush(o); - return; - } - - /* hide cursor */ - output_printf(o, "\e[?25l"); - - /* frame-content is contant; only resizes can change it */ - if (o->resized || o->in_menu != menu) { - output_printf(o, "\e[2J" /* erase-in-display: whole screen */ - "\e[H"); /* cursor-position: home */ - output_draw_frame(o); - o->resized = false; - o->in_menu = menu; - } - - /* move cursor to child's position */ - output_move_to(o, 1, 3); - - if (menu) - output_draw_menu(o); - else - output_draw_screen(o, screen); - - /* - * Hack: sd-term was not written to support TTY as output-objects, thus - * expects callers to use term_screen_feed_keyboard(). However, we - * forward TTY input directly. Hence, we're not notified about keypad - * changes. Update the related modes djring redraw to keep them at least - * in sync. - */ - if (screen->flags & TERM_FLAG_CURSOR_KEYS) - output_printf(o, "\e[?1h"); - else - output_printf(o, "\e[?1l"); - - if (screen->flags & TERM_FLAG_KEYPAD_MODE) - output_printf(o, "\e="); - else - output_printf(o, "\e>"); - - output_flush(o); -} - -/* - * Terminal Handling - */ - -static void terminal_dirty(Terminal *t) { - usec_t usec; - int r; - - assert(t); - - if (t->is_scheduled) { - t->is_dirty = true; - return; - } - - /* 16ms timer */ - r = sd_event_now(t->event, CLOCK_MONOTONIC, &usec); - assert(r >= 0); - - usec += 16 * USEC_PER_MSEC; - r = sd_event_source_set_time(t->frame_timer, usec); - if (r >= 0) { - r = sd_event_source_set_enabled(t->frame_timer, SD_EVENT_ONESHOT); - if (r >= 0) - t->is_scheduled = true; - } - - t->is_dirty = false; - output_draw(t->output, t->is_menu, t->screen); -} - -static int terminal_frame_timer_fn(sd_event_source *source, uint64_t usec, void *userdata) { - Terminal *t = userdata; - - t->is_scheduled = false; - if (t->is_dirty) - terminal_dirty(t); - - return 0; -} - -static int terminal_winch_fn(sd_event_source *source, const struct signalfd_siginfo *ssi, void *userdata) { - Terminal *t = userdata; - int r; - - output_winch(t->output); - - if (t->pty) { - r = pty_resize(t->pty, t->output->in_width, t->output->in_height); - if (r < 0) - log_error_errno(r, "error: pty_resize() (%d): %m", r); - } - - r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height); - if (r < 0) - log_error_errno(r, "error: term_screen_resize() (%d): %m", r); - - terminal_dirty(t); - - return 0; -} - -static int terminal_push_tmp(Terminal *t, uint32_t ucs4) { - char buf[4]; - size_t len; - int r; - - assert(t); - - len = utf8_encode_unichar(buf, ucs4); - if (len < 1) - return 0; - - r = ring_push(&t->out_ring, buf, len); - if (r < 0) - log_oom(); - - return r; -} - -static int terminal_write_tmp(Terminal *t) { - struct iovec vec[2]; - size_t num, i; - int r; - - assert(t); - - num = ring_peek(&t->out_ring, vec); - if (num < 1) - return 0; - - if (t->pty) { - for (i = 0; i < num; ++i) { - r = pty_write(t->pty, vec[i].iov_base, vec[i].iov_len); - if (r < 0) - return log_error_errno(r, "error: cannot write to PTY (%d): %m", r); - } - } - - ring_flush(&t->out_ring); - return 0; -} - -static void terminal_discard_tmp(Terminal *t) { - assert(t); - - ring_flush(&t->out_ring); -} - -static int terminal_menu(Terminal *t, const term_seq *seq) { - switch (seq->type) { - case TERM_SEQ_IGNORE: - break; - case TERM_SEQ_GRAPHIC: - switch (seq->terminator) { - case 'q': - sd_event_exit(t->event, 0); - return 0; - } - - break; - case TERM_SEQ_CONTROL: - switch (seq->terminator) { - case 0x03: - terminal_push_tmp(t, 0x03); - terminal_write_tmp(t); - break; - } - - break; - } - - t->is_menu = false; - terminal_dirty(t); - - return 0; -} - -static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Terminal *t = userdata; - char buf[4096]; - ssize_t len, i; - int r, type; - - len = read(fd, buf, sizeof(buf)); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - log_error_errno(errno, "error: cannot read from TTY (%d): %m", -errno); - return -errno; - } - - for (i = 0; i < len; ++i) { - const term_seq *seq; - uint32_t *str; - size_t n_str, j; - - n_str = term_utf8_decode(&t->utf8, &str, buf[i]); - for (j = 0; j < n_str; ++j) { - type = term_parser_feed(t->parser, &seq, str[j]); - if (type < 0) - return log_error_errno(type, "error: term_parser_feed() (%d): %m", type); - - if (!t->is_menu) { - r = terminal_push_tmp(t, str[j]); - if (r < 0) - return r; - } - - if (type == TERM_SEQ_NONE) { - /* We only intercept one-char sequences, so in - * case term_parser_feed() couldn't parse a - * sequence, it is waiting for more data. We - * know it can never be a one-char sequence - * then, so we can safely forward the data. - * This avoids withholding ESC or other values - * that may be one-shot depending on the - * application. */ - r = terminal_write_tmp(t); - if (r < 0) - return r; - } else if (t->is_menu) { - r = terminal_menu(t, seq); - if (r < 0) - return r; - } else if (seq->type == TERM_SEQ_CONTROL && seq->terminator == 0x03) { /* ^C opens the menu */ - terminal_discard_tmp(t); - t->is_menu = true; - terminal_dirty(t); - } else { - r = terminal_write_tmp(t); - if (r < 0) - return r; - } - } - } - - return 0; -} - -static int terminal_pty_fn(Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size) { - Terminal *t = userdata; - int r; - - switch (event) { - case PTY_CHILD: - sd_event_exit(t->event, 0); - break; - case PTY_DATA: - r = term_screen_feed_text(t->screen, ptr, size); - if (r < 0) - return log_error_errno(r, "error: term_screen_feed_text() (%d): %m", r); - - terminal_dirty(t); - break; - } - - return 0; -} - -static int terminal_write_fn(term_screen *screen, void *userdata, const void *buf, size_t size) { - Terminal *t = userdata; - int r; - - if (!t->pty) - return 0; - - r = ring_push(&t->out_ring, buf, size); - if (r < 0) - log_oom(); - - return r; -} - -static int terminal_cmd_fn(term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq) { - return 0; -} - -static Terminal *terminal_free(Terminal *t) { - if (!t) - return NULL; - - ring_clear(&t->out_ring); - term_screen_unref(t->screen); - term_parser_free(t->parser); - output_free(t->output); - sd_event_source_unref(t->frame_timer); - sd_event_unref(t->event); - tcsetattr(t->in_fd, TCSANOW, &t->saved_in_attr); - tcsetattr(t->out_fd, TCSANOW, &t->saved_out_attr); - free(t); - - return NULL; -} - -static int terminal_new(Terminal **out, int in_fd, int out_fd) { - struct termios in_attr, out_attr; - Terminal *t; - int r; - - assert_return(out, -EINVAL); - - r = tcgetattr(in_fd, &in_attr); - if (r < 0) - return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno); - - r = tcgetattr(out_fd, &out_attr); - if (r < 0) - return log_error_errno(errno, "error: tcgetattr() (%d): %m", -errno); - - t = new0(Terminal, 1); - if (!t) - return log_oom(); - - t->in_fd = in_fd; - t->out_fd = out_fd; - memcpy(&t->saved_in_attr, &in_attr, sizeof(in_attr)); - memcpy(&t->saved_out_attr, &out_attr, sizeof(out_attr)); - - cfmakeraw(&in_attr); - cfmakeraw(&out_attr); - - r = tcsetattr(t->in_fd, TCSANOW, &in_attr); - if (r < 0) { - log_error_errno(r, "error: tcsetattr() (%d): %m", r); - goto error; - } - - r = tcsetattr(t->out_fd, TCSANOW, &out_attr); - if (r < 0) { - log_error_errno(r, "error: tcsetattr() (%d): %m", r); - goto error; - } - - r = sd_event_default(&t->event); - if (r < 0) { - log_error_errno(r, "error: sd_event_default() (%d): %m", r); - goto error; - } - - r = sigprocmask_many(SIG_BLOCK, NULL, SIGINT, SIGQUIT, SIGTERM, SIGWINCH, SIGCHLD, -1); - if (r < 0) { - log_error_errno(r, "error: sigprocmask_many() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGINT, NULL, NULL); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGQUIT, NULL, NULL); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGTERM, NULL, NULL); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - r = sd_event_add_signal(t->event, NULL, SIGWINCH, terminal_winch_fn, t); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_signal() (%d): %m", r); - goto error; - } - - /* force initial redraw on event-loop enter */ - t->is_dirty = true; - r = sd_event_add_time(t->event, &t->frame_timer, CLOCK_MONOTONIC, 0, 0, terminal_frame_timer_fn, t); - if (r < 0) { - log_error_errno(r, "error: sd_event_add_time() (%d): %m", r); - goto error; - } - - r = output_new(&t->output, out_fd); - if (r < 0) - goto error; - - r = term_parser_new(&t->parser, true); - if (r < 0) - goto error; - - r = term_screen_new(&t->screen, terminal_write_fn, t, terminal_cmd_fn, t); - if (r < 0) - goto error; - - r = term_screen_set_answerback(t->screen, "systemd-subterm"); - if (r < 0) - goto error; - - r = term_screen_resize(t->screen, t->output->in_width, t->output->in_height); - if (r < 0) { - log_error_errno(r, "error: term_screen_resize() (%d): %m", r); - goto error; - } - - r = sd_event_add_io(t->event, NULL, in_fd, EPOLLIN, terminal_io_fn, t); - if (r < 0) - goto error; - - *out = t; - return 0; - -error: - terminal_free(t); - return r; -} - -static int terminal_run(Terminal *t) { - pid_t pid; - - assert_return(t, -EINVAL); - - pid = pty_fork(&t->pty, t->event, terminal_pty_fn, t, t->output->in_width, t->output->in_height); - if (pid < 0) - return log_error_errno(pid, "error: cannot fork PTY (%d): %m", pid); - else if (pid == 0) { - /* child */ - - char **argv = (char*[]){ - (char*)getenv("SHELL") ? : (char*)_PATH_BSHELL, - NULL - }; - - setenv("TERM", "xterm-256color", 1); - setenv("COLORTERM", "systemd-subterm", 1); - - execve(argv[0], argv, environ); - log_error_errno(errno, "error: cannot exec %s (%d): %m", argv[0], -errno); - _exit(1); - } - - /* parent */ - - return sd_event_loop(t->event); -} - -/* - * Context Handling - */ - -int main(int argc, char *argv[]) { - Terminal *t = NULL; - int r; - - r = terminal_new(&t, 0, 1); - if (r < 0) - goto out; - - r = terminal_run(t); - if (r < 0) - goto out; - -out: - if (r < 0) - log_error_errno(r, "error: terminal failed (%d): %m", r); - terminal_free(t); - return -r; -} diff --git a/src/libsystemd-terminal/sysview-internal.h b/src/libsystemd-terminal/sysview-internal.h deleted file mode 100644 index 251c8d7300..0000000000 --- a/src/libsystemd-terminal/sysview-internal.h +++ /dev/null @@ -1,144 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "hashmap.h" -#include "list.h" -#include "macro.h" -#include "util.h" -#include "sysview.h" - -/* - * Devices - */ - -struct sysview_device { - sysview_seat *seat; - char *name; - unsigned int type; - - union { - struct { - struct udev_device *ud; - } evdev, drm; - }; -}; - -sysview_device *sysview_find_device(sysview_context *c, const char *name); - -int sysview_device_new(sysview_device **out, sysview_seat *seat, const char *name); -sysview_device *sysview_device_free(sysview_device *device); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_device*, sysview_device_free); - -/* - * Sessions - */ - -struct sysview_session { - sysview_seat *seat; - char *name; - char *path; - void *userdata; - - sd_bus_slot *slot_take_control; - - bool custom : 1; - bool public : 1; - bool wants_control : 1; - bool has_control : 1; -}; - -sysview_session *sysview_find_session(sysview_context *c, const char *name); - -int sysview_session_new(sysview_session **out, sysview_seat *seat, const char *name); -sysview_session *sysview_session_free(sysview_session *session); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_session*, sysview_session_free); - -/* - * Seats - */ - -struct sysview_seat { - sysview_context *context; - char *name; - char *path; - - Hashmap *session_map; - Hashmap *device_map; - - bool scanned : 1; - bool public : 1; -}; - -sysview_seat *sysview_find_seat(sysview_context *c, const char *name); - -int sysview_seat_new(sysview_seat **out, sysview_context *c, const char *name); -sysview_seat *sysview_seat_free(sysview_seat *seat); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_seat*, sysview_seat_free); - -/* - * Contexts - */ - -struct sysview_context { - sd_event *event; - sd_bus *sysbus; - struct udev *ud; - uint64_t custom_sid; - unsigned int n_probe; - - Hashmap *seat_map; - Hashmap *session_map; - Hashmap *device_map; - - sd_event_source *scan_src; - sysview_event_fn event_fn; - void *userdata; - - /* udev scanner */ - struct udev_monitor *ud_monitor; - sd_event_source *ud_monitor_src; - - /* logind scanner */ - sd_bus_slot *ld_slot_manager_signal; - sd_bus_slot *ld_slot_list_seats; - sd_bus_slot *ld_slot_list_sessions; - - bool scan_logind : 1; - bool scan_evdev : 1; - bool scan_drm : 1; - bool running : 1; - bool scanned : 1; - bool rescan : 1; - bool settled : 1; -}; - -int sysview_context_rescan(sysview_context *c); diff --git a/src/libsystemd-terminal/sysview.c b/src/libsystemd-terminal/sysview.c deleted file mode 100644 index 2e9b15859a..0000000000 --- a/src/libsystemd-terminal/sysview.c +++ /dev/null @@ -1,1554 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-login.h" -#include "macro.h" -#include "udev-util.h" -#include "util.h" -#include "bus-util.h" -#include "sysview.h" -#include "sysview-internal.h" - -static int context_raise_session_control(sysview_context *c, sysview_session *session, int error); - -/* - * Devices - */ - -sysview_device *sysview_find_device(sysview_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->device_map, name); -} - -int sysview_device_new(sysview_device **out, sysview_seat *seat, const char *name) { - _cleanup_(sysview_device_freep) sysview_device *device = NULL; - int r; - - assert_return(seat, -EINVAL); - assert_return(name, -EINVAL); - - device = new0(sysview_device, 1); - if (!device) - return -ENOMEM; - - device->seat = seat; - device->type = (unsigned)-1; - - device->name = strdup(name); - if (!device->name) - return -ENOMEM; - - r = hashmap_put(seat->context->device_map, device->name, device); - if (r < 0) - return r; - - r = hashmap_put(seat->device_map, device->name, device); - if (r < 0) - return r; - - if (out) - *out = device; - device = NULL; - return 0; -} - -sysview_device *sysview_device_free(sysview_device *device) { - if (!device) - return NULL; - - if (device->name) { - hashmap_remove_value(device->seat->device_map, device->name, device); - hashmap_remove_value(device->seat->context->device_map, device->name, device); - } - - switch (device->type) { - case SYSVIEW_DEVICE_EVDEV: - device->evdev.ud = udev_device_unref(device->evdev.ud); - break; - case SYSVIEW_DEVICE_DRM: - device->drm.ud = udev_device_unref(device->drm.ud); - break; - } - - free(device->name); - free(device); - - return NULL; -} - -const char *sysview_device_get_name(sysview_device *device) { - assert_return(device, NULL); - - return device->name; -} - -unsigned int sysview_device_get_type(sysview_device *device) { - assert_return(device, (unsigned)-1); - - return device->type; -} - -struct udev_device *sysview_device_get_ud(sysview_device *device) { - assert_return(device, NULL); - - switch (device->type) { - case SYSVIEW_DEVICE_EVDEV: - return device->evdev.ud; - case SYSVIEW_DEVICE_DRM: - return device->drm.ud; - default: - assert_return(0, NULL); - } -} - -static int device_new_ud(sysview_device **out, sysview_seat *seat, unsigned int type, struct udev_device *ud) { - _cleanup_(sysview_device_freep) sysview_device *device = NULL; - int r; - - assert_return(seat, -EINVAL); - assert_return(ud, -EINVAL); - - r = sysview_device_new(&device, seat, udev_device_get_syspath(ud)); - if (r < 0) - return r; - - device->type = type; - - switch (type) { - case SYSVIEW_DEVICE_EVDEV: - device->evdev.ud = udev_device_ref(ud); - break; - case SYSVIEW_DEVICE_DRM: - device->drm.ud = udev_device_ref(ud); - break; - default: - assert_not_reached("sysview: invalid udev-device type"); - } - - if (out) - *out = device; - device = NULL; - return 0; -} - -/* - * Sessions - */ - -sysview_session *sysview_find_session(sysview_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->session_map, name); -} - -int sysview_session_new(sysview_session **out, sysview_seat *seat, const char *name) { - _cleanup_(sysview_session_freep) sysview_session *session = NULL; - int r; - - assert_return(seat, -EINVAL); - - session = new0(sysview_session, 1); - if (!session) - return -ENOMEM; - - session->seat = seat; - - if (name) { - /* - * If a name is given, we require it to be a logind session - * name. The session will be put in managed mode and we use - * logind to request controller access. - */ - - session->name = strdup(name); - if (!session->name) - return -ENOMEM; - - r = sd_bus_path_encode("/org/freedesktop/login1/session", - session->name, &session->path); - if (r < 0) - return r; - - session->custom = false; - } else { - /* - * No session name was given. We assume this is an unmanaged - * session controlled by the application. We don't use logind - * at all and leave session management to the application. The - * name of the session-object is set to a unique random string - * that does not clash with the logind namespace. - */ - - r = asprintf(&session->name, "@custom%" PRIu64, - ++seat->context->custom_sid); - if (r < 0) - return -ENOMEM; - - session->custom = true; - } - - r = hashmap_put(seat->context->session_map, session->name, session); - if (r < 0) - return r; - - r = hashmap_put(seat->session_map, session->name, session); - if (r < 0) - return r; - - if (out) - *out = session; - session = NULL; - return 0; -} - -sysview_session *sysview_session_free(sysview_session *session) { - if (!session) - return NULL; - - assert(!session->public); - assert(!session->wants_control); - - if (session->name) { - hashmap_remove_value(session->seat->session_map, session->name, session); - hashmap_remove_value(session->seat->context->session_map, session->name, session); - } - - free(session->path); - free(session->name); - free(session); - - return NULL; -} - -void sysview_session_set_userdata(sysview_session *session, void *userdata) { - assert(session); - - session->userdata = userdata; -} - -void *sysview_session_get_userdata(sysview_session *session) { - assert_return(session, NULL); - - return session->userdata; -} - -const char *sysview_session_get_name(sysview_session *session) { - assert_return(session, NULL); - - return session->name; -} - -sysview_seat *sysview_session_get_seat(sysview_session *session) { - assert_return(session, NULL); - - return session->seat; -} - -static int session_take_control_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - sysview_session *session = userdata; - int r, error; - - session->slot_take_control = sd_bus_slot_unref(session->slot_take_control); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *e = sd_bus_message_get_error(reply); - - log_debug("sysview: %s: TakeControl failed: %s: %s", - session->name, e->name, e->message); - error = -sd_bus_error_get_errno(e); - } else { - session->has_control = true; - error = 0; - } - - r = context_raise_session_control(session->seat->context, session, error); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while signalling session control '%d' on session '%s': %m", - error, session->name); - - return 0; -} - -int sysview_session_take_control(sysview_session *session) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - assert_return(session, -EINVAL); - assert_return(!session->custom, -EINVAL); - - if (session->wants_control) - return 0; - - r = sd_bus_message_new_method_call(session->seat->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "TakeControl"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "b", 0); - if (r < 0) - return r; - - r = sd_bus_call_async(session->seat->context->sysbus, - &session->slot_take_control, - m, - session_take_control_fn, - session, - 0); - if (r < 0) - return r; - - session->wants_control = true; - return 0; -} - -void sysview_session_release_control(sysview_session *session) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - assert(session); - assert(!session->custom); - - if (!session->wants_control) - return; - - session->wants_control = false; - - if (!session->has_control && !session->slot_take_control) - return; - - session->has_control = false; - session->slot_take_control = sd_bus_slot_unref(session->slot_take_control); - - r = sd_bus_message_new_method_call(session->seat->context->sysbus, - &m, - "org.freedesktop.login1", - session->path, - "org.freedesktop.login1.Session", - "ReleaseControl"); - if (r >= 0) - r = sd_bus_send(session->seat->context->sysbus, m, NULL); - - if (r < 0 && r != -ENOTCONN) - log_debug_errno(r, "sysview: %s: cannot send ReleaseControl: %m", - session->name); -} - -/* - * Seats - */ - -sysview_seat *sysview_find_seat(sysview_context *c, const char *name) { - assert_return(c, NULL); - assert_return(name, NULL); - - return hashmap_get(c->seat_map, name); -} - -int sysview_seat_new(sysview_seat **out, sysview_context *c, const char *name) { - _cleanup_(sysview_seat_freep) sysview_seat *seat = NULL; - int r; - - assert_return(c, -EINVAL); - assert_return(name, -EINVAL); - - seat = new0(sysview_seat, 1); - if (!seat) - return -ENOMEM; - - seat->context = c; - - seat->name = strdup(name); - if (!seat->name) - return -ENOMEM; - - r = sd_bus_path_encode("/org/freedesktop/login1/seat", seat->name, &seat->path); - if (r < 0) - return r; - - seat->session_map = hashmap_new(&string_hash_ops); - if (!seat->session_map) - return -ENOMEM; - - seat->device_map = hashmap_new(&string_hash_ops); - if (!seat->device_map) - return -ENOMEM; - - r = hashmap_put(c->seat_map, seat->name, seat); - if (r < 0) - return r; - - if (out) - *out = seat; - seat = NULL; - return 0; -} - -sysview_seat *sysview_seat_free(sysview_seat *seat) { - if (!seat) - return NULL; - - assert(!seat->public); - assert(hashmap_size(seat->device_map) == 0); - assert(hashmap_size(seat->session_map) == 0); - - if (seat->name) - hashmap_remove_value(seat->context->seat_map, seat->name, seat); - - hashmap_free(seat->device_map); - hashmap_free(seat->session_map); - free(seat->path); - free(seat->name); - free(seat); - - return NULL; -} - -const char *sysview_seat_get_name(sysview_seat *seat) { - assert_return(seat, NULL); - - return seat->name; -} - -int sysview_seat_switch_to(sysview_seat *seat, uint32_t nr) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - assert_return(seat, -EINVAL); - assert_return(seat->context->sysbus, -EINVAL); - - r = sd_bus_message_new_method_call(seat->context->sysbus, - &m, - "org.freedesktop.login1", - seat->path, - "org.freedesktop.login1.Seat", - "SwitchTo"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "u", nr); - if (r < 0) - return r; - - return sd_bus_send(seat->context->sysbus, m, NULL); -} - -/* - * Contexts - */ - -static int context_raise(sysview_context *c, sysview_event *event, int def) { - return c->running ? c->event_fn(c, c->userdata, event) : def; -} - -static int context_raise_settle(sysview_context *c) { - sysview_event event = { - .type = SYSVIEW_EVENT_SETTLE, - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_seat_add(sysview_context *c, sysview_seat *seat) { - sysview_event event = { - .type = SYSVIEW_EVENT_SEAT_ADD, - .seat_add = { - .seat = seat, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_seat_remove(sysview_context *c, sysview_seat *seat) { - sysview_event event = { - .type = SYSVIEW_EVENT_SEAT_REMOVE, - .seat_remove = { - .seat = seat, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_filter(sysview_context *c, - const char *id, - const char *seatid, - const char *username, - unsigned int uid) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_FILTER, - .session_filter = { - .id = id, - .seatid = seatid, - .username = username, - .uid = uid, - } - }; - - return context_raise(c, &event, 1); -} - -static int context_raise_session_add(sysview_context *c, sysview_session *session) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_ADD, - .session_add = { - .session = session, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_remove(sysview_context *c, sysview_session *session) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_REMOVE, - .session_remove = { - .session = session, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_control(sysview_context *c, sysview_session *session, int error) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_CONTROL, - .session_control = { - .session = session, - .error = error, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_attach(sysview_context *c, sysview_session *session, sysview_device *device) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_ATTACH, - .session_attach = { - .session = session, - .device = device, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_detach(sysview_context *c, sysview_session *session, sysview_device *device) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_DETACH, - .session_detach = { - .session = session, - .device = device, - } - }; - - return context_raise(c, &event, 0); -} - -static int context_raise_session_refresh(sysview_context *c, sysview_session *session, sysview_device *device, struct udev_device *ud) { - sysview_event event = { - .type = SYSVIEW_EVENT_SESSION_REFRESH, - .session_refresh = { - .session = session, - .device = device, - .ud = ud, - } - }; - - return context_raise(c, &event, 0); -} - -static void context_settle(sysview_context *c) { - int r; - - if (c->n_probe <= 0 || --c->n_probe > 0) - return; - - log_debug("sysview: settle"); - - c->settled = true; - - r = context_raise_settle(c); - if (r < 0) - log_debug_errno(r, "sysview: callback failed on settle: %m"); -} - -static void context_add_device(sysview_context *c, sysview_device *device) { - sysview_session *session; - Iterator i; - int r; - - assert(c); - assert(device); - - log_debug("sysview: add device '%s' on seat '%s'", - device->name, device->seat->name); - - HASHMAP_FOREACH(session, device->seat->session_map, i) { - if (!session->public) - continue; - - r = context_raise_session_attach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while attaching device '%s' to session '%s': %m", - device->name, session->name); - } -} - -static void context_remove_device(sysview_context *c, sysview_device *device) { - sysview_session *session; - Iterator i; - int r; - - assert(c); - assert(device); - - log_debug("sysview: remove device '%s'", device->name); - - HASHMAP_FOREACH(session, device->seat->session_map, i) { - if (!session->public) - continue; - - r = context_raise_session_detach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while detaching device '%s' from session '%s': %m", - device->name, session->name); - } - - sysview_device_free(device); -} - -static void context_change_device(sysview_context *c, sysview_device *device, struct udev_device *ud) { - sysview_session *session; - Iterator i; - int r; - - assert(c); - assert(device); - - log_debug("sysview: change device '%s'", device->name); - - HASHMAP_FOREACH(session, device->seat->session_map, i) { - if (!session->public) - continue; - - r = context_raise_session_refresh(c, session, device, ud); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while changing device '%s' on session '%s': %m", - device->name, session->name); - } -} - -static void context_add_session(sysview_context *c, sysview_seat *seat, const char *id) { - sysview_session *session; - sysview_device *device; - Iterator i; - int r; - - assert(c); - assert(seat); - assert(id); - - session = sysview_find_session(c, id); - if (session) - return; - - log_debug("sysview: add session '%s' on seat '%s'", id, seat->name); - - r = sysview_session_new(&session, seat, id); - if (r < 0) - goto error; - - if (!seat->scanned) { - r = sysview_context_rescan(c); - if (r < 0) - goto error; - } - - if (seat->public) { - session->public = true; - r = context_raise_session_add(c, session); - if (r < 0) { - log_debug_errno(r, "sysview: callback failed while adding session '%s': %m", - session->name); - session->public = false; - goto error; - } - - HASHMAP_FOREACH(device, seat->device_map, i) { - r = context_raise_session_attach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while attaching device '%s' to new session '%s': %m", - device->name, session->name); - } - } - - return; - -error: - if (r < 0) - log_debug_errno(r, "sysview: error while adding session '%s': %m", - id); -} - -static void context_remove_session(sysview_context *c, sysview_session *session) { - sysview_device *device; - Iterator i; - int r; - - assert(c); - assert(session); - - log_debug("sysview: remove session '%s'", session->name); - - if (session->public) { - HASHMAP_FOREACH(device, session->seat->device_map, i) { - r = context_raise_session_detach(c, session, device); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while detaching device '%s' from old session '%s': %m", - device->name, session->name); - } - - session->public = false; - r = context_raise_session_remove(c, session); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while removing session '%s': %m", - session->name); - } - - if (!session->custom) - sysview_session_release_control(session); - - sysview_session_free(session); -} - -static void context_add_seat(sysview_context *c, const char *id) { - sysview_seat *seat; - int r; - - assert(c); - assert(id); - - seat = sysview_find_seat(c, id); - if (seat) - return; - - log_debug("sysview: add seat '%s'", id); - - r = sysview_seat_new(&seat, c, id); - if (r < 0) - goto error; - - seat->public = true; - r = context_raise_seat_add(c, seat); - if (r < 0) { - log_debug_errno(r, "sysview: callback failed while adding seat '%s': %m", - seat->name); - seat->public = false; - } - - return; - -error: - if (r < 0) - log_debug_errno(r, "sysview: error while adding seat '%s': %m", - id); -} - -static void context_remove_seat(sysview_context *c, sysview_seat *seat) { - sysview_session *session; - sysview_device *device; - int r; - - assert(c); - assert(seat); - - log_debug("sysview: remove seat '%s'", seat->name); - - while ((device = hashmap_first(seat->device_map))) - context_remove_device(c, device); - - while ((session = hashmap_first(seat->session_map))) - context_remove_session(c, session); - - if (seat->public) { - seat->public = false; - r = context_raise_seat_remove(c, seat); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while removing seat '%s': %m", - seat->name); - } - - sysview_seat_free(seat); -} - -int sysview_context_new(sysview_context **out, - unsigned int flags, - sd_event *event, - sd_bus *sysbus, - struct udev *ud) { - _cleanup_(sysview_context_freep) sysview_context *c = NULL; - int r; - - assert_return(out, -EINVAL); - assert_return(event, -EINVAL); - - log_debug("sysview: new"); - - c = new0(sysview_context, 1); - if (!c) - return -ENOMEM; - - c->event = sd_event_ref(event); - if (flags & SYSVIEW_CONTEXT_SCAN_LOGIND) - c->scan_logind = true; - if (flags & SYSVIEW_CONTEXT_SCAN_EVDEV) - c->scan_evdev = true; - if (flags & SYSVIEW_CONTEXT_SCAN_DRM) - c->scan_drm = true; - - if (sysbus) { - c->sysbus = sd_bus_ref(sysbus); - } else if (c->scan_logind) { - r = sd_bus_open_system(&c->sysbus); - if (r < 0) - return r; - } - - if (ud) { - c->ud = udev_ref(ud); - } else if (c->scan_evdev || c->scan_drm) { - errno = 0; - c->ud = udev_new(); - if (!c->ud) - return errno > 0 ? -errno : -EFAULT; - } - - c->seat_map = hashmap_new(&string_hash_ops); - if (!c->seat_map) - return -ENOMEM; - - c->session_map = hashmap_new(&string_hash_ops); - if (!c->session_map) - return -ENOMEM; - - c->device_map = hashmap_new(&string_hash_ops); - if (!c->device_map) - return -ENOMEM; - - *out = c; - c = NULL; - return 0; -} - -sysview_context *sysview_context_free(sysview_context *c) { - if (!c) - return NULL; - - log_debug("sysview: free"); - - sysview_context_stop(c); - - assert(hashmap_size(c->device_map) == 0); - assert(hashmap_size(c->session_map) == 0); - assert(hashmap_size(c->seat_map) == 0); - - hashmap_free(c->device_map); - hashmap_free(c->session_map); - hashmap_free(c->seat_map); - c->ud = udev_unref(c->ud); - c->sysbus = sd_bus_unref(c->sysbus); - c->event = sd_event_unref(c->event); - free(c); - - return NULL; -} - -static int context_ud_prepare_monitor(sysview_context *c, struct udev_monitor *m) { - int r; - - if (c->scan_evdev) { - r = udev_monitor_filter_add_match_subsystem_devtype(m, "input", NULL); - if (r < 0) - return r; - } - - if (c->scan_drm) { - r = udev_monitor_filter_add_match_subsystem_devtype(m, "drm", NULL); - if (r < 0) - return r; - } - - return 0; -} - -static int context_ud_prepare_scan(sysview_context *c, struct udev_enumerate *e) { - int r; - - if (c->scan_evdev) { - r = udev_enumerate_add_match_subsystem(e, "input"); - if (r < 0) - return r; - } - - if (c->scan_drm) { - r = udev_enumerate_add_match_subsystem(e, "drm"); - if (r < 0) - return r; - } - - r = udev_enumerate_add_match_is_initialized(e); - if (r < 0) - return r; - - return 0; -} - -static int context_ud_hotplug(sysview_context *c, struct udev_device *d) { - const char *syspath, *sysname, *subsystem, *action, *seatname; - sysview_device *device; - int r; - - syspath = udev_device_get_syspath(d); - sysname = udev_device_get_sysname(d); - subsystem = udev_device_get_subsystem(d); - action = udev_device_get_action(d); - - /* not interested in custom devices without syspath/etc */ - if (!syspath || !sysname || !subsystem) - return 0; - - device = sysview_find_device(c, syspath); - - if (streq_ptr(action, "remove")) { - if (!device) - return 0; - - context_remove_device(c, device); - } else if (streq_ptr(action, "change")) { - if (!device) - return 0; - - context_change_device(c, device, d); - } else if (!action || streq_ptr(action, "add")) { - struct udev_device *p; - unsigned int type, t; - sysview_seat *seat; - - if (device) - return 0; - - if (streq(subsystem, "input") && startswith(sysname, "event") && safe_atou(sysname + 5, &t) >= 0) - type = SYSVIEW_DEVICE_EVDEV; - else if (streq(subsystem, "drm") && startswith(sysname, "card")) - type = SYSVIEW_DEVICE_DRM; - else - type = (unsigned)-1; - - if (type >= SYSVIEW_DEVICE_CNT) - return 0; - - p = d; - seatname = NULL; - do { - seatname = udev_device_get_property_value(p, "ID_SEAT"); - if (seatname) - break; - } while ((p = udev_device_get_parent(p))); - - seat = sysview_find_seat(c, seatname ? : "seat0"); - if (!seat) - return 0; - - r = device_new_ud(&device, seat, type, d); - if (r < 0) - return log_debug_errno(r, "sysview: cannot create device for udev-device '%s': %m", - syspath); - - context_add_device(c, device); - } - - return 0; -} - -static int context_ud_monitor_fn(sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - sysview_context *c = userdata; - struct udev_device *d; - int r; - - if (revents & EPOLLIN) { - while ((d = udev_monitor_receive_device(c->ud_monitor))) { - r = context_ud_hotplug(c, d); - udev_device_unref(d); - if (r != 0) - return r; - } - - /* as long as EPOLLIN is signalled, read pending data */ - return 0; - } - - if (revents & (EPOLLHUP | EPOLLERR)) { - log_debug("sysview: HUP on udev-monitor"); - c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src); - } - - return 0; -} - -static int context_ud_start(sysview_context *c) { - int r, fd; - - if (!c->ud) - return 0; - - errno = 0; - c->ud_monitor = udev_monitor_new_from_netlink(c->ud, "udev"); - if (!c->ud_monitor) - return errno > 0 ? -errno : -EFAULT; - - r = context_ud_prepare_monitor(c, c->ud_monitor); - if (r < 0) - return r; - - r = udev_monitor_enable_receiving(c->ud_monitor); - if (r < 0) - return r; - - fd = udev_monitor_get_fd(c->ud_monitor); - r = sd_event_add_io(c->event, - &c->ud_monitor_src, - fd, - EPOLLHUP | EPOLLERR | EPOLLIN, - context_ud_monitor_fn, - c); - if (r < 0) - return r; - - return 0; -} - -static void context_ud_stop(sysview_context *c) { - c->ud_monitor_src = sd_event_source_unref(c->ud_monitor_src); - c->ud_monitor = udev_monitor_unref(c->ud_monitor); -} - -static int context_ud_scan(sysview_context *c) { - _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL; - struct udev_list_entry *entry; - struct udev_device *d; - int r; - - if (!c->ud_monitor) - return 0; - - errno = 0; - e = udev_enumerate_new(c->ud); - if (!e) - return errno > 0 ? -errno : -EFAULT; - - r = context_ud_prepare_scan(c, e); - if (r < 0) - return r; - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return r; - - udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { - const char *name; - - name = udev_list_entry_get_name(entry); - - errno = 0; - d = udev_device_new_from_syspath(c->ud, name); - if (!d) { - r = errno > 0 ? -errno : -EFAULT; - log_debug_errno(r, "sysview: cannot create udev-device for %s: %m", - name); - continue; - } - - r = context_ud_hotplug(c, d); - udev_device_unref(d); - if (r != 0) - return r; - } - - return 0; -} - -static int context_ld_seat_new(sysview_context *c, sd_bus_message *signal) { - const char *id, *path; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SeatNew from logind: %m"); - - context_add_seat(c, id); - return 0; -} - -static int context_ld_seat_removed(sysview_context *c, sd_bus_message *signal) { - const char *id, *path; - sysview_seat *seat; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SeatRemoved from logind: %m"); - - seat = sysview_find_seat(c, id); - if (!seat) - return 0; - - context_remove_seat(c, seat); - return 0; -} - -static int context_ld_session_new(sysview_context *c, sd_bus_message *signal) { - _cleanup_free_ char *seatid = NULL, *username = NULL; - const char *id, *path; - sysview_seat *seat; - uid_t uid; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SessionNew from logind: %m"); - - /* - * As the dbus message didn't contain enough information, we - * read missing bits via sd-login. Note that this might race session - * destruction, so we handle ENOENT properly. - */ - - /* ENOENT is also returned for sessions without seats */ - r = sd_session_get_seat(id, &seatid); - if (r == -ENOENT) - return 0; - else if (r < 0) - goto error; - - seat = sysview_find_seat(c, seatid); - if (!seat) - return 0; - - r = sd_session_get_uid(id, &uid); - if (r == -ENOENT) - return 0; - else if (r < 0) - goto error; - - username = lookup_uid(uid); - if (!username) { - r = -ENOMEM; - goto error; - } - - r = context_raise_session_filter(c, id, seatid, username, uid); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m", - id); - else if (r > 0) - context_add_session(c, seat, id); - - return 0; - -error: - return log_debug_errno(r, "sysview: failed retrieving information for new session '%s': %m", - id); -} - -static int context_ld_session_removed(sysview_context *c, sd_bus_message *signal) { - sysview_session *session; - const char *id, *path; - int r; - - r = sd_bus_message_read(signal, "so", &id, &path); - if (r < 0) - return log_debug_errno(r, "sysview: cannot parse SessionRemoved from logind: %m"); - - session = sysview_find_session(c, id); - if (!session) - return 0; - - context_remove_session(c, session); - return 0; -} - -static int context_ld_manager_signal_fn(sd_bus_message *signal, - void *userdata, - sd_bus_error *ret_error) { - sysview_context *c = userdata; - - if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatNew")) - return context_ld_seat_new(c, signal); - else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SeatRemoved")) - return context_ld_seat_removed(c, signal); - else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionNew")) - return context_ld_session_new(c, signal); - else if (sd_bus_message_is_signal(signal, "org.freedesktop.login1.Manager", "SessionRemoved")) - return context_ld_session_removed(c, signal); - else - return 0; -} - -static int context_ld_start(sysview_context *c) { - int r; - - if (!c->scan_logind) - return 0; - - r = sd_bus_add_match(c->sysbus, - &c->ld_slot_manager_signal, - "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Manager'," - "path='/org/freedesktop/login1'", - context_ld_manager_signal_fn, - c); - if (r < 0) - return r; - - return 0; -} - -static void context_ld_stop(sysview_context *c) { - c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions); - c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats); - c->ld_slot_manager_signal = sd_bus_slot_unref(c->ld_slot_manager_signal); -} - -static int context_ld_list_seats_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - sysview_context *c = userdata; - int r; - - c->ld_slot_list_seats = sd_bus_slot_unref(c->ld_slot_list_seats); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("sysview: ListSeats on logind failed: %s: %s", - error->name, error->message); - r = -sd_bus_error_get_errno(error); - goto settle; - } - - r = sd_bus_message_enter_container(reply, 'a', "(so)"); - if (r < 0) - goto error; - - while ((r = sd_bus_message_enter_container(reply, 'r', "so")) > 0) { - const char *id, *path; - - r = sd_bus_message_read(reply, "so", &id, &path); - if (r < 0) - goto error; - - context_add_seat(c, id); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - } - - if (r < 0) - goto error; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - - r = 0; - goto settle; - -error: - log_debug_errno(r, "sysview: erroneous ListSeats response from logind: %m"); -settle: - context_settle(c); - return r; -} - -static int context_ld_list_sessions_fn(sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - sysview_context *c = userdata; - int r; - - c->ld_slot_list_sessions = sd_bus_slot_unref(c->ld_slot_list_sessions); - - if (sd_bus_message_is_method_error(reply, NULL)) { - const sd_bus_error *error = sd_bus_message_get_error(reply); - - log_debug("sysview: ListSessions on logind failed: %s: %s", - error->name, error->message); - r = -sd_bus_error_get_errno(error); - goto settle; - } - - r = sd_bus_message_enter_container(reply, 'a', "(susso)"); - if (r < 0) - goto error; - - while ((r = sd_bus_message_enter_container(reply, 'r', "susso")) > 0) { - const char *id, *username, *seatid, *path; - sysview_seat *seat; - unsigned int uid; - - r = sd_bus_message_read(reply, - "susso", - &id, - &uid, - &username, - &seatid, - &path); - if (r < 0) - goto error; - - seat = sysview_find_seat(c, seatid); - if (seat) { - r = context_raise_session_filter(c, id, seatid, username, uid); - if (r < 0) - log_debug_errno(r, "sysview: callback failed while filtering session '%s': %m", - id); - else if (r > 0) - context_add_session(c, seat, id); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - } - - if (r < 0) - goto error; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto error; - - r = 0; - goto settle; - -error: - log_debug_errno(r, "sysview: erroneous ListSessions response from logind: %m"); -settle: - context_settle(c); - return r; -} - -static int context_ld_scan(sysview_context *c) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int r; - - if (!c->ld_slot_manager_signal) - return 0; - - /* request seat list */ - - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ListSeats"); - if (r < 0) - return r; - - r = sd_bus_call_async(c->sysbus, - &c->ld_slot_list_seats, - m, - context_ld_list_seats_fn, - c, - 0); - if (r < 0) - return r; - - if (!c->settled) - ++c->n_probe; - - /* request session list */ - - m = sd_bus_message_unref(m); - r = sd_bus_message_new_method_call(c->sysbus, - &m, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ListSessions"); - if (r < 0) - return r; - - r = sd_bus_call_async(c->sysbus, - &c->ld_slot_list_sessions, - m, - context_ld_list_sessions_fn, - c, - 0); - if (r < 0) - return r; - - if (!c->settled) - ++c->n_probe; - - return 0; -} - -bool sysview_context_is_running(sysview_context *c) { - return c && c->running; -} - -int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata) { - int r; - - assert_return(c, -EINVAL); - assert_return(event_fn, -EINVAL); - - if (c->running) - return -EALREADY; - - log_debug("sysview: start"); - - c->running = true; - c->event_fn = event_fn; - c->userdata = userdata; - - r = context_ld_start(c); - if (r < 0) - goto error; - - r = context_ud_start(c); - if (r < 0) - goto error; - - r = sysview_context_rescan(c); - if (r < 0) - goto error; - - return 0; - -error: - sysview_context_stop(c); - return r; -} - -void sysview_context_stop(sysview_context *c) { - sysview_session *session; - sysview_device *device; - sysview_seat *seat; - - assert(c); - - if (!c->running) - return; - - log_debug("sysview: stop"); - - while ((device = hashmap_first(c->device_map))) - context_remove_device(c, device); - - while ((session = hashmap_first(c->session_map))) - context_remove_session(c, session); - - while ((seat = hashmap_first(c->seat_map))) - context_remove_seat(c, seat); - - c->running = false; - c->scanned = false; - c->settled = false; - c->n_probe = 0; - c->event_fn = NULL; - c->userdata = NULL; - c->scan_src = sd_event_source_unref(c->scan_src); - context_ud_stop(c); - context_ld_stop(c); -} - -static int context_scan_fn(sd_event_source *s, void *userdata) { - sysview_context *c = userdata; - sysview_seat *seat; - Iterator i; - int r; - - c->rescan = false; - - if (!c->scanned) { - r = context_ld_scan(c); - if (r < 0) - return log_debug_errno(r, "sysview: logind scan failed: %m"); - } - - /* skip device scans if no sessions are available */ - if (hashmap_size(c->session_map) > 0) { - r = context_ud_scan(c); - if (r < 0) - return log_debug_errno(r, "sysview: udev scan failed: %m"); - - HASHMAP_FOREACH(seat, c->seat_map, i) - seat->scanned = true; - } - - c->scanned = true; - context_settle(c); - - return 0; -} - -int sysview_context_rescan(sysview_context *c) { - assert(c); - - if (!c->running) - return 0; - - if (!c->rescan) { - c->rescan = true; - if (!c->settled) - ++c->n_probe; - } - - if (c->scan_src) - return sd_event_source_set_enabled(c->scan_src, SD_EVENT_ONESHOT); - else - return sd_event_add_defer(c->event, &c->scan_src, context_scan_fn, c); -} diff --git a/src/libsystemd-terminal/sysview.h b/src/libsystemd-terminal/sysview.h deleted file mode 100644 index a5e7a38df3..0000000000 --- a/src/libsystemd-terminal/sysview.h +++ /dev/null @@ -1,162 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * System View - * The sysview interface scans and monitors the system for seats, sessions and - * devices. It basically mirrors the state of logind on the application side. - * It's meant as base for session services that require managed device access. - * The logind controller API is employed to allow unprivileged access to all - * devices of a user. - * Furthermore, the sysview interface can be used for system services that run - * in situations where logind is not available, but session-like services are - * needed. For instance, the initrd does not run logind but might require - * graphics access. It cannot run session services, though. The sysview - * interface pretends that a session is available and provides the same - * interface as to normal session services. - */ - -#pragma once - -#include -#include "sd-bus.h" -#include "sd-event.h" - -typedef struct sysview_event sysview_event; -typedef struct sysview_device sysview_device; -typedef struct sysview_session sysview_session; -typedef struct sysview_seat sysview_seat; -typedef struct sysview_context sysview_context; - -/* - * Events - */ - -enum { - SYSVIEW_EVENT_SETTLE, - - SYSVIEW_EVENT_SEAT_ADD, - SYSVIEW_EVENT_SEAT_REMOVE, - - SYSVIEW_EVENT_SESSION_FILTER, - SYSVIEW_EVENT_SESSION_ADD, - SYSVIEW_EVENT_SESSION_REMOVE, - SYSVIEW_EVENT_SESSION_ATTACH, - SYSVIEW_EVENT_SESSION_DETACH, - SYSVIEW_EVENT_SESSION_REFRESH, - SYSVIEW_EVENT_SESSION_CONTROL, -}; - -struct sysview_event { - unsigned int type; - - union { - struct { - sysview_seat *seat; - } seat_add, seat_remove; - - struct { - const char *id; - const char *seatid; - const char *username; - unsigned int uid; - } session_filter; - - struct { - sysview_session *session; - } session_add, session_remove; - - struct { - sysview_session *session; - sysview_device *device; - } session_attach, session_detach; - - struct { - sysview_session *session; - sysview_device *device; - struct udev_device *ud; - } session_refresh; - - struct { - sysview_session *session; - int error; - } session_control; - }; -}; - -typedef int (*sysview_event_fn) (sysview_context *c, void *userdata, sysview_event *e); - -/* - * Devices - */ - -enum { - SYSVIEW_DEVICE_EVDEV, - SYSVIEW_DEVICE_DRM, - SYSVIEW_DEVICE_CNT -}; - -const char *sysview_device_get_name(sysview_device *device); -unsigned int sysview_device_get_type(sysview_device *device); -struct udev_device *sysview_device_get_ud(sysview_device *device); - -/* - * Sessions - */ - -void sysview_session_set_userdata(sysview_session *session, void *userdata); -void *sysview_session_get_userdata(sysview_session *session); - -const char *sysview_session_get_name(sysview_session *session); -sysview_seat *sysview_session_get_seat(sysview_session *session); - -int sysview_session_take_control(sysview_session *session); -void sysview_session_release_control(sysview_session *session); - -/* - * Seats - */ - -const char *sysview_seat_get_name(sysview_seat *seat); -int sysview_seat_switch_to(sysview_seat *seat, uint32_t nr); - -/* - * Contexts - */ - -enum { - SYSVIEW_CONTEXT_SCAN_LOGIND = (1 << 0), - SYSVIEW_CONTEXT_SCAN_EVDEV = (1 << 1), - SYSVIEW_CONTEXT_SCAN_DRM = (1 << 2), -}; - -int sysview_context_new(sysview_context **out, - unsigned int flags, - sd_event *event, - sd_bus *sysbus, - struct udev *ud); -sysview_context *sysview_context_free(sysview_context *c); - -DEFINE_TRIVIAL_CLEANUP_FUNC(sysview_context*, sysview_context_free); - -bool sysview_context_is_running(sysview_context *c); -int sysview_context_start(sysview_context *c, sysview_event_fn event_fn, void *userdata); -void sysview_context_stop(sysview_context *c); diff --git a/src/libsystemd-terminal/term-charset.c b/src/libsystemd-terminal/term-charset.c deleted file mode 100644 index 9db178861c..0000000000 --- a/src/libsystemd-terminal/term-charset.c +++ /dev/null @@ -1,488 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * VTE Character Sets - * These are predefined charactersets that can be loaded into GL and GR. By - * default we use unicode_lower and unicode_upper, that is, both sets have the - * exact unicode mapping. unicode_lower is effectively ASCII and unicode_upper - * as defined by the unicode standard (I guess, ISO 8859-1). - * Several other character sets are defined here. However, all of them are - * limited to the 96 character space of GL or GR. Everything beyond GR (which - * was not supported by the classic VTs by DEC but is available in VT emulators - * that support unicode/UTF8) is always mapped to unicode and cannot be changed - * by these character sets. Even mapping GL and GR is only available for - * backwards compatibility as new applications can use the Unicode functionality - * of the VTE. - * - * Moreover, mapping GR is almost unnecessary to support. In fact, Unicode UTF-8 - * support in VTE works by reading every incoming data as UTF-8 stream. This - * maps GL/ASCII to ASCII, as UTF-8 is backwards compatible to ASCII, however, - * everything that has the 8th bit set is a >=2-byte haracter in UTF-8. That is, - * this is in no way backwards compatible to >=VT220 8bit support. Therefore, if - * someone maps a character set into GR and wants to use them with this VTE, - * then they must already send UTF-8 characters to use GR (all GR characters are - * 8-bits). Hence, they can easily also send the correct UTF-8 character for the - * unicode mapping. - * The only advantage is that most characters in many sets are 3-byte UTF-8 - * characters and by mapping the set into GR/GL you can use 2 or 1 byte UTF-8 - * characters which saves bandwidth. - * Another reason is, if you have older applications that use the VT220 8-bit - * support and you put a ASCII/8bit-extension to UTF-8 converter in between, you - * need these mappings to have the application behave correctly if it uses GL/GR - * mappings extensively. - * - * Anyway, we support GL/GR mappings so here are the most commonly used maps as - * defined by Unicode-standard, DEC-private maps and other famous charmaps. - * - * Characters 1-32 are always the control characters (part of CL) and cannot be - * mapped. Characters 34-127 (94 characters) are part of GL and can be mapped. - * Characters 33 and 128 are not part of GL and always mapped by the VTE. - * However, for GR they can be mapped differently (96 chars) so we have to - * include them. The mapper has to take care not to use them in GL. - */ - -#include "term-internal.h" - -/* - * Lower Unicode character set. This maps the characters to the basic ASCII - * characters 33-126. These are all graphics characters defined in ASCII. - */ -term_charset term_unicode_lower = { - [0] = 32, - [1] = 33, - [2] = 34, - [3] = 35, - [4] = 36, - [5] = 37, - [6] = 38, - [7] = 39, - [8] = 40, - [9] = 41, - [10] = 42, - [11] = 43, - [12] = 44, - [13] = 45, - [14] = 46, - [15] = 47, - [16] = 48, - [17] = 49, - [18] = 50, - [19] = 51, - [20] = 52, - [21] = 53, - [22] = 54, - [23] = 55, - [24] = 56, - [25] = 57, - [26] = 58, - [27] = 59, - [28] = 60, - [29] = 61, - [30] = 62, - [31] = 63, - [32] = 64, - [33] = 65, - [34] = 66, - [35] = 67, - [36] = 68, - [37] = 69, - [38] = 70, - [39] = 71, - [40] = 72, - [41] = 73, - [42] = 74, - [43] = 75, - [44] = 76, - [45] = 77, - [46] = 78, - [47] = 79, - [48] = 80, - [49] = 81, - [50] = 82, - [51] = 83, - [52] = 84, - [53] = 85, - [54] = 86, - [55] = 87, - [56] = 88, - [57] = 89, - [58] = 90, - [59] = 91, - [60] = 92, - [61] = 93, - [62] = 94, - [63] = 95, - [64] = 96, - [65] = 97, - [66] = 98, - [67] = 99, - [68] = 100, - [69] = 101, - [70] = 102, - [71] = 103, - [72] = 104, - [73] = 105, - [74] = 106, - [75] = 107, - [76] = 108, - [77] = 109, - [78] = 110, - [79] = 111, - [80] = 112, - [81] = 113, - [82] = 114, - [83] = 115, - [84] = 116, - [85] = 117, - [86] = 118, - [87] = 119, - [88] = 120, - [89] = 121, - [90] = 122, - [91] = 123, - [92] = 124, - [93] = 125, - [94] = 126, - [95] = 127, -}; - -/* - * Upper Unicode Table - * This maps all characters to the upper unicode characters 161-254. These are - * not compatible to any older 8 bit character sets. See the Unicode standard - * for the definitions of each symbol. - */ -term_charset term_unicode_upper = { - [0] = 160, - [1] = 161, - [2] = 162, - [3] = 163, - [4] = 164, - [5] = 165, - [6] = 166, - [7] = 167, - [8] = 168, - [9] = 169, - [10] = 170, - [11] = 171, - [12] = 172, - [13] = 173, - [14] = 174, - [15] = 175, - [16] = 176, - [17] = 177, - [18] = 178, - [19] = 179, - [20] = 180, - [21] = 181, - [22] = 182, - [23] = 183, - [24] = 184, - [25] = 185, - [26] = 186, - [27] = 187, - [28] = 188, - [29] = 189, - [30] = 190, - [31] = 191, - [32] = 192, - [33] = 193, - [34] = 194, - [35] = 195, - [36] = 196, - [37] = 197, - [38] = 198, - [39] = 199, - [40] = 200, - [41] = 201, - [42] = 202, - [43] = 203, - [44] = 204, - [45] = 205, - [46] = 206, - [47] = 207, - [48] = 208, - [49] = 209, - [50] = 210, - [51] = 211, - [52] = 212, - [53] = 213, - [54] = 214, - [55] = 215, - [56] = 216, - [57] = 217, - [58] = 218, - [59] = 219, - [60] = 220, - [61] = 221, - [62] = 222, - [63] = 223, - [64] = 224, - [65] = 225, - [66] = 226, - [67] = 227, - [68] = 228, - [69] = 229, - [70] = 230, - [71] = 231, - [72] = 232, - [73] = 233, - [74] = 234, - [75] = 235, - [76] = 236, - [77] = 237, - [78] = 238, - [79] = 239, - [80] = 240, - [81] = 241, - [82] = 242, - [83] = 243, - [84] = 244, - [85] = 245, - [86] = 246, - [87] = 247, - [88] = 248, - [89] = 249, - [90] = 250, - [91] = 251, - [92] = 252, - [93] = 253, - [94] = 254, - [95] = 255, -}; - -/* - * The DEC supplemental graphics set. For its definition see here: - * http://vt100.net/docs/vt220-rm/table2-3b.html - * Its basically a mixture of common European symbols that are not part of - * ASCII. Most often, this is mapped into GR to extend the basci ASCII part. - * - * This is very similar to unicode_upper, however, few symbols differ so do not - * mix them up! - */ -term_charset term_dec_supplemental_graphics = { - [0] = -1, /* undefined */ - [1] = 161, - [2] = 162, - [3] = 163, - [4] = 0, - [5] = 165, - [6] = 0, - [7] = 167, - [8] = 164, - [9] = 169, - [10] = 170, - [11] = 171, - [12] = 0, - [13] = 0, - [14] = 0, - [15] = 0, - [16] = 176, - [17] = 177, - [18] = 178, - [19] = 179, - [20] = 0, - [21] = 181, - [22] = 182, - [23] = 183, - [24] = 0, - [25] = 185, - [26] = 186, - [27] = 187, - [28] = 188, - [29] = 189, - [30] = 0, - [31] = 191, - [32] = 192, - [33] = 193, - [34] = 194, - [35] = 195, - [36] = 196, - [37] = 197, - [38] = 198, - [39] = 199, - [40] = 200, - [41] = 201, - [42] = 202, - [43] = 203, - [44] = 204, - [45] = 205, - [46] = 206, - [47] = 207, - [48] = 0, - [49] = 209, - [50] = 210, - [51] = 211, - [52] = 212, - [53] = 213, - [54] = 214, - [55] = 338, - [56] = 216, - [57] = 217, - [58] = 218, - [59] = 219, - [60] = 220, - [61] = 376, - [62] = 0, - [63] = 223, - [64] = 224, - [65] = 225, - [66] = 226, - [67] = 227, - [68] = 228, - [69] = 229, - [70] = 230, - [71] = 231, - [72] = 232, - [73] = 233, - [74] = 234, - [75] = 235, - [76] = 236, - [77] = 237, - [78] = 238, - [79] = 239, - [80] = 0, - [81] = 241, - [82] = 242, - [83] = 243, - [84] = 244, - [85] = 245, - [86] = 246, - [87] = 339, - [88] = 248, - [89] = 249, - [90] = 250, - [91] = 251, - [92] = 252, - [93] = 255, - [94] = 0, - [95] = -1, /* undefined */ -}; - -/* - * DEC special graphics character set. See here for its definition: - * http://vt100.net/docs/vt220-rm/table2-4.html - * This contains several characters to create ASCII drawings and similar. Its - * commonly mapped into GR to extend the basic ASCII characters. - * - * Lower 62 characters map to ASCII 33-64, everything beyond is special and - * commonly used for ASCII drawings. It depends on the Unicode Standard 3.2 for - * the extended horizontal scan-line characters 3, 5, 7, and 9. - */ -term_charset term_dec_special_graphics = { - [0] = -1, /* undefined */ - [1] = 33, - [2] = 34, - [3] = 35, - [4] = 36, - [5] = 37, - [6] = 38, - [7] = 39, - [8] = 40, - [9] = 41, - [10] = 42, - [11] = 43, - [12] = 44, - [13] = 45, - [14] = 46, - [15] = 47, - [16] = 48, - [17] = 49, - [18] = 50, - [19] = 51, - [20] = 52, - [21] = 53, - [22] = 54, - [23] = 55, - [24] = 56, - [25] = 57, - [26] = 58, - [27] = 59, - [28] = 60, - [29] = 61, - [30] = 62, - [31] = 63, - [32] = 64, - [33] = 65, - [34] = 66, - [35] = 67, - [36] = 68, - [37] = 69, - [38] = 70, - [39] = 71, - [40] = 72, - [41] = 73, - [42] = 74, - [43] = 75, - [44] = 76, - [45] = 77, - [46] = 78, - [47] = 79, - [48] = 80, - [49] = 81, - [50] = 82, - [51] = 83, - [52] = 84, - [53] = 85, - [54] = 86, - [55] = 87, - [56] = 88, - [57] = 89, - [58] = 90, - [59] = 91, - [60] = 92, - [61] = 93, - [62] = 94, - [63] = 0, - [64] = 9830, - [65] = 9618, - [66] = 9225, - [67] = 9228, - [68] = 9229, - [69] = 9226, - [70] = 176, - [71] = 177, - [72] = 9252, - [73] = 9227, - [74] = 9496, - [75] = 9488, - [76] = 9484, - [77] = 9492, - [78] = 9532, - [79] = 9146, - [80] = 9147, - [81] = 9472, - [82] = 9148, - [83] = 9149, - [84] = 9500, - [85] = 9508, - [86] = 9524, - [87] = 9516, - [88] = 9474, - [89] = 8804, - [90] = 8805, - [91] = 960, - [92] = 8800, - [93] = 163, - [94] = 8901, - [95] = -1, /* undefined */ -}; diff --git a/src/libsystemd-terminal/term-internal.h b/src/libsystemd-terminal/term-internal.h deleted file mode 100644 index 8c6a00188c..0000000000 --- a/src/libsystemd-terminal/term-internal.h +++ /dev/null @@ -1,650 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include -#include -#include -#include "term.h" -#include "util.h" - -typedef struct term_char term_char_t; -typedef struct term_charbuf term_charbuf_t; - -typedef struct term_cell term_cell; -typedef struct term_line term_line; - -typedef struct term_page term_page; -typedef struct term_history term_history; - -typedef uint32_t term_charset[96]; -typedef struct term_state term_state; - -/* - * Miscellaneous - * Sundry things and external helpers. - */ - -int mk_wcwidth(wchar_t ucs4); -int mk_wcwidth_cjk(wchar_t ucs4); -int mk_wcswidth(const wchar_t *str, size_t len); -int mk_wcswidth_cjk(const wchar_t *str, size_t len); - -/* - * Characters - * Each cell in a terminal page contains only a single character. This is - * usually a single UCS-4 value. However, Unicode allows combining-characters, - * therefore, the number of UCS-4 characters per cell must be unlimited. The - * term_char_t object wraps the internal combining char API so it can be - * treated as a single object. - */ - -struct term_char { - /* never access this value directly */ - uint64_t _value; -}; - -struct term_charbuf { - /* 3 bytes + zero-terminator */ - uint32_t buf[4]; -}; - -#define TERM_CHAR_INIT(_val) ((term_char_t){ ._value = (_val) }) -#define TERM_CHAR_NULL TERM_CHAR_INIT(0) - -term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4); -term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4); -term_char_t term_char_dup(term_char_t ch); -term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4); - -const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b); -unsigned int term_char_lookup_width(term_char_t ch); - -/* true if @ch is TERM_CHAR_NULL, otherwise false */ -static inline bool term_char_is_null(term_char_t ch) { - return ch._value == 0; -} - -/* true if @ch is dynamically allocated and needs to be freed */ -static inline bool term_char_is_allocated(term_char_t ch) { - return !term_char_is_null(ch) && !(ch._value & 0x1); -} - -/* true if (a == b), otherwise false; this is (a == b), NOT (*a == *b) */ -static inline bool term_char_same(term_char_t a, term_char_t b) { - return a._value == b._value; -} - -/* true if (*a == *b), otherwise false; this is implied by (a == b) */ -static inline bool term_char_equal(term_char_t a, term_char_t b) { - const uint32_t *sa, *sb; - term_charbuf_t ca, cb; - size_t na, nb; - - sa = term_char_resolve(a, &na, &ca); - sb = term_char_resolve(b, &nb, &cb); - return na == nb && !memcmp(sa, sb, sizeof(*sa) * na); -} - -/* free @ch in case it is dynamically allocated */ -static inline term_char_t term_char_free(term_char_t ch) { - if (term_char_is_allocated(ch)) - term_char_set(ch, 0); - - return TERM_CHAR_NULL; -} - -/* gcc _cleanup_ helpers */ -#define _term_char_free_ _cleanup_(term_char_freep) -static inline void term_char_freep(term_char_t *p) { - term_char_free(*p); -} - -/* - * Cells - * The term_cell structure respresents a single cell in a terminal page. It - * contains the stored character, the age of the cell and all its attributes. - */ - -struct term_cell { - term_char_t ch; /* stored char or TERM_CHAR_NULL */ - term_age_t age; /* cell age or TERM_AGE_NULL */ - term_attr attr; /* cell attributes */ - unsigned int cwidth; /* cached term_char_lookup_width(cell->ch) */ -}; - -/* - * Lines - * Instead of storing cells in a 2D array, we store them in an array of - * dynamically allocated lines. This way, scrolling can be implemented very - * fast without moving any cells at all. Similarly, the scrollback-buffer is - * much simpler to implement. - * We use term_line to store a single line. It contains an array of cells, a - * fill-state which remembers the amount of blanks on the right side, a - * separate age just for the line which can overwrite the age for all cells, - * and some management data. - */ - -struct term_line { - term_line *lines_next; /* linked-list for histories */ - term_line *lines_prev; /* linked-list for histories */ - - unsigned int width; /* visible width of line */ - unsigned int n_cells; /* # of allocated cells */ - term_cell *cells; /* cell-array */ - - term_age_t age; /* line age */ - unsigned int fill; /* # of valid cells; starting left */ -}; - -int term_line_new(term_line **out); -term_line *term_line_free(term_line *line); - -#define _term_line_free_ _cleanup_(term_line_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_line*, term_line_free); - -int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width); -void term_line_set_width(term_line *line, unsigned int width); -void term_line_write(term_line *line, unsigned int pos_x, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode); -void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age); -void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age); -void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age); -void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected); -void term_line_reset(term_line *line, const term_attr *attr, term_age_t age); - -void term_line_link(term_line *line, term_line **first, term_line **last); -void term_line_link_tail(term_line *line, term_line **first, term_line **last); -void term_line_unlink(term_line *line, term_line **first, term_line **last); - -#define TERM_LINE_LINK(_line, _head) term_line_link((_line), &(_head)->lines_first, &(_head)->lines_last) -#define TERM_LINE_LINK_TAIL(_line, _head) term_line_link_tail((_line), &(_head)->lines_first, &(_head)->lines_last) -#define TERM_LINE_UNLINK(_line, _head) term_line_unlink((_line), &(_head)->lines_first, &(_head)->lines_last) - -/* - * Pages - * A page represents the 2D table containing all cells of a terminal. It stores - * lines as an array of pointers so scrolling becomes a simple line-shuffle - * operation. - * Scrolling is always targeted only at the scroll-region defined via scroll_idx - * and scroll_num. The fill-state keeps track of the number of touched lines in - * the scroll-region. @width and @height describe the visible region of the page - * and are guaranteed to be allocated at all times. - */ - -struct term_page { - term_age_t age; /* page age */ - - term_line **lines; /* array of line-pointers */ - term_line **line_cache; /* cache for temporary operations */ - unsigned int n_lines; /* # of allocated lines */ - - unsigned int width; /* width of visible area */ - unsigned int height; /* height of visible area */ - unsigned int scroll_idx; /* scrolling-region start index */ - unsigned int scroll_num; /* scrolling-region length in lines */ - unsigned int scroll_fill; /* # of valid scroll-lines */ -}; - -int term_page_new(term_page **out); -term_page *term_page_free(term_page *page); - -#define _term_page_free_ _cleanup_(term_page_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_page*, term_page_free); - -term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y); - -int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age); -void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history); -void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode); -void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age); -void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age); -void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age); -void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected); -void term_page_reset(term_page *page, const term_attr *attr, term_age_t age); - -void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num); -void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history); -void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history); -void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age); -void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age); - -/* - * Histories - * Scroll-back buffers use term_history objects to store scroll-back lines. A - * page is independent of the history used. All page operations that modify a - * history take it as separate argument. You're free to pass NULL at all times - * if no history should be used. - * Lines are stored in a linked list as no complex operations are ever done on - * history lines, besides pushing/poping. Note that history lines do not have a - * guaranteed minimum length. Any kind of line might be stored there. Missing - * cells should be cleared to the background color. - */ - -struct term_history { - term_line *lines_first; - term_line *lines_last; - unsigned int n_lines; - unsigned int max_lines; -}; - -int term_history_new(term_history **out); -term_history *term_history_free(term_history *history); - -#define _term_history_free_ _cleanup_(term_history_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_history*, term_history_free); - -void term_history_clear(term_history *history); -void term_history_trim(term_history *history, unsigned int max); -void term_history_push(term_history *history, term_line *line); -term_line *term_history_pop(term_history *history, unsigned int reserve_width, const term_attr *attr, term_age_t age); -unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age); - -/* - * Parsers - * The term_parser object parses control-sequences for both host and terminal - * side. Based on this parser, there is a set of command-parsers that take a - * term_seq sequence and returns the command it represents. This is different - * for host and terminal side so a different set of parsers is provided. - */ - -enum { - TERM_SEQ_NONE, /* placeholder, no sequence parsed */ - - TERM_SEQ_IGNORE, /* no-op character */ - TERM_SEQ_GRAPHIC, /* graphic character */ - TERM_SEQ_CONTROL, /* control character */ - TERM_SEQ_ESCAPE, /* escape sequence */ - TERM_SEQ_CSI, /* control sequence function */ - TERM_SEQ_DCS, /* device control string */ - TERM_SEQ_OSC, /* operating system control */ - - TERM_SEQ_CNT -}; - -enum { - /* these must be kept compatible to (1U << (ch - 0x20)) */ - - TERM_SEQ_FLAG_SPACE = (1U << 0), /* char: */ - TERM_SEQ_FLAG_BANG = (1U << 1), /* char: ! */ - TERM_SEQ_FLAG_DQUOTE = (1U << 2), /* char: " */ - TERM_SEQ_FLAG_HASH = (1U << 3), /* char: # */ - TERM_SEQ_FLAG_CASH = (1U << 4), /* char: $ */ - TERM_SEQ_FLAG_PERCENT = (1U << 5), /* char: % */ - TERM_SEQ_FLAG_AND = (1U << 6), /* char: & */ - TERM_SEQ_FLAG_SQUOTE = (1U << 7), /* char: ' */ - TERM_SEQ_FLAG_POPEN = (1U << 8), /* char: ( */ - TERM_SEQ_FLAG_PCLOSE = (1U << 9), /* char: ) */ - TERM_SEQ_FLAG_MULT = (1U << 10), /* char: * */ - TERM_SEQ_FLAG_PLUS = (1U << 11), /* char: + */ - TERM_SEQ_FLAG_COMMA = (1U << 12), /* char: , */ - TERM_SEQ_FLAG_MINUS = (1U << 13), /* char: - */ - TERM_SEQ_FLAG_DOT = (1U << 14), /* char: . */ - TERM_SEQ_FLAG_SLASH = (1U << 15), /* char: / */ - - /* 16-35 is reserved for numbers; unused */ - - /* COLON is reserved = (1U << 26), char: : */ - /* SEMICOLON is reserved = (1U << 27), char: ; */ - TERM_SEQ_FLAG_LT = (1U << 28), /* char: < */ - TERM_SEQ_FLAG_EQUAL = (1U << 29), /* char: = */ - TERM_SEQ_FLAG_GT = (1U << 30), /* char: > */ - TERM_SEQ_FLAG_WHAT = (1U << 31), /* char: ? */ -}; - -enum { - TERM_CMD_NONE, /* placeholder */ - TERM_CMD_GRAPHIC, /* graphics character */ - - TERM_CMD_BEL, /* bell */ - TERM_CMD_BS, /* backspace */ - TERM_CMD_CBT, /* cursor-backward-tabulation */ - TERM_CMD_CHA, /* cursor-horizontal-absolute */ - TERM_CMD_CHT, /* cursor-horizontal-forward-tabulation */ - TERM_CMD_CNL, /* cursor-next-line */ - TERM_CMD_CPL, /* cursor-previous-line */ - TERM_CMD_CR, /* carriage-return */ - TERM_CMD_CUB, /* cursor-backward */ - TERM_CMD_CUD, /* cursor-down */ - TERM_CMD_CUF, /* cursor-forward */ - TERM_CMD_CUP, /* cursor-position */ - TERM_CMD_CUU, /* cursor-up */ - TERM_CMD_DA1, /* primary-device-attributes */ - TERM_CMD_DA2, /* secondary-device-attributes */ - TERM_CMD_DA3, /* tertiary-device-attributes */ - TERM_CMD_DC1, /* device-control-1 or XON */ - TERM_CMD_DC3, /* device-control-3 or XOFF */ - TERM_CMD_DCH, /* delete-character */ - TERM_CMD_DECALN, /* screen-alignment-pattern */ - TERM_CMD_DECANM, /* ansi-mode */ - TERM_CMD_DECBI, /* back-index */ - TERM_CMD_DECCARA, /* change-attributes-in-rectangular-area */ - TERM_CMD_DECCRA, /* copy-rectangular-area */ - TERM_CMD_DECDC, /* delete-column */ - TERM_CMD_DECDHL_BH, /* double-width-double-height-line: bottom half */ - TERM_CMD_DECDHL_TH, /* double-width-double-height-line: top half */ - TERM_CMD_DECDWL, /* double-width-single-height-line */ - TERM_CMD_DECEFR, /* enable-filter-rectangle */ - TERM_CMD_DECELF, /* enable-local-functions */ - TERM_CMD_DECELR, /* enable-locator-reporting */ - TERM_CMD_DECERA, /* erase-rectangular-area */ - TERM_CMD_DECFI, /* forward-index */ - TERM_CMD_DECFRA, /* fill-rectangular-area */ - TERM_CMD_DECIC, /* insert-column */ - TERM_CMD_DECID, /* return-terminal-id */ - TERM_CMD_DECINVM, /* invoke-macro */ - TERM_CMD_DECKBD, /* keyboard-language-selection */ - TERM_CMD_DECKPAM, /* keypad-application-mode */ - TERM_CMD_DECKPNM, /* keypad-numeric-mode */ - TERM_CMD_DECLFKC, /* local-function-key-control */ - TERM_CMD_DECLL, /* load-leds */ - TERM_CMD_DECLTOD, /* load-time-of-day */ - TERM_CMD_DECPCTERM, /* pcterm-mode */ - TERM_CMD_DECPKA, /* program-key-action */ - TERM_CMD_DECPKFMR, /* program-key-free-memory-report */ - TERM_CMD_DECRARA, /* reverse-attributes-in-rectangular-area */ - TERM_CMD_DECRC, /* restore-cursor */ - TERM_CMD_DECREQTPARM, /* request-terminal-parameters */ - TERM_CMD_DECRPKT, /* report-key-type */ - TERM_CMD_DECRQCRA, /* request-checksum-of-rectangular-area */ - TERM_CMD_DECRQDE, /* request-display-extent */ - TERM_CMD_DECRQKT, /* request-key-type */ - TERM_CMD_DECRQLP, /* request-locator-position */ - TERM_CMD_DECRQM_ANSI, /* request-mode-ansi */ - TERM_CMD_DECRQM_DEC, /* request-mode-dec */ - TERM_CMD_DECRQPKFM, /* request-program-key-free-memory */ - TERM_CMD_DECRQPSR, /* request-presentation-state-report */ - TERM_CMD_DECRQTSR, /* request-terminal-state-report */ - TERM_CMD_DECRQUPSS, /* request-user-preferred-supplemental-set */ - TERM_CMD_DECSACE, /* select-attribute-change-extent */ - TERM_CMD_DECSASD, /* select-active-status-display */ - TERM_CMD_DECSC, /* save-cursor */ - TERM_CMD_DECSCA, /* select-character-protection-attribute */ - TERM_CMD_DECSCL, /* select-conformance-level */ - TERM_CMD_DECSCP, /* select-communication-port */ - TERM_CMD_DECSCPP, /* select-columns-per-page */ - TERM_CMD_DECSCS, /* select-communication-speed */ - TERM_CMD_DECSCUSR, /* set-cursor-style */ - TERM_CMD_DECSDDT, /* select-disconnect-delay-time */ - TERM_CMD_DECSDPT, /* select-digital-printed-data-type */ - TERM_CMD_DECSED, /* selective-erase-in-display */ - TERM_CMD_DECSEL, /* selective-erase-in-line */ - TERM_CMD_DECSERA, /* selective-erase-rectangular-area */ - TERM_CMD_DECSFC, /* select-flow-control */ - TERM_CMD_DECSKCV, /* set-key-click-volume */ - TERM_CMD_DECSLCK, /* set-lock-key-style */ - TERM_CMD_DECSLE, /* select-locator-events */ - TERM_CMD_DECSLPP, /* set-lines-per-page */ - TERM_CMD_DECSLRM_OR_SC, /* set-left-and-right-margins or save-cursor */ - TERM_CMD_DECSMBV, /* set-margin-bell-volume */ - TERM_CMD_DECSMKR, /* select-modifier-key-reporting */ - TERM_CMD_DECSNLS, /* set-lines-per-screen */ - TERM_CMD_DECSPP, /* set-port-parameter */ - TERM_CMD_DECSPPCS, /* select-pro-printer-character-set */ - TERM_CMD_DECSPRTT, /* select-printer-type */ - TERM_CMD_DECSR, /* secure-reset */ - TERM_CMD_DECSRFR, /* select-refresh-rate */ - TERM_CMD_DECSSCLS, /* set-scroll-speed */ - TERM_CMD_DECSSDT, /* select-status-display-line-type */ - TERM_CMD_DECSSL, /* select-setup-language */ - TERM_CMD_DECST8C, /* set-tab-at-every-8-columns */ - TERM_CMD_DECSTBM, /* set-top-and-bottom-margins */ - TERM_CMD_DECSTR, /* soft-terminal-reset */ - TERM_CMD_DECSTRL, /* set-transmit-rate-limit */ - TERM_CMD_DECSWBV, /* set-warning-bell-volume */ - TERM_CMD_DECSWL, /* single-width-single-height-line */ - TERM_CMD_DECTID, /* select-terminal-id */ - TERM_CMD_DECTME, /* terminal-mode-emulation */ - TERM_CMD_DECTST, /* invoke-confidence-test */ - TERM_CMD_DL, /* delete-line */ - TERM_CMD_DSR_ANSI, /* device-status-report-ansi */ - TERM_CMD_DSR_DEC, /* device-status-report-dec */ - TERM_CMD_ECH, /* erase-character */ - TERM_CMD_ED, /* erase-in-display */ - TERM_CMD_EL, /* erase-in-line */ - TERM_CMD_ENQ, /* enquiry */ - TERM_CMD_EPA, /* end-of-guarded-area */ - TERM_CMD_FF, /* form-feed */ - TERM_CMD_HPA, /* horizontal-position-absolute */ - TERM_CMD_HPR, /* horizontal-position-relative */ - TERM_CMD_HT, /* horizontal-tab */ - TERM_CMD_HTS, /* horizontal-tab-set */ - TERM_CMD_HVP, /* horizontal-and-vertical-position */ - TERM_CMD_ICH, /* insert-character */ - TERM_CMD_IL, /* insert-line */ - TERM_CMD_IND, /* index */ - TERM_CMD_LF, /* line-feed */ - TERM_CMD_LS1R, /* locking-shift-1-right */ - TERM_CMD_LS2, /* locking-shift-2 */ - TERM_CMD_LS2R, /* locking-shift-2-right */ - TERM_CMD_LS3, /* locking-shift-3 */ - TERM_CMD_LS3R, /* locking-shift-3-right */ - TERM_CMD_MC_ANSI, /* media-copy-ansi */ - TERM_CMD_MC_DEC, /* media-copy-dec */ - TERM_CMD_NEL, /* next-line */ - TERM_CMD_NP, /* next-page */ - TERM_CMD_NULL, /* null */ - TERM_CMD_PP, /* preceding-page */ - TERM_CMD_PPA, /* page-position-absolute */ - TERM_CMD_PPB, /* page-position-backward */ - TERM_CMD_PPR, /* page-position-relative */ - TERM_CMD_RC, /* restore-cursor */ - TERM_CMD_REP, /* repeat */ - TERM_CMD_RI, /* reverse-index */ - TERM_CMD_RIS, /* reset-to-initial-state */ - TERM_CMD_RM_ANSI, /* reset-mode-ansi */ - TERM_CMD_RM_DEC, /* reset-mode-dec */ - TERM_CMD_S7C1T, /* set-7bit-c1-terminal */ - TERM_CMD_S8C1T, /* set-8bit-c1-terminal */ - TERM_CMD_SCS, /* select-character-set */ - TERM_CMD_SD, /* scroll-down */ - TERM_CMD_SGR, /* select-graphics-rendition */ - TERM_CMD_SI, /* shift-in */ - TERM_CMD_SM_ANSI, /* set-mode-ansi */ - TERM_CMD_SM_DEC, /* set-mode-dec */ - TERM_CMD_SO, /* shift-out */ - TERM_CMD_SPA, /* start-of-protected-area */ - TERM_CMD_SS2, /* single-shift-2 */ - TERM_CMD_SS3, /* single-shift-3 */ - TERM_CMD_ST, /* string-terminator */ - TERM_CMD_SU, /* scroll-up */ - TERM_CMD_SUB, /* substitute */ - TERM_CMD_TBC, /* tab-clear */ - TERM_CMD_VPA, /* vertical-line-position-absolute */ - TERM_CMD_VPR, /* vertical-line-position-relative */ - TERM_CMD_VT, /* vertical-tab */ - TERM_CMD_XTERM_CLLHP, /* xterm-cursor-lower-left-hp-bugfix */ - TERM_CMD_XTERM_IHMT, /* xterm-initiate-highlight-mouse-tracking */ - TERM_CMD_XTERM_MLHP, /* xterm-memory-lock-hp-bugfix */ - TERM_CMD_XTERM_MUHP, /* xterm-memory-unlock-hp-bugfix */ - TERM_CMD_XTERM_RPM, /* xterm-restore-private-mode */ - TERM_CMD_XTERM_RRV, /* xterm-reset-resource-value */ - TERM_CMD_XTERM_RTM, /* xterm-reset-title-mode */ - TERM_CMD_XTERM_SACL1, /* xterm-set-ansi-conformance-level-1 */ - TERM_CMD_XTERM_SACL2, /* xterm-set-ansi-conformance-level-2 */ - TERM_CMD_XTERM_SACL3, /* xterm-set-ansi-conformance-level-3 */ - TERM_CMD_XTERM_SDCS, /* xterm-set-default-character-set */ - TERM_CMD_XTERM_SGFX, /* xterm-sixel-graphics */ - TERM_CMD_XTERM_SPM, /* xterm-set-private-mode */ - TERM_CMD_XTERM_SRV, /* xterm-set-resource-value */ - TERM_CMD_XTERM_STM, /* xterm-set-title-mode */ - TERM_CMD_XTERM_SUCS, /* xterm-set-utf8-character-set */ - TERM_CMD_XTERM_WM, /* xterm-window-management */ - - TERM_CMD_CNT -}; - -enum { - /* - * Charsets: DEC marks charsets according to "Digital Equ. Corp.". - * NRCS marks charsets according to the "National Replacement - * Character Sets". ISO marks charsets according to ISO-8859. - * The USERDEF charset is special and can be modified by the host. - */ - - TERM_CHARSET_NONE, - - /* 96-compat charsets */ - TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL, - TERM_CHARSET_BRITISH_NRCS = TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL, - TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL, - TERM_CHARSET_AMERICAN_NRCS = TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL, - TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL, - TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL, - TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL, - TERM_CHARSET_ISO_LATIN_CYRILLIC, - - TERM_CHARSET_96_CNT, - - /* 94-compat charsets */ - TERM_CHARSET_DEC_SPECIAL_GRAPHIC = TERM_CHARSET_96_CNT, - TERM_CHARSET_DEC_SUPPLEMENTAL, - TERM_CHARSET_DEC_TECHNICAL, - TERM_CHARSET_CYRILLIC_DEC, - TERM_CHARSET_DUTCH_NRCS, - TERM_CHARSET_FINNISH_NRCS, - TERM_CHARSET_FRENCH_NRCS, - TERM_CHARSET_FRENCH_CANADIAN_NRCS, - TERM_CHARSET_GERMAN_NRCS, - TERM_CHARSET_GREEK_DEC, - TERM_CHARSET_GREEK_NRCS, - TERM_CHARSET_HEBREW_DEC, - TERM_CHARSET_HEBREW_NRCS, - TERM_CHARSET_ITALIAN_NRCS, - TERM_CHARSET_NORWEGIAN_DANISH_NRCS, - TERM_CHARSET_PORTUGUESE_NRCS, - TERM_CHARSET_RUSSIAN_NRCS, - TERM_CHARSET_SCS_NRCS, - TERM_CHARSET_SPANISH_NRCS, - TERM_CHARSET_SWEDISH_NRCS, - TERM_CHARSET_SWISS_NRCS, - TERM_CHARSET_TURKISH_DEC, - TERM_CHARSET_TURKISH_NRCS, - - TERM_CHARSET_94_CNT, - - /* special charsets */ - TERM_CHARSET_USERPREF_SUPPLEMENTAL = TERM_CHARSET_94_CNT, - - TERM_CHARSET_CNT, -}; - -extern term_charset term_unicode_lower; -extern term_charset term_unicode_upper; -extern term_charset term_dec_supplemental_graphics; -extern term_charset term_dec_special_graphics; - -#define TERM_PARSER_ARG_MAX (16) -#define TERM_PARSER_ST_MAX (4096) - -struct term_seq { - unsigned int type; - unsigned int command; - uint32_t terminator; - unsigned int intermediates; - unsigned int charset; - unsigned int n_args; - int args[TERM_PARSER_ARG_MAX]; - unsigned int n_st; - char *st; -}; - -struct term_parser { - term_seq seq; - size_t st_alloc; - unsigned int state; - - bool is_host : 1; -}; - -/* - * Screens - * A term_screen object represents the terminal-side of the communication. It - * connects the term-parser and term-pages and handles all required commands. - * All state is managed by it. - */ - -enum { - TERM_FLAG_7BIT_MODE = (1U << 0), /* 7bit mode (default: on) */ - TERM_FLAG_HIDE_CURSOR = (1U << 1), /* hide cursor caret (default: off) */ - TERM_FLAG_INHIBIT_TPARM = (1U << 2), /* do not send TPARM unrequested (default: off) */ - TERM_FLAG_NEWLINE_MODE = (1U << 3), /* perform carriage-return on line-feeds (default: off) */ - TERM_FLAG_PENDING_WRAP = (1U << 4), /* wrap-around is pending */ - TERM_FLAG_KEYPAD_MODE = (1U << 5), /* application-keypad mode (default: off) */ - TERM_FLAG_CURSOR_KEYS = (1U << 6), /* enable application cursor-keys (default: off) */ -}; - -enum { - TERM_CONFORMANCE_LEVEL_VT52, - TERM_CONFORMANCE_LEVEL_VT100, - TERM_CONFORMANCE_LEVEL_VT400, - TERM_CONFORMANCE_LEVEL_CNT, -}; - -struct term_state { - unsigned int cursor_x; - unsigned int cursor_y; - term_attr attr; - term_charset **gl; - term_charset **gr; - term_charset **glt; - term_charset **grt; - - bool auto_wrap : 1; - bool origin_mode : 1; -}; - -struct term_screen { - unsigned long ref; - term_age_t age; - - term_page *page; - term_page *page_main; - term_page *page_alt; - term_history *history; - term_history *history_main; - - unsigned int n_tabs; - uint8_t *tabs; - - term_utf8 utf8; - term_parser *parser; - - term_screen_write_fn write_fn; - void *write_fn_data; - term_screen_cmd_fn cmd_fn; - void *cmd_fn_data; - - unsigned int flags; - unsigned int conformance_level; - term_attr default_attr; - - term_charset *g0; - term_charset *g1; - term_charset *g2; - term_charset *g3; - - char *answerback; - - term_state state; - term_state saved; - term_state saved_alt; -}; diff --git a/src/libsystemd-terminal/term-page.c b/src/libsystemd-terminal/term-page.c deleted file mode 100644 index bac85200f1..0000000000 --- a/src/libsystemd-terminal/term-page.c +++ /dev/null @@ -1,2091 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Terminal Page/Line/Cell/Char Handling - * This file implements page handling of a terminal. It is split into pages, - * lines, cells and characters. Each object is independent of the next upper - * object. - * - * The Terminal layer keeps each line of a terminal separate and dynamically - * allocated. This allows us to move lines from main-screen to history-buffers - * very fast. Same is true for scrolling, top/bottom borders and other buffer - * operations. - * - * While lines are dynamically allocated, cells are not. This would be a waste - * of memory and causes heavy fragmentation. Furthermore, cells are moved much - * less frequently than lines so the performance-penalty is pretty small. - * However, to support combining-characters, we have to initialize and cleanup - * cells properly and cannot just release the underlying memory. Therefore, - * cells are treated as proper objects despite being allocated in arrays. - * - * Each cell has a set of attributes and a stored character. This is usually a - * single Unicode character stored as 32bit UCS-4 char. However, we need to - * support Unicode combining-characters, therefore this gets more complicated. - * Characters themselves are represented by a "term_char_t" object. It - * should be treated as a normal integer and passed by value. The - * surrounding struct is just to hide the internals. A term-char can contain a - * base character together with up to 2 combining-chars in a single integer. - * Only if you need more combining-chars (very unlikely!) a term-char is a - * pointer to an allocated storage. This requires you to always free term-char - * objects once no longer used (even though this is a no-op most of the time). - * Furthermore, term-char objects are not ref-counted so you must duplicate them - * in case you want to store it somewhere and retain a copy yourself. By - * convention, all functions that take a term-char object will not duplicate - * it but implicitly take ownership of the passed value. It's up to the caller - * to duplicate it beforehand, in case it wants to retain a copy. - * - * If it turns out, that more than 2 comb-chars become common in specific - * languages, we can try to optimize this. One idea is to ref-count allocated - * characters and store them in a hash-table (like gnome's libvte3 does). This - * way we will never have two allocated chars for the same content. Or we can - * simply put two uint64_t into a "term_char_t". This will slow down operations - * on systems that don't need that many comb-chars, but avoid the dynamic - * allocations on others. - * Anyhow, until we have proper benchmarks, we will keep the current code. It - * seems to compete very well with other solutions so far. - * - * The page-layer is a one-dimensional array of lines. Considering that each - * line is a one-dimensional array of cells, the page layer provides the - * two-dimensional cell-page required for terminals. The page itself only - * operates on lines. All cell-related operations are forwarded to the correct - * line. - * A page does not contain any cursor tracking. It only provides the raw - * operations to shuffle lines and modify the page. - */ - -#include -#include -#include -#include "macro.h" -#include "term-internal.h" -#include "util.h" - -/* maximum UCS-4 character */ -#define CHAR_UCS4_MAX (0x10ffff) -/* mask for valid UCS-4 characters (21bit) */ -#define CHAR_UCS4_MASK (0x1fffff) -/* UCS-4 replacement character */ -#define CHAR_UCS4_REPLACEMENT (0xfffd) - -/* real storage behind "term_char_t" in case it's not packed */ -typedef struct term_character { - uint8_t n; - uint32_t codepoints[]; -} term_character; - -/* - * char_pack() takes 3 UCS-4 values and packs them into a term_char_t object. - * Note that UCS-4 chars only take 21 bits, so we still have the LSB as marker. - * We set it to 1 so others can distinguish it from pointers. - */ -static inline term_char_t char_pack(uint32_t v1, uint32_t v2, uint32_t v3) { - uint64_t packed, u1, u2, u3; - - u1 = v1; - u2 = v2; - u3 = v3; - - packed = 0x01; - packed |= (u1 & (uint64_t)CHAR_UCS4_MASK) << 43; - packed |= (u2 & (uint64_t)CHAR_UCS4_MASK) << 22; - packed |= (u3 & (uint64_t)CHAR_UCS4_MASK) << 1; - - return TERM_CHAR_INIT(packed); -} - -#define char_pack1(_v1) char_pack2((_v1), CHAR_UCS4_MAX + 1) -#define char_pack2(_v1, _v2) char_pack3((_v1), (_v2), CHAR_UCS4_MAX + 1) -#define char_pack3(_v1, _v2, _v3) char_pack((_v1), (_v2), (_v3)) - -/* - * char_unpack() is the inverse of char_pack(). It extracts the 3 stored UCS-4 - * characters and returns them. Note that this does not validate the passed - * term_char_t. That's the responsibility of the caller. - * This returns the number of characters actually packed. This obviously is a - * number between 0 and 3 (inclusive). - */ -static inline uint8_t char_unpack(term_char_t packed, uint32_t *out_v1, uint32_t *out_v2, uint32_t *out_v3) { - uint32_t v1, v2, v3; - - v1 = (packed._value >> 43) & (uint64_t)CHAR_UCS4_MASK; - v2 = (packed._value >> 22) & (uint64_t)CHAR_UCS4_MASK; - v3 = (packed._value >> 1) & (uint64_t)CHAR_UCS4_MASK; - - if (out_v1) - *out_v1 = v1; - if (out_v2) - *out_v2 = v2; - if (out_v3) - *out_v3 = v3; - - return (v1 > CHAR_UCS4_MAX) ? 0 : - ((v2 > CHAR_UCS4_MAX) ? 1 : - ((v3 > CHAR_UCS4_MAX) ? 2 : - 3)); -} - -/* cast a term_char_t to a term_character* */ -static inline term_character *char_to_ptr(term_char_t ch) { - return (term_character*)(unsigned long)ch._value; -} - -/* cast a term_character* to a term_char_t */ -static inline term_char_t char_from_ptr(term_character *c) { - return TERM_CHAR_INIT((unsigned long)c); -} - -/* - * char_alloc() allocates a properly aligned term_character object and returns - * a pointer to it. NULL is returned on allocation errors. The object will have - * enough room for @n following UCS-4 chars. - * Note that we allocate (n+1) characters and set the last one to 0 in case - * anyone prints this string for debugging. - */ -static term_character *char_alloc(uint8_t n) { - term_character *c; - int r; - - r = posix_memalign((void**)&c, - MAX(sizeof(void*), (size_t)2), - sizeof(*c) + sizeof(*c->codepoints) * (n + 1)); - if (r) - return NULL; - - c->n = n; - c->codepoints[n] = 0; - - return c; -} - -/* - * char_free() frees the memory allocated via char_alloc(). It is safe to call - * this on any term_char_t, only allocated characters are freed. - */ -static inline void char_free(term_char_t ch) { - if (term_char_is_allocated(ch)) - free(char_to_ptr(ch)); -} - -/* - * This appends @append_ucs4 to the existing character @base and returns - * it as a new character. In case that's not possible, @base is returned. The - * caller can use term_char_same() to test whether the returned character was - * freshly allocated or not. - */ -static term_char_t char_build(term_char_t base, uint32_t append_ucs4) { - /* soft-limit for combining-chars; hard-limit is currently 255 */ - const size_t climit = 64; - term_character *c; - uint32_t buf[3], *t; - uint8_t n; - - /* ignore invalid UCS-4 */ - if (append_ucs4 > CHAR_UCS4_MAX) - return base; - - if (term_char_is_null(base)) { - return char_pack1(append_ucs4); - } else if (!term_char_is_allocated(base)) { - /* unpack and try extending the packed character */ - n = char_unpack(base, &buf[0], &buf[1], &buf[2]); - - switch (n) { - case 0: - return char_pack1(append_ucs4); - case 1: - if (climit < 2) - return base; - - return char_pack2(buf[0], append_ucs4); - case 2: - if (climit < 3) - return base; - - return char_pack3(buf[0], buf[1], append_ucs4); - default: - /* fallthrough */ - break; - } - - /* already fully packed, we need to allocate a new one */ - t = buf; - } else { - /* already an allocated type, we need to allocate a new one */ - c = char_to_ptr(base); - t = c->codepoints; - n = c->n; - } - - /* bail out if soft-limit is reached */ - if (n >= climit) - return base; - - /* allocate new char */ - c = char_alloc(n + 1); - if (!c) - return base; - - memcpy(c->codepoints, t, sizeof(*t) * n); - c->codepoints[n] = append_ucs4; - - return char_from_ptr(c); -} - -/** - * term_char_set() - Reset character to a single UCS-4 character - * @previous: term-char to reset - * @append_ucs4: UCS-4 char to set - * - * This frees all resources in @previous and re-initializes it to @append_ucs4. - * The new char is returned. - * - * Usually, this is used like this: - * obj->ch = term_char_set(obj->ch, ucs4); - * - * Returns: The previous character reset to @append_ucs4. - */ -term_char_t term_char_set(term_char_t previous, uint32_t append_ucs4) { - char_free(previous); - return char_build(TERM_CHAR_NULL, append_ucs4); -} - -/** - * term_char_merge() - Merge UCS-4 char at the end of an existing char - * @base: existing term-char - * @append_ucs4: UCS-4 character to append - * - * This appends @append_ucs4 to @base and returns the result. @base is - * invalidated by this function and must no longer be used. The returned value - * replaces the old one. - * - * Usually, this is used like this: - * obj->ch = term_char_merge(obj->ch, ucs4); - * - * Returns: The new merged character. - */ -term_char_t term_char_merge(term_char_t base, uint32_t append_ucs4) { - term_char_t ch; - - ch = char_build(base, append_ucs4); - if (!term_char_same(ch, base)) - term_char_free(base); - - return ch; -} - -/** - * term_char_dup() - Duplicate character - * @ch: character to duplicate - * - * This duplicates a term-character. In case the character is not allocated, - * nothing is done. Otherwise, the underlying memory is copied and returned. You - * need to call term_char_free() on the returned character to release it again. - * On allocation errors, a replacement character is returned. Therefore, the - * caller can safely assume that this function always succeeds. - * - * Returns: The duplicated term-character. - */ -term_char_t term_char_dup(term_char_t ch) { - term_character *c, *newc; - - if (!term_char_is_allocated(ch)) - return ch; - - c = char_to_ptr(ch); - newc = char_alloc(c->n); - if (!newc) - return char_pack1(CHAR_UCS4_REPLACEMENT); - - memcpy(newc->codepoints, c->codepoints, sizeof(*c->codepoints) * c->n); - return char_from_ptr(newc); -} - -/** - * term_char_dup_append() - Duplicate tsm-char with UCS-4 character appended - * @base: existing term-char - * @append_ucs4: UCS-4 character to append - * - * This is similar to term_char_merge(), but it returns a separately allocated - * character. That is, @base will stay valid after this returns and is not - * touched. In case the append-operation fails, @base is duplicated and - * returned. That is, the returned char is always independent of @base. - * - * Returns: Newly allocated character with @append_ucs4 appended to @base. - */ -term_char_t term_char_dup_append(term_char_t base, uint32_t append_ucs4) { - term_char_t ch; - - ch = char_build(base, append_ucs4); - if (term_char_same(ch, base)) - ch = term_char_dup(base); - - return ch; -} - -/** - * term_char_resolve() - Retrieve the UCS-4 string for a term-char - * @ch: character to resolve - * @s: storage for size of string or NULL - * @b: storage for string or NULL - * - * This takes a term-character and returns the UCS-4 string associated with it. - * In case @ch is not allocated, the string is stored in @b (in case @b is NULL - * static storage is used). Otherwise, a pointer to the allocated storage is - * returned. - * - * The returned string is only valid as long as @ch and @b are valid. The string - * is zero-terminated and can safely be printed via long-character printf(). - * The length of the string excluding the zero-character is returned in @s. - * - * This never returns NULL. Even if the size is 0, this points to a buffer of at - * least a zero-terminator. - * - * Returns: The UCS-4 string-representation of @ch, and its size in @s. - */ -const uint32_t *term_char_resolve(term_char_t ch, size_t *s, term_charbuf_t *b) { - static term_charbuf_t static_b; - term_character *c; - uint32_t *cache; - size_t len; - - if (b) - cache = b->buf; - else - cache = static_b.buf; - - if (term_char_is_null(ch)) { - len = 0; - cache[0] = 0; - } else if (term_char_is_allocated(ch)) { - c = char_to_ptr(ch); - len = c->n; - cache = c->codepoints; - } else { - len = char_unpack(ch, &cache[0], &cache[1], &cache[2]); - cache[len] = 0; - } - - if (s) - *s = len; - - return cache; -} - -/** - * term_char_lookup_width() - Lookup cell-width of a character - * @ch: character to return cell-width for - * - * This is an equivalent of wcwidth() for term_char_t. It can deal directly - * with UCS-4 and combining-characters and avoids the mess that is wchar_t and - * locale handling. - * - * Returns: 0 for unprintable characters, >0 for everything else. - */ -unsigned int term_char_lookup_width(term_char_t ch) { - term_charbuf_t b; - const uint32_t *str; - unsigned int max; - size_t i, len; - int r; - - max = 0; - str = term_char_resolve(ch, &len, &b); - - for (i = 0; i < len; ++i) { - /* - * Oh god, C99 locale handling strikes again: wcwidth() expects - * wchar_t, but there is no way for us to know the - * internal encoding of wchar_t. Moreover, it is nearly - * impossible to convert UCS-4 into wchar_t (except for iconv, - * which is way too much overhead). - * Therefore, we use our own copy of wcwidth(). Lets just hope - * that glibc will one day export it's internal UCS-4 and UTF-8 - * helpers for direct use. - */ - assert_cc(sizeof(wchar_t) >= 4); - r = mk_wcwidth((wchar_t)str[i]); - if (r > 0 && (unsigned int)r > max) - max = r; - } - - return max; -} - -/** - * term_cell_init() - Initialize a new cell - * @cell: cell to initialize - * @ch: character to set on the cell or TERM_CHAR_NULL - * @cwidth: character width of @ch - * @attr: attributes to set on the cell or NULL - * @age: age to set on the cell or TERM_AGE_NULL - * - * This initializes a new cell. The backing-memory of the cell must be allocated - * by the caller beforehand. The caller is responsible to destroy the cell via - * term_cell_destroy() before freeing the backing-memory. - * - * It is safe (and supported!) to use: - * zero(*c); - * instead of: - * term_cell_init(c, TERM_CHAR_NULL, NULL, TERM_AGE_NULL); - * - * Note that this call takes ownership of @ch. If you want to use it yourself - * after this call, you need to duplicate it before calling this. - */ -static void term_cell_init(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) { - assert(cell); - - cell->ch = ch; - cell->cwidth = cwidth; - cell->age = age; - - if (attr) - memcpy(&cell->attr, attr, sizeof(*attr)); - else - zero(cell->attr); -} - -/** - * term_cell_destroy() - Destroy previously initialized cell - * @cell: cell to destroy or NULL - * - * This releases all resources associated with a cell. The backing memory is - * kept as-is. It's the responsibility of the caller to manage it. - * - * You must not call any other cell operations on this cell after this call - * returns. You must re-initialize the cell via term_cell_init() before you can - * use it again. - * - * If @cell is NULL, this is a no-op. - */ -static void term_cell_destroy(term_cell *cell) { - if (!cell) - return; - - term_char_free(cell->ch); -} - -/** - * term_cell_set() - Change contents of a cell - * @cell: cell to modify - * @ch: character to set on the cell or cell->ch - * @cwidth: character width of @ch or cell->cwidth - * @attr: attributes to set on the cell or NULL - * @age: age to set on the cell or cell->age - * - * This changes the contents of a cell. It can be used to change the character, - * attributes and age. To keep the current character, pass cell->ch as @ch. To - * reset the current attributes, pass NULL. To keep the current age, pass - * cell->age. - * - * This call takes ownership of @ch. You need to duplicate it first, in case you - * want to use it for your own purposes after this call. - * - * The cell must have been initialized properly before calling this. See - * term_cell_init(). - */ -static void term_cell_set(term_cell *cell, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age) { - assert(cell); - - if (!term_char_same(ch, cell->ch)) { - term_char_free(cell->ch); - cell->ch = ch; - } - - cell->cwidth = cwidth; - cell->age = age; - - if (attr) - memcpy(&cell->attr, attr, sizeof(*attr)); - else - zero(cell->attr); -} - -/** - * term_cell_append() - Append a combining-char to a cell - * @cell: cell to modify - * @ucs4: UCS-4 character to append to the cell - * @age: new age to set on the cell or cell->age - * - * This appends a combining-character to a cell. No validation of the UCS-4 - * character is done, so this can be used to append any character. Additionally, - * this can update the age of the cell. - * - * The cell must have been initialized properly before calling this. See - * term_cell_init(). - */ -static void term_cell_append(term_cell *cell, uint32_t ucs4, term_age_t age) { - assert(cell); - - cell->ch = term_char_merge(cell->ch, ucs4); - cell->age = age; -} - -/** - * term_cell_init_n() - Initialize an array of cells - * @cells: pointer to an array of cells to initialize - * @n: number of cells - * @attr: attributes to set on all cells or NULL - * @age: age to set on all cells - * - * This is the same as term_cell_init() but initializes an array of cells. - * Furthermore, this always sets the character to TERM_CHAR_NULL. - * If you want to set a specific characters on all cells, you need to hard-code - * this loop and duplicate the character for each cell. - */ -static void term_cell_init_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) { - for ( ; n > 0; --n, ++cells) - term_cell_init(cells, TERM_CHAR_NULL, 0, attr, age); -} - -/** - * term_cell_destroy_n() - Destroy an array of cells - * @cells: pointer to an array of cells to destroy - * @n: number of cells - * - * This is the same as term_cell_destroy() but destroys an array of cells. - */ -static void term_cell_destroy_n(term_cell *cells, unsigned int n) { - for ( ; n > 0; --n, ++cells) - term_cell_destroy(cells); -} - -/** - * term_cell_clear_n() - Clear contents of an array of cells - * @cells: pointer to an array of cells to modify - * @n: number of cells - * @attr: attributes to set on all cells or NULL - * @age: age to set on all cells - * - * This is the same as term_cell_set() but operates on an array of cells. Note - * that all characters are always set to TERM_CHAR_NULL, unlike term_cell_set() - * which takes the character as argument. - * If you want to set a specific characters on all cells, you need to hard-code - * this loop and duplicate the character for each cell. - */ -static void term_cell_clear_n(term_cell *cells, unsigned int n, const term_attr *attr, term_age_t age) { - for ( ; n > 0; --n, ++cells) - term_cell_set(cells, TERM_CHAR_NULL, 0, attr, age); -} - -/** - * term_line_new() - Allocate a new line - * @out: place to store pointer to new line - * - * This allocates and initialized a new line. The line is unlinked and - * independent of any page. It can be used for any purpose. The initial - * cell-count is set to 0. - * - * The line has to be freed via term_line_free() once it's no longer needed. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_line_new(term_line **out) { - _term_line_free_ term_line *line = NULL; - - assert_return(out, -EINVAL); - - line = new0(term_line, 1); - if (!line) - return -ENOMEM; - - *out = line; - line = NULL; - return 0; -} - -/** - * term_line_free() - Free a line - * @line: line to free or NULL - * - * This frees a line that was previously allocated via term_line_free(). All its - * cells are released, too. - * - * If @line is NULL, this is a no-op. - */ -term_line *term_line_free(term_line *line) { - if (!line) - return NULL; - - term_cell_destroy_n(line->cells, line->n_cells); - free(line->cells); - free(line); - - return NULL; -} - -/** - * term_line_reserve() - Pre-allocate cells for a line - * @line: line to pre-allocate cells for - * @width: numbers of cells the line shall have pre-allocated - * @attr: attribute for all allocated cells or NULL - * @age: current age for all modifications - * @protect_width: width to protect from erasure - * - * This pre-allocates cells for this line. Please note that @width is the number - * of cells the line is guaranteed to have allocated after this call returns. - * It's not the number of cells that are added, neither is it the new width of - * the line. - * - * This function never frees memory. That is, reducing the line-width will - * always succeed, same is true for increasing the width to a previously set - * width. - * - * @attr and @age are used to initialize new cells. Additionally, any - * existing cell outside of the protected area specified by @protect_width are - * cleared and reset with @attr and @age. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_line_reserve(term_line *line, unsigned int width, const term_attr *attr, term_age_t age, unsigned int protect_width) { - unsigned int min_width; - term_cell *t; - - assert_return(line, -EINVAL); - - /* reset existing cells if required */ - min_width = MIN(line->n_cells, width); - if (min_width > protect_width) - term_cell_clear_n(line->cells + protect_width, - min_width - protect_width, - attr, - age); - - /* allocate new cells if required */ - - if (width > line->n_cells) { - t = realloc_multiply(line->cells, sizeof(*t), width); - if (!t) - return -ENOMEM; - - if (!attr && !age) - memzero(t + line->n_cells, - sizeof(*t) * (width - line->n_cells)); - else - term_cell_init_n(t + line->n_cells, - width - line->n_cells, - attr, - age); - - line->cells = t; - line->n_cells = width; - } - - line->fill = MIN(line->fill, protect_width); - - return 0; -} - -/** - * term_line_set_width() - Change width of a line - * @line: line to modify - * @width: new width - * - * This changes the actual width of a line. It is the caller's responsibility - * to use term_line_reserve() to make sure enough space is allocated. If @width - * is greater than the allocated size, it is cropped. - * - * This does not modify any cells. Use term_line_reserve() or term_line_erase() - * to clear any newly added cells. - * - * NOTE: The fill state is cropped at line->width. Therefore, if you increase - * the line-width afterwards, but there is a multi-cell character at the - * end of the line that got cropped, then the fill-state will _not_ be - * adjusted. - * This means, the fill-state always includes the cells up to the start - * of the right-most character, but it might or might not cover it until - * its end. This should be totally fine, though. You should never access - * multi-cell tails directly, anyway. - */ -void term_line_set_width(term_line *line, unsigned int width) { - assert(line); - - if (width > line->n_cells) - width = line->n_cells; - - line->width = width; - line->fill = MIN(line->fill, width); -} - -/** - * line_insert() - Insert characters and move existing cells to the right - * @from: position to insert cells at - * @num: number of cells to insert - * @head_char: character that is set on the first cell - * @head_cwidth: character-length of @head_char - * @attr: attribute for all inserted cells or NULL - * @age: current age for all modifications - * - * The INSERT operation (or writes with INSERT_MODE) writes data at a specific - * position on a line and shifts the existing cells to the right. Cells that are - * moved beyond the right hand border are discarded. - * - * This helper contains the actual INSERT implementation which is independent of - * the data written. It works on cells, not on characters. The first cell is set - * to @head_char, all others are reset to TERM_CHAR_NULL. See each caller for a - * more detailed description. - */ -static inline void line_insert(term_line *line, unsigned int from, unsigned int num, term_char_t head_char, unsigned int head_cwidth, const term_attr *attr, term_age_t age) { - unsigned int i, rem, move; - - if (from >= line->width) - return; - if (from + num < from || from + num > line->width) - num = line->width - from; - if (!num) - return; - - move = line->width - from - num; - rem = MIN(num, move); - - if (rem > 0) { - /* - * Make room for @num cells; shift cells to the right if - * required. @rem is the number of remaining cells that we will - * knock off on the right and overwrite during the right shift. - * - * For INSERT_MODE, @num/@rem are usually 1 or 2, @move is 50% - * of the line on average. Therefore, the actual move is quite - * heavy and we can safely invalidate cells manually instead of - * the whole line. - * However, for INSERT operations, any parameters are - * possible. But we cannot place any assumption on its usage - * across applications, so we just handle it the same as - * INSERT_MODE and do per-cell invalidation. - */ - - /* destroy cells that are knocked off on the right */ - term_cell_destroy_n(line->cells + line->width - rem, rem); - - /* move remaining bulk of cells */ - memmove(line->cells + from + num, - line->cells + from, - sizeof(*line->cells) * move); - - /* invalidate cells */ - for (i = 0; i < move; ++i) - line->cells[from + num + i].age = age; - - /* initialize fresh head-cell */ - term_cell_init(line->cells + from, - head_char, - head_cwidth, - attr, - age); - - /* initialize fresh tail-cells */ - term_cell_init_n(line->cells + from + 1, - num - 1, - attr, - age); - - /* adjust fill-state */ - line->fill = MIN(line->width, - MAX(line->fill + num, - from + num)); - } else { - /* modify head-cell */ - term_cell_set(line->cells + from, - head_char, - head_cwidth, - attr, - age); - - /* reset tail-cells */ - term_cell_clear_n(line->cells + from + 1, - num - 1, - attr, - age); - - /* adjust fill-state */ - line->fill = line->width; - } -} - -/** - * term_line_write() - Write to a single, specific cell - * @line: line to write to - * @pos_x: x-position of cell in @line to write to - * @ch: character to write to the cell - * @cwidth: character width of @ch - * @attr: attributes to set on the cell or NULL - * @age: current age for all modifications - * @insert_mode: true if INSERT-MODE is enabled - * - * This writes to a specific cell in a line. The cell is addressed by its - * X-position @pos_x. If that cell does not exist, this is a no-op. - * - * @ch and @attr are set on this cell. - * - * If @insert_mode is true, this inserts the character instead of overwriting - * existing data (existing data is now moved to the right before writing). - * - * This function is the low-level handler of normal writes to a terminal. - */ -void term_line_write(term_line *line, unsigned int pos_x, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) { - unsigned int len; - - assert(line); - - if (pos_x >= line->width) - return; - - len = MAX(1U, cwidth); - if (pos_x + len < pos_x || pos_x + len > line->width) - len = line->width - pos_x; - if (!len) - return; - - if (insert_mode) { - /* Use line_insert() to insert the character-head and fill - * the remains with NULLs. */ - line_insert(line, pos_x, len, ch, cwidth, attr, age); - } else { - /* modify head-cell */ - term_cell_set(line->cells + pos_x, ch, cwidth, attr, age); - - /* reset tail-cells */ - term_cell_clear_n(line->cells + pos_x + 1, - len - 1, - attr, - age); - - /* adjust fill-state */ - line->fill = MIN(line->width, - MAX(line->fill, - pos_x + len)); - } -} - -/** - * term_line_insert() - Insert empty cells - * @line: line to insert empty cells into - * @from: x-position where to insert cells - * @num: number of cells to insert - * @attr: attributes to set on the cells or NULL - * @age: current age for all modifications - * - * This inserts @num empty cells at position @from in line @line. All existing - * cells to the right are shifted to make room for the new cells. Cells that get - * pushed beyond the right hand border are discarded. - */ -void term_line_insert(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) { - /* use line_insert() to insert @num empty cells */ - return line_insert(line, from, num, TERM_CHAR_NULL, 0, attr, age); -} - -/** - * term_line_delete() - Delete cells from line - * @line: line to delete cells from - * @from: position to delete cells at - * @num: number of cells to delete - * @attr: attributes to set on any new cells - * @age: current age for all modifications - * - * Delete cells from a line. All cells to the right of the deleted cells are - * shifted to the left to fill the empty space. New cells appearing on the right - * hand border are cleared and initialized with @attr. - */ -void term_line_delete(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age) { - unsigned int rem, move, i; - - assert(line); - - if (from >= line->width) - return; - if (from + num < from || from + num > line->width) - num = line->width - from; - if (!num) - return; - - /* destroy and move as many upfront as possible */ - move = line->width - from - num; - rem = MIN(num, move); - if (rem > 0) { - /* destroy to be removed cells */ - term_cell_destroy_n(line->cells + from, rem); - - /* move tail upfront */ - memmove(line->cells + from, - line->cells + from + num, - sizeof(*line->cells) * move); - - /* invalidate copied cells */ - for (i = 0; i < move; ++i) - line->cells[from + i].age = age; - - /* initialize tail that was moved away */ - term_cell_init_n(line->cells + line->width - rem, - rem, - attr, - age); - - /* reset remaining cells in case the move was too small */ - if (num > move) - term_cell_clear_n(line->cells + from + move, - num - move, - attr, - age); - } else { - /* reset cells */ - term_cell_clear_n(line->cells + from, - num, - attr, - age); - } - - /* adjust fill-state */ - if (from + num < line->fill) - line->fill -= num; - else if (from < line->fill) - line->fill = from; -} - -/** - * term_line_append_combchar() - Append combining char to existing cell - * @line: line to modify - * @pos_x: position of cell to append combining char to - * @ucs4: combining character to append - * @age: current age for all modifications - * - * Unicode allows trailing combining characters, which belong to the - * char in front of them. The caller is responsible of detecting - * combining characters and calling term_line_append_combchar() instead of - * term_line_write(). This simply appends the char to the correct cell then. - * If the cell is not in the visible area, this call is skipped. - * - * Note that control-sequences are not 100% compatible with combining - * characters as they require delayed parsing. However, we must handle - * control-sequences immediately. Therefore, there might be trailing - * combining chars that should be discarded by the parser. - * However, to prevent programming errors, we're also being pedantic - * here and discard weirdly placed combining chars. This prevents - * situations were invalid content is parsed into the terminal and you - * might end up with cells containing only combining chars. - * - * Long story short: To get combining-characters working with old-fashioned - * terminal-emulation, we parse them exclusively for direct cell-writes. Other - * combining-characters are usually simply discarded and ignored. - */ -void term_line_append_combchar(term_line *line, unsigned int pos_x, uint32_t ucs4, term_age_t age) { - assert(line); - - if (pos_x >= line->width) - return; - - /* Unused cell? Skip appending any combining chars then. */ - if (term_char_is_null(line->cells[pos_x].ch)) - return; - - term_cell_append(line->cells + pos_x, ucs4, age); -} - -/** - * term_line_erase() - Erase parts of a line - * @line: line to modify - * @from: position to start the erase - * @num: number of cells to erase - * @attr: attributes to initialize erased cells with - * @age: current age for all modifications - * @keep_protected: true if protected cells should be kept - * - * This is the standard erase operation. It clears all cells in the targeted - * area and re-initializes them. Cells to the right are not shifted left, you - * must use DELETE to achieve that. Cells outside the visible area are skipped. - * - * If @keep_protected is true, protected cells will not be erased. - */ -void term_line_erase(term_line *line, unsigned int from, unsigned int num, const term_attr *attr, term_age_t age, bool keep_protected) { - term_cell *cell; - unsigned int i, last_protected; - - assert(line); - - if (from >= line->width) - return; - if (from + num < from || from + num > line->width) - num = line->width - from; - if (!num) - return; - - last_protected = 0; - for (i = 0; i < num; ++i) { - cell = line->cells + from + i; - if (keep_protected && cell->attr.protect) { - /* only count protected-cells inside the fill-region */ - if (from + i < line->fill) - last_protected = from + i; - - continue; - } - - term_cell_set(cell, TERM_CHAR_NULL, 0, attr, age); - } - - /* Adjust fill-state. This is a bit tricks, we can only adjust it in - * case the erase-region starts inside the fill-region and ends at the - * tail or beyond the fill-region. Otherwise, the current fill-state - * stays as it was. - * Furthermore, we must account for protected cells. The loop above - * ensures that protected-cells are only accounted for if they're - * inside the fill-region. */ - if (from < line->fill && from + num >= line->fill) - line->fill = MAX(from, last_protected); -} - -/** - * term_line_reset() - Reset a line - * @line: line to reset - * @attr: attributes to initialize all cells with - * @age: current age for all modifications - * - * This resets all visible cells of a line and sets their attributes and ages - * to @attr and @age. This is equivalent to erasing a whole line via - * term_line_erase(). - */ -void term_line_reset(term_line *line, const term_attr *attr, term_age_t age) { - assert(line); - - return term_line_erase(line, 0, line->width, attr, age, 0); -} - -/** - * term_line_link() - Link line in front of a list - * @line: line to link - * @first: member pointing to first entry - * @last: member pointing to last entry - * - * This links a line into a list of lines. The line is inserted at the front and - * must not be linked, yet. See the TERM_LINE_LINK() macro for an easier usage of - * this. - */ -void term_line_link(term_line *line, term_line **first, term_line **last) { - assert(line); - assert(first); - assert(last); - assert(!line->lines_prev); - assert(!line->lines_next); - - line->lines_prev = NULL; - line->lines_next = *first; - if (*first) - (*first)->lines_prev = line; - else - *last = line; - *first = line; -} - -/** - * term_line_link_tail() - Link line at tail of a list - * @line: line to link - * @first: member pointing to first entry - * @last: member pointing to last entry - * - * Same as term_line_link() but links the line at the tail. - */ -void term_line_link_tail(term_line *line, term_line **first, term_line **last) { - assert(line); - assert(first); - assert(last); - assert(!line->lines_prev); - assert(!line->lines_next); - - line->lines_next = NULL; - line->lines_prev = *last; - if (*last) - (*last)->lines_next = line; - else - *first = line; - *last = line; -} - -/** - * term_line_unlink() - Unlink line from a list - * @line: line to unlink - * @first: member pointing to first entry - * @last: member pointing to last entry - * - * This unlinks a previously linked line. See TERM_LINE_UNLINK() for an easier to - * use macro. - */ -void term_line_unlink(term_line *line, term_line **first, term_line **last) { - assert(line); - assert(first); - assert(last); - - if (line->lines_prev) - line->lines_prev->lines_next = line->lines_next; - else - *first = line->lines_next; - if (line->lines_next) - line->lines_next->lines_prev = line->lines_prev; - else - *last = line->lines_prev; - - line->lines_prev = NULL; - line->lines_next = NULL; -} - -/** - * term_page_new() - Allocate new page - * @out: storage for pointer to new page - * - * Allocate a new page. The initial dimensions are 0/0. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_page_new(term_page **out) { - _term_page_free_ term_page *page = NULL; - - assert_return(out, -EINVAL); - - page = new0(term_page, 1); - if (!page) - return -ENOMEM; - - *out = page; - page = NULL; - return 0; -} - -/** - * term_page_free() - Free page - * @page: page to free or NULL - * - * Free a previously allocated page and all associated data. If @page is NULL, - * this is a no-op. - * - * Returns: NULL - */ -term_page *term_page_free(term_page *page) { - unsigned int i; - - if (!page) - return NULL; - - for (i = 0; i < page->n_lines; ++i) - term_line_free(page->lines[i]); - - free(page->line_cache); - free(page->lines); - free(page); - - return NULL; -} - -/** - * term_page_get_cell() - Return pointer to requested cell - * @page: page to operate on - * @x: x-position of cell - * @y: y-position of cell - * - * This returns a pointer to the cell at position @x/@y. You're free to modify - * this cell as much as you like. However, once you call any other function on - * the page, you must drop the pointer to the cell. - * - * Returns: Pointer to the cell or NULL if out of the visible area. - */ -term_cell *term_page_get_cell(term_page *page, unsigned int x, unsigned int y) { - assert_return(page, NULL); - - if (x >= page->width) - return NULL; - if (y >= page->height) - return NULL; - - return &page->lines[y]->cells[x]; -} - -/** - * page_scroll_up() - Scroll up - * @page: page to operate on - * @new_width: width to use for any new line moved into the visible area - * @num: number of lines to scroll up - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for old lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are cleared and reset - * with the given attributes. Old lines are moved into the history if non-NULL. - * If a new line is allocated, moved from the history buffer or moved from - * outside the visible region into the visible region, this call makes sure it - * has at least @width cells allocated. If a possible memory-allocation fails, - * the previous line is reused. This has the side effect, that it will not be - * linked into the history buffer. - * - * If the scroll-region is empty, this is a no-op. - */ -static void page_scroll_up(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - term_line *line, **cache; - unsigned int i; - int r; - - assert(page); - - if (num > page->scroll_num) - num = page->scroll_num; - if (num < 1) - return; - - /* Better safe than sorry: avoid under-allocating lines, even when - * resizing. */ - new_width = MAX(new_width, page->width); - - cache = page->line_cache; - - /* Try moving lines into history and allocate new lines for each moved - * line. In case allocation fails, or if we have no history, reuse the - * line. - * We keep the lines in the line-cache so we can safely move the - * remaining lines around. */ - for (i = 0; i < num; ++i) { - line = page->lines[page->scroll_idx + i]; - - r = -EAGAIN; - if (history) { - r = term_line_new(&cache[i]); - if (r >= 0) { - r = term_line_reserve(cache[i], - new_width, - attr, - age, - 0); - if (r < 0) - term_line_free(cache[i]); - else - term_line_set_width(cache[i], page->width); - } - } - - if (r >= 0) { - term_history_push(history, line); - } else { - cache[i] = line; - term_line_reset(line, attr, age); - } - } - - if (num < page->scroll_num) { - memmove(page->lines + page->scroll_idx, - page->lines + page->scroll_idx + num, - sizeof(*page->lines) * (page->scroll_num - num)); - - /* update age of moved lines */ - for (i = 0; i < page->scroll_num - num; ++i) - page->lines[page->scroll_idx + i]->age = age; - } - - /* copy remaining lines from cache; age is already updated */ - memcpy(page->lines + page->scroll_idx + page->scroll_num - num, - cache, - sizeof(*cache) * num); - - /* update fill */ - page->scroll_fill -= MIN(page->scroll_fill, num); -} - -/** - * page_scroll_down() - Scroll down - * @page: page to operate on - * @new_width: width to use for any new line moved into the visible area - * @num: number of lines to scroll down - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for new lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are retrieved from - * the history or cleared if the history is empty or NULL. - * - * Usually, scroll-down implies that new lines are cleared. Therefore, you're - * highly encouraged to set @history to NULL. However, if you resize a terminal, - * you might want to include history-lines in the new area. In that case, you - * should set @history to non-NULL. - * - * If a new line is allocated, moved from the history buffer or moved from - * outside the visible region into the visible region, this call makes sure it - * has at least @width cells allocated. If a possible memory-allocation fails, - * the previous line is reused. This will have the side-effect that lines from - * the history will not get visible on-screen but kept in history. - * - * If the scroll-region is empty, this is a no-op. - */ -static void page_scroll_down(term_page *page, unsigned int new_width, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - term_line *line, **cache, *t; - unsigned int i, last_idx; - - assert(page); - - if (num > page->scroll_num) - num = page->scroll_num; - if (num < 1) - return; - - /* Better safe than sorry: avoid under-allocating lines, even when - * resizing. */ - new_width = MAX(new_width, page->width); - - cache = page->line_cache; - last_idx = page->scroll_idx + page->scroll_num - 1; - - /* Try pulling out lines from history; if history is empty or if no - * history is given, we reuse the to-be-removed lines. Otherwise, those - * lines are released. */ - for (i = 0; i < num; ++i) { - line = page->lines[last_idx - i]; - - t = NULL; - if (history) - t = term_history_pop(history, new_width, attr, age); - - if (t) { - cache[num - 1 - i] = t; - term_line_free(line); - } else { - cache[num - 1 - i] = line; - term_line_reset(line, attr, age); - } - } - - if (num < page->scroll_num) { - memmove(page->lines + page->scroll_idx + num, - page->lines + page->scroll_idx, - sizeof(*page->lines) * (page->scroll_num - num)); - - /* update age of moved lines */ - for (i = 0; i < page->scroll_num - num; ++i) - page->lines[page->scroll_idx + num + i]->age = age; - } - - /* copy remaining lines from cache; age is already updated */ - memcpy(page->lines + page->scroll_idx, - cache, - sizeof(*cache) * num); - - /* update fill; but only if there's already content in it */ - if (page->scroll_fill > 0) - page->scroll_fill = MIN(page->scroll_num, - page->scroll_fill + num); -} - -/** - * page_reserve() - Reserve page area - * @page: page to modify - * @cols: required columns (width) - * @rows: required rows (height) - * @attr: attributes for newly allocated cells - * @age: age to set on any modified cells - * - * This allocates the required amount of lines and cells to guarantee that the - * page has at least the demanded dimensions of @cols x @rows. Note that this - * never shrinks the page-memory. We keep cells allocated for performance - * reasons. - * - * Additionally to allocating lines, this also clears any newly added cells so - * you can safely change the size afterwards without clearing new cells. - * - * Note that you must be careful what operations you call on the page between - * page_reserve() and updating page->width/height. Any newly allocated line (or - * shifted line) might not meet your new width/height expectations. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_page_reserve(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age) { - _term_line_free_ term_line *line = NULL; - unsigned int i, min_lines; - term_line **t; - int r; - - assert_return(page, -EINVAL); - - /* - * First make sure the first MIN(page->n_lines, rows) lines have at - * least the required width of @cols. This does not modify any visible - * cells in the existing @page->width x @page->height area, therefore, - * we can safely bail out afterwards in case anything else fails. - * Note that lines in between page->height and page->n_lines might be - * shorter than page->width. Hence, we need to resize them all, but we - * can skip some of them for better performance. - */ - min_lines = MIN(page->n_lines, rows); - for (i = 0; i < min_lines; ++i) { - /* lines below page->height have at least page->width cells */ - if (cols < page->width && i < page->height) - continue; - - r = term_line_reserve(page->lines[i], - cols, - attr, - age, - (i < page->height) ? page->width : 0); - if (r < 0) - return r; - } - - /* - * We now know the first @min_lines lines have at least width @cols and - * are prepared for resizing. We now only have to allocate any - * additional lines below @min_lines in case @rows is greater than - * page->n_lines. - */ - if (rows > page->n_lines) { - t = realloc_multiply(page->lines, sizeof(*t), rows); - if (!t) - return -ENOMEM; - page->lines = t; - - t = realloc_multiply(page->line_cache, sizeof(*t), rows); - if (!t) - return -ENOMEM; - page->line_cache = t; - - while (page->n_lines < rows) { - r = term_line_new(&line); - if (r < 0) - return r; - - r = term_line_reserve(line, cols, attr, age, 0); - if (r < 0) - return r; - - page->lines[page->n_lines++] = line; - line = NULL; - } - } - - return 0; -} - -/** - * term_page_resize() - Resize page - * @page: page to modify - * @cols: number of columns (width) - * @rows: number of rows (height) - * @attr: attributes for newly allocated cells - * @age: age to set on any modified cells - * @history: history buffer to use for new/old lines or NULL - * - * This changes the visible dimensions of a page. You must have called - * term_page_reserve() beforehand, otherwise, this will fail. - * - * Returns: 0 on success, negative error code on failure. - */ -void term_page_resize(term_page *page, unsigned int cols, unsigned int rows, const term_attr *attr, term_age_t age, term_history *history) { - unsigned int i, num, empty, max, old_height; - term_line *line; - - assert(page); - assert(page->n_lines >= rows); - - old_height = page->height; - - if (rows < old_height) { - /* - * If we decrease the terminal-height, we emulate a scroll-up. - * This way, existing data from the scroll-area is moved into - * the history, making space at the bottom to reduce the screen - * height. In case the scroll-fill indicates empty lines, we - * reduce the amount of scrolled lines. - * Once scrolled, we have to move the lower margin from below - * the scroll area up so it is preserved. - */ - - /* move lines to history if scroll region is filled */ - num = old_height - rows; - empty = page->scroll_num - page->scroll_fill; - if (num > empty) - page_scroll_up(page, - cols, - num - empty, - attr, - age, - history); - - /* move lower margin up; drop its lines if not enough space */ - num = LESS_BY(old_height, page->scroll_idx + page->scroll_num); - max = LESS_BY(rows, page->scroll_idx); - num = MIN(num, max); - if (num > 0) { - unsigned int top, bottom; - - top = rows - num; - bottom = page->scroll_idx + page->scroll_num; - - /* might overlap; must run topdown, not bottomup */ - for (i = 0; i < num; ++i) { - line = page->lines[top + i]; - page->lines[top + i] = page->lines[bottom + i]; - page->lines[bottom + i] = line; - } - } - - /* update vertical extents */ - page->height = rows; - page->scroll_idx = MIN(page->scroll_idx, rows); - page->scroll_num -= MIN(page->scroll_num, old_height - rows); - /* fill is already up-to-date or 0 due to scroll-up */ - } else if (rows > old_height) { - /* - * If we increase the terminal-height, we emulate a scroll-down - * and fetch new lines from the history. - * New lines are always accounted to the scroll-region. Thus we - * have to preserve the lower margin first, by moving it down. - */ - - /* move lower margin down */ - num = LESS_BY(old_height, page->scroll_idx + page->scroll_num); - if (num > 0) { - unsigned int top, bottom; - - top = page->scroll_idx + page->scroll_num; - bottom = top + (rows - old_height); - - /* might overlap; must run bottomup, not topdown */ - for (i = num; i-- > 0; ) { - line = page->lines[top + i]; - page->lines[top + i] = page->lines[bottom + i]; - page->lines[bottom + i] = line; - } - } - - /* update vertical extents */ - page->height = rows; - page->scroll_num = MIN(LESS_BY(rows, page->scroll_idx), - page->scroll_num + (rows - old_height)); - - /* check how many lines can be received from history */ - if (history) - num = term_history_peek(history, - rows - old_height, - cols, - attr, - age); - else - num = 0; - - /* retrieve new lines from history if available */ - if (num > 0) - page_scroll_down(page, - cols, - num, - attr, - age, - history); - } - - /* set horizontal extents */ - page->width = cols; - for (i = 0; i < page->height; ++i) - term_line_set_width(page->lines[i], cols); -} - -/** - * term_page_write() - Write to a single cell - * @page: page to operate on - * @pos_x: x-position of cell to write to - * @pos_y: y-position of cell to write to - * @ch: character to write - * @cwidth: character-width of @ch - * @attr: attributes to set on the cell or NULL - * @age: age to use for all modifications - * @insert_mode: true if INSERT-MODE is enabled - * - * This writes a character to a specific cell. If the cell is beyond bounds, - * this is a no-op. @attr and @age are used to update the cell. @flags can be - * used to alter the behavior of this function. - * - * This is a wrapper around term_line_write(). - * - * This call does not wrap around lines. That is, this only operates on a single - * line. - */ -void term_page_write(term_page *page, unsigned int pos_x, unsigned int pos_y, term_char_t ch, unsigned int cwidth, const term_attr *attr, term_age_t age, bool insert_mode) { - assert(page); - - if (pos_y >= page->height) - return; - - term_line_write(page->lines[pos_y], pos_x, ch, cwidth, attr, age, insert_mode); -} - -/** - * term_page_insert_cells() - Insert cells into a line - * @page: page to operate on - * @from_x: x-position where to insert new cells - * @from_y: y-position where to insert new cells - * @num: number of cells to insert - * @attr: attributes to set on new cells or NULL - * @age: age to use for all modifications - * - * This inserts new cells into a given line. This is a wrapper around - * term_line_insert(). - * - * This call does not wrap around lines. That is, this only operates on a single - * line. - */ -void term_page_insert_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) { - assert(page); - - if (from_y >= page->height) - return; - - term_line_insert(page->lines[from_y], from_x, num, attr, age); -} - -/** - * term_page_delete_cells() - Delete cells from a line - * @page: page to operate on - * @from_x: x-position where to delete cells - * @from_y: y-position where to delete cells - * @num: number of cells to delete - * @attr: attributes to set on new cells or NULL - * @age: age to use for all modifications - * - * This deletes cells from a given line. This is a wrapper around - * term_line_delete(). - * - * This call does not wrap around lines. That is, this only operates on a single - * line. - */ -void term_page_delete_cells(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int num, const term_attr *attr, term_age_t age) { - assert(page); - - if (from_y >= page->height) - return; - - term_line_delete(page->lines[from_y], from_x, num, attr, age); -} - -/** - * term_page_append_combchar() - Append combining-character to a cell - * @page: page to operate on - * @pos_x: x-position of target cell - * @pos_y: y-position of target cell - * @ucs4: combining character to append - * @age: age to use for all modifications - * - * This appends a combining-character to a specific cell. This is a wrapper - * around term_line_append_combchar(). - */ -void term_page_append_combchar(term_page *page, unsigned int pos_x, unsigned int pos_y, uint32_t ucs4, term_age_t age) { - assert(page); - - if (pos_y >= page->height) - return; - - term_line_append_combchar(page->lines[pos_y], pos_x, ucs4, age); -} - -/** - * term_page_erase() - Erase parts of a page - * @page: page to operate on - * @from_x: x-position where to start erasure (inclusive) - * @from_y: y-position where to start erasure (inclusive) - * @to_x: x-position where to stop erasure (inclusive) - * @to_y: y-position where to stop erasure (inclusive) - * @attr: attributes to set on cells - * @age: age to use for all modifications - * @keep_protected: true if protected cells should be kept - * - * This erases all cells starting at @from_x/@from_y up to @to_x/@to_y. Note - * that this wraps around line-boundaries so lines between @from_y and @to_y - * are cleared entirely. - * - * Lines outside the visible area are left untouched. - */ -void term_page_erase(term_page *page, unsigned int from_x, unsigned int from_y, unsigned int to_x, unsigned int to_y, const term_attr *attr, term_age_t age, bool keep_protected) { - unsigned int i, from, to; - - assert(page); - - for (i = from_y; i <= to_y && i < page->height; ++i) { - from = 0; - to = page->width; - - if (i == from_y) - from = from_x; - if (i == to_y) - to = to_x; - - term_line_erase(page->lines[i], - from, - LESS_BY(to, from), - attr, - age, - keep_protected); - } -} - -/** - * term_page_reset() - Reset page - * @page: page to modify - * @attr: attributes to set on cells - * @age: age to use for all modifications - * - * This erases the whole visible page. See term_page_erase(). - */ -void term_page_reset(term_page *page, const term_attr *attr, term_age_t age) { - assert(page); - - return term_page_erase(page, - 0, 0, - page->width - 1, page->height - 1, - attr, - age, - 0); -} - -/** - * term_page_set_scroll_region() - Set scroll region - * @page: page to operate on - * @idx: start-index of scroll region - * @num: number of lines in scroll region - * - * This sets the scroll region of a page. Whenever an operation needs to scroll - * lines, it scrolls them inside of that region. Lines outside the region are - * left untouched. In case a scroll-operation is targeted outside of this - * region, it will implicitly get a scroll-region of only one line (i.e., no - * scroll region at all). - * - * Note that the scroll-region is clipped to the current page-extents. Growing - * or shrinking the page always accounts new/old lines to the scroll region and - * moves top/bottom margins accordingly so they're preserved. - */ -void term_page_set_scroll_region(term_page *page, unsigned int idx, unsigned int num) { - assert(page); - - if (page->height < 1) { - page->scroll_idx = 0; - page->scroll_num = 0; - } else { - page->scroll_idx = MIN(idx, page->height - 1); - page->scroll_num = MIN(num, page->height - page->scroll_idx); - } -} - -/** - * term_page_scroll_up() - Scroll up - * @page: page to operate on - * @num: number of lines to scroll up - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for old lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are cleared and reset - * with the given attributes. Old lines are moved into the history if non-NULL. - * - * If the scroll-region is empty, this is a no-op. - */ -void term_page_scroll_up(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - page_scroll_up(page, page->width, num, attr, age, history); -} - -/** - * term_page_scroll_down() - Scroll down - * @page: page to operate on - * @num: number of lines to scroll down - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * @history: history to use for new lines or NULL - * - * This scrolls the scroll-region by @num lines. New lines are retrieved from - * the history or cleared if the history is empty or NULL. - * - * Usually, scroll-down implies that new lines are cleared. Therefore, you're - * highly encouraged to set @history to NULL. However, if you resize a terminal, - * you might want to include history-lines in the new area. In that case, you - * should set @history to non-NULL. - * - * If the scroll-region is empty, this is a no-op. - */ -void term_page_scroll_down(term_page *page, unsigned int num, const term_attr *attr, term_age_t age, term_history *history) { - page_scroll_down(page, page->width, num, attr, age, history); -} - -/** - * term_page_insert_lines() - Insert new lines - * @page: page to operate on - * @pos_y: y-position where to insert new lines - * @num: number of lines to insert - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * - * This inserts @num new lines at position @pos_y. If @pos_y is beyond - * boundaries or @num is 0, this is a no-op. - * All lines below @pos_y are moved down to make space for the new lines. Lines - * on the bottom are dropped. Note that this only moves lines above or inside - * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of - * one line is implied (which means the line is simply cleared). - */ -void term_page_insert_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) { - unsigned int scroll_idx, scroll_num; - - assert(page); - - if (pos_y >= page->height) - return; - if (num >= page->height) - num = page->height; - - /* remember scroll-region */ - scroll_idx = page->scroll_idx; - scroll_num = page->scroll_num; - - /* set scroll-region temporarily so we can reuse scroll_down() */ - { - page->scroll_idx = pos_y; - if (pos_y >= scroll_idx + scroll_num) - page->scroll_num = 1; - else if (pos_y >= scroll_idx) - page->scroll_num -= pos_y - scroll_idx; - else - page->scroll_num += scroll_idx - pos_y; - - term_page_scroll_down(page, num, attr, age, NULL); - } - - /* reset scroll-region */ - page->scroll_idx = scroll_idx; - page->scroll_num = scroll_num; -} - -/** - * term_page_delete_lines() - Delete lines - * @page: page to operate on - * @pos_y: y-position where to delete lines - * @num: number of lines to delete - * @attr: attributes to set on new lines - * @age: age to use for all modifications - * - * This deletes @num lines at position @pos_y. If @pos_y is beyond boundaries or - * @num is 0, this is a no-op. - * All lines below @pos_y are moved up into the newly made space. New lines - * on the bottom are clear. Note that this only moves lines above or inside - * the scroll-region. If @pos_y is below the scroll-region, a scroll-region of - * one line is implied (which means the line is simply cleared). - */ -void term_page_delete_lines(term_page *page, unsigned int pos_y, unsigned int num, const term_attr *attr, term_age_t age) { - unsigned int scroll_idx, scroll_num; - - assert(page); - - if (pos_y >= page->height) - return; - if (num >= page->height) - num = page->height; - - /* remember scroll-region */ - scroll_idx = page->scroll_idx; - scroll_num = page->scroll_num; - - /* set scroll-region temporarily so we can reuse scroll_up() */ - { - page->scroll_idx = pos_y; - if (pos_y >= scroll_idx + scroll_num) - page->scroll_num = 1; - else if (pos_y > scroll_idx) - page->scroll_num -= pos_y - scroll_idx; - else - page->scroll_num += scroll_idx - pos_y; - - term_page_scroll_up(page, num, attr, age, NULL); - } - - /* reset scroll-region */ - page->scroll_idx = scroll_idx; - page->scroll_num = scroll_num; -} - -/** - * term_history_new() - Create new history object - * @out: storage for pointer to new history - * - * Create a new history object. Histories are used to store scrollback-lines - * from VTE pages. You're highly recommended to set a history-limit on - * history->max_lines and trim it via term_history_trim(), otherwise history - * allocations are unlimited. - * - * Returns: 0 on success, negative error code on failure. - */ -int term_history_new(term_history **out) { - _term_history_free_ term_history *history = NULL; - - assert_return(out, -EINVAL); - - history = new0(term_history, 1); - if (!history) - return -ENOMEM; - - history->max_lines = 4096; - - *out = history; - history = NULL; - return 0; -} - -/** - * term_history_free() - Free history - * @history: history to free - * - * Clear and free history. You must not access the object afterwards. - * - * Returns: NULL - */ -term_history *term_history_free(term_history *history) { - if (!history) - return NULL; - - term_history_clear(history); - free(history); - return NULL; -} - -/** - * term_history_clear() - Clear history - * @history: history to clear - * - * Remove all linked lines from a history and reset it to its initial state. - */ -void term_history_clear(term_history *history) { - return term_history_trim(history, 0); -} - -/** - * term_history_trim() - Trim history - * @history: history to trim - * @max: maximum number of lines to be left in history - * - * This removes lines from the history until it is smaller than @max. Lines are - * removed from the top. - */ -void term_history_trim(term_history *history, unsigned int max) { - term_line *line; - - if (!history) - return; - - while (history->n_lines > max && (line = history->lines_first)) { - TERM_LINE_UNLINK(line, history); - term_line_free(line); - --history->n_lines; - } -} - -/** - * term_history_push() - Push line into history - * @history: history to work on - * @line: line to push into history - * - * This pushes a line into the given history. It is linked at the tail. In case - * the history is limited, the top-most line might be freed. - */ -void term_history_push(term_history *history, term_line *line) { - assert(history); - assert(line); - - TERM_LINE_LINK_TAIL(line, history); - if (history->max_lines > 0 && history->n_lines >= history->max_lines) { - line = history->lines_first; - TERM_LINE_UNLINK(line, history); - term_line_free(line); - } else { - ++history->n_lines; - } -} - -/** - * term_history_pop() - Retrieve last line from history - * @history: history to work on - * @new_width: width to reserve and set on the line - * @attr: attributes to use for cell reservation - * @age: age to use for cell reservation - * - * This unlinks the last linked line of the history and returns it. This also - * makes sure the line has the given width pre-allocated (see - * term_line_reserve()). If the pre-allocation fails, this returns NULL, so it - * is treated like there's no line in history left. This simplifies - * history-handling on the caller's side in case of allocation errors. No need - * to throw lines away just because the reservation failed. We can keep them in - * history safely, and make them available as scrollback. - * - * Returns: Line from history or NULL - */ -term_line *term_history_pop(term_history *history, unsigned int new_width, const term_attr *attr, term_age_t age) { - term_line *line; - int r; - - assert_return(history, NULL); - - line = history->lines_last; - if (!line) - return NULL; - - r = term_line_reserve(line, new_width, attr, age, line->width); - if (r < 0) - return NULL; - - term_line_set_width(line, new_width); - TERM_LINE_UNLINK(line, history); - --history->n_lines; - - return line; -} - -/** - * term_history_peek() - Return number of available history-lines - * @history: history to work on - * @max: maximum number of lines to look at - * @reserve_width: width to reserve on the line - * @attr: attributes to use for cell reservation - * @age: age to use for cell reservation - * - * This returns the number of available lines in the history given as @history. - * It returns at most @max. For each line that is looked at, the line is - * verified to have at least @reserve_width cells. Valid cells are preserved, - * new cells are initialized with @attr and @age. In case an allocation fails, - * we bail out and return the number of lines that are valid so far. - * - * Usually, this function should be used before running a loop on - * term_history_pop(). This function guarantees that term_history_pop() (with - * the same arguments) will succeed at least the returned number of times. - * - * Returns: Number of valid lines that can be received via term_history_pop(). - */ -unsigned int term_history_peek(term_history *history, unsigned int max, unsigned int reserve_width, const term_attr *attr, term_age_t age) { - unsigned int num; - term_line *line; - int r; - - assert(history); - - num = 0; - line = history->lines_last; - - while (num < max && line) { - r = term_line_reserve(line, reserve_width, attr, age, line->width); - if (r < 0) - break; - - ++num; - line = line->lines_prev; - } - - return num; -} diff --git a/src/libsystemd-terminal/term-parser.c b/src/libsystemd-terminal/term-parser.c deleted file mode 100644 index 8dc1da2f9c..0000000000 --- a/src/libsystemd-terminal/term-parser.c +++ /dev/null @@ -1,1702 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Terminal Parser - * This file contains a bunch of UTF-8 helpers and the main ctlseq-parser. The - * parser is a simple state-machine that correctly parses all CSI, DCS, OSC, ST - * control sequences and generic escape sequences. - * The parser itself does not perform any actions but lets the caller react to - * detected sequences. - */ - -#include -#include -#include -#include "macro.h" -#include "term-internal.h" -#include "util.h" - -static const uint8_t default_palette[18][3] = { - { 0, 0, 0 }, /* black */ - { 205, 0, 0 }, /* red */ - { 0, 205, 0 }, /* green */ - { 205, 205, 0 }, /* yellow */ - { 0, 0, 238 }, /* blue */ - { 205, 0, 205 }, /* magenta */ - { 0, 205, 205 }, /* cyan */ - { 229, 229, 229 }, /* light grey */ - { 127, 127, 127 }, /* dark grey */ - { 255, 0, 0 }, /* light red */ - { 0, 255, 0 }, /* light green */ - { 255, 255, 0 }, /* light yellow */ - { 92, 92, 255 }, /* light blue */ - { 255, 0, 255 }, /* light magenta */ - { 0, 255, 255 }, /* light cyan */ - { 255, 255, 255 }, /* white */ - - { 229, 229, 229 }, /* light grey */ - { 0, 0, 0 }, /* black */ -}; - -static uint32_t term_color_to_argb32(const term_color *color, const term_attr *attr, const uint8_t *palette) { - static const uint8_t bval[] = { - 0x00, 0x5f, 0x87, - 0xaf, 0xd7, 0xff, - }; - uint8_t r, g, b, t; - - assert(color); - - if (!palette) - palette = (void*)default_palette; - - switch (color->ccode) { - case TERM_CCODE_RGB: - r = color->red; - g = color->green; - b = color->blue; - - break; - case TERM_CCODE_256: - t = color->c256; - if (t < 16) { - r = palette[t * 3 + 0]; - g = palette[t * 3 + 1]; - b = palette[t * 3 + 2]; - } else if (t < 232) { - t -= 16; - b = bval[t % 6]; - t /= 6; - g = bval[t % 6]; - t /= 6; - r = bval[t % 6]; - } else { - t = (t - 232) * 10 + 8; - r = t; - g = t; - b = t; - } - - break; - case TERM_CCODE_BLACK ... TERM_CCODE_LIGHT_WHITE: - t = color->ccode - TERM_CCODE_BLACK; - - /* bold causes light colors (only for foreground colors) */ - if (t < 8 && attr->bold && color == &attr->fg) - t += 8; - - r = palette[t * 3 + 0]; - g = palette[t * 3 + 1]; - b = palette[t * 3 + 2]; - break; - case TERM_CCODE_DEFAULT: - /* fallthrough */ - default: - t = 16 + !(color == &attr->fg); - r = palette[t * 3 + 0]; - g = palette[t * 3 + 1]; - b = palette[t * 3 + 2]; - break; - } - - return (0xff << 24) | (r << 16) | (g << 8) | b; -} - -/** - * term_attr_to_argb32() - Encode terminal colors as native ARGB32 value - * @color: Terminal attributes to work on - * @fg: Storage for foreground color (or NULL) - * @bg: Storage for background color (or NULL) - * @palette: The color palette to use (or NULL for default) - * - * This encodes the colors attr->fg and attr->bg as native-endian ARGB32 values - * and returns them. Any color conversions are automatically applied. - */ -void term_attr_to_argb32(const term_attr *attr, uint32_t *fg, uint32_t *bg, const uint8_t *palette) { - uint32_t f, b, t; - - assert(attr); - - f = term_color_to_argb32(&attr->fg, attr, palette); - b = term_color_to_argb32(&attr->bg, attr, palette); - - if (attr->inverse) { - t = f; - f = b; - b = t; - } - - if (fg) - *fg = f; - if (bg) - *bg = b; -} - -/** - * term_utf8_decode() - Try decoding the next UCS-4 character - * @p: decoder object to operate on or NULL - * @out_len: output storage for pointer to decoded UCS-4 string or NULL - * @c: next char to push into decoder - * - * This decodes a UTF-8 stream. It must be called for each input-byte of the - * UTF-8 stream and returns a UCS-4 stream. A pointer to the parsed UCS-4 - * string is stored in @out_buf if non-NULL. The length of this string (number - * of parsed UCS4 characters) is returned as result. The string is not - * zero-terminated! Furthermore, the string is only valid until the next - * invocation of this function. It is also bound to the parser state @p and - * must not be freed nor written to by the caller. - * - * This function is highly optimized to work with terminal-emulators. Instead - * of being strict about UTF-8 validity, this tries to perform a fallback to - * ISO-8859-1 in case a wrong series was detected. Therefore, this function - * might return multiple UCS-4 characters by parsing just a single UTF-8 byte. - * - * The parser state @p should be allocated and managed by the caller. There're - * no helpers to do that for you. To initialize it, simply reset it to all - * zero. You can reset or free the object at any point in time. - * - * Returns: Number of parsed UCS4 characters - */ -size_t term_utf8_decode(term_utf8 *p, uint32_t **out_buf, char c) { - static uint32_t ucs4_null = 0; - uint32_t t, *res = NULL; - uint8_t byte; - size_t len = 0; - - if (!p) - goto out; - - byte = c; - - if (!p->valid || p->i_bytes >= p->n_bytes) { - /* - * If the previous sequence was invalid or fully parsed, start - * parsing a fresh new sequence. - */ - - if ((byte & 0xE0) == 0xC0) { - /* start of two byte sequence */ - t = byte & 0x1F; - p->n_bytes = 2; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF0) == 0xE0) { - /* start of three byte sequence */ - t = byte & 0x0F; - p->n_bytes = 3; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF8) == 0xF0) { - /* start of four byte sequence */ - t = byte & 0x07; - p->n_bytes = 4; - p->i_bytes = 1; - p->valid = 1; - } else { - /* Either of: - * - single ASCII 7-bit char - * - out-of-sync continuation byte - * - overlong encoding - * All of them are treated as single byte ISO-8859-1 */ - t = byte; - p->n_bytes = 1; - p->i_bytes = 1; - p->valid = 0; - } - - p->chars[0] = byte; - p->ucs4 = t << (6 * (p->n_bytes - p->i_bytes)); - } else { - /* - * ..otherwise, try to continue the previous sequence.. - */ - - if ((byte & 0xC0) == 0x80) { - /* - * Valid continuation byte. Append to sequence and - * update the ucs4 cache accordingly. - */ - - t = byte & 0x3F; - p->chars[p->i_bytes++] = byte; - p->ucs4 |= t << (6 * (p->n_bytes - p->i_bytes)); - } else { - /* - * Invalid continuation? Treat cached sequence as - * ISO-8859-1, but parse the new char as valid new - * starting character. If it's a new single-byte UTF-8 - * sequence, we immediately return it in the same run, - * otherwise, we might suffer from starvation. - */ - - if ((byte & 0xE0) == 0xC0 || - (byte & 0xF0) == 0xE0 || - (byte & 0xF8) == 0xF0) { - /* - * New multi-byte sequence. Move to-be-returned - * data at the end and start new sequence. Only - * return the old sequence. - */ - - memmove(p->chars + 1, - p->chars, - sizeof(*p->chars) * p->i_bytes); - res = p->chars + 1; - len = p->i_bytes; - - if ((byte & 0xE0) == 0xC0) { - /* start of two byte sequence */ - t = byte & 0x1F; - p->n_bytes = 2; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF0) == 0xE0) { - /* start of three byte sequence */ - t = byte & 0x0F; - p->n_bytes = 3; - p->i_bytes = 1; - p->valid = 1; - } else if ((byte & 0xF8) == 0xF0) { - /* start of four byte sequence */ - t = byte & 0x07; - p->n_bytes = 4; - p->i_bytes = 1; - p->valid = 1; - } else - assert_not_reached("Should not happen"); - - p->chars[0] = byte; - p->ucs4 = t << (6 * (p->n_bytes - p->i_bytes)); - - goto out; - } else { - /* - * New single byte sequence, append to output - * and return combined sequence. - */ - - p->chars[p->i_bytes++] = byte; - p->valid = 0; - } - } - } - - /* - * Check whether a full sequence (valid or invalid) has been parsed and - * then return it. Otherwise, return nothing. - */ - if (p->valid) { - /* still parsing? then bail out */ - if (p->i_bytes < p->n_bytes) - goto out; - - res = &p->ucs4; - len = 1; - } else { - res = p->chars; - len = p->i_bytes; - } - - p->valid = 0; - p->i_bytes = 0; - p->n_bytes = 0; - -out: - if (out_buf) - *out_buf = res ? : &ucs4_null; - return len; -} - -/* - * Command Parser - * The ctl-seq parser "term_parser" only detects whole sequences, it does not - * detect the specific command. Once a sequence is parsed, the command-parsers - * are used to figure out their meaning. Note that this depends on whether we - * run on the host or terminal side. - */ - -static unsigned int term_parse_host_control(const term_seq *seq) { - assert_return(seq, TERM_CMD_NONE); - - switch (seq->terminator) { - case 0x00: /* NUL */ - return TERM_CMD_NULL; - case 0x05: /* ENQ */ - return TERM_CMD_ENQ; - case 0x07: /* BEL */ - return TERM_CMD_BEL; - case 0x08: /* BS */ - return TERM_CMD_BS; - case 0x09: /* HT */ - return TERM_CMD_HT; - case 0x0a: /* LF */ - return TERM_CMD_LF; - case 0x0b: /* VT */ - return TERM_CMD_VT; - case 0x0c: /* FF */ - return TERM_CMD_FF; - case 0x0d: /* CR */ - return TERM_CMD_CR; - case 0x0e: /* SO */ - return TERM_CMD_SO; - case 0x0f: /* SI */ - return TERM_CMD_SI; - case 0x11: /* DC1 */ - return TERM_CMD_DC1; - case 0x13: /* DC3 */ - return TERM_CMD_DC3; - case 0x18: /* CAN */ - /* this is already handled by the state-machine */ - break; - case 0x1a: /* SUB */ - return TERM_CMD_SUB; - case 0x1b: /* ESC */ - /* this is already handled by the state-machine */ - break; - case 0x1f: /* DEL */ - /* this is already handled by the state-machine */ - break; - case 0x84: /* IND */ - return TERM_CMD_IND; - case 0x85: /* NEL */ - return TERM_CMD_NEL; - case 0x88: /* HTS */ - return TERM_CMD_HTS; - case 0x8d: /* RI */ - return TERM_CMD_RI; - case 0x8e: /* SS2 */ - return TERM_CMD_SS2; - case 0x8f: /* SS3 */ - return TERM_CMD_SS3; - case 0x90: /* DCS */ - /* this is already handled by the state-machine */ - break; - case 0x96: /* SPA */ - return TERM_CMD_SPA; - case 0x97: /* EPA */ - return TERM_CMD_EPA; - case 0x98: /* SOS */ - /* this is already handled by the state-machine */ - break; - case 0x9a: /* DECID */ - return TERM_CMD_DECID; - case 0x9b: /* CSI */ - /* this is already handled by the state-machine */ - break; - case 0x9c: /* ST */ - return TERM_CMD_ST; - case 0x9d: /* OSC */ - /* this is already handled by the state-machine */ - break; - case 0x9e: /* PM */ - /* this is already handled by the state-machine */ - break; - case 0x9f: /* APC */ - /* this is already handled by the state-machine */ - break; - } - - return TERM_CMD_NONE; -} - -static inline int charset_from_cmd(uint32_t raw, unsigned int flags, bool require_96) { - static const struct { - uint32_t raw; - unsigned int flags; - } charset_cmds[] = { - /* 96-compat charsets */ - [TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL] = { .raw = 'A', .flags = 0 }, - [TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL] = { .raw = 'B', .flags = 0 }, - [TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL] = { .raw = 'M', .flags = 0 }, - [TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL] = { .raw = 'F', .flags = 0 }, - [TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL] = { .raw = 'H', .flags = 0 }, - [TERM_CHARSET_ISO_LATIN_CYRILLIC] = { .raw = 'L', .flags = 0 }, - - /* 94-compat charsets */ - [TERM_CHARSET_DEC_SPECIAL_GRAPHIC] = { .raw = '0', .flags = 0 }, - [TERM_CHARSET_DEC_SUPPLEMENTAL] = { .raw = '5', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_DEC_TECHNICAL] = { .raw = '>', .flags = 0 }, - [TERM_CHARSET_CYRILLIC_DEC] = { .raw = '4', .flags = TERM_SEQ_FLAG_AND }, - [TERM_CHARSET_DUTCH_NRCS] = { .raw = '4', .flags = 0 }, - [TERM_CHARSET_FINNISH_NRCS] = { .raw = '5', .flags = 0 }, - [TERM_CHARSET_FRENCH_NRCS] = { .raw = 'R', .flags = 0 }, - [TERM_CHARSET_FRENCH_CANADIAN_NRCS] = { .raw = '9', .flags = 0 }, - [TERM_CHARSET_GERMAN_NRCS] = { .raw = 'K', .flags = 0 }, - [TERM_CHARSET_GREEK_DEC] = { .raw = '?', .flags = TERM_SEQ_FLAG_DQUOTE }, - [TERM_CHARSET_GREEK_NRCS] = { .raw = '>', .flags = TERM_SEQ_FLAG_DQUOTE }, - [TERM_CHARSET_HEBREW_DEC] = { .raw = '4', .flags = TERM_SEQ_FLAG_DQUOTE }, - [TERM_CHARSET_HEBREW_NRCS] = { .raw = '=', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_ITALIAN_NRCS] = { .raw = 'Y', .flags = 0 }, - [TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = '`', .flags = 0 }, - [TERM_CHARSET_PORTUGUESE_NRCS] = { .raw = '6', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_RUSSIAN_NRCS] = { .raw = '5', .flags = TERM_SEQ_FLAG_AND }, - [TERM_CHARSET_SCS_NRCS] = { .raw = '3', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_SPANISH_NRCS] = { .raw = 'Z', .flags = 0 }, - [TERM_CHARSET_SWEDISH_NRCS] = { .raw = '7', .flags = 0 }, - [TERM_CHARSET_SWISS_NRCS] = { .raw = '=', .flags = 0 }, - [TERM_CHARSET_TURKISH_DEC] = { .raw = '0', .flags = TERM_SEQ_FLAG_PERCENT }, - [TERM_CHARSET_TURKISH_NRCS] = { .raw = '2', .flags = TERM_SEQ_FLAG_PERCENT }, - - /* special charsets */ - [TERM_CHARSET_USERPREF_SUPPLEMENTAL] = { .raw = '<', .flags = 0 }, - - /* secondary choices */ - [TERM_CHARSET_CNT + TERM_CHARSET_FINNISH_NRCS] = { .raw = 'C', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_FRENCH_NRCS] = { .raw = 'f', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_FRENCH_CANADIAN_NRCS] = { .raw = 'Q', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = 'E', .flags = 0 }, - [TERM_CHARSET_CNT + TERM_CHARSET_SWEDISH_NRCS] = { .raw = 'H', .flags = 0 }, /* unused; conflicts with ISO_HEBREW */ - - /* tertiary choices */ - [TERM_CHARSET_CNT + TERM_CHARSET_CNT + TERM_CHARSET_NORWEGIAN_DANISH_NRCS] = { .raw = '6', .flags = 0 }, - }; - size_t i, cs; - - /* - * Secondary choice on SWEDISH_NRCS and primary choice on - * ISO_HEBREW_SUPPLEMENTAL have a conflict: raw=="H", flags==0. - * We always choose the ISO 96-compat set, which is what VT510 does. - */ - - for (i = 0; i < ELEMENTSOF(charset_cmds); ++i) { - if (charset_cmds[i].raw == raw && charset_cmds[i].flags == flags) { - cs = i; - while (cs >= TERM_CHARSET_CNT) - cs -= TERM_CHARSET_CNT; - - if (!require_96 || cs < TERM_CHARSET_96_CNT || cs >= TERM_CHARSET_94_CNT) - return cs; - } - } - - return -ENOENT; -} - -/* true if exactly one bit in @value is set */ -static inline bool exactly_one_bit_set(unsigned int value) { - return __builtin_popcount(value) == 1; -} - -static unsigned int term_parse_host_escape(const term_seq *seq, unsigned int *cs_out) { - unsigned int t, flags; - int cs; - - assert_return(seq, TERM_CMD_NONE); - - flags = seq->intermediates; - t = TERM_SEQ_FLAG_POPEN | TERM_SEQ_FLAG_PCLOSE | TERM_SEQ_FLAG_MULT | - TERM_SEQ_FLAG_PLUS | TERM_SEQ_FLAG_MINUS | TERM_SEQ_FLAG_DOT | - TERM_SEQ_FLAG_SLASH; - - if (exactly_one_bit_set(flags & t)) { - switch (flags & t) { - case TERM_SEQ_FLAG_POPEN: - case TERM_SEQ_FLAG_PCLOSE: - case TERM_SEQ_FLAG_MULT: - case TERM_SEQ_FLAG_PLUS: - cs = charset_from_cmd(seq->terminator, flags & ~t, false); - break; - case TERM_SEQ_FLAG_MINUS: - case TERM_SEQ_FLAG_DOT: - case TERM_SEQ_FLAG_SLASH: - cs = charset_from_cmd(seq->terminator, flags & ~t, true); - break; - default: - cs = -ENOENT; - break; - } - - if (cs >= 0) { - if (cs_out) - *cs_out = cs; - return TERM_CMD_SCS; - } - - /* looked like a charset-cmd but wasn't; continue */ - } - - switch (seq->terminator) { - case '3': - if (flags == TERM_SEQ_FLAG_HASH) /* DECDHL top-half */ - return TERM_CMD_DECDHL_TH; - break; - case '4': - if (flags == TERM_SEQ_FLAG_HASH) /* DECDHL bottom-half */ - return TERM_CMD_DECDHL_BH; - break; - case '5': - if (flags == TERM_SEQ_FLAG_HASH) /* DECSWL */ - return TERM_CMD_DECSWL; - break; - case '6': - if (flags == 0) /* DECBI */ - return TERM_CMD_DECBI; - else if (flags == TERM_SEQ_FLAG_HASH) /* DECDWL */ - return TERM_CMD_DECDWL; - break; - case '7': - if (flags == 0) /* DECSC */ - return TERM_CMD_DECSC; - break; - case '8': - if (flags == 0) /* DECRC */ - return TERM_CMD_DECRC; - else if (flags == TERM_SEQ_FLAG_HASH) /* DECALN */ - return TERM_CMD_DECALN; - break; - case '9': - if (flags == 0) /* DECFI */ - return TERM_CMD_DECFI; - break; - case '<': - if (flags == 0) /* DECANM */ - return TERM_CMD_DECANM; - break; - case '=': - if (flags == 0) /* DECKPAM */ - return TERM_CMD_DECKPAM; - break; - case '>': - if (flags == 0) /* DECKPNM */ - return TERM_CMD_DECKPNM; - break; - case '@': - if (flags == TERM_SEQ_FLAG_PERCENT) { - /* Select default character set */ - return TERM_CMD_XTERM_SDCS; - } - break; - case 'D': - if (flags == 0) /* IND */ - return TERM_CMD_IND; - break; - case 'E': - if (flags == 0) /* NEL */ - return TERM_CMD_NEL; - break; - case 'F': - if (flags == 0) /* Cursor to lower-left corner of screen */ - return TERM_CMD_XTERM_CLLHP; - else if (flags == TERM_SEQ_FLAG_SPACE) /* S7C1T */ - return TERM_CMD_S7C1T; - break; - case 'G': - if (flags == TERM_SEQ_FLAG_SPACE) { /* S8C1T */ - return TERM_CMD_S8C1T; - } else if (flags == TERM_SEQ_FLAG_PERCENT) { - /* Select UTF-8 character set */ - return TERM_CMD_XTERM_SUCS; - } - break; - case 'H': - if (flags == 0) /* HTS */ - return TERM_CMD_HTS; - break; - case 'L': - if (flags == TERM_SEQ_FLAG_SPACE) { - /* Set ANSI conformance level 1 */ - return TERM_CMD_XTERM_SACL1; - } - break; - case 'M': - if (flags == 0) { /* RI */ - return TERM_CMD_RI; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* Set ANSI conformance level 2 */ - return TERM_CMD_XTERM_SACL2; - } - break; - case 'N': - if (flags == 0) { /* SS2 */ - return TERM_CMD_SS2; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* Set ANSI conformance level 3 */ - return TERM_CMD_XTERM_SACL3; - } - break; - case 'O': - if (flags == 0) /* SS3 */ - return TERM_CMD_SS3; - break; - case 'P': - if (flags == 0) /* DCS: this is already handled by the state-machine */ - return 0; - break; - case 'V': - if (flags == 0) /* SPA */ - return TERM_CMD_SPA; - break; - case 'W': - if (flags == 0) /* EPA */ - return TERM_CMD_EPA; - break; - case 'X': - if (flags == 0) { /* SOS */ - /* this is already handled by the state-machine */ - break; - } - break; - case 'Z': - if (flags == 0) /* DECID */ - return TERM_CMD_DECID; - break; - case '[': - if (flags == 0) { /* CSI */ - /* this is already handled by the state-machine */ - break; - } - break; - case '\\': - if (flags == 0) /* ST */ - return TERM_CMD_ST; - break; - case ']': - if (flags == 0) { /* OSC */ - /* this is already handled by the state-machine */ - break; - } - break; - case '^': - if (flags == 0) { /* PM */ - /* this is already handled by the state-machine */ - break; - } - break; - case '_': - if (flags == 0) { /* APC */ - /* this is already handled by the state-machine */ - break; - } - break; - case 'c': - if (flags == 0) /* RIS */ - return TERM_CMD_RIS; - break; - case 'l': - if (flags == 0) /* Memory lock */ - return TERM_CMD_XTERM_MLHP; - break; - case 'm': - if (flags == 0) /* Memory unlock */ - return TERM_CMD_XTERM_MUHP; - break; - case 'n': - if (flags == 0) /* LS2 */ - return TERM_CMD_LS2; - break; - case 'o': - if (flags == 0) /* LS3 */ - return TERM_CMD_LS3; - break; - case '|': - if (flags == 0) /* LS3R */ - return TERM_CMD_LS3R; - break; - case '}': - if (flags == 0) /* LS2R */ - return TERM_CMD_LS2R; - break; - case '~': - if (flags == 0) /* LS1R */ - return TERM_CMD_LS1R; - break; - } - - return TERM_CMD_NONE; -} - -static unsigned int term_parse_host_csi(const term_seq *seq) { - unsigned int flags; - - assert_return(seq, TERM_CMD_NONE); - - flags = seq->intermediates; - - switch (seq->terminator) { - case 'A': - if (flags == 0) /* CUU */ - return TERM_CMD_CUU; - break; - case 'a': - if (flags == 0) /* HPR */ - return TERM_CMD_HPR; - break; - case 'B': - if (flags == 0) /* CUD */ - return TERM_CMD_CUD; - break; - case 'b': - if (flags == 0) /* REP */ - return TERM_CMD_REP; - break; - case 'C': - if (flags == 0) /* CUF */ - return TERM_CMD_CUF; - break; - case 'c': - if (flags == 0) /* DA1 */ - return TERM_CMD_DA1; - else if (flags == TERM_SEQ_FLAG_GT) /* DA2 */ - return TERM_CMD_DA2; - else if (flags == TERM_SEQ_FLAG_EQUAL) /* DA3 */ - return TERM_CMD_DA3; - break; - case 'D': - if (flags == 0) /* CUB */ - return TERM_CMD_CUB; - break; - case 'd': - if (flags == 0) /* VPA */ - return TERM_CMD_VPA; - break; - case 'E': - if (flags == 0) /* CNL */ - return TERM_CMD_CNL; - break; - case 'e': - if (flags == 0) /* VPR */ - return TERM_CMD_VPR; - break; - case 'F': - if (flags == 0) /* CPL */ - return TERM_CMD_CPL; - break; - case 'f': - if (flags == 0) /* HVP */ - return TERM_CMD_HVP; - break; - case 'G': - if (flags == 0) /* CHA */ - return TERM_CMD_CHA; - break; - case 'g': - if (flags == 0) /* TBC */ - return TERM_CMD_TBC; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECLFKC */ - return TERM_CMD_DECLFKC; - break; - case 'H': - if (flags == 0) /* CUP */ - return TERM_CMD_CUP; - break; - case 'h': - if (flags == 0) /* SM ANSI */ - return TERM_CMD_SM_ANSI; - else if (flags == TERM_SEQ_FLAG_WHAT) /* SM DEC */ - return TERM_CMD_SM_DEC; - break; - case 'I': - if (flags == 0) /* CHT */ - return TERM_CMD_CHT; - break; - case 'i': - if (flags == 0) /* MC ANSI */ - return TERM_CMD_MC_ANSI; - else if (flags == TERM_SEQ_FLAG_WHAT) /* MC DEC */ - return TERM_CMD_MC_DEC; - break; - case 'J': - if (flags == 0) /* ED */ - return TERM_CMD_ED; - else if (flags == TERM_SEQ_FLAG_WHAT) /* DECSED */ - return TERM_CMD_DECSED; - break; - case 'K': - if (flags == 0) /* EL */ - return TERM_CMD_EL; - else if (flags == TERM_SEQ_FLAG_WHAT) /* DECSEL */ - return TERM_CMD_DECSEL; - break; - case 'L': - if (flags == 0) /* IL */ - return TERM_CMD_IL; - break; - case 'l': - if (flags == 0) /* RM ANSI */ - return TERM_CMD_RM_ANSI; - else if (flags == TERM_SEQ_FLAG_WHAT) /* RM DEC */ - return TERM_CMD_RM_DEC; - break; - case 'M': - if (flags == 0) /* DL */ - return TERM_CMD_DL; - break; - case 'm': - if (flags == 0) /* SGR */ - return TERM_CMD_SGR; - else if (flags == TERM_SEQ_FLAG_GT) /* XTERM SMR */ - return TERM_CMD_XTERM_SRV; - break; - case 'n': - if (flags == 0) /* DSR ANSI */ - return TERM_CMD_DSR_ANSI; - else if (flags == TERM_SEQ_FLAG_GT) /* XTERM RMR */ - return TERM_CMD_XTERM_RRV; - else if (flags == TERM_SEQ_FLAG_WHAT) /* DSR DEC */ - return TERM_CMD_DSR_DEC; - break; - case 'P': - if (flags == 0) /* DCH */ - return TERM_CMD_DCH; - else if (flags == TERM_SEQ_FLAG_SPACE) /* PPA */ - return TERM_CMD_PPA; - break; - case 'p': - if (flags == 0) /* DECSSL */ - return TERM_CMD_DECSSL; - else if (flags == TERM_SEQ_FLAG_SPACE) /* DECSSCLS */ - return TERM_CMD_DECSSCLS; - else if (flags == TERM_SEQ_FLAG_BANG) /* DECSTR */ - return TERM_CMD_DECSTR; - else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECSCL */ - return TERM_CMD_DECSCL; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECRQM-ANSI */ - return TERM_CMD_DECRQM_ANSI; - else if (flags == (TERM_SEQ_FLAG_CASH | TERM_SEQ_FLAG_WHAT)) /* DECRQM-DEC */ - return TERM_CMD_DECRQM_DEC; - else if (flags == TERM_SEQ_FLAG_PCLOSE) /* DECSDPT */ - return TERM_CMD_DECSDPT; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSPPCS */ - return TERM_CMD_DECSPPCS; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECSR */ - return TERM_CMD_DECSR; - else if (flags == TERM_SEQ_FLAG_COMMA) /* DECLTOD */ - return TERM_CMD_DECLTOD; - else if (flags == TERM_SEQ_FLAG_GT) /* XTERM SPM */ - return TERM_CMD_XTERM_SPM; - break; - case 'Q': - if (flags == TERM_SEQ_FLAG_SPACE) /* PPR */ - return TERM_CMD_PPR; - break; - case 'q': - if (flags == 0) /* DECLL */ - return TERM_CMD_DECLL; - else if (flags == TERM_SEQ_FLAG_SPACE) /* DECSCUSR */ - return TERM_CMD_DECSCUSR; - else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECSCA */ - return TERM_CMD_DECSCA; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECSDDT */ - return TERM_CMD_DECSDDT; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSRC */ - return TERM_CMD_DECSR; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECELF */ - return TERM_CMD_DECELF; - else if (flags == TERM_SEQ_FLAG_COMMA) /* DECTID */ - return TERM_CMD_DECTID; - break; - case 'R': - if (flags == TERM_SEQ_FLAG_SPACE) /* PPB */ - return TERM_CMD_PPB; - break; - case 'r': - if (flags == 0) { - /* DECSTBM */ - return TERM_CMD_DECSTBM; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* DECSKCV */ - return TERM_CMD_DECSKCV; - } else if (flags == TERM_SEQ_FLAG_CASH) { - /* DECCARA */ - return TERM_CMD_DECCARA; - } else if (flags == TERM_SEQ_FLAG_MULT) { - /* DECSCS */ - return TERM_CMD_DECSCS; - } else if (flags == TERM_SEQ_FLAG_PLUS) { - /* DECSMKR */ - return TERM_CMD_DECSMKR; - } else if (flags == TERM_SEQ_FLAG_WHAT) { - /* - * There's a conflict between DECPCTERM and XTERM-RPM. - * XTERM-RPM takes a single argument, DECPCTERM takes 2. - * Split both up and forward the call to the closer - * match. - */ - if (seq->n_args <= 1) /* XTERM RPM */ - return TERM_CMD_XTERM_RPM; - else if (seq->n_args >= 2) /* DECPCTERM */ - return TERM_CMD_DECPCTERM; - } - break; - case 'S': - if (flags == 0) /* SU */ - return TERM_CMD_SU; - else if (flags == TERM_SEQ_FLAG_WHAT) /* XTERM SGFX */ - return TERM_CMD_XTERM_SGFX; - break; - case 's': - if (flags == 0) { - /* - * There's a conflict between DECSLRM and SC-ANSI which - * cannot be resolved without knowing the state of - * DECLRMM. We leave that decision up to the caller. - */ - return TERM_CMD_DECSLRM_OR_SC; - } else if (flags == TERM_SEQ_FLAG_CASH) { - /* DECSPRTT */ - return TERM_CMD_DECSPRTT; - } else if (flags == TERM_SEQ_FLAG_MULT) { - /* DECSFC */ - return TERM_CMD_DECSFC; - } else if (flags == TERM_SEQ_FLAG_WHAT) { - /* XTERM SPM */ - return TERM_CMD_XTERM_SPM; - } - break; - case 'T': - if (flags == 0) { - /* - * Awesome: There's a conflict between SD and XTERM IHMT - * that we have to resolve by checking the parameter - * count.. XTERM_IHMT needs exactly 5 arguments, SD - * takes 0 or 1. We're conservative here and give both - * a wider range to allow unused arguments (compat...). - */ - if (seq->n_args >= 5) { - /* XTERM IHMT */ - return TERM_CMD_XTERM_IHMT; - } else if (seq->n_args < 5) { - /* SD */ - return TERM_CMD_SD; - } - } else if (flags == TERM_SEQ_FLAG_GT) { - /* XTERM RTM */ - return TERM_CMD_XTERM_RTM; - } - break; - case 't': - if (flags == 0) { - if (seq->n_args > 0 && seq->args[0] < 24) { - /* XTERM WM */ - return TERM_CMD_XTERM_WM; - } else { - /* DECSLPP */ - return TERM_CMD_DECSLPP; - } - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* DECSWBV */ - return TERM_CMD_DECSWBV; - } else if (flags == TERM_SEQ_FLAG_DQUOTE) { - /* DECSRFR */ - return TERM_CMD_DECSRFR; - } else if (flags == TERM_SEQ_FLAG_CASH) { - /* DECRARA */ - return TERM_CMD_DECRARA; - } else if (flags == TERM_SEQ_FLAG_GT) { - /* XTERM STM */ - return TERM_CMD_XTERM_STM; - } - break; - case 'U': - if (flags == 0) /* NP */ - return TERM_CMD_NP; - break; - case 'u': - if (flags == 0) { - /* RC */ - return TERM_CMD_RC; - } else if (flags == TERM_SEQ_FLAG_SPACE) { - /* DECSMBV */ - return TERM_CMD_DECSMBV; - } else if (flags == TERM_SEQ_FLAG_DQUOTE) { - /* DECSTRL */ - return TERM_CMD_DECSTRL; - } else if (flags == TERM_SEQ_FLAG_WHAT) { - /* DECRQUPSS */ - return TERM_CMD_DECRQUPSS; - } else if (seq->args[0] == 1 && flags == TERM_SEQ_FLAG_CASH) { - /* DECRQTSR */ - return TERM_CMD_DECRQTSR; - } else if (flags == TERM_SEQ_FLAG_MULT) { - /* DECSCP */ - return TERM_CMD_DECSCP; - } else if (flags == TERM_SEQ_FLAG_COMMA) { - /* DECRQKT */ - return TERM_CMD_DECRQKT; - } - break; - case 'V': - if (flags == 0) /* PP */ - return TERM_CMD_PP; - break; - case 'v': - if (flags == TERM_SEQ_FLAG_SPACE) /* DECSLCK */ - return TERM_CMD_DECSLCK; - else if (flags == TERM_SEQ_FLAG_DQUOTE) /* DECRQDE */ - return TERM_CMD_DECRQDE; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECCRA */ - return TERM_CMD_DECCRA; - else if (flags == TERM_SEQ_FLAG_COMMA) /* DECRPKT */ - return TERM_CMD_DECRPKT; - break; - case 'W': - if (seq->args[0] == 5 && flags == TERM_SEQ_FLAG_WHAT) { - /* DECST8C */ - return TERM_CMD_DECST8C; - } - break; - case 'w': - if (flags == TERM_SEQ_FLAG_CASH) /* DECRQPSR */ - return TERM_CMD_DECRQPSR; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECEFR */ - return TERM_CMD_DECEFR; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECSPP */ - return TERM_CMD_DECSPP; - break; - case 'X': - if (flags == 0) /* ECH */ - return TERM_CMD_ECH; - break; - case 'x': - if (flags == 0) /* DECREQTPARM */ - return TERM_CMD_DECREQTPARM; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECFRA */ - return TERM_CMD_DECFRA; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSACE */ - return TERM_CMD_DECSACE; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECRQPKFM */ - return TERM_CMD_DECRQPKFM; - break; - case 'y': - if (flags == 0) /* DECTST */ - return TERM_CMD_DECTST; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECRQCRA */ - return TERM_CMD_DECRQCRA; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECPKFMR */ - return TERM_CMD_DECPKFMR; - break; - case 'Z': - if (flags == 0) /* CBT */ - return TERM_CMD_CBT; - break; - case 'z': - if (flags == TERM_SEQ_FLAG_CASH) /* DECERA */ - return TERM_CMD_DECERA; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECELR */ - return TERM_CMD_DECELR; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECINVM */ - return TERM_CMD_DECINVM; - else if (flags == TERM_SEQ_FLAG_PLUS) /* DECPKA */ - return TERM_CMD_DECPKA; - break; - case '@': - if (flags == 0) /* ICH */ - return TERM_CMD_ICH; - break; - case '`': - if (flags == 0) /* HPA */ - return TERM_CMD_HPA; - break; - case '{': - if (flags == TERM_SEQ_FLAG_CASH) /* DECSERA */ - return TERM_CMD_DECSERA; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECSLE */ - return TERM_CMD_DECSLE; - break; - case '|': - if (flags == TERM_SEQ_FLAG_CASH) /* DECSCPP */ - return TERM_CMD_DECSCPP; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECRQLP */ - return TERM_CMD_DECRQLP; - else if (flags == TERM_SEQ_FLAG_MULT) /* DECSNLS */ - return TERM_CMD_DECSNLS; - break; - case '}': - if (flags == TERM_SEQ_FLAG_SPACE) /* DECKBD */ - return TERM_CMD_DECKBD; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECSASD */ - return TERM_CMD_DECSASD; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECIC */ - return TERM_CMD_DECIC; - break; - case '~': - if (flags == TERM_SEQ_FLAG_SPACE) /* DECTME */ - return TERM_CMD_DECTME; - else if (flags == TERM_SEQ_FLAG_CASH) /* DECSSDT */ - return TERM_CMD_DECSSDT; - else if (flags == TERM_SEQ_FLAG_SQUOTE) /* DECDC */ - return TERM_CMD_DECDC; - break; - } - - return TERM_CMD_NONE; -} - -/* - * State Machine - * This parser controls the parser-state and returns any detected sequence to - * the caller. The parser is based on this state-diagram from Paul Williams: - * http://vt100.net/emu/ - * It was written from scratch and extended where needed. - * This parser is fully compatible up to the vt500 series. We expect UCS-4 as - * input. It's the callers responsibility to do any UTF-8 parsing. - */ - -enum parser_state { - STATE_NONE, /* placeholder */ - STATE_GROUND, /* initial state and ground */ - STATE_ESC, /* ESC sequence was started */ - STATE_ESC_INT, /* intermediate escape characters */ - STATE_CSI_ENTRY, /* starting CSI sequence */ - STATE_CSI_PARAM, /* CSI parameters */ - STATE_CSI_INT, /* intermediate CSI characters */ - STATE_CSI_IGNORE, /* CSI error; ignore this CSI sequence */ - STATE_DCS_ENTRY, /* starting DCS sequence */ - STATE_DCS_PARAM, /* DCS parameters */ - STATE_DCS_INT, /* intermediate DCS characters */ - STATE_DCS_PASS, /* DCS data passthrough */ - STATE_DCS_IGNORE, /* DCS error; ignore this DCS sequence */ - STATE_OSC_STRING, /* parsing OSC sequence */ - STATE_ST_IGNORE, /* unimplemented seq; ignore until ST */ - STATE_NUM -}; - -enum parser_action { - ACTION_NONE, /* placeholder */ - ACTION_CLEAR, /* clear parameters */ - ACTION_IGNORE, /* ignore the character entirely */ - ACTION_PRINT, /* print the character on the console */ - ACTION_EXECUTE, /* execute single control character (C0/C1) */ - ACTION_COLLECT, /* collect intermediate character */ - ACTION_PARAM, /* collect parameter character */ - ACTION_ESC_DISPATCH, /* dispatch escape sequence */ - ACTION_CSI_DISPATCH, /* dispatch csi sequence */ - ACTION_DCS_START, /* start of DCS data */ - ACTION_DCS_COLLECT, /* collect DCS data */ - ACTION_DCS_CONSUME, /* consume DCS terminator */ - ACTION_DCS_DISPATCH, /* dispatch dcs sequence */ - ACTION_OSC_START, /* start of OSC data */ - ACTION_OSC_COLLECT, /* collect OSC data */ - ACTION_OSC_CONSUME, /* consume OSC terminator */ - ACTION_OSC_DISPATCH, /* dispatch osc sequence */ - ACTION_NUM -}; - -int term_parser_new(term_parser **out, bool host) { - _term_parser_free_ term_parser *parser = NULL; - - assert_return(out, -EINVAL); - - parser = new0(term_parser, 1); - if (!parser) - return -ENOMEM; - - parser->is_host = host; - parser->st_alloc = 64; - parser->seq.st = new0(char, parser->st_alloc + 1); - if (!parser->seq.st) - return -ENOMEM; - - *out = parser; - parser = NULL; - return 0; -} - -term_parser *term_parser_free(term_parser *parser) { - if (!parser) - return NULL; - - free(parser->seq.st); - free(parser); - return NULL; -} - -static inline void parser_clear(term_parser *parser) { - unsigned int i; - - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = 0; - parser->seq.intermediates = 0; - parser->seq.charset = TERM_CHARSET_NONE; - parser->seq.n_args = 0; - for (i = 0; i < TERM_PARSER_ARG_MAX; ++i) - parser->seq.args[i] = -1; - - parser->seq.n_st = 0; - parser->seq.st[0] = 0; -} - -static int parser_ignore(term_parser *parser, uint32_t raw) { - parser_clear(parser); - parser->seq.type = TERM_SEQ_IGNORE; - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - - return parser->seq.type; -} - -static int parser_print(term_parser *parser, uint32_t raw) { - parser_clear(parser); - parser->seq.type = TERM_SEQ_GRAPHIC; - parser->seq.command = TERM_CMD_GRAPHIC; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - - return parser->seq.type; -} - -static int parser_execute(term_parser *parser, uint32_t raw) { - parser_clear(parser); - parser->seq.type = TERM_SEQ_CONTROL; - parser->seq.command = TERM_CMD_GRAPHIC; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - if (!parser->is_host) - parser->seq.command = term_parse_host_control(&parser->seq); - - return parser->seq.type; -} - -static void parser_collect(term_parser *parser, uint32_t raw) { - /* - * Usually, characters from 0x30 to 0x3f are only allowed as leading - * markers (or as part of the parameters), characters from 0x20 to 0x2f - * are only allowed as trailing markers. However, our state-machine - * already verifies those restrictions so we can handle them the same - * way here. Note that we safely allow markers to be specified multiple - * times. - */ - - if (raw >= 0x20 && raw <= 0x3f) - parser->seq.intermediates |= 1 << (raw - 0x20); -} - -static void parser_param(term_parser *parser, uint32_t raw) { - int new; - - if (raw == ';') { - if (parser->seq.n_args < TERM_PARSER_ARG_MAX) - ++parser->seq.n_args; - - return; - } - - if (parser->seq.n_args >= TERM_PARSER_ARG_MAX) - return; - - if (raw >= '0' && raw <= '9') { - new = parser->seq.args[parser->seq.n_args]; - if (new < 0) - new = 0; - new = new * 10 + raw - '0'; - - /* VT510 tells us to clamp all values to [0, 9999], however, it - * also allows commands with values up to 2^15-1. We simply use - * 2^16 as maximum here to be compatible to all commands, but - * avoid overflows in any calculations. */ - if (new > 0xffff) - new = 0xffff; - - parser->seq.args[parser->seq.n_args] = new; - } -} - -static int parser_esc(term_parser *parser, uint32_t raw) { - parser->seq.type = TERM_SEQ_ESCAPE; - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - if (!parser->is_host) - parser->seq.command = term_parse_host_escape(&parser->seq, &parser->seq.charset); - - return parser->seq.type; -} - -static int parser_csi(term_parser *parser, uint32_t raw) { - /* parser->seq is cleared during CSI-ENTER state, thus there's no need - * to clear invalid fields here. */ - - if (parser->seq.n_args < TERM_PARSER_ARG_MAX) { - if (parser->seq.n_args > 0 || - parser->seq.args[parser->seq.n_args] >= 0) - ++parser->seq.n_args; - } - - parser->seq.type = TERM_SEQ_CSI; - parser->seq.command = TERM_CMD_NONE; - parser->seq.terminator = raw; - parser->seq.charset = TERM_CHARSET_NONE; - if (!parser->is_host) - parser->seq.command = term_parse_host_csi(&parser->seq); - - return parser->seq.type; -} - -/* perform state transition and dispatch related actions */ -static int parser_transition(term_parser *parser, uint32_t raw, unsigned int state, unsigned int action) { - if (state != STATE_NONE) - parser->state = state; - - switch (action) { - case ACTION_NONE: - return TERM_SEQ_NONE; - case ACTION_CLEAR: - parser_clear(parser); - return TERM_SEQ_NONE; - case ACTION_IGNORE: - return parser_ignore(parser, raw); - case ACTION_PRINT: - return parser_print(parser, raw); - case ACTION_EXECUTE: - return parser_execute(parser, raw); - case ACTION_COLLECT: - parser_collect(parser, raw); - return TERM_SEQ_NONE; - case ACTION_PARAM: - parser_param(parser, raw); - return TERM_SEQ_NONE; - case ACTION_ESC_DISPATCH: - return parser_esc(parser, raw); - case ACTION_CSI_DISPATCH: - return parser_csi(parser, raw); - case ACTION_DCS_START: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_DCS_COLLECT: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_DCS_CONSUME: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_DCS_DISPATCH: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_START: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_COLLECT: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_CONSUME: - /* not implemented */ - return TERM_SEQ_NONE; - case ACTION_OSC_DISPATCH: - /* not implemented */ - return TERM_SEQ_NONE; - default: - assert_not_reached("invalid vte-parser action"); - return TERM_SEQ_NONE; - } -} - -static int parser_feed_to_state(term_parser *parser, uint32_t raw) { - switch (parser->state) { - case STATE_NONE: - /* - * During initialization, parser->state is cleared. Treat this - * as STATE_GROUND. We will then never get to STATE_NONE again. - */ - case STATE_GROUND: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - case 0x80 ... 0x9b: /* C1 \ { ST } */ - case 0x9d ... 0x9f: - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_PRINT); - case STATE_ESC: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_ESC_INT, ACTION_COLLECT); - case 0x30 ... 0x4f: /* ['0' - '~'] \ { 'P', 'X', '[', ']', '^', '_' } */ - case 0x51 ... 0x57: - case 0x59 ... 0x5a: - case 0x5c: - case 0x60 ... 0x7e: - return parser_transition(parser, raw, STATE_GROUND, ACTION_ESC_DISPATCH); - case 0x50: /* 'P' */ - return parser_transition(parser, raw, STATE_DCS_ENTRY, ACTION_CLEAR); - case 0x5b: /* '[' */ - return parser_transition(parser, raw, STATE_CSI_ENTRY, ACTION_CLEAR); - case 0x5d: /* ']' */ - return parser_transition(parser, raw, STATE_OSC_STRING, ACTION_CLEAR); - case 0x58: /* 'X' */ - case 0x5e: /* '^' */ - case 0x5f: /* '_' */ - return parser_transition(parser, raw, STATE_ST_IGNORE, ACTION_NONE); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_ESC_INT, ACTION_COLLECT); - case STATE_ESC_INT: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case 0x30 ... 0x7e: /* ['0' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_ESC_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case STATE_CSI_ENTRY: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_CSI_INT, ACTION_COLLECT); - case 0x3a: /* ':' */ - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_CSI_PARAM, ACTION_PARAM); - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_CSI_PARAM, ACTION_COLLECT); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case STATE_CSI_PARAM: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_CSI_INT, ACTION_COLLECT); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_NONE, ACTION_PARAM); - case 0x3a: /* ':' */ - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case STATE_CSI_INT: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case 0x30 ... 0x3f: /* ['0' - '?'] */ - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_CSI_DISPATCH); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_CSI_IGNORE, ACTION_NONE); - case STATE_CSI_IGNORE: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_EXECUTE); - case 0x20 ... 0x3f: /* [' ' - '?'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_NONE); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - case STATE_DCS_ENTRY: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_DCS_INT, ACTION_COLLECT); - case 0x3a: /* ':' */ - return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_DCS_PARAM, ACTION_PARAM); - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_DCS_PARAM, ACTION_COLLECT); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case STATE_DCS_PARAM: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_DCS_INT, ACTION_COLLECT); - case 0x30 ... 0x39: /* ['0' - '9'] */ - case 0x3b: /* ';' */ - return parser_transition(parser, raw, STATE_NONE, ACTION_PARAM); - case 0x3a: /* ':' */ - case 0x3c ... 0x3f: /* ['<' - '?'] */ - return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case STATE_DCS_INT: - switch (raw) { - case 0x00 ... 0x1f: /* C0 */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x2f: /* [' ' - '\'] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_COLLECT); - case 0x30 ... 0x3f: /* ['0' - '?'] */ - return parser_transition(parser, raw, STATE_DCS_IGNORE, ACTION_NONE); - case 0x40 ... 0x7e: /* ['@' - '~'] */ - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_DCS_PASS, ACTION_DCS_CONSUME); - case STATE_DCS_PASS: - switch (raw) { - case 0x00 ... 0x7e: /* ASCII \ { DEL } */ - return parser_transition(parser, raw, STATE_NONE, ACTION_DCS_COLLECT); - case 0x7f: /* DEL */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_DCS_DISPATCH); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_DCS_COLLECT); - case STATE_DCS_IGNORE: - switch (raw) { - case 0x00 ... 0x7f: /* ASCII */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_NONE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - case STATE_OSC_STRING: - switch (raw) { - case 0x00 ... 0x06: /* C0 \ { BEL } */ - case 0x08 ... 0x1f: - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x20 ... 0x7f: /* [' ' - DEL] */ - return parser_transition(parser, raw, STATE_NONE, ACTION_OSC_COLLECT); - case 0x07: /* BEL */ - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_OSC_DISPATCH); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_OSC_COLLECT); - case STATE_ST_IGNORE: - switch (raw) { - case 0x00 ... 0x7f: /* ASCII */ - return parser_transition(parser, raw, STATE_NONE, ACTION_IGNORE); - case 0x9c: /* ST */ - return parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - } - - return parser_transition(parser, raw, STATE_NONE, ACTION_NONE); - } - - assert_not_reached("bad vte-parser state"); - return -EINVAL; -} - -int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw) { - int r; - - assert_return(parser, -EINVAL); - assert_return(seq_out, -EINVAL); - - /* - * Notes: - * * DEC treats GR codes as GL. We don't do that as we require UTF-8 - * as charset and, thus, it doesn't make sense to treat GR special. - * * During control sequences, unexpected C1 codes cancel the sequence - * and immediately start a new one. C0 codes, however, may or may not - * be ignored/executed depending on the sequence. - */ - - switch (raw) { - case 0x18: /* CAN */ - r = parser_transition(parser, raw, STATE_GROUND, ACTION_IGNORE); - break; - case 0x1a: /* SUB */ - r = parser_transition(parser, raw, STATE_GROUND, ACTION_EXECUTE); - break; - case 0x80 ... 0x8f: /* C1 \ {DCS, SOS, CSI, ST, OSC, PM, APC} */ - case 0x91 ... 0x97: - case 0x99 ... 0x9a: - r = parser_transition(parser, raw, STATE_GROUND, ACTION_EXECUTE); - break; - case 0x1b: /* ESC */ - r = parser_transition(parser, raw, STATE_ESC, ACTION_CLEAR); - break; - case 0x98: /* SOS */ - case 0x9e: /* PM */ - case 0x9f: /* APC */ - r = parser_transition(parser, raw, STATE_ST_IGNORE, ACTION_NONE); - break; - case 0x90: /* DCS */ - r = parser_transition(parser, raw, STATE_DCS_ENTRY, ACTION_CLEAR); - break; - case 0x9d: /* OSC */ - r = parser_transition(parser, raw, STATE_OSC_STRING, ACTION_CLEAR); - break; - case 0x9b: /* CSI */ - r = parser_transition(parser, raw, STATE_CSI_ENTRY, ACTION_CLEAR); - break; - default: - r = parser_feed_to_state(parser, raw); - break; - } - - if (r <= 0) - *seq_out = NULL; - else - *seq_out = &parser->seq; - - return r; -} diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c deleted file mode 100644 index 0e38ff41c6..0000000000 --- a/src/libsystemd-terminal/term-screen.c +++ /dev/null @@ -1,4333 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Terminal Screens - * The term_screen layer implements the terminal-side. It handles all commands - * returned by the seq-parser and applies them to its own pages. - * - * While there are a lot of legacy control-sequences, we only support a small - * subset. There is no reason to implement unused codes like horizontal - * scrolling. - * If you implement new commands, make sure to document them properly. - * - * Standards: - * ECMA-48 - * ANSI X3.64 - * ISO/IEC 6429 - * References: - * http://www.vt100.net/emu/ctrlseq_dec.html - * http://www.vt100.net/docs/vt100-ug/chapter3.html - * http://www.vt100.net/docs/vt510-rm/chapter4 - * http://www.vt100.net/docs/vt510-rm/contents - * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html - * ASCII - * http://en.wikipedia.org/wiki/C0_and_C1_control_codes - * https://en.wikipedia.org/wiki/ANSI_color - */ - -#include -#include -#include -#include -#include "macro.h" -#include "term-internal.h" -#include "util.h" -#include "utf8.h" - -int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data) { - _cleanup_(term_screen_unrefp) term_screen *screen = NULL; - int r; - - assert_return(out, -EINVAL); - - screen = new0(term_screen, 1); - if (!screen) - return -ENOMEM; - - screen->ref = 1; - screen->age = 1; - screen->write_fn = write_fn; - screen->write_fn_data = write_fn_data; - screen->cmd_fn = cmd_fn; - screen->cmd_fn_data = cmd_fn_data; - screen->flags = TERM_FLAG_7BIT_MODE; - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; - screen->g0 = &term_unicode_lower; - screen->g1 = &term_unicode_upper; - screen->g2 = &term_unicode_lower; - screen->g3 = &term_unicode_upper; - screen->state.gl = &screen->g0; - screen->state.gr = &screen->g1; - screen->saved = screen->state; - screen->saved_alt = screen->saved; - - r = term_page_new(&screen->page_main); - if (r < 0) - return r; - - r = term_page_new(&screen->page_alt); - if (r < 0) - return r; - - r = term_parser_new(&screen->parser, false); - if (r < 0) - return r; - - r = term_history_new(&screen->history_main); - if (r < 0) - return r; - - screen->page = screen->page_main; - screen->history = screen->history_main; - - *out = screen; - screen = NULL; - return 0; -} - -term_screen *term_screen_ref(term_screen *screen) { - if (!screen) - return NULL; - - assert_return(screen->ref > 0, NULL); - - ++screen->ref; - return screen; -} - -term_screen *term_screen_unref(term_screen *screen) { - if (!screen) - return NULL; - - assert_return(screen->ref > 0, NULL); - - if (--screen->ref) - return NULL; - - free(screen->answerback); - free(screen->tabs); - term_history_free(screen->history_main); - term_page_free(screen->page_alt); - term_page_free(screen->page_main); - term_parser_free(screen->parser); - free(screen); - - return NULL; -} - -/* - * Write-Helpers - * Unfortunately, 7bit/8bit compat mode requires us to send C1 controls encoded - * as 7bit if asked by the application. This is really used in the wild, so we - * cannot fall back to "always 7bit". - * screen_write() is the underlying backend which forwards any writes to the - * users's callback. It's the users responsibility to buffer these and write - * them out once their call to term_screen_feed_*() returns. - * The SEQ_WRITE() and SEQ_WRITE_KEY() macros allow constructing C0/C1 sequences - * directly in the code-base without requiring any intermediate buffer during - * runtime. - */ - -#define C0_CSI "\e[" -#define C1_CSI "\x9b" - -#define SEQ(_screen, _prefix_esc, _c0, _c1, _seq) \ - (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \ - ((_prefix_esc) ? ("\e" _c0 _seq) : (_c0 _seq)) : \ - ((_prefix_esc) ? ("\e" _c1 _seq) : (_c1 _seq))) - -#define SEQ_SIZE(_screen, _prefix_esc, _c0, _c1, _seq) \ - (((_screen)->flags & TERM_FLAG_7BIT_MODE) ? \ - ((_prefix_esc) ? sizeof("\e" _c0 _seq) : sizeof(_c0 _seq)) : \ - ((_prefix_esc) ? sizeof("\e" _c1 _seq) : sizeof(_c1 _seq))) - -#define SEQ_WRITE_KEY(_screen, _prefix_esc, _c0, _c1, _seq) \ - screen_write((_screen), \ - SEQ((_screen), (_prefix_esc), \ - _c0, _c1, _seq), \ - SEQ_SIZE((_screen), (_prefix_esc), \ - _c0, _c1, _seq) - 1) - -#define SEQ_WRITE(_screen, _c0, _c1, _seq) \ - SEQ_WRITE_KEY((_screen), false, _c0, _c1, _seq) - -static int screen_write(term_screen *screen, const void *buf, size_t len) { - if (len < 1 || !screen->write_fn) - return 0; - - return screen->write_fn(screen, screen->write_fn_data, buf, len); -} - -/* - * Command Forwarding - * Some commands cannot be handled by the screen-layer directly. Those are - * forwarded to the command-handler of the caller. This is rarely used and can - * safely be set to NULL. - */ - -static int screen_forward(term_screen *screen, unsigned int cmd, const term_seq *seq) { - if (!screen->cmd_fn) - return 0; - - return screen->cmd_fn(screen, screen->cmd_fn_data, cmd, seq); -} - -/* - * Screen Helpers - * These helpers implement common-operations like cursor-handler and more, which - * are used by several command dispatchers. - */ - -static unsigned int screen_clamp_x(term_screen *screen, unsigned int x) { - if (x >= screen->page->width) - return (screen->page->width > 0) ? screen->page->width - 1 : 0; - - return x; -} - -static unsigned int screen_clamp_y(term_screen *screen, unsigned int y) { - if (y >= screen->page->height) - return (screen->page->height > 0) ? screen->page->height - 1 : 0; - - return y; -} - -static bool screen_tab_is_set(term_screen *screen, unsigned int pos) { - if (pos >= screen->page->width) - return false; - - return screen->tabs[pos / 8] & (1 << (pos % 8)); -} - -static inline void screen_age_cursor(term_screen *screen) { - term_cell *cell; - - cell = term_page_get_cell(screen->page, screen->state.cursor_x, screen->state.cursor_y); - if (cell) - cell->age = screen->age; -} - -static void screen_cursor_clear_wrap(term_screen *screen) { - screen->flags &= ~TERM_FLAG_PENDING_WRAP; -} - -static void screen_cursor_set(term_screen *screen, unsigned int x, unsigned int y) { - x = screen_clamp_x(screen, x); - y = screen_clamp_y(screen, y); - - if (x == screen->state.cursor_x && y == screen->state.cursor_y) - return; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - - screen->state.cursor_x = x; - screen->state.cursor_y = y; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); -} - -static void screen_cursor_set_rel(term_screen *screen, unsigned int x, unsigned int y) { - if (screen->state.origin_mode) { - x = screen_clamp_x(screen, x); - y = screen_clamp_x(screen, y) + screen->page->scroll_idx; - - if (y >= screen->page->scroll_idx + screen->page->scroll_num) { - y = screen->page->scroll_idx + screen->page->scroll_num; - if (screen->page->scroll_num > 0) - y -= 1; - } - } - - screen_cursor_set(screen, x, y); -} - -static void screen_cursor_left(term_screen *screen, unsigned int num) { - if (num > screen->state.cursor_x) - num = screen->state.cursor_x; - - screen_cursor_set(screen, screen->state.cursor_x - num, screen->state.cursor_y); -} - -static void screen_cursor_left_tab(term_screen *screen, unsigned int num) { - unsigned int i; - - i = screen->state.cursor_x; - while (i > 0 && num > 0) { - if (screen_tab_is_set(screen, --i)) - --num; - } - - screen_cursor_set(screen, i, screen->state.cursor_y); -} - -static void screen_cursor_right(term_screen *screen, unsigned int num) { - if (num > screen->page->width) - num = screen->page->width; - - screen_cursor_set(screen, screen->state.cursor_x + num, screen->state.cursor_y); -} - -static void screen_cursor_right_tab(term_screen *screen, unsigned int num) { - unsigned int i; - - i = screen->state.cursor_x; - while (i + 1 < screen->page->width && num > 0) { - if (screen_tab_is_set(screen, ++i)) - --num; - } - - screen_cursor_set(screen, i, screen->state.cursor_y); -} - -static void screen_cursor_up(term_screen *screen, unsigned int num, bool scroll) { - unsigned int max; - - if (screen->state.cursor_y < screen->page->scroll_idx) { - if (num > screen->state.cursor_y) - num = screen->state.cursor_y; - - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y - num); - } else { - max = screen->state.cursor_y - screen->page->scroll_idx; - if (num > max) { - if (num < 1) - return; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - - if (scroll) - term_page_scroll_down(screen->page, num - max, &screen->state.attr, screen->age, NULL); - - screen->state.cursor_y = screen->page->scroll_idx; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - } else { - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y - num); - } - } -} - -static void screen_cursor_down(term_screen *screen, unsigned int num, bool scroll) { - unsigned int max; - - if (screen->state.cursor_y >= screen->page->scroll_idx + screen->page->scroll_num) { - if (num > screen->page->height) - num = screen->page->height; - - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y - num); - } else { - max = screen->page->scroll_idx + screen->page->scroll_num - 1 - screen->state.cursor_y; - if (num > max) { - if (num < 1) - return; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - - if (scroll) - term_page_scroll_up(screen->page, num - max, &screen->state.attr, screen->age, screen->history); - - screen->state.cursor_y = screen->page->scroll_idx + screen->page->scroll_num - 1; - - if (!(screen->flags & TERM_FLAG_HIDE_CURSOR)) - screen_age_cursor(screen); - } else { - screen_cursor_set(screen, screen->state.cursor_x, screen->state.cursor_y + num); - } - } -} - -static void screen_save_state(term_screen *screen, term_state *where) { - *where = screen->state; -} - -static void screen_restore_state(term_screen *screen, term_state *from) { - screen_cursor_set(screen, from->cursor_x, from->cursor_y); - screen->state = *from; -} - -static void screen_reset_page(term_screen *screen, term_page *page) { - term_page_set_scroll_region(page, 0, page->height); - term_page_erase(page, 0, 0, page->width, page->height, &screen->state.attr, screen->age, false); -} - -static void screen_change_alt(term_screen *screen, bool set) { - if (set) { - screen->page = screen->page_alt; - screen->history = NULL; - } else { - screen->page = screen->page_main; - screen->history = screen->history_main; - } - - screen->page->age = screen->age; -} - -static inline void set_reset(term_screen *screen, unsigned int flag, bool set) { - if (set) - screen->flags |= flag; - else - screen->flags &= ~flag; -} - -static void screen_mode_change_ansi(term_screen *screen, unsigned mode, bool set) { - switch (mode) { - case 20: - /* - * LNM: line-feed/new-line mode - * TODO - */ - set_reset(screen, TERM_FLAG_NEWLINE_MODE, set); - - break; - default: - log_debug("terminal: failed to %s unknown ANSI mode %u", set ? "set" : "unset", mode); - } -} - -static void screen_mode_change_dec(term_screen *screen, unsigned int mode, bool set) { - switch (mode) { - case 1: - /* - * DECCKM: cursor-keys - * TODO - */ - set_reset(screen, TERM_FLAG_CURSOR_KEYS, set); - - break; - case 6: - /* - * DECOM: origin-mode - * TODO - */ - screen->state.origin_mode = set; - - break; - case 7: - /* - * DECAWN: auto-wrap mode - * TODO - */ - screen->state.auto_wrap = set; - - break; - case 25: - /* - * DECTCEM: text-cursor-enable - * TODO - */ - set_reset(screen, TERM_FLAG_HIDE_CURSOR, !set); - screen_age_cursor(screen); - - break; - case 47: - /* - * XTERM-ASB: alternate-screen-buffer - * This enables/disables the alternate screen-buffer. - * It effectively saves the current page content and - * allows you to restore it when changing to the - * original screen-buffer again. - */ - screen_change_alt(screen, set); - - break; - case 1047: - /* - * XTERM-ASBPE: alternate-screen-buffer-post-erase - * This is the same as XTERM-ASB but erases the - * alternate screen-buffer before switching back to the - * original buffer. Use it to discard any data on the - * alternate screen buffer when done. - */ - if (!set) - screen_reset_page(screen, screen->page_alt); - - screen_change_alt(screen, set); - - break; - case 1048: - /* - * XTERM-ASBCS: alternate-screen-buffer-cursor-state - * This has the same effect as DECSC/DECRC, but uses a - * separate state buffer. It is usually used in - * combination with alternate screen buffers to provide - * stacked state storage. - */ - if (set) - screen_save_state(screen, &screen->saved_alt); - else - screen_restore_state(screen, &screen->saved_alt); - - break; - case 1049: - /* - * XTERM-ASBX: alternate-screen-buffer-extended - * This combines XTERM-ASBPE and XTERM-ASBCS somewhat. - * When enabling, state is saved, alternate screen - * buffer is activated and cleared. - * When disabled, alternate screen buffer is cleared, - * then normal screen buffer is enabled and state is - * restored. - */ - if (set) - screen_save_state(screen, &screen->saved_alt); - - screen_reset_page(screen, screen->page_alt); - screen_change_alt(screen, set); - - if (!set) - screen_restore_state(screen, &screen->saved_alt); - - break; - default: - log_debug("terminal: failed to %s unknown DEC mode %u", set ? "set" : "unset", mode); - } -} - -/* map a character according to current GL and GR maps */ -static uint32_t screen_map(term_screen *screen, uint32_t val) { - uint32_t nval = -1U; - - /* 32 and 127 always map to identity. 160 and 255 map to identity iff a - * 96 character set is loaded into GR. Values above 255 always map to - * identity. */ - switch (val) { - case 33 ... 126: - if (screen->state.glt) { - nval = (**screen->state.glt)[val - 32]; - screen->state.glt = NULL; - } else { - nval = (**screen->state.gl)[val - 32]; - } - break; - case 160 ... 255: - if (screen->state.grt) { - nval = (**screen->state.grt)[val - 160]; - screen->state.grt = NULL; - } else { - nval = (**screen->state.gr)[val - 160]; - } - break; - } - - return (nval == -1U) ? val : nval; -} - -/* - * Command Handlers - * This is the unofficial documentation of all the TERM_CMD_* definitions. Each - * handled command has a separate function with an extensive comment on the - * semantics of the command. - * Note that many semantics are unknown and need to be verified. This is mostly - * about error-handling, though. Applications rarely rely on those features. - */ - -static int screen_DA1(term_screen *screen, const term_seq *seq); -static int screen_LF(term_screen *screen, const term_seq *seq); - -static int screen_GRAPHIC(term_screen *screen, const term_seq *seq) { - term_char_t ch = TERM_CHAR_NULL; - - if (screen->state.cursor_x + 1 == screen->page->width - && screen->flags & TERM_FLAG_PENDING_WRAP - && screen->state.auto_wrap) { - screen_cursor_down(screen, 1, true); - screen_cursor_set(screen, 0, screen->state.cursor_y); - } - - screen_cursor_clear_wrap(screen); - - ch = term_char_merge(ch, screen_map(screen, seq->terminator)); - term_page_write(screen->page, screen->state.cursor_x, screen->state.cursor_y, ch, 1, &screen->state.attr, screen->age, false); - - if (screen->state.cursor_x + 1 == screen->page->width) - screen->flags |= TERM_FLAG_PENDING_WRAP; - else - screen_cursor_right(screen, 1); - - return 0; -} - -static int screen_BEL(term_screen *screen, const term_seq *seq) { - /* - * BEL - sound bell tone - * This command should trigger an acoustic bell. Usually, this is - * forwarded directly to the pcspkr. However, bells have become quite - * uncommon and annoying, so we're not implementing them here. Instead, - * it's one of the commands we forward to the caller. - */ - - return screen_forward(screen, TERM_CMD_BEL, seq); -} - -static int screen_BS(term_screen *screen, const term_seq *seq) { - /* - * BS - backspace - * Move cursor one cell to the left. If already at the left margin, - * nothing happens. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_left(screen, 1); - return 0; -} - -static int screen_CBT(term_screen *screen, const term_seq *seq) { - /* - * CBT - cursor-backward-tabulation - * Move the cursor @args[0] tabs backwards (to the left). The - * current cursor cell, in case it's a tab, is not counted. - * Furthermore, the cursor cannot be moved beyond position 0 and - * it will stop there. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_left_tab(screen, num); - - return 0; -} - -static int screen_CHA(term_screen *screen, const term_seq *seq) { - /* - * CHA - cursor-horizontal-absolute - * Move the cursor to position @args[0] in the current line. The - * cursor cannot be moved beyond the rightmost cell and will stop - * there. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int pos = 1; - - if (seq->args[0] > 0) - pos = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, pos - 1, screen->state.cursor_y); - - return 0; -} - -static int screen_CHT(term_screen *screen, const term_seq *seq) { - /* - * CHT - cursor-horizontal-forward-tabulation - * Move the cursor @args[0] tabs forward (to the right). The - * current cursor cell, in case it's a tab, is not counted. - * Furthermore, the cursor cannot be moved beyond the rightmost cell - * and will stop there. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_right_tab(screen, num); - - return 0; -} - -static int screen_CNL(term_screen *screen, const term_seq *seq) { - /* - * CNL - cursor-next-line - * Move the cursor @args[0] lines down. - * - * TODO: Does this stop at the bottom or cause a scroll-up? - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, num, false); - - return 0; -} - -static int screen_CPL(term_screen *screen, const term_seq *seq) { - /* - * CPL - cursor-preceding-line - * Move the cursor @args[0] lines up. - * - * TODO: Does this stop at the top or cause a scroll-up? - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_up(screen, num, false); - - return 0; -} - -static int screen_CR(term_screen *screen, const term_seq *seq) { - /* - * CR - carriage-return - * Move the cursor to the left margin on the current line. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, 0, screen->state.cursor_y); - - return 0; -} - -static int screen_CUB(term_screen *screen, const term_seq *seq) { - /* - * CUB - cursor-backward - * Move the cursor @args[0] positions to the left. The cursor stops - * at the left-most position. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_left(screen, num); - - return 0; -} - -static int screen_CUD(term_screen *screen, const term_seq *seq) { - /* - * CUD - cursor-down - * Move the cursor @args[0] positions down. The cursor stops at the - * bottom margin. If it was already moved further, it stops at the - * bottom line. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, num, false); - - return 0; -} - -static int screen_CUF(term_screen *screen, const term_seq *seq) { - /* - * CUF -cursor-forward - * Move the cursor @args[0] positions to the right. The cursor stops - * at the right-most position. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_right(screen, num); - - return 0; -} - -static int screen_CUP(term_screen *screen, const term_seq *seq) { - /* - * CUP - cursor-position - * Moves the cursor to position @args[1] x @args[0]. If either is 0, it - * is treated as 1. The positions are subject to the origin-mode and - * clamped to the addressable with/height. - * - * Defaults: - * args[0]: 1 - * args[1]: 1 - */ - - unsigned int x = 1, y = 1; - - if (seq->args[0] > 0) - y = seq->args[0]; - if (seq->args[1] > 0) - x = seq->args[1]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set_rel(screen, x - 1, y - 1); - - return 0; -} - -static int screen_CUU(term_screen *screen, const term_seq *seq) { - /* - * CUU - cursor-up - * Move the cursor @args[0] positions up. The cursor stops at the - * top margin. If it was already moved further, it stops at the - * top line. - * - * Defaults: - * args[0]: 1 - * - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_up(screen, num, false); - - return 0; -} - -static int screen_DA1(term_screen *screen, const term_seq *seq) { - /* - * DA1 - primary-device-attributes - * The primary DA asks for basic terminal features. We simply return - * a hard-coded list of features we implement. - * Note that the primary DA asks for supported features, not currently - * enabled features. - * - * The terminal's answer is: - * ^[ ? 64 ; ARGS c - * The first argument, 64, is fixed and denotes a VT420, the last - * DEC-term that extended this number. - * All following arguments denote supported features. Note - * that at most 15 features can be sent (max CSI args). It is safe to - * send more, but clients might not be able to parse them. This is a - * client's problem and we shouldn't care. There is no other way to - * send those feature lists, so we have to extend them beyond 15 in - * those cases. - * - * Known modes: - * 1: 132 column mode - * The 132 column mode is supported by the terminal. - * 2: printer port - * A priner-port is supported and can be addressed via - * control-codes. - * 3: ReGIS graphics - * Support for ReGIS graphics is available. The ReGIS routines - * provide the "remote graphics instruction set" and allow basic - * vector-rendering. - * 4: sixel - * Support of Sixel graphics is available. This provides access - * to the sixel bitmap routines. - * 6: selective erase - * The terminal supports DECSCA and related selective-erase - * functions. This allows to protect specific cells from being - * erased, if specified. - * 7: soft character set (DRCS) - * TODO: ? - * 8: user-defined keys (UDKs) - * TODO: ? - * 9: national-replacement character sets (NRCS) - * National-replacement character-sets are available. - * 12: Yugoslavian (SCS) - * TODO: ? - * 15: technical character set - * The DEC technical-character-set is available. - * 18: windowing capability - * TODO: ? - * 21: horizontal scrolling - * TODO: ? - * 22: ANSII color - * TODO: ? - * 23: Greek - * TODO: ? - * 24: Turkish - * TODO: ? - * 29: ANSI text locator - * TODO: ? - * 42: ISO Latin-2 character set - * TODO: ? - * 44: PCTerm - * TODO: ? - * 45: soft keymap - * TODO: ? - * 46: ASCII emulation - * TODO: ? - */ - - return SEQ_WRITE(screen, C0_CSI, C1_CSI, "?64;1;6;9;15c"); -} - -static int screen_DA2(term_screen *screen, const term_seq *seq) { - /* - * DA2 - secondary-device-attributes - * The secondary DA asks for the terminal-ID, firmware versions and - * other non-primary attributes. All these values are - * informational-only and should not be used by the host to detect - * terminal features. - * - * The terminal's response is: - * ^[ > 61 ; FIRMWARE ; KEYBOARD c - * whereas 65 is fixed for VT525 terminals, the last terminal-line that - * increased this number. FIRMWARE is the firmware - * version encoded as major/minor (20 == 2.0) and KEYBOARD is 0 for STD - * keyboard and 1 for PC keyboards. - * - * We replace the firmware-version with the systemd-version so clients - * can decode it again. - */ - - return SEQ_WRITE(screen, C0_CSI, C1_CSI, ">65;" PACKAGE_VERSION ";1c"); -} - -static int screen_DA3(term_screen *screen, const term_seq *seq) { - /* - * DA3 - tertiary-device-attributes - * The tertiary DA is used to query the terminal-ID. - * - * The terminal's response is: - * ^P ! | XX AA BB CC ^\ - * whereas all four parameters are hexadecimal-encoded pairs. XX - * denotes the manufacturing site, AA BB CC is the terminal's ID. - */ - - /* we do not support tertiary DAs */ - return 0; -} - -static int screen_DC1(term_screen *screen, const term_seq *seq) { - /* - * DC1 - device-control-1 or XON - * This clears any previous XOFF and resumes terminal-transmission. - */ - - /* we do not support XON */ - return 0; -} - -static int screen_DC3(term_screen *screen, const term_seq *seq) { - /* - * DC3 - device-control-3 or XOFF - * Stops terminal transmission. No further characters are sent until - * an XON is received. - */ - - /* we do not support XOFF */ - return 0; -} - -static int screen_DCH(term_screen *screen, const term_seq *seq) { - /* - * DCH - delete-character - * This deletes @argv[0] characters at the current cursor position. As - * characters are deleted, the remaining characters between the cursor - * and right margin move to the left. Character attributes move with the - * characters. The terminal adds blank spaces with no visual character - * attributes at the right margin. DCH has no effect outside the - * scrolling margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - term_page_delete_cells(screen->page, screen->state.cursor_x, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_DECALN(term_screen *screen, const term_seq *seq) { - /* - * DECALN - screen-alignment-pattern - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECANM(term_screen *screen, const term_seq *seq) { - /* - * DECANM - ansi-mode - * Set the terminal into VT52 compatibility mode. Control sequences - * overlap with regular sequences so we have to detect them early before - * dispatching them. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECBI(term_screen *screen, const term_seq *seq) { - /* - * DECBI - back-index - * This control function moves the cursor backward one column. If the - * cursor is at the left margin, then all screen data within the margin - * moves one column to the right. The column that shifted past the right - * margin is lost. - * DECBI adds a new column at the left margin with no visual attributes. - * DECBI does not affect the margins. If the cursor is beyond the - * left-margin at the left border, then the terminal ignores DECBI. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECCARA(term_screen *screen, const term_seq *seq) { - /* - * DECCARA - change-attributes-in-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECCRA(term_screen *screen, const term_seq *seq) { - /* - * DECCRA - copy-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDC(term_screen *screen, const term_seq *seq) { - /* - * DECDC - delete-column - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDHL_BH(term_screen *screen, const term_seq *seq) { - /* - * DECDHL_BH - double-width-double-height-line: bottom half - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDHL_TH(term_screen *screen, const term_seq *seq) { - /* - * DECDHL_TH - double-width-double-height-line: top half - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECDWL(term_screen *screen, const term_seq *seq) { - /* - * DECDWL - double-width-single-height-line - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECEFR(term_screen *screen, const term_seq *seq) { - /* - * DECEFR - enable-filter-rectangle - * Defines the coordinates of a filter rectangle (top, left, bottom, - * right as @args[0] to @args[3]) and activates it. - * Anytime the locator is detected outside of the filter rectangle, an - * outside rectangle event is generated and the rectangle is disabled. - * Filter rectangles are always treated as "one-shot" events. Any - * parameters that are omitted default to the current locator position. - * If all parameters are omitted, any locator motion will be reported. - * DECELR always cancels any prevous rectangle definition. - * - * The locator is usually associated with the mouse-cursor, but based - * on cells instead of pixels. See DECELR how to initialize and enable - * it. DECELR can also enable pixel-mode instead of cell-mode. - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECELF(term_screen *screen, const term_seq *seq) { - /* - * DECELF - enable-local-functions - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECELR(term_screen *screen, const term_seq *seq) { - /* - * DECELR - enable-locator-reporting - * This changes the locator-reporting mode. @args[0] specifies the mode - * to set, 0 disables locator-reporting, 1 enables it continuously, 2 - * enables it for a single report. @args[1] specifies the - * precision-mode. 0 and 2 set the reporting to cell-precision, 1 sets - * pixel-precision. - * - * Defaults: - * args[0]: 0 - * args[1]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECERA(term_screen *screen, const term_seq *seq) { - /* - * DECERA - erase-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECFI(term_screen *screen, const term_seq *seq) { - /* - * DECFI - forward-index - * This control function moves the cursor forward one column. If the - * cursor is at the right margin, then all screen data within the - * margins moves one column to the left. The column shifted past the - * left margin is lost. - * DECFI adds a new column at the right margin, with no visual - * attributes. DECFI does not affect margins. If the cursor is beyond - * the right margin at the border of the page when the terminal - * receives DECFI, then the terminal ignores DECFI. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECFRA(term_screen *screen, const term_seq *seq) { - /* - * DECFRA - fill-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECIC(term_screen *screen, const term_seq *seq) { - /* - * DECIC - insert-column - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECID(term_screen *screen, const term_seq *seq) { - /* - * DECID - return-terminal-id - * This is an obsolete form of TERM_CMD_DA1. - */ - - return screen_DA1(screen, seq); -} - -static int screen_DECINVM(term_screen *screen, const term_seq *seq) { - /* - * DECINVM - invoke-macro - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECKBD(term_screen *screen, const term_seq *seq) { - /* - * DECKBD - keyboard-language-selection - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECKPAM(term_screen *screen, const term_seq *seq) { - /* - * DECKPAM - keypad-application-mode - * Enables the keypad-application mode. If enabled, the keypad sends - * special characters instead of the printed characters. This way, - * applications can detect whether a numeric key was pressed on the - * top-row or on the keypad. - * Default is keypad-numeric-mode. - */ - - screen->flags |= TERM_FLAG_KEYPAD_MODE; - - return 0; -} - -static int screen_DECKPNM(term_screen *screen, const term_seq *seq) { - /* - * DECKPNM - keypad-numeric-mode - * This disables the keypad-application-mode (DECKPAM) and returns to - * the keypad-numeric-mode. Keypresses on the keypad generate the same - * sequences as corresponding keypresses on the main keyboard. - * Default is keypad-numeric-mode. - */ - - screen->flags &= ~TERM_FLAG_KEYPAD_MODE; - - return 0; -} - -static int screen_DECLFKC(term_screen *screen, const term_seq *seq) { - /* - * DECLFKC - local-function-key-control - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECLL(term_screen *screen, const term_seq *seq) { - /* - * DECLL - load-leds - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECLTOD(term_screen *screen, const term_seq *seq) { - /* - * DECLTOD - load-time-of-day - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECPCTERM(term_screen *screen, const term_seq *seq) { - /* - * DECPCTERM - pcterm-mode - * This enters/exits the PCTerm mode. Default mode is VT-mode. It can - * also select parameters for scancode/keycode mappings in SCO mode. - * - * Definitely not worth implementing. Lets kill PCTerm/SCO modes! - */ - - return 0; -} - -static int screen_DECPKA(term_screen *screen, const term_seq *seq) { - /* - * DECPKA - program-key-action - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECPKFMR(term_screen *screen, const term_seq *seq) { - /* - * DECPKFMR - program-key-free-memory-report - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRARA(term_screen *screen, const term_seq *seq) { - /* - * DECRARA - reverse-attributes-in-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRC(term_screen *screen, const term_seq *seq) { - /* - * DECRC - restore-cursor - * Restores the terminal to the state saved by the save cursor (DECSC) - * function. This includes more than just the cursor-position. - * - * If nothing was saved by DECSC, then DECRC performs the following - * actions: - * * Moves the cursor to the home position (upper left of screen). - * * Resets origin mode (DECOM). - * * Turns all character attributes off (normal setting). - * * Maps the ASCII character set into GL, and the DEC Supplemental - * Graphic set into GR. - * - * The terminal maintains a separate DECSC buffer for the main display - * and the status line. This feature lets you save a separate operating - * state for the main display and the status line. - */ - - screen_restore_state(screen, &screen->saved); - - return 0; -} - -static int screen_DECREQTPARM(term_screen *screen, const term_seq *seq) { - /* - * DECREQTPARM - request-terminal-parameters - * The sequence DECREPTPARM is sent by the terminal controller to notify - * the host of the status of selected terminal parameters. The status - * sequence may be sent when requested by the host or at the terminal's - * discretion. DECREPTPARM is sent upon receipt of a DECREQTPARM. - * - * If @args[0] is 0, this marks a request and the terminal is allowed - * to send DECREPTPARM messages without request. If it is 1, the same - * applies but the terminal should no longer send DECREPTPARM - * unrequested. - * 2 and 3 mark a report, but 3 is only used if the terminal answers as - * an explicit request with @args[0] == 1. - * - * The other arguments are ignored in requests, but have the following - * meaning in responses: - * args[1]: 1=no-parity-set 4=parity-set-and-odd 5=parity-set-and-even - * args[2]: 1=8bits-per-char 2=7bits-per-char - * args[3]: transmission-speed - * args[4]: receive-speed - * args[5]: 1=bit-rate-multiplier-is-16 - * args[6]: This value communicates the four switch values in block 5 - * of SETUP B, which are only visible to the user when an STP - * option is installed. These bits may be assigned for an STP - * device. The four bits are a decimal-encoded binary number. - * Value between 0-15. - * - * The transmission/receive speeds have mappings for number => bits/s - * which are quite weird. Examples are: 96->3600, 112->9600, 120->19200 - * - * Defaults: - * args[0]: 0 - */ - - if (seq->n_args < 1 || seq->args[0] == 0) { - screen->flags &= ~TERM_FLAG_INHIBIT_TPARM; - return SEQ_WRITE(screen, C0_CSI, C1_CSI, "2;1;1;120;120;1;0x"); - } else if (seq->args[0] == 1) { - screen->flags |= TERM_FLAG_INHIBIT_TPARM; - return SEQ_WRITE(screen, C0_CSI, C1_CSI, "3;1;1;120;120;1;0x"); - } else { - return 0; - } -} - -static int screen_DECRPKT(term_screen *screen, const term_seq *seq) { - /* - * DECRPKT - report-key-type - * Response to DECRQKT, we can safely ignore it as we're the one sending - * it to the host. - */ - - return 0; -} - -static int screen_DECRQCRA(term_screen *screen, const term_seq *seq) { - /* - * DECRQCRA - request-checksum-of-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQDE(term_screen *screen, const term_seq *seq) { - /* - * DECRQDE - request-display-extent - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQKT(term_screen *screen, const term_seq *seq) { - /* - * DECRQKT - request-key-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQLP(term_screen *screen, const term_seq *seq) { - /* - * DECRQLP - request-locator-position - * See DECELR for locator-information. - * - * TODO: document and implement - */ - - return 0; -} - -static int screen_DECRQM_ANSI(term_screen *screen, const term_seq *seq) { - /* - * DECRQM_ANSI - request-mode-ansi - * The host sends this control function to find out if a particular mode - * is set or reset. The terminal responds with a report mode function. - * @args[0] contains the mode to query. - * - * Response is DECRPM with the first argument set to the mode that was - * queried, second argument is 0 if mode is invalid, 1 if mode is set, - * 2 if mode is not set (reset), 3 if mode is permanently set and 4 if - * mode is permanently not set (reset): - * ANSI: ^[ MODE ; VALUE $ y - * DEC: ^[ ? MODE ; VALUE $ y - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECRQM_DEC(term_screen *screen, const term_seq *seq) { - /* - * DECRQM_DEC - request-mode-dec - * Same as DECRQM_ANSI but for DEC modes. - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECRQPKFM(term_screen *screen, const term_seq *seq) { - /* - * DECRQPKFM - request-program-key-free-memory - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQPSR(term_screen *screen, const term_seq *seq) { - /* - * DECRQPSR - request-presentation-state-report - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQTSR(term_screen *screen, const term_seq *seq) { - /* - * DECRQTSR - request-terminal-state-report - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECRQUPSS(term_screen *screen, const term_seq *seq) { - /* - * DECRQUPSS - request-user-preferred-supplemental-set - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSACE(term_screen *screen, const term_seq *seq) { - /* - * DECSACE - select-attribute-change-extent - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSASD(term_screen *screen, const term_seq *seq) { - /* - * DECSASD - select-active-status-display - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSC(term_screen *screen, const term_seq *seq) { - /* - * DECSC - save-cursor - * Save cursor and terminal state so it can be restored later on. - * Saves the following items in the terminal's memory: - * * Cursor position - * * Character attributes set by the SGR command - * * Character sets (G0, G1, G2, or G3) currently in GL and GR - * * Wrap flag (autowrap or no autowrap) - * * State of origin mode (DECOM) - * * Selective erase attribute - * * Any single shift 2 (SS2) or single shift 3 (SS3) functions sent - */ - - screen_save_state(screen, &screen->saved); - - return 0; -} - -static int screen_DECSCA(term_screen *screen, const term_seq *seq) { - /* - * DECSCA - select-character-protection-attribute - * Defines the characters that come after it as erasable or not erasable - * from the screen. The selective erase control functions (DECSED and - * DECSEL) can only erase characters defined as erasable. - * - * @args[0] specifies the new mode. 0 and 2 mark any following character - * as erasable, 1 marks it as not erasable. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - case 2: - screen->state.attr.protect = 0; - break; - case 1: - screen->state.attr.protect = 1; - break; - } - - return 0; -} - -static int screen_DECSCL(term_screen *screen, const term_seq *seq) { - /* - * DECSCL - select-conformance-level - * Select the terminal's operating level. The factory default is - * level 4 (VT Level 4 mode, 7-bit controls). - * When you change the conformance level, the terminal performs a hard - * reset (RIS). - * - * @args[0] defines the conformance-level, valid values are: - * 61: Level 1 (VT100) - * 62: Level 2 (VT200) - * 63: Level 3 (VT300) - * 64: Level 4 (VT400) - * @args[1] defines the 8bit-mode, valid values are: - * 0: 8-bit controls - * 1: 7-bit controls - * 2: 8-bit controls (same as 0) - * - * If @args[0] is 61, then @args[1] is ignored and 7bit controls are - * enforced. - * - * Defaults: - * args[0]: 64 - * args[1]: 0 - */ - - unsigned int level = 64, bit = 0; - - if (seq->n_args > 0) { - level = seq->args[0]; - if (seq->n_args > 1) - bit = seq->args[1]; - } - - term_screen_hard_reset(screen); - - switch (level) { - case 61: - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT100; - screen->flags |= TERM_FLAG_7BIT_MODE; - break; - case 62 ... 69: - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; - if (bit == 1) - screen->flags |= TERM_FLAG_7BIT_MODE; - else - screen->flags &= ~TERM_FLAG_7BIT_MODE; - break; - } - - return 0; -} - -static int screen_DECSCP(term_screen *screen, const term_seq *seq) { - /* - * DECSCP - select-communication-port - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSCPP(term_screen *screen, const term_seq *seq) { - /* - * DECSCPP - select-columns-per-page - * Select columns per page. The number of rows is unaffected by this. - * @args[0] selectes the number of columns (width), DEC only defines 80 - * and 132, but we allow any integer here. 0 is equivalent to 80. - * Page content is *not* cleared and the cursor is left untouched. - * However, if the page is reduced in width and the cursor would be - * outside the visible region, it's set to the right border. Newly added - * cells are cleared. No data is retained outside the visible region. - * - * Defaults: - * args[0]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSCS(term_screen *screen, const term_seq *seq) { - /* - * DECSCS - select-communication-speed - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSCUSR(term_screen *screen, const term_seq *seq) { - /* - * DECSCUSR - set-cursor-style - * This changes the style of the cursor. @args[0] can be one of: - * 0, 1: blinking block - * 2: steady block - * 3: blinking underline - * 4: steady underline - * Changing this setting does _not_ affect the cursor visibility itself. - * Use DECTCEM for that. - * - * Defaults: - * args[0]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSDDT(term_screen *screen, const term_seq *seq) { - /* - * DECSDDT - select-disconnect-delay-time - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSDPT(term_screen *screen, const term_seq *seq) { - /* - * DECSDPT - select-digital-printed-data-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSED(term_screen *screen, const term_seq *seq) { - /* - * DECSED - selective-erase-in-display - * This control function erases some or all of the erasable characters - * in the display. DECSED can only erase characters defined as erasable - * by the DECSCA control function. DECSED works inside or outside the - * scrolling margins. - * - * @args[0] defines which regions are erased. If it is 0, all cells from - * the cursor (inclusive) till the end of the display are erase. If it - * is 1, all cells from the start of the display till the cursor - * (inclusive) are erased. If it is 2, all cells are erased. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, true); - break; - case 1: - term_page_erase(screen->page, - 0, 0, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - case 2: - term_page_erase(screen->page, - 0, 0, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, true); - break; - } - - return 0; -} - -static int screen_DECSEL(term_screen *screen, const term_seq *seq) { - /* - * DECSEL - selective-erase-in-line - * This control function erases some or all of the erasable characters - * in a single line of text. DECSEL erases only those characters defined - * as erasable by the DECSCA control function. DECSEL works inside or - * outside the scrolling margins. - * - * @args[0] defines the region to be erased. If it is 0, all cells from - * the cursor (inclusive) till the end of the line are erase. If it is - * 1, all cells from the start of the line till the cursor (inclusive) - * are erased. If it is 2, the whole line of the cursor is erased. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - case 1: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - case 2: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, true); - break; - } - - return 0; -} - -static int screen_DECSERA(term_screen *screen, const term_seq *seq) { - /* - * DECSERA - selective-erase-rectangular-area - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSFC(term_screen *screen, const term_seq *seq) { - /* - * DECSFC - select-flow-control - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSKCV(term_screen *screen, const term_seq *seq) { - /* - * DECSKCV - set-key-click-volume - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSLCK(term_screen *screen, const term_seq *seq) { - /* - * DECSLCK - set-lock-key-style - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSLE(term_screen *screen, const term_seq *seq) { - /* - * DECSLE - select-locator-events - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSLPP(term_screen *screen, const term_seq *seq) { - /* - * DECSLPP - set-lines-per-page - * Set the number of lines used for the page. @args[0] specifies the - * number of lines to be used. DEC only allows a limited number of - * choices, however, we allow all integers. 0 is equivalent to 24. - * - * Defaults: - * args[0]: 0 - * - * TODO: implement - */ - - return 0; -} - -static int screen_DECSLRM_OR_SC(term_screen *screen, const term_seq *seq) { - /* - * DECSLRM_OR_SC - set-left-and-right-margins or save-cursor - * - * TODO: Detect save-cursor and run it. DECSLRM is not worth - * implementing. - */ - - return 0; -} - -static int screen_DECSMBV(term_screen *screen, const term_seq *seq) { - /* - * DECSMBV - set-margin-bell-volume - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSMKR(term_screen *screen, const term_seq *seq) { - /* - * DECSMKR - select-modifier-key-reporting - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSNLS(term_screen *screen, const term_seq *seq) { - /* - * DECSNLS - set-lines-per-screen - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSPP(term_screen *screen, const term_seq *seq) { - /* - * DECSPP - set-port-parameter - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSPPCS(term_screen *screen, const term_seq *seq) { - /* - * DECSPPCS - select-pro-printer-character-set - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSPRTT(term_screen *screen, const term_seq *seq) { - /* - * DECSPRTT - select-printer-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSR(term_screen *screen, const term_seq *seq) { - /* - * DECSR - secure-reset - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSRFR(term_screen *screen, const term_seq *seq) { - /* - * DECSRFR - select-refresh-rate - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSSCLS(term_screen *screen, const term_seq *seq) { - /* - * DECSSCLS - set-scroll-speed - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSSDT(term_screen *screen, const term_seq *seq) { - /* - * DECSSDT - select-status-display-line-type - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSSL(term_screen *screen, const term_seq *seq) { - /* - * DECSSL - select-setup-language - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECST8C(term_screen *screen, const term_seq *seq) { - /* - * DECST8C - set-tab-at-every-8-columns - * Clear the tab-ruler and reset it to a tab at every 8th column, - * starting at 9 (though, setting a tab at 1 is fine as it has no - * effect). - */ - - unsigned int i; - - for (i = 0; i < screen->page->width; i += 8) - screen->tabs[i / 8] = 0x1; - - return 0; -} - -static int screen_DECSTBM(term_screen *screen, const term_seq *seq) { - /* - * DECSTBM - set-top-and-bottom-margins - * This control function sets the top and bottom margins for the current - * page. You cannot perform scrolling outside the margins. - * - * @args[0] defines the top margin, @args[1] defines the bottom margin. - * The bottom margin must be lower than the top-margin. - * - * This call resets the cursor position to 0/0 of the page. - * - * Defaults: - * args[0]: 1 - * args[1]: last page-line - */ - - unsigned int top, bottom; - - top = 1; - bottom = screen->page->height; - - if (seq->args[0] > 0) - top = seq->args[0]; - if (seq->args[1] > 0) - bottom = seq->args[1]; - - if (top > screen->page->height) - top = screen->page->height; - if (bottom > screen->page->height) - bottom = screen->page->height; - - if (top >= bottom || top > screen->page->height || bottom > screen->page->height) { - top = 1; - bottom = screen->page->height; - } - - term_page_set_scroll_region(screen->page, top - 1, bottom - top + 1); - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, 0, 0); - - return 0; -} - -static int screen_DECSTR(term_screen *screen, const term_seq *seq) { - /* - * DECSTR - soft-terminal-reset - * Perform a soft reset to the default values. - */ - - term_screen_soft_reset(screen); - - return 0; -} - -static int screen_DECSTRL(term_screen *screen, const term_seq *seq) { - /* - * DECSTRL - set-transmit-rate-limit - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSWBV(term_screen *screen, const term_seq *seq) { - /* - * DECSWBV - set-warning-bell-volume - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECSWL(term_screen *screen, const term_seq *seq) { - /* - * DECSWL - single-width-single-height-line - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECTID(term_screen *screen, const term_seq *seq) { - /* - * DECTID - select-terminal-id - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECTME(term_screen *screen, const term_seq *seq) { - /* - * DECTME - terminal-mode-emulation - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DECTST(term_screen *screen, const term_seq *seq) { - /* - * DECTST - invoke-confidence-test - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_DL(term_screen *screen, const term_seq *seq) { - /* - * DL - delete-line - * This control function deletes one or more lines in the scrolling - * region, starting with the line that has the cursor. @args[0] defines - * the number of lines to delete. 0 is treated the same as 1. - * As lines are deleted, lines below the cursor and in the scrolling - * region move up. The terminal adds blank lines with no visual - * character attributes at the bottom of the scrolling region. If it is - * greater than the number of lines remaining on the page, DL deletes - * only the remaining lines. DL has no effect outside the scrolling - * margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_delete_lines(screen->page, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_DSR_ANSI(term_screen *screen, const term_seq *seq) { - /* - * DSR_ANSI - device-status-report-ansi - * - * TODO: implement - */ - - return 0; -} - -static int screen_DSR_DEC(term_screen *screen, const term_seq *seq) { - /* - * DSR_DEC - device-status-report-dec - * - * TODO: implement - */ - - return 0; -} - -static int screen_ECH(term_screen *screen, const term_seq *seq) { - /* - * ECH - erase-character - * This control function erases one or more characters, from the cursor - * position to the right. ECH clears character attributes from erased - * character positions. ECH works inside or outside the scrolling - * margins. - * @args[0] defines the number of characters to erase. 0 is treated the - * same as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->state.cursor_x + num, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - - return 0; -} - -static int screen_ED(term_screen *screen, const term_seq *seq) { - /* - * ED - erase-in-display - * This control function erases characters from part or all of the - * display. When you erase complete lines, they become single-height, - * single-width lines, with all visual character attributes cleared. ED - * works inside or outside the scrolling margins. - * - * @args[0] defines the region to erase. 0 means from cursor (inclusive) - * till the end of the screen. 1 means from the start of the screen till - * the cursor (inclusive) and 2 means the whole screen. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, false); - break; - case 1: - term_page_erase(screen->page, - 0, 0, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - case 2: - term_page_erase(screen->page, - 0, 0, - screen->page->width, screen->page->height, - &screen->state.attr, screen->age, false); - break; - } - - return 0; -} - -static int screen_EL(term_screen *screen, const term_seq *seq) { - /* - * EL - erase-in-line - * This control function erases characters on the line that has the - * cursor. EL clears all character attributes from erased character - * positions. EL works inside or outside the scrolling margins. - * - * @args[0] defines the region to erase. 0 means from cursor (inclusive) - * till the end of the line. 1 means from the start of the line till the - * cursor (inclusive) and 2 means the whole line. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - term_page_erase(screen->page, - screen->state.cursor_x, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - case 1: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->state.cursor_x, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - case 2: - term_page_erase(screen->page, - 0, screen->state.cursor_y, - screen->page->width, screen->state.cursor_y, - &screen->state.attr, screen->age, false); - break; - } - - return 0; -} - -static int screen_ENQ(term_screen *screen, const term_seq *seq) { - /* - * ENQ - enquiry - * Transmit the answerback-string. If none is set, do nothing. - */ - - if (screen->answerback) - return screen_write(screen, screen->answerback, strlen(screen->answerback)); - - return 0; -} - -static int screen_EPA(term_screen *screen, const term_seq *seq) { - /* - * EPA - end-of-guarded-area - * - * TODO: What is this? - */ - - return 0; -} - -static int screen_FF(term_screen *screen, const term_seq *seq) { - /* - * FF - form-feed - * This causes the cursor to jump to the next line. It is treated the - * same as LF. - */ - - return screen_LF(screen, seq); -} - -static int screen_HPA(term_screen *screen, const term_seq *seq) { - /* - * HPA - horizontal-position-absolute - * HPA causes the active position to be moved to the n-th horizontal - * position of the active line. If an attempt is made to move the active - * position past the last position on the line, then the active position - * stops at the last position on the line. - * - * @args[0] defines the horizontal position. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set(screen, num - 1, screen->state.cursor_y); - - return 0; -} - -static int screen_HPR(term_screen *screen, const term_seq *seq) { - /* - * HPR - horizontal-position-relative - * HPR causes the active position to be moved to the n-th following - * horizontal position of the active line. If an attempt is made to move - * the active position past the last position on the line, then the - * active position stops at the last position on the line. - * - * @args[0] defines the horizontal position. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_right(screen, num); - - return 0; -} - -static int screen_HT(term_screen *screen, const term_seq *seq) { - /* - * HT - horizontal-tab - * Moves the cursor to the next tab stop. If there are no more tab - * stops, the cursor moves to the right margin. HT does not cause text - * to auto wrap. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_right_tab(screen, 1); - - return 0; -} - -static int screen_HTS(term_screen *screen, const term_seq *seq) { - /* - * HTS - horizontal-tab-set - * HTS sets a horizontal tab stop at the column position indicated by - * the value of the active column when the terminal receives an HTS. - * - * Executing an HTS does not effect the other horizontal tab stop - * settings. - */ - - unsigned int pos; - - pos = screen->state.cursor_x; - if (screen->page->width > 0) - screen->tabs[pos / 8] |= 1U << (pos % 8); - - return 0; -} - -static int screen_HVP(term_screen *screen, const term_seq *seq) { - /* - * HVP - horizontal-and-vertical-position - * This control function works the same as the cursor position (CUP) - * function. Origin mode (DECOM) selects line numbering and the ability - * to move the cursor into margins. - * - * Defaults: - * args[0]: 1 - * args[1]: 1 - */ - - return screen_CUP(screen, seq); -} - -static int screen_ICH(term_screen *screen, const term_seq *seq) { - /* - * ICH - insert-character - * This control function inserts one or more space (SP) characters - * starting at the cursor position. @args[0] is the number of characters - * to insert. 0 is treated as 1. - * - * The ICH sequence inserts blank characters with the normal - * character attribute. The cursor remains at the beginning of the blank - * characters. Text between the cursor and right margin moves to the - * right. Characters scrolled past the right margin are lost. ICH has no - * effect outside the scrolling margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - term_page_insert_cells(screen->page, screen->state.cursor_x, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_IL(term_screen *screen, const term_seq *seq) { - /* - * IL - insert-line - * This control function inserts one or more blank lines, starting at - * the cursor. @args[0] is the number of lines to insert. 0 is treated - * as 1. - * - * As lines are inserted, lines below the cursor and in the scrolling - * region move down. Lines scrolled off the page are lost. IL has no - * effect outside the page margins. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - term_page_insert_lines(screen->page, screen->state.cursor_y, num, &screen->state.attr, screen->age); - - return 0; -} - -static int screen_IND(term_screen *screen, const term_seq *seq) { - /* - * IND - index - * IND moves the cursor down one line in the same column. If the cursor - * is at the bottom margin, then the screen performs a scroll-up. - */ - - screen_cursor_down(screen, 1, true); - - return 0; -} - -static int screen_LF(term_screen *screen, const term_seq *seq) { - /* - * LF - line-feed - * Causes a line feed or a new line operation, depending on the setting - * of line feed/new line mode. - */ - - screen_cursor_down(screen, 1, true); - if (screen->flags & TERM_FLAG_NEWLINE_MODE) - screen_cursor_left(screen, screen->state.cursor_x); - - return 0; -} - -static int screen_LS1R(term_screen *screen, const term_seq *seq) { - /* - * LS1R - locking-shift-1-right - * Map G1 into GR. - */ - - screen->state.gr = &screen->g1; - - return 0; -} - -static int screen_LS2(term_screen *screen, const term_seq *seq) { - /* - * LS2 - locking-shift-2 - * Map G2 into GL. - */ - - screen->state.gl = &screen->g2; - - return 0; -} - -static int screen_LS2R(term_screen *screen, const term_seq *seq) { - /* - * LS2R - locking-shift-2-right - * Map G2 into GR. - */ - - screen->state.gr = &screen->g2; - - return 0; -} - -static int screen_LS3(term_screen *screen, const term_seq *seq) { - /* - * LS3 - locking-shift-3 - * Map G3 into GL. - */ - - screen->state.gl = &screen->g3; - - return 0; -} - -static int screen_LS3R(term_screen *screen, const term_seq *seq) { - /* - * LS3R - locking-shift-3-right - * Map G3 into GR. - */ - - screen->state.gr = &screen->g3; - - return 0; -} - -static int screen_MC_ANSI(term_screen *screen, const term_seq *seq) { - /* - * MC_ANSI - media-copy-ansi - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_MC_DEC(term_screen *screen, const term_seq *seq) { - /* - * MC_DEC - media-copy-dec - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_NEL(term_screen *screen, const term_seq *seq) { - /* - * NEL - next-line - * Moves cursor to first position on next line. If cursor is at bottom - * margin, then screen performs a scroll-up. - */ - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, 1, true); - screen_cursor_set(screen, 0, screen->state.cursor_y); - - return 0; -} - -static int screen_NP(term_screen *screen, const term_seq *seq) { - /* - * NP - next-page - * This control function moves the cursor forward to the home position - * on one of the following pages in page memory. If there is only one - * page, then the terminal ignores NP. - * If NP tries to move the cursor past the last page in memory, then the - * cursor stops at the last page. - * - * @args[0] defines the number of pages to forward. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_NULL(term_screen *screen, const term_seq *seq) { - /* - * NULL - null - * The NULL operation does nothing. ASCII NULL is always ignored. - */ - - return 0; -} - -static int screen_PP(term_screen *screen, const term_seq *seq) { - /* - * PP - preceding-page - * This control function moves the cursor backward to the home position - * on one of the preceding pages in page memory. If there is only one - * page, then the terminal ignores PP. - * If PP tries to move the cursor back farther than the first page in - * memory, then the cursor stops at the first page. - * - * @args[0] defines the number of pages to go backwards. 0 is treated - * as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_PPA(term_screen *screen, const term_seq *seq) { - /* - * PPA - page-position-absolute - * This control function can move the cursor to the corresponding row - * and column on any page in page memory. You select the page by its - * number. If there is only one page, then the terminal ignores PPA. - * - * @args[0] is the number of the page to move the cursor to. If it is - * greater than the number of the last page in memory, then the cursor - * stops at the last page. If it is less than the number of the first - * page, then the cursor stops at the first page. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_PPB(term_screen *screen, const term_seq *seq) { - /* - * PPB - page-position-backward - * This control function moves the cursor backward to the corresponding - * row and column on one of the preceding pages in page memory. If there - * is only one page, then the terminal ignores PPB. - * - * @args[0] indicates the number of pages to move the cursor backward. - * If it tries to move the cursor back farther than the first page in - * memory, then the cursor stops at the first page. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_PPR(term_screen *screen, const term_seq *seq) { - /* - * PPR - page-position-relative - * This control function moves the cursor forward to the corresponding - * row and column on one of the following pages in page memory. If there - * is only one page, then the terminal ignores PPR. - * - * @args[0] indicates how many pages to move the cursor forward. If it - * tries to move the cursor beyond the last page in memory, then the - * cursor stops at the last page. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. We only support a single page. - */ - - return 0; -} - -static int screen_RC(term_screen *screen, const term_seq *seq) { - /* - * RC - restore-cursor - */ - - return screen_DECRC(screen, seq); -} - -static int screen_REP(term_screen *screen, const term_seq *seq) { - /* - * REP - repeat - * Repeat the preceding graphics-character the given number of times. - * @args[0] specifies how often it shall be repeated. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_RI(term_screen *screen, const term_seq *seq) { - /* - * RI - reverse-index - * Moves the cursor up one line in the same column. If the cursor is at - * the top margin, the page scrolls down. - */ - - screen_cursor_up(screen, 1, true); - - return 0; -} - -static int screen_RIS(term_screen *screen, const term_seq *seq) { - /* - * RIS - reset-to-initial-state - * This control function causes a nonvolatile memory (NVR) recall to - * occur. RIS replaces all set-up features with their saved settings. - * - * The terminal stores these saved settings in NVR memory. The saved - * setting for a feature is the same as the factory-default setting, - * unless you saved a new setting. - */ - - term_screen_hard_reset(screen); - - return 0; -} - -static int screen_RM_ANSI(term_screen *screen, const term_seq *seq) { - /* - * RM_ANSI - reset-mode-ansi - * - * TODO: implement (see VT510rm manual) - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_ansi(screen, seq->args[i], false); - - return 0; -} - -static int screen_RM_DEC(term_screen *screen, const term_seq *seq) { - /* - * RM_DEC - reset-mode-dec - * This is the same as RM_ANSI but for DEC modes. - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_dec(screen, seq->args[i], false); - - return 0; -} - -static int screen_S7C1T(term_screen *screen, const term_seq *seq) { - /* - * S7C1T - set-7bit-c1-terminal - * This causes the terminal to start sending C1 controls as 7bit - * sequences instead of 8bit C1 controls. - * This is ignored if the terminal is below level-2 emulation mode - * (VT100 and below), the terminal already sends 7bit controls then. - */ - - if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100) - screen->flags |= TERM_FLAG_7BIT_MODE; - - return 0; -} - -static int screen_S8C1T(term_screen *screen, const term_seq *seq) { - /* - * S8C1T - set-8bit-c1-terminal - * This causes the terminal to start sending C1 controls as 8bit C1 - * control instead of 7bit sequences. - * This is ignored if the terminal is below level-2 emulation mode - * (VT100 and below). The terminal always sends 7bit controls in those - * modes. - */ - - if (screen->conformance_level > TERM_CONFORMANCE_LEVEL_VT100) - screen->flags &= ~TERM_FLAG_7BIT_MODE; - - return 0; -} - -static int screen_SCS(term_screen *screen, const term_seq *seq) { - /* - * SCS - select-character-set - * Designate character sets to G-sets. The mapping from intermediates - * and terminal characters in the escape sequence to G-sets and - * character-sets is non-trivial and implemented separately. See there - * for more information. - * This call simply sets the selected G-set to the desired - * character-set. - */ - - term_charset *cs = NULL; - - /* TODO: support more of them? */ - switch (seq->charset) { - case TERM_CHARSET_ISO_LATIN1_SUPPLEMENTAL: - case TERM_CHARSET_ISO_LATIN2_SUPPLEMENTAL: - case TERM_CHARSET_ISO_LATIN5_SUPPLEMENTAL: - case TERM_CHARSET_ISO_GREEK_SUPPLEMENTAL: - case TERM_CHARSET_ISO_HEBREW_SUPPLEMENTAL: - case TERM_CHARSET_ISO_LATIN_CYRILLIC: - break; - - case TERM_CHARSET_DEC_SPECIAL_GRAPHIC: - cs = &term_dec_special_graphics; - break; - case TERM_CHARSET_DEC_SUPPLEMENTAL: - cs = &term_dec_supplemental_graphics; - break; - case TERM_CHARSET_DEC_TECHNICAL: - case TERM_CHARSET_CYRILLIC_DEC: - case TERM_CHARSET_DUTCH_NRCS: - case TERM_CHARSET_FINNISH_NRCS: - case TERM_CHARSET_FRENCH_NRCS: - case TERM_CHARSET_FRENCH_CANADIAN_NRCS: - case TERM_CHARSET_GERMAN_NRCS: - case TERM_CHARSET_GREEK_DEC: - case TERM_CHARSET_GREEK_NRCS: - case TERM_CHARSET_HEBREW_DEC: - case TERM_CHARSET_HEBREW_NRCS: - case TERM_CHARSET_ITALIAN_NRCS: - case TERM_CHARSET_NORWEGIAN_DANISH_NRCS: - case TERM_CHARSET_PORTUGUESE_NRCS: - case TERM_CHARSET_RUSSIAN_NRCS: - case TERM_CHARSET_SCS_NRCS: - case TERM_CHARSET_SPANISH_NRCS: - case TERM_CHARSET_SWEDISH_NRCS: - case TERM_CHARSET_SWISS_NRCS: - case TERM_CHARSET_TURKISH_DEC: - case TERM_CHARSET_TURKISH_NRCS: - break; - - case TERM_CHARSET_USERPREF_SUPPLEMENTAL: - break; - } - - if (seq->intermediates & TERM_SEQ_FLAG_POPEN) - screen->g0 = cs ? : &term_unicode_lower; - else if (seq->intermediates & TERM_SEQ_FLAG_PCLOSE) - screen->g1 = cs ? : &term_unicode_upper; - else if (seq->intermediates & TERM_SEQ_FLAG_MULT) - screen->g2 = cs ? : &term_unicode_lower; - else if (seq->intermediates & TERM_SEQ_FLAG_PLUS) - screen->g3 = cs ? : &term_unicode_upper; - else if (seq->intermediates & TERM_SEQ_FLAG_MINUS) - screen->g1 = cs ? : &term_unicode_upper; - else if (seq->intermediates & TERM_SEQ_FLAG_DOT) - screen->g2 = cs ? : &term_unicode_lower; - else if (seq->intermediates & TERM_SEQ_FLAG_SLASH) - screen->g3 = cs ? : &term_unicode_upper; - - return 0; -} - -static int screen_SD(term_screen *screen, const term_seq *seq) { - /* - * SD - scroll-down - * This control function moves the user window down a specified number - * of lines in page memory. - * @args[0] is the number of lines to move the - * user window up in page memory. New lines appear at the top of the - * display. Old lines disappear at the bottom of the display. You - * cannot pan past the top margin of the current page. 0 is treated - * as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_scroll_down(screen->page, num, &screen->state.attr, screen->age, NULL); - - return 0; -} - -static int screen_SGR(term_screen *screen, const term_seq *seq) { - /* - * SGR - select-graphics-rendition - */ - - term_color *dst; - unsigned int i, code; - int v; - - if (seq->n_args < 1) { - zero(screen->state.attr); - return 0; - } - - for (i = 0; i < seq->n_args; ++i) { - v = seq->args[i]; - switch (v) { - case 1: - screen->state.attr.bold = 1; - break; - case 3: - screen->state.attr.italic = 1; - break; - case 4: - screen->state.attr.underline = 1; - break; - case 5: - screen->state.attr.blink = 1; - break; - case 7: - screen->state.attr.inverse = 1; - break; - case 8: - screen->state.attr.hidden = 1; - break; - case 22: - screen->state.attr.bold = 0; - break; - case 23: - screen->state.attr.italic = 0; - break; - case 24: - screen->state.attr.underline = 0; - break; - case 25: - screen->state.attr.blink = 0; - break; - case 27: - screen->state.attr.inverse = 0; - break; - case 28: - screen->state.attr.hidden = 0; - break; - case 30 ... 37: - screen->state.attr.fg.ccode = v - 30 + TERM_CCODE_BLACK; - break; - case 39: - screen->state.attr.fg.ccode = 0; - break; - case 40 ... 47: - screen->state.attr.bg.ccode = v - 40 + TERM_CCODE_BLACK; - break; - case 49: - screen->state.attr.bg.ccode = 0; - break; - case 90 ... 97: - screen->state.attr.fg.ccode = v - 90 + TERM_CCODE_LIGHT_BLACK; - break; - case 100 ... 107: - screen->state.attr.bg.ccode = v - 100 + TERM_CCODE_LIGHT_BLACK; - break; - case 38: - /* fallthrough */ - case 48: - - if (v == 38) - dst = &screen->state.attr.fg; - else - dst = &screen->state.attr.bg; - - ++i; - if (i >= seq->n_args) - break; - - switch (seq->args[i]) { - case 2: - /* 24bit-color support */ - - i += 3; - if (i >= seq->n_args) - break; - - dst->ccode = TERM_CCODE_RGB; - dst->red = (seq->args[i - 2] >= 0) ? seq->args[i - 2] : 0; - dst->green = (seq->args[i - 1] >= 0) ? seq->args[i - 1] : 0; - dst->blue = (seq->args[i] >= 0) ? seq->args[i] : 0; - - break; - case 5: - /* 256-color support */ - - ++i; - if (i >= seq->n_args || seq->args[i] < 0) - break; - - dst->ccode = TERM_CCODE_256; - code = seq->args[i]; - dst->c256 = code < 256 ? code : 0; - - break; - } - - break; - case -1: - /* fallthrough */ - case 0: - zero(screen->state.attr); - break; - } - } - - return 0; -} - -static int screen_SI(term_screen *screen, const term_seq *seq) { - /* - * SI - shift-in - * Map G0 into GL. - */ - - screen->state.gl = &screen->g0; - - return 0; -} - -static int screen_SM_ANSI(term_screen *screen, const term_seq *seq) { - /* - * SM_ANSI - set-mode-ansi - * - * TODO: implement - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_ansi(screen, seq->args[i], true); - - return 0; -} - -static int screen_SM_DEC(term_screen *screen, const term_seq *seq) { - /* - * SM_DEC - set-mode-dec - * This is the same as SM_ANSI but for DEC modes. - */ - - unsigned int i; - - for (i = 0; i < seq->n_args; ++i) - screen_mode_change_dec(screen, seq->args[i], true); - - return 0; -} - -static int screen_SO(term_screen *screen, const term_seq *seq) { - /* - * SO - shift-out - * Map G1 into GL. - */ - - screen->state.gl = &screen->g1; - - return 0; -} - -static int screen_SPA(term_screen *screen, const term_seq *seq) { - /* - * SPA - start-of-protected-area - * - * TODO: What is this? - */ - - return 0; -} - -static int screen_SS2(term_screen *screen, const term_seq *seq) { - /* - * SS2 - single-shift-2 - * Temporarily map G2 into GL for the next graphics character. - */ - - screen->state.glt = &screen->g2; - - return 0; -} - -static int screen_SS3(term_screen *screen, const term_seq *seq) { - /* - * SS3 - single-shift-3 - * Temporarily map G3 into GL for the next graphics character - */ - - screen->state.glt = &screen->g3; - - return 0; -} - -static int screen_ST(term_screen *screen, const term_seq *seq) { - /* - * ST - string-terminator - * The string-terminator is usually part of control-sequences and - * handled by the parser. In all other situations it is silently - * ignored. - */ - - return 0; -} - -static int screen_SU(term_screen *screen, const term_seq *seq) { - /* - * SU - scroll-up - * This control function moves the user window up a specified number of - * lines in page memory. - * @args[0] is the number of lines to move the - * user window down in page memory. New lines appear at the bottom of - * the display. Old lines disappear at the top of the display. You - * cannot pan past the bottom margin of the current page. 0 is treated - * as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - term_page_scroll_up(screen->page, num, &screen->state.attr, screen->age, screen->history); - - return 0; -} - -static int screen_SUB(term_screen *screen, const term_seq *seq) { - /* - * SUB - substitute - * Cancel the current control-sequence and print a replacement - * character. Our parser already handles this so all we have to do is - * print the replacement character. - */ - - static const term_seq rep = { - .type = TERM_SEQ_GRAPHIC, - .command = TERM_CMD_GRAPHIC, - .terminator = 0xfffd, - }; - - return screen_GRAPHIC(screen, &rep); -} - -static int screen_TBC(term_screen *screen, const term_seq *seq) { - /* - * TBC - tab-clear - * This clears tab-stops. If @args[0] is 0, the tab-stop at the current - * cursor position is cleared. If it is 3, all tab stops are cleared. - * - * Defaults: - * args[0]: 0 - */ - - unsigned int mode = 0, pos; - - if (seq->args[0] > 0) - mode = seq->args[0]; - - switch (mode) { - case 0: - pos = screen->state.cursor_x; - if (screen->page->width > 0) - screen->tabs[pos / 8] &= ~(1U << (pos % 8)); - break; - case 3: - if (screen->page->width > 0) - memzero(screen->tabs, (screen->page->width + 7) / 8); - break; - } - - return 0; -} - -static int screen_VPA(term_screen *screen, const term_seq *seq) { - /* - * VPA - vertical-line-position-absolute - * VPA causes the active position to be moved to the corresponding - * horizontal position. @args[0] specifies the line to jump to. If an - * attempt is made to move the active position below the last line, then - * the active position stops on the last line. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int pos = 1; - - if (seq->args[0] > 0) - pos = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_set_rel(screen, screen->state.cursor_x, pos - 1); - - return 0; -} - -static int screen_VPR(term_screen *screen, const term_seq *seq) { - /* - * VPR - vertical-line-position-relative - * VPR causes the active position to be moved to the corresponding - * horizontal position. @args[0] specifies the number of lines to jump - * down relative to the current cursor position. If an attempt is made - * to move the active position below the last line, the active position - * stops at the last line. 0 is treated as 1. - * - * Defaults: - * args[0]: 1 - */ - - unsigned int num = 1; - - if (seq->args[0] > 0) - num = seq->args[0]; - - screen_cursor_clear_wrap(screen); - screen_cursor_down(screen, num, false); - - return 0; -} - -static int screen_VT(term_screen *screen, const term_seq *seq) { - /* - * VT - vertical-tab - * This causes a vertical jump by one line. Terminals treat it exactly - * the same as LF. - */ - - return screen_LF(screen, seq); -} - -static int screen_XTERM_CLLHP(term_screen *screen, const term_seq *seq) { - /* - * XTERM_CLLHP - xterm-cursor-lower-left-hp-bugfix - * Move the cursor to the lower-left corner of the page. This is an HP - * bugfix by xterm. - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_IHMT(term_screen *screen, const term_seq *seq) { - /* - * XTERM_IHMT - xterm-initiate-highlight-mouse-tracking - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_MLHP(term_screen *screen, const term_seq *seq) { - /* - * XTERM_MLHP - xterm-memory-lock-hp-bugfix - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_MUHP(term_screen *screen, const term_seq *seq) { - /* - * XTERM_MUHP - xterm-memory-unlock-hp-bugfix - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_RPM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_RPM - xterm-restore-private-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_RRV(term_screen *screen, const term_seq *seq) { - /* - * XTERM_RRV - xterm-reset-resource-value - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_RTM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_RTM - xterm-reset-title-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SACL1(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SACL1 - xterm-set-ansi-conformance-level-1 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SACL2(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SACL2 - xterm-set-ansi-conformance-level-2 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SACL3(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SACL3 - xterm-set-ansi-conformance-level-3 - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SDCS(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SDCS - xterm-set-default-character-set - * Select the default character set. We treat this the same as UTF-8 as - * this is our default character set. As we always use UTF-8, this - * becomes as no-op. - */ - - return 0; -} - -static int screen_XTERM_SGFX(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SGFX - xterm-sixel-graphics - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SPM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SPM - xterm-set-private-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SRV(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SRV - xterm-set-resource-value - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_STM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_STM - xterm-set-title-mode - * - * Probably not worth implementing. - */ - - return 0; -} - -static int screen_XTERM_SUCS(term_screen *screen, const term_seq *seq) { - /* - * XTERM_SUCS - xterm-select-utf8-character-set - * Select UTF-8 as character set. This is our default on only character - * set. Hence, this is a no-op. - */ - - return 0; -} - -static int screen_XTERM_WM(term_screen *screen, const term_seq *seq) { - /* - * XTERM_WM - xterm-window-management - * - * Probably not worth implementing. - */ - - return 0; -} - -/* - * Feeding data - * The screen_feed_*() handlers take data from the user and feed it into the - * screen. Once the parser has detected a sequence, we parse the command-type - * and forward it to the command-dispatchers. - */ - -static int screen_feed_cmd(term_screen *screen, const term_seq *seq) { - switch (seq->command) { - case TERM_CMD_GRAPHIC: - return screen_GRAPHIC(screen, seq); - case TERM_CMD_BEL: - return screen_BEL(screen, seq); - case TERM_CMD_BS: - return screen_BS(screen, seq); - case TERM_CMD_CBT: - return screen_CBT(screen, seq); - case TERM_CMD_CHA: - return screen_CHA(screen, seq); - case TERM_CMD_CHT: - return screen_CHT(screen, seq); - case TERM_CMD_CNL: - return screen_CNL(screen, seq); - case TERM_CMD_CPL: - return screen_CPL(screen, seq); - case TERM_CMD_CR: - return screen_CR(screen, seq); - case TERM_CMD_CUB: - return screen_CUB(screen, seq); - case TERM_CMD_CUD: - return screen_CUD(screen, seq); - case TERM_CMD_CUF: - return screen_CUF(screen, seq); - case TERM_CMD_CUP: - return screen_CUP(screen, seq); - case TERM_CMD_CUU: - return screen_CUU(screen, seq); - case TERM_CMD_DA1: - return screen_DA1(screen, seq); - case TERM_CMD_DA2: - return screen_DA2(screen, seq); - case TERM_CMD_DA3: - return screen_DA3(screen, seq); - case TERM_CMD_DC1: - return screen_DC1(screen, seq); - case TERM_CMD_DC3: - return screen_DC3(screen, seq); - case TERM_CMD_DCH: - return screen_DCH(screen, seq); - case TERM_CMD_DECALN: - return screen_DECALN(screen, seq); - case TERM_CMD_DECANM: - return screen_DECANM(screen, seq); - case TERM_CMD_DECBI: - return screen_DECBI(screen, seq); - case TERM_CMD_DECCARA: - return screen_DECCARA(screen, seq); - case TERM_CMD_DECCRA: - return screen_DECCRA(screen, seq); - case TERM_CMD_DECDC: - return screen_DECDC(screen, seq); - case TERM_CMD_DECDHL_BH: - return screen_DECDHL_BH(screen, seq); - case TERM_CMD_DECDHL_TH: - return screen_DECDHL_TH(screen, seq); - case TERM_CMD_DECDWL: - return screen_DECDWL(screen, seq); - case TERM_CMD_DECEFR: - return screen_DECEFR(screen, seq); - case TERM_CMD_DECELF: - return screen_DECELF(screen, seq); - case TERM_CMD_DECELR: - return screen_DECELR(screen, seq); - case TERM_CMD_DECERA: - return screen_DECERA(screen, seq); - case TERM_CMD_DECFI: - return screen_DECFI(screen, seq); - case TERM_CMD_DECFRA: - return screen_DECFRA(screen, seq); - case TERM_CMD_DECIC: - return screen_DECIC(screen, seq); - case TERM_CMD_DECID: - return screen_DECID(screen, seq); - case TERM_CMD_DECINVM: - return screen_DECINVM(screen, seq); - case TERM_CMD_DECKBD: - return screen_DECKBD(screen, seq); - case TERM_CMD_DECKPAM: - return screen_DECKPAM(screen, seq); - case TERM_CMD_DECKPNM: - return screen_DECKPNM(screen, seq); - case TERM_CMD_DECLFKC: - return screen_DECLFKC(screen, seq); - case TERM_CMD_DECLL: - return screen_DECLL(screen, seq); - case TERM_CMD_DECLTOD: - return screen_DECLTOD(screen, seq); - case TERM_CMD_DECPCTERM: - return screen_DECPCTERM(screen, seq); - case TERM_CMD_DECPKA: - return screen_DECPKA(screen, seq); - case TERM_CMD_DECPKFMR: - return screen_DECPKFMR(screen, seq); - case TERM_CMD_DECRARA: - return screen_DECRARA(screen, seq); - case TERM_CMD_DECRC: - return screen_DECRC(screen, seq); - case TERM_CMD_DECREQTPARM: - return screen_DECREQTPARM(screen, seq); - case TERM_CMD_DECRPKT: - return screen_DECRPKT(screen, seq); - case TERM_CMD_DECRQCRA: - return screen_DECRQCRA(screen, seq); - case TERM_CMD_DECRQDE: - return screen_DECRQDE(screen, seq); - case TERM_CMD_DECRQKT: - return screen_DECRQKT(screen, seq); - case TERM_CMD_DECRQLP: - return screen_DECRQLP(screen, seq); - case TERM_CMD_DECRQM_ANSI: - return screen_DECRQM_ANSI(screen, seq); - case TERM_CMD_DECRQM_DEC: - return screen_DECRQM_DEC(screen, seq); - case TERM_CMD_DECRQPKFM: - return screen_DECRQPKFM(screen, seq); - case TERM_CMD_DECRQPSR: - return screen_DECRQPSR(screen, seq); - case TERM_CMD_DECRQTSR: - return screen_DECRQTSR(screen, seq); - case TERM_CMD_DECRQUPSS: - return screen_DECRQUPSS(screen, seq); - case TERM_CMD_DECSACE: - return screen_DECSACE(screen, seq); - case TERM_CMD_DECSASD: - return screen_DECSASD(screen, seq); - case TERM_CMD_DECSC: - return screen_DECSC(screen, seq); - case TERM_CMD_DECSCA: - return screen_DECSCA(screen, seq); - case TERM_CMD_DECSCL: - return screen_DECSCL(screen, seq); - case TERM_CMD_DECSCP: - return screen_DECSCP(screen, seq); - case TERM_CMD_DECSCPP: - return screen_DECSCPP(screen, seq); - case TERM_CMD_DECSCS: - return screen_DECSCS(screen, seq); - case TERM_CMD_DECSCUSR: - return screen_DECSCUSR(screen, seq); - case TERM_CMD_DECSDDT: - return screen_DECSDDT(screen, seq); - case TERM_CMD_DECSDPT: - return screen_DECSDPT(screen, seq); - case TERM_CMD_DECSED: - return screen_DECSED(screen, seq); - case TERM_CMD_DECSEL: - return screen_DECSEL(screen, seq); - case TERM_CMD_DECSERA: - return screen_DECSERA(screen, seq); - case TERM_CMD_DECSFC: - return screen_DECSFC(screen, seq); - case TERM_CMD_DECSKCV: - return screen_DECSKCV(screen, seq); - case TERM_CMD_DECSLCK: - return screen_DECSLCK(screen, seq); - case TERM_CMD_DECSLE: - return screen_DECSLE(screen, seq); - case TERM_CMD_DECSLPP: - return screen_DECSLPP(screen, seq); - case TERM_CMD_DECSLRM_OR_SC: - return screen_DECSLRM_OR_SC(screen, seq); - case TERM_CMD_DECSMBV: - return screen_DECSMBV(screen, seq); - case TERM_CMD_DECSMKR: - return screen_DECSMKR(screen, seq); - case TERM_CMD_DECSNLS: - return screen_DECSNLS(screen, seq); - case TERM_CMD_DECSPP: - return screen_DECSPP(screen, seq); - case TERM_CMD_DECSPPCS: - return screen_DECSPPCS(screen, seq); - case TERM_CMD_DECSPRTT: - return screen_DECSPRTT(screen, seq); - case TERM_CMD_DECSR: - return screen_DECSR(screen, seq); - case TERM_CMD_DECSRFR: - return screen_DECSRFR(screen, seq); - case TERM_CMD_DECSSCLS: - return screen_DECSSCLS(screen, seq); - case TERM_CMD_DECSSDT: - return screen_DECSSDT(screen, seq); - case TERM_CMD_DECSSL: - return screen_DECSSL(screen, seq); - case TERM_CMD_DECST8C: - return screen_DECST8C(screen, seq); - case TERM_CMD_DECSTBM: - return screen_DECSTBM(screen, seq); - case TERM_CMD_DECSTR: - return screen_DECSTR(screen, seq); - case TERM_CMD_DECSTRL: - return screen_DECSTRL(screen, seq); - case TERM_CMD_DECSWBV: - return screen_DECSWBV(screen, seq); - case TERM_CMD_DECSWL: - return screen_DECSWL(screen, seq); - case TERM_CMD_DECTID: - return screen_DECTID(screen, seq); - case TERM_CMD_DECTME: - return screen_DECTME(screen, seq); - case TERM_CMD_DECTST: - return screen_DECTST(screen, seq); - case TERM_CMD_DL: - return screen_DL(screen, seq); - case TERM_CMD_DSR_ANSI: - return screen_DSR_ANSI(screen, seq); - case TERM_CMD_DSR_DEC: - return screen_DSR_DEC(screen, seq); - case TERM_CMD_ECH: - return screen_ECH(screen, seq); - case TERM_CMD_ED: - return screen_ED(screen, seq); - case TERM_CMD_EL: - return screen_EL(screen, seq); - case TERM_CMD_ENQ: - return screen_ENQ(screen, seq); - case TERM_CMD_EPA: - return screen_EPA(screen, seq); - case TERM_CMD_FF: - return screen_FF(screen, seq); - case TERM_CMD_HPA: - return screen_HPA(screen, seq); - case TERM_CMD_HPR: - return screen_HPR(screen, seq); - case TERM_CMD_HT: - return screen_HT(screen, seq); - case TERM_CMD_HTS: - return screen_HTS(screen, seq); - case TERM_CMD_HVP: - return screen_HVP(screen, seq); - case TERM_CMD_ICH: - return screen_ICH(screen, seq); - case TERM_CMD_IL: - return screen_IL(screen, seq); - case TERM_CMD_IND: - return screen_IND(screen, seq); - case TERM_CMD_LF: - return screen_LF(screen, seq); - case TERM_CMD_LS1R: - return screen_LS1R(screen, seq); - case TERM_CMD_LS2: - return screen_LS2(screen, seq); - case TERM_CMD_LS2R: - return screen_LS2R(screen, seq); - case TERM_CMD_LS3: - return screen_LS3(screen, seq); - case TERM_CMD_LS3R: - return screen_LS3R(screen, seq); - case TERM_CMD_MC_ANSI: - return screen_MC_ANSI(screen, seq); - case TERM_CMD_MC_DEC: - return screen_MC_DEC(screen, seq); - case TERM_CMD_NEL: - return screen_NEL(screen, seq); - case TERM_CMD_NP: - return screen_NP(screen, seq); - case TERM_CMD_NULL: - return screen_NULL(screen, seq); - case TERM_CMD_PP: - return screen_PP(screen, seq); - case TERM_CMD_PPA: - return screen_PPA(screen, seq); - case TERM_CMD_PPB: - return screen_PPB(screen, seq); - case TERM_CMD_PPR: - return screen_PPR(screen, seq); - case TERM_CMD_RC: - return screen_RC(screen, seq); - case TERM_CMD_REP: - return screen_REP(screen, seq); - case TERM_CMD_RI: - return screen_RI(screen, seq); - case TERM_CMD_RIS: - return screen_RIS(screen, seq); - case TERM_CMD_RM_ANSI: - return screen_RM_ANSI(screen, seq); - case TERM_CMD_RM_DEC: - return screen_RM_DEC(screen, seq); - case TERM_CMD_S7C1T: - return screen_S7C1T(screen, seq); - case TERM_CMD_S8C1T: - return screen_S8C1T(screen, seq); - case TERM_CMD_SCS: - return screen_SCS(screen, seq); - case TERM_CMD_SD: - return screen_SD(screen, seq); - case TERM_CMD_SGR: - return screen_SGR(screen, seq); - case TERM_CMD_SI: - return screen_SI(screen, seq); - case TERM_CMD_SM_ANSI: - return screen_SM_ANSI(screen, seq); - case TERM_CMD_SM_DEC: - return screen_SM_DEC(screen, seq); - case TERM_CMD_SO: - return screen_SO(screen, seq); - case TERM_CMD_SPA: - return screen_SPA(screen, seq); - case TERM_CMD_SS2: - return screen_SS2(screen, seq); - case TERM_CMD_SS3: - return screen_SS3(screen, seq); - case TERM_CMD_ST: - return screen_ST(screen, seq); - case TERM_CMD_SU: - return screen_SU(screen, seq); - case TERM_CMD_SUB: - return screen_SUB(screen, seq); - case TERM_CMD_TBC: - return screen_TBC(screen, seq); - case TERM_CMD_VPA: - return screen_VPA(screen, seq); - case TERM_CMD_VPR: - return screen_VPR(screen, seq); - case TERM_CMD_VT: - return screen_VT(screen, seq); - case TERM_CMD_XTERM_CLLHP: - return screen_XTERM_CLLHP(screen, seq); - case TERM_CMD_XTERM_IHMT: - return screen_XTERM_IHMT(screen, seq); - case TERM_CMD_XTERM_MLHP: - return screen_XTERM_MLHP(screen, seq); - case TERM_CMD_XTERM_MUHP: - return screen_XTERM_MUHP(screen, seq); - case TERM_CMD_XTERM_RPM: - return screen_XTERM_RPM(screen, seq); - case TERM_CMD_XTERM_RRV: - return screen_XTERM_RRV(screen, seq); - case TERM_CMD_XTERM_RTM: - return screen_XTERM_RTM(screen, seq); - case TERM_CMD_XTERM_SACL1: - return screen_XTERM_SACL1(screen, seq); - case TERM_CMD_XTERM_SACL2: - return screen_XTERM_SACL2(screen, seq); - case TERM_CMD_XTERM_SACL3: - return screen_XTERM_SACL3(screen, seq); - case TERM_CMD_XTERM_SDCS: - return screen_XTERM_SDCS(screen, seq); - case TERM_CMD_XTERM_SGFX: - return screen_XTERM_SGFX(screen, seq); - case TERM_CMD_XTERM_SPM: - return screen_XTERM_SPM(screen, seq); - case TERM_CMD_XTERM_SRV: - return screen_XTERM_SRV(screen, seq); - case TERM_CMD_XTERM_STM: - return screen_XTERM_STM(screen, seq); - case TERM_CMD_XTERM_SUCS: - return screen_XTERM_SUCS(screen, seq); - case TERM_CMD_XTERM_WM: - return screen_XTERM_WM(screen, seq); - } - - return 0; -} - -unsigned int term_screen_get_width(term_screen *screen) { - assert_return(screen, -EINVAL); - - return screen->page->width; -} - -unsigned int term_screen_get_height(term_screen *screen) { - assert_return(screen, -EINVAL); - - return screen->page->height; -} - -uint64_t term_screen_get_age(term_screen *screen) { - assert_return(screen, 0); - - return screen->age; -} - -int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) { - uint32_t *ucs4_str; - size_t i, j, ucs4_len; - const term_seq *seq; - int r; - - assert_return(screen, -EINVAL); - - ++screen->age; - - /* Feed bytes into utf8 decoder and handle parsed ucs4 chars. We always - * treat data as UTF-8, but the parser makes sure to fall back to raw - * 8bit mode if the stream is not valid UTF-8. This should be more than - * enough to support old 7bit/8bit modes. */ - for (i = 0; i < size; ++i) { - ucs4_len = term_utf8_decode(&screen->utf8, &ucs4_str, in[i]); - for (j = 0; j < ucs4_len; ++j) { - r = term_parser_feed(screen->parser, &seq, ucs4_str[j]); - if (r < 0) { - return r; - } else if (r != TERM_SEQ_NONE) { - r = screen_feed_cmd(screen, seq); - if (r < 0) - return r; - } - } - } - - return 0; -} - -static char *screen_map_key(term_screen *screen, - char *p, - const uint32_t *keysyms, - size_t n_syms, - uint32_t ascii, - const uint32_t *ucs4, - unsigned int mods) { - char ch, ch2, ch_mods; - uint32_t v; - size_t i; - - /* TODO: All these key-mappings need to be verified. Public information - * on those mappings is pretty scarce and every emulator seems to do it - * slightly differently. - * A lot of mappings are also missing. */ - - if (n_syms < 1) - return p; - - if (n_syms == 1) - v = keysyms[0]; - else - v = XKB_KEY_NoSymbol; - - /* In some mappings, the modifiers are encoded as CSI parameters. The - * encoding is rather arbitrary, but seems to work. */ - ch_mods = 0; - switch (mods & (TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT | TERM_KBDMOD_CTRL)) { - case TERM_KBDMOD_SHIFT: - ch_mods = '2'; - break; - case TERM_KBDMOD_ALT: - ch_mods = '3'; - break; - case TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT: - ch_mods = '4'; - break; - case TERM_KBDMOD_CTRL: - ch_mods = '5'; - break; - case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT: - ch_mods = '6'; - break; - case TERM_KBDMOD_CTRL | TERM_KBDMOD_ALT: - ch_mods = '7'; - break; - case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT: - ch_mods = '8'; - break; - } - - /* A user might actually use multiple layouts for keyboard - * input. @keysyms[0] contains the actual keysym that the user - * used. But if this keysym is not in the ascii range, the - * input handler does check all other layouts that the user - * specified whether one of them maps the key to some ASCII - * keysym and provides this via @ascii. We always use the real - * keysym except when handling CTRL+ shortcuts we use the - * ascii keysym. This is for compatibility to xterm et. al. so - * ctrl+c always works regardless of the currently active - * keyboard layout. But if no ascii-sym is found, we still use - * the real keysym. */ - if (ascii == XKB_KEY_NoSymbol) - ascii = v; - - /* map CTRL+ */ - if (mods & TERM_KBDMOD_CTRL) { - switch (ascii) { - case 0x60 ... 0x7e: - /* Right hand side is mapped to the left and then - * treated equally. Fall through to left-hand side.. */ - ascii -= 0x20; - case 0x20 ... 0x5f: - /* Printable ASCII is mapped 1-1 in XKB and in - * combination with CTRL bit 7 is flipped. This - * is equivalent to the caret-notation. */ - *p++ = ascii ^ 0x40; - return p; - } - } - - /* map cursor keys */ - ch = 0; - switch (v) { - case XKB_KEY_Up: - ch = 'A'; - break; - case XKB_KEY_Down: - ch = 'B'; - break; - case XKB_KEY_Right: - ch = 'C'; - break; - case XKB_KEY_Left: - ch = 'D'; - break; - case XKB_KEY_Home: - ch = 'H'; - break; - case XKB_KEY_End: - ch = 'F'; - break; - } - if (ch) { - *p++ = 0x1b; - if (screen->flags & TERM_FLAG_CURSOR_KEYS) - *p++ = 'O'; - else - *p++ = '['; - if (ch_mods) { - *p++ = '1'; - *p++ = ';'; - *p++ = ch_mods; - } - *p++ = ch; - return p; - } - - /* map action keys */ - ch = 0; - switch (v) { - case XKB_KEY_Find: - ch = '1'; - break; - case XKB_KEY_Insert: - ch = '2'; - break; - case XKB_KEY_Delete: - ch = '3'; - break; - case XKB_KEY_Select: - ch = '4'; - break; - case XKB_KEY_Page_Up: - ch = '5'; - break; - case XKB_KEY_Page_Down: - ch = '6'; - break; - } - if (ch) { - *p++ = 0x1b; - *p++ = '['; - *p++ = ch; - if (ch_mods) { - *p++ = ';'; - *p++ = ch_mods; - } - *p++ = '~'; - return p; - } - - /* map lower function keys */ - ch = 0; - switch (v) { - case XKB_KEY_F1: - ch = 'P'; - break; - case XKB_KEY_F2: - ch = 'Q'; - break; - case XKB_KEY_F3: - ch = 'R'; - break; - case XKB_KEY_F4: - ch = 'S'; - break; - } - if (ch) { - if (ch_mods) { - *p++ = 0x1b; - *p++ = '['; - *p++ = '1'; - *p++ = ';'; - *p++ = ch_mods; - *p++ = ch; - } else { - *p++ = 0x1b; - *p++ = 'O'; - *p++ = ch; - } - - return p; - } - - /* map upper function keys */ - ch = 0; - ch2 = 0; - switch (v) { - case XKB_KEY_F5: - ch = '1'; - ch2 = '5'; - break; - case XKB_KEY_F6: - ch = '1'; - ch2 = '7'; - break; - case XKB_KEY_F7: - ch = '1'; - ch2 = '8'; - break; - case XKB_KEY_F8: - ch = '1'; - ch2 = '9'; - break; - case XKB_KEY_F9: - ch = '2'; - ch2 = '0'; - break; - case XKB_KEY_F10: - ch = '2'; - ch2 = '1'; - break; - case XKB_KEY_F11: - ch = '2'; - ch2 = '2'; - break; - case XKB_KEY_F12: - ch = '2'; - ch2 = '3'; - break; - } - if (ch) { - *p++ = 0x1b; - *p++ = '['; - *p++ = ch; - if (ch2) - *p++ = ch2; - if (ch_mods) { - *p++ = ';'; - *p++ = ch_mods; - } - *p++ = '~'; - return p; - } - - /* map special keys */ - switch (v) { - case 0xff08: /* XKB_KEY_BackSpace */ - case 0xff09: /* XKB_KEY_Tab */ - case 0xff0a: /* XKB_KEY_Linefeed */ - case 0xff0b: /* XKB_KEY_Clear */ - case 0xff15: /* XKB_KEY_Sys_Req */ - case 0xff1b: /* XKB_KEY_Escape */ - case 0xffff: /* XKB_KEY_Delete */ - *p++ = v - 0xff00; - return p; - case 0xff13: /* XKB_KEY_Pause */ - /* TODO: What should we do with this key? - * Sending XOFF is awful as there is no simple - * way on modern keyboards to send XON again. - * If someone wants this, we can re-eanble - * optionally. */ - return p; - case 0xff14: /* XKB_KEY_Scroll_Lock */ - /* TODO: What should we do on scroll-lock? - * Sending 0x14 is what the specs say but it is - * not used today the way most users would - * expect so we disable it. If someone wants - * this, we can re-enable it (optionally). */ - return p; - case XKB_KEY_Return: - *p++ = 0x0d; - if (screen->flags & TERM_FLAG_NEWLINE_MODE) - *p++ = 0x0a; - return p; - case XKB_KEY_ISO_Left_Tab: - *p++ = 0x09; - return p; - } - - /* map unicode keys */ - for (i = 0; i < n_syms; ++i) - p += utf8_encode_unichar(p, ucs4[i]); - - return p; -} - -int term_screen_feed_keyboard(term_screen *screen, - const uint32_t *keysyms, - size_t n_syms, - uint32_t ascii, - const uint32_t *ucs4, - unsigned int mods) { - _cleanup_free_ char *dyn = NULL; - static const size_t padding = 1; - char buf[128], *start, *p; - - assert_return(screen, -EINVAL); - - /* allocate buffer if too small */ - start = buf; - if (4 * n_syms + padding > sizeof(buf)) { - dyn = malloc(4 * n_syms + padding); - if (!dyn) - return -ENOMEM; - - start = dyn; - } - - /* reserve prefix space */ - start += padding; - p = start; - - p = screen_map_key(screen, p, keysyms, n_syms, ascii, ucs4, mods); - if (!p || p - start < 1) - return 0; - - /* The ALT modifier causes ESC to be prepended to any key-stroke. We - * already accounted for that buffer space above, so simply prepend it - * here. - * TODO: is altSendsEscape a suitable default? What are the semantics - * exactly? Is it used in C0/C1 conversion? Is it prepended if there - * already is an escape character? */ - if (mods & TERM_KBDMOD_ALT && *start != 0x1b) - *--start = 0x1b; - - /* turn C0 into C1 */ - if (!(screen->flags & TERM_FLAG_7BIT_MODE) && p - start >= 2) - if (start[0] == 0x1b && start[1] >= 0x40 && start[1] <= 0x5f) - *++start ^= 0x40; - - return screen_write(screen, start, p - start); -} - -int term_screen_resize(term_screen *screen, unsigned int x, unsigned int y) { - unsigned int i; - uint8_t *t; - int r; - - assert_return(screen, -EINVAL); - - r = term_page_reserve(screen->page_main, x, y, &screen->state.attr, screen->age); - if (r < 0) - return r; - - r = term_page_reserve(screen->page_alt, x, y, &screen->state.attr, screen->age); - if (r < 0) - return r; - - if (x > screen->n_tabs) { - t = realloc(screen->tabs, (x + 7) / 8); - if (!t) - return -ENOMEM; - - screen->tabs = t; - screen->n_tabs = x; - } - - for (i = (screen->page->width + 7) / 8 * 8; i < x; i += 8) - screen->tabs[i / 8] = 0x1; - - term_page_resize(screen->page_main, x, y, &screen->state.attr, screen->age, screen->history); - term_page_resize(screen->page_alt, x, y, &screen->state.attr, screen->age, NULL); - - screen->state.cursor_x = screen_clamp_x(screen, screen->state.cursor_x); - screen->state.cursor_y = screen_clamp_x(screen, screen->state.cursor_y); - screen_cursor_clear_wrap(screen); - - return 0; -} - -void term_screen_soft_reset(term_screen *screen) { - unsigned int i; - - assert(screen); - - screen->g0 = &term_unicode_lower; - screen->g1 = &term_unicode_upper; - screen->g2 = &term_unicode_lower; - screen->g3 = &term_unicode_upper; - screen->state.attr = screen->default_attr; - screen->state.gl = &screen->g0; - screen->state.gr = &screen->g1; - screen->state.glt = NULL; - screen->state.grt = NULL; - screen->state.auto_wrap = 0; - screen->state.origin_mode = 0; - - screen->saved = screen->state; - screen->saved.cursor_x = 0; - screen->saved.cursor_y = 0; - screen->saved_alt = screen->saved; - - screen->page = screen->page_main; - screen->history = screen->history_main; - screen->flags = TERM_FLAG_7BIT_MODE; - screen->conformance_level = TERM_CONFORMANCE_LEVEL_VT400; - - for (i = 0; i < screen->page->width; i += 8) - screen->tabs[i / 8] = 0x1; - - term_page_set_scroll_region(screen->page_main, 0, screen->page->height); - term_page_set_scroll_region(screen->page_alt, 0, screen->page->height); -} - -void term_screen_hard_reset(term_screen *screen) { - assert(screen); - - term_screen_soft_reset(screen); - zero(screen->utf8); - screen->state.cursor_x = 0; - screen->state.cursor_y = 0; - term_page_erase(screen->page_main, 0, 0, screen->page->width, screen->page->height, &screen->state.attr, screen->age, false); - term_page_erase(screen->page_alt, 0, 0, screen->page->width, screen->page->height, &screen->state.attr, screen->age, false); -} - -int term_screen_set_answerback(term_screen *screen, const char *answerback) { - char *t = NULL; - - assert_return(screen, -EINVAL); - - if (answerback) { - t = strdup(answerback); - if (!t) - return -ENOMEM; - } - - free(screen->answerback); - screen->answerback = t; - - return 0; -} - -int term_screen_draw(term_screen *screen, - int (*draw_fn) (term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width), - void *userdata, - uint64_t *fb_age) { - uint64_t cell_age, line_age, age = 0; - term_charbuf_t ch_buf; - const uint32_t *ch_str; - unsigned int i, j, cw; - term_page *page; - term_line *line; - term_cell *cell; - size_t ch_n; - int r; - - assert(screen); - assert(draw_fn); - - if (fb_age) - age = *fb_age; - - page = screen->page; - - for (j = 0; j < page->height; ++j) { - line = page->lines[j]; - line_age = MAX(line->age, page->age); - - for (i = 0; i < page->width; ++i) { - term_attr attr; - - cell = &line->cells[i]; - cell_age = MAX(cell->age, line_age); - - if (age != 0 && cell_age <= age) - continue; - - ch_str = term_char_resolve(cell->ch, &ch_n, &ch_buf); - - /* Character-width of 0 is used for cleared cells. - * Always treat this as single-cell character, so - * renderers can assume ch_width is set properpy. */ - cw = MAX(cell->cwidth, 1U); - - attr = cell->attr; - if (i == screen->state.cursor_x && j == screen->state.cursor_y && - !(screen->flags & TERM_FLAG_HIDE_CURSOR)) - attr.inverse ^= 1; - - r = draw_fn(screen, - userdata, - i, - j, - &attr, - ch_str, - ch_n, - cw); - if (r != 0) - return r; - } - } - - if (fb_age) - *fb_age = screen->age; - - return 0; -} diff --git a/src/libsystemd-terminal/term-wcwidth.c b/src/libsystemd-terminal/term-wcwidth.c deleted file mode 100644 index 833a099bd7..0000000000 --- a/src/libsystemd-terminal/term-wcwidth.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * (Minimal changes made by David Herrmann, to make clean for inclusion in - * systemd. Original header follows.) - * - * This is an implementation of wcwidth() and wcswidth() (defined in - * IEEE Std 1002.1-2001) for Unicode. - * - * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html - * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html - * - * In fixed-width output devices, Latin characters all occupy a single - * "cell" position of equal width, whereas ideographic CJK characters - * occupy two such cells. Interoperability between terminal-line - * applications and (teletype-style) character terminals using the - * UTF-8 encoding requires agreement on which character should advance - * the cursor by how many cell positions. No established formal - * standards exist at present on which Unicode character shall occupy - * how many cell positions on character terminals. These routines are - * a first attempt of defining such behavior based on simple rules - * applied to data provided by the Unicode Consortium. - * - * For some graphical characters, the Unicode standard explicitly - * defines a character-cell width via the definition of the East Asian - * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. - * In all these cases, there is no ambiguity about which width a - * terminal shall use. For characters in the East Asian Ambiguous (A) - * class, the width choice depends purely on a preference of backward - * compatibility with either historic CJK or Western practice. - * Choosing single-width for these characters is easy to justify as - * the appropriate long-term solution, as the CJK practice of - * displaying these characters as double-width comes from historic - * implementation simplicity (8-bit encoded characters were displayed - * single-width and 16-bit ones double-width, even for Greek, - * Cyrillic, etc.) and not any typographic considerations. - * - * Much less clear is the choice of width for the Not East Asian - * (Neutral) class. Existing practice does not dictate a width for any - * of these characters. It would nevertheless make sense - * typographically to allocate two character cells to characters such - * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be - * represented adequately with a single-width glyph. The following - * routines at present merely assign a single-cell width to all - * neutral characters, in the interest of simplicity. This is not - * entirely satisfactory and should be reconsidered before - * establishing a formal standard in this area. At the moment, the - * decision which Not East Asian (Neutral) characters should be - * represented by double-width glyphs cannot yet be answered by - * applying a simple rule from the Unicode database content. Setting - * up a proper standard for the behavior of UTF-8 character terminals - * will require a careful analysis not only of each Unicode character, - * but also of each presentation form, something the author of these - * routines has avoided to do so far. - * - * http://www.unicode.org/unicode/reports/tr11/ - * - * Markus Kuhn -- 2007-05-26 (Unicode 5.0) - * - * Permission to use, copy, modify, and distribute this software - * for any purpose and without fee is hereby granted. The author - * disclaims all warranties with regard to this software. - * - * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c - */ - -#include "term-internal.h" - -struct interval { - wchar_t first; - wchar_t last; -}; - -/* auxiliary function for binary search in interval table */ -static int bisearch(wchar_t ucs, const struct interval *table, int max) { - int min = 0; - int mid; - - if (ucs < table[0].first || ucs > table[max].last) - return 0; - while (max >= min) { - mid = (min + max) / 2; - if (ucs > table[mid].last) - min = mid + 1; - else if (ucs < table[mid].first) - max = mid - 1; - else - return 1; - } - - return 0; -} - - -/* The following two functions define the column width of an ISO 10646 - * character as follows: - * - * - The null character (U+0000) has a column width of 0. - * - * - Other C0/C1 control characters and DEL will lead to a return - * value of -1. - * - * - Non-spacing and enclosing combining characters (general - * category code Mn or Me in the Unicode database) have a - * column width of 0. - * - * - SOFT HYPHEN (U+00AD) has a column width of 1. - * - * - Other format characters (general category code Cf in the Unicode - * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. - * - * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) - * have a column width of 0. - * - * - Spacing characters in the East Asian Wide (W) or East Asian - * Full-width (F) category as defined in Unicode Technical - * Report #11 have a column width of 2. - * - * - All remaining characters (including all printable - * ISO 8859-1 and WGL4 characters, Unicode control characters, - * etc.) have a column width of 1. - * - * This implementation assumes that wchar_t characters are encoded - * in ISO 10646. - */ - -int mk_wcwidth(wchar_t ucs) -{ - /* sorted list of non-overlapping intervals of non-spacing characters */ - /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ - static const struct interval combining[] = { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF } - }; - - /* test for 8-bit control characters */ - if (ucs == 0) - return 0; - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) - return -1; - - /* binary search in table of non-spacing characters */ - if (bisearch(ucs, combining, - sizeof(combining) / sizeof(struct interval) - 1)) - return 0; - - /* if we arrive here, ucs is not a combining or C0/C1 control character */ - - return 1 + - (ucs >= 0x1100 && - (ucs <= 0x115f || /* Hangul Jamo init. consonants */ - ucs == 0x2329 || ucs == 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && - ucs != 0x303f) || /* CJK ... Yi */ - (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ - (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ - (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ - (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ - (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ - (ucs >= 0xffe0 && ucs <= 0xffe6) || - (ucs >= 0x20000 && ucs <= 0x2fffd) || - (ucs >= 0x30000 && ucs <= 0x3fffd))); -} - - -int mk_wcswidth(const wchar_t *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} - - -/* - * The following functions are the same as mk_wcwidth() and - * mk_wcswidth(), except that spacing characters in the East Asian - * Ambiguous (A) category as defined in Unicode Technical Report #11 - * have a column width of 2. This variant might be useful for users of - * CJK legacy encodings who want to migrate to UCS without changing - * the traditional terminal character-width behaviour. It is not - * otherwise recommended for general use. - */ -int mk_wcwidth_cjk(wchar_t ucs) -{ - /* sorted list of non-overlapping intervals of East Asian Ambiguous - * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ - static const struct interval ambiguous[] = { - { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, - { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, - { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, - { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, - { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, - { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, - { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, - { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, - { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, - { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, - { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, - { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, - { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, - { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, - { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, - { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, - { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, - { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, - { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, - { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, - { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, - { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, - { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, - { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, - { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, - { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, - { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, - { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, - { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, - { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, - { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, - { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, - { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, - { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, - { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, - { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, - { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, - { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, - { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, - { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, - { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, - { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, - { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, - { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, - { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, - { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, - { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, - { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, - { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, - { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, - { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, - { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } - }; - - /* binary search in table of non-spacing characters */ - if (bisearch(ucs, ambiguous, - sizeof(ambiguous) / sizeof(struct interval) - 1)) - return 2; - - return mk_wcwidth(ucs); -} - - -int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) -{ - int w, width = 0; - - for (;*pwcs && n-- > 0; pwcs++) - if ((w = mk_wcwidth_cjk(*pwcs)) < 0) - return -1; - else - width += w; - - return width; -} diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h deleted file mode 100644 index 1a78a81184..0000000000 --- a/src/libsystemd-terminal/term.h +++ /dev/null @@ -1,183 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include -#include -#include -#include "util.h" - -typedef struct term_color term_color; -typedef struct term_attr term_attr; - -typedef struct term_utf8 term_utf8; -typedef struct term_seq term_seq; -typedef struct term_parser term_parser; - -typedef struct term_screen term_screen; - -/* - * Ageing - */ - -typedef uint64_t term_age_t; - -#define TERM_AGE_NULL 0 - -/* - * Attributes - */ - -enum { - /* special color-codes */ - TERM_CCODE_DEFAULT, /* default foreground/background color */ - TERM_CCODE_256, /* 256color code */ - TERM_CCODE_RGB, /* color is specified as RGB */ - - /* dark color-codes */ - TERM_CCODE_BLACK, - TERM_CCODE_RED, - TERM_CCODE_GREEN, - TERM_CCODE_YELLOW, - TERM_CCODE_BLUE, - TERM_CCODE_MAGENTA, - TERM_CCODE_CYAN, - TERM_CCODE_WHITE, /* technically: light grey */ - - /* light color-codes */ - TERM_CCODE_LIGHT_BLACK = TERM_CCODE_BLACK + 8, /* technically: dark grey */ - TERM_CCODE_LIGHT_RED = TERM_CCODE_RED + 8, - TERM_CCODE_LIGHT_GREEN = TERM_CCODE_GREEN + 8, - TERM_CCODE_LIGHT_YELLOW = TERM_CCODE_YELLOW + 8, - TERM_CCODE_LIGHT_BLUE = TERM_CCODE_BLUE + 8, - TERM_CCODE_LIGHT_MAGENTA = TERM_CCODE_MAGENTA + 8, - TERM_CCODE_LIGHT_CYAN = TERM_CCODE_CYAN + 8, - TERM_CCODE_LIGHT_WHITE = TERM_CCODE_WHITE + 8, - - TERM_CCODE_CNT, -}; - -struct term_color { - uint8_t ccode; - uint8_t c256; - uint8_t red; - uint8_t green; - uint8_t blue; -}; - -struct term_attr { - term_color fg; /* foreground color */ - term_color bg; /* background color */ - - unsigned int bold : 1; /* bold font */ - unsigned int italic : 1; /* italic font */ - unsigned int underline : 1; /* underline text */ - unsigned int inverse : 1; /* inverse fg/bg */ - unsigned int protect : 1; /* protect from erase */ - unsigned int blink : 1; /* blink text */ - unsigned int hidden : 1; /* hidden */ -}; - -void term_attr_to_argb32(const term_attr *attr, uint32_t *fg, uint32_t *bg, const uint8_t *palette); - -/* - * UTF-8 - */ - -struct term_utf8 { - uint32_t chars[5]; - uint32_t ucs4; - - unsigned int i_bytes : 3; - unsigned int n_bytes : 3; - unsigned int valid : 1; -}; - -size_t term_utf8_decode(term_utf8 *p, uint32_t **out_buf, char c); - -/* - * Parsers - */ - -int term_parser_new(term_parser **out, bool host); -term_parser *term_parser_free(term_parser *parser); -int term_parser_feed(term_parser *parser, const term_seq **seq_out, uint32_t raw); - -#define _term_parser_free_ _cleanup_(term_parser_freep) -DEFINE_TRIVIAL_CLEANUP_FUNC(term_parser*, term_parser_free); - -/* - * Screens - */ - -enum { - TERM_KBDMOD_IDX_SHIFT, - TERM_KBDMOD_IDX_CTRL, - TERM_KBDMOD_IDX_ALT, - TERM_KBDMOD_IDX_LINUX, - TERM_KBDMOD_IDX_CAPS, - TERM_KBDMOD_CNT, - - TERM_KBDMOD_SHIFT = 1 << TERM_KBDMOD_IDX_SHIFT, - TERM_KBDMOD_CTRL = 1 << TERM_KBDMOD_IDX_CTRL, - TERM_KBDMOD_ALT = 1 << TERM_KBDMOD_IDX_ALT, - TERM_KBDMOD_LINUX = 1 << TERM_KBDMOD_IDX_LINUX, - TERM_KBDMOD_CAPS = 1 << TERM_KBDMOD_IDX_CAPS, -}; - -typedef int (*term_screen_write_fn) (term_screen *screen, void *userdata, const void *buf, size_t size); -typedef int (*term_screen_cmd_fn) (term_screen *screen, void *userdata, unsigned int cmd, const term_seq *seq); - -int term_screen_new(term_screen **out, term_screen_write_fn write_fn, void *write_fn_data, term_screen_cmd_fn cmd_fn, void *cmd_fn_data); -term_screen *term_screen_ref(term_screen *screen); -term_screen *term_screen_unref(term_screen *screen); - -DEFINE_TRIVIAL_CLEANUP_FUNC(term_screen*, term_screen_unref); - -unsigned int term_screen_get_width(term_screen *screen); -unsigned int term_screen_get_height(term_screen *screen); -uint64_t term_screen_get_age(term_screen *screen); - -int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size); -int term_screen_feed_keyboard(term_screen *screen, - const uint32_t *keysyms, - size_t n_syms, - uint32_t ascii, - const uint32_t *ucs4, - unsigned int mods); -int term_screen_resize(term_screen *screen, unsigned int width, unsigned int height); -void term_screen_soft_reset(term_screen *screen); -void term_screen_hard_reset(term_screen *screen); - -int term_screen_set_answerback(term_screen *screen, const char *answerback); - -int term_screen_draw(term_screen *screen, - int (*draw_fn) (term_screen *screen, - void *userdata, - unsigned int x, - unsigned int y, - const term_attr *attr, - const uint32_t *ch, - size_t n_ch, - unsigned int ch_width), - void *userdata, - uint64_t *fb_age); diff --git a/src/libsystemd-terminal/test-term-page.c b/src/libsystemd-terminal/test-term-page.c deleted file mode 100644 index d59139b62d..0000000000 --- a/src/libsystemd-terminal/test-term-page.c +++ /dev/null @@ -1,459 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Terminal Page/Line/Cell/Char Tests - * This tests internals of terminal page, line, cell and char handling. It - * relies on some implementation details, so it might need to be updated if - * those internals are changed. They should be fairly obvious, though. - */ - -#include -#include -#include "macro.h" -#include "term-internal.h" - -#define MY_ASSERT_VALS __FILE__, __LINE__, __PRETTY_FUNCTION__ -#define MY_ASSERT_FORW _FILE, _LINE, _FUNC -#define MY_ASSERT_ARGS const char *_FILE, int _LINE, const char *_FUNC -#define MY_ASSERT(expr) \ - do { \ - if (_unlikely_(!(expr))) \ - log_assert_failed(#expr, _FILE, _LINE, _FUNC); \ - } while (false) \ - -/* - * Character Tests - * - * These tests rely on some implementation details of term_char_t, including - * the way we pack characters and the internal layout of "term_char_t". These - * tests have to be updated once we change the implementation. - */ - -#define PACK(v1, v2, v3) \ - TERM_CHAR_INIT( \ - (((((uint64_t)v1) & 0x1fffffULL) << 43) | \ - ((((uint64_t)v2) & 0x1fffffULL) << 22) | \ - ((((uint64_t)v3) & 0x1fffffULL) << 1) | \ - 0x1) \ - ) -#define PACK1(v1) PACK2((v1), 0x110000) -#define PACK2(v1, v2) PACK3((v1), (v2), 0x110000) -#define PACK3(v1, v2, v3) PACK((v1), (v2), (v3)) - -static void test_term_char_misc(void) { - term_char_t c, t; - - /* test TERM_CHAR_NULL handling */ - - c = TERM_CHAR_NULL; /* c is NULL */ - assert_se(term_char_same(c, TERM_CHAR_NULL)); - assert_se(term_char_equal(c, TERM_CHAR_NULL)); - assert_se(term_char_is_null(c)); - assert_se(term_char_is_null(TERM_CHAR_NULL)); - assert_se(!term_char_is_allocated(c)); - - /* test single char handling */ - - t = term_char_dup_append(c, 'A'); /* t is >A< now */ - assert_se(!term_char_same(c, t)); - assert_se(!term_char_equal(c, t)); - assert_se(!term_char_is_allocated(t)); - assert_se(!term_char_is_null(t)); - - /* test basic combined char handling */ - - t = term_char_dup_append(t, '~'); - t = term_char_dup_append(t, '^'); /* t is >A~^< now */ - assert_se(!term_char_same(c, t)); - assert_se(!term_char_is_allocated(t)); - assert_se(!term_char_is_null(t)); - - c = term_char_dup_append(c, 'A'); - c = term_char_dup_append(c, '~'); - c = term_char_dup_append(c, '^'); /* c is >A~^< now */ - assert_se(term_char_same(c, t)); - assert_se(term_char_equal(c, t)); - - /* test more than 2 comb-chars so the chars are allocated */ - - t = term_char_dup_append(t, '`'); /* t is >A~^`< now */ - c = term_char_dup_append(c, '`'); /* c is >A~^`< now */ - assert_se(!term_char_same(c, t)); - assert_se(term_char_equal(c, t)); - - /* test dup_append() on allocated chars */ - - term_char_free(t); - t = term_char_dup_append(c, '"'); /* t is >A~^`"< now */ - assert_se(!term_char_same(c, t)); - assert_se(!term_char_equal(c, t)); - c = term_char_merge(c, '"'); /* c is >A~^`"< now */ - assert_se(!term_char_same(c, t)); - assert_se(term_char_equal(c, t)); - - term_char_free(t); - term_char_free(c); -} - -static void test_term_char_packing(void) { - uint32_t seqs[][1024] = { - { -1 }, - { 0, -1 }, - { 'A', '~', -1 }, - { 'A', '~', 0, -1 }, - { 'A', '~', 'a', -1 }, - }; - term_char_t res[] = { - TERM_CHAR_NULL, - PACK1(0), - PACK2('A', '~'), - PACK3('A', '~', 0), - PACK3('A', '~', 'a'), - }; - uint32_t next; - unsigned int i, j; - term_char_t c = TERM_CHAR_NULL; - - /* - * This creates term_char_t objects based on the data in @seqs and - * compares the result to @res. Only basic packed types are tested, no - * allocations are done. - */ - - for (i = 0; i < ELEMENTSOF(seqs); ++i) { - for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) { - next = seqs[i][j]; - if (next == (uint32_t)-1) - break; - - c = term_char_merge(c, next); - } - - assert_se(!memcmp(&c, &res[i], sizeof(c))); - c = term_char_free(c); - } -} - -static void test_term_char_allocating(void) { - uint32_t seqs[][1024] = { - { 0, -1 }, - { 'A', '~', -1 }, - { 'A', '~', 0, -1 }, - { 'A', '~', 'a', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 }, - /* exceeding implementation-defined soft-limit of 64 */ - { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 }, - }; - term_char_t res[] = { - PACK1(0), - PACK2('A', '~'), - PACK3('A', '~', 0), - PACK3('A', '~', 'a'), - TERM_CHAR_NULL, /* allocated */ - TERM_CHAR_NULL, /* allocated */ - TERM_CHAR_NULL, /* allocated */ - }; - uint32_t str[][1024] = { - { 0, -1 }, - { 'A', '~', -1 }, - { 'A', '~', 0, -1 }, - { 'A', '~', 'a', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', -1 }, - { 'A', '~', 'a', 'b', 'c', 'd', 0, '^', -1 }, - { 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', - 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', -1 }, - }; - size_t n; - uint32_t next; - unsigned int i, j; - const uint32_t *t; - - /* - * This builds term_char_t objects based on the data in @seqs. It - * compares the result to @res for packed chars, otherwise it requires - * them to be allocated. - * After that, we resolve the UCS-4 string and compare it to the - * expected strings in @str. - */ - - for (i = 0; i < ELEMENTSOF(seqs); ++i) { - _term_char_free_ term_char_t c = TERM_CHAR_NULL; - - for (j = 0; j < ELEMENTSOF(seqs[i]); ++j) { - next = seqs[i][j]; - if (next == (uint32_t)-1) - break; - - c = term_char_merge(c, next); - } - - /* we use TERM_CHAR_NULL as marker for allocated chars here */ - if (term_char_is_null(res[i])) - assert_se(term_char_is_allocated(c)); - else - assert_se(!memcmp(&c, &res[i], sizeof(c))); - - t = term_char_resolve(c, &n, NULL); - for (j = 0; j < ELEMENTSOF(str[i]); ++j) { - next = str[i][j]; - if (next == (uint32_t)-1) - break; - - assert_se(t[j] == next); - } - - assert_se(n == j); - } -} - -/* - * Line Tests - * - * The following tests work on term_line objects and verify their behavior when - * we modify them. To verify and set line layouts, we have two simple helpers - * to avoid harcoding the cell-verification all the time: - * line_set(): Set a line to a given layout - * line_assert(): Verify that a line has a given layout - * - * These functions take the line-layout encoded as a string and verify it - * against, or set it on, a term_line object. The format used to describe a - * line looks like this: - * example: "| | A | | | | | | 10 *AB* |" - * - * The string describes the contents of all cells of a line, separated by - * pipe-symbols ('|'). Whitespace are ignored, the leading pipe-symbol is - * optional. - * The description of each cell can contain an arbitrary amount of characters - * in the range 'A'-'Z', 'a'-'z'. All those are combined and used as term_char_t - * on this cell. Any numbers in the description are combined and are used as - * cell-age. - * The occurrence of a '*'-symbol marks the cell as bold, '/' marks it as italic. - * You can use those characters multiple times, but only the first one has an - * effect. - * For further symbols, see parse_attr(). - * - * Therefore, the following descriptions are equivalent: - * 1) "| | /A* | | | | | | 10 *AB* |" - * 2) "| | /A** | | | | | | 10 *AB* |" - * 3) "| | A* // | | | | | | 10 *AB* |" - * 4) "| | A* // | | | | | | 1 *AB* 0 |" - * 5) "| | A* // | | | | | | A1B0* |" - * - * The parser isn't very strict about placement of alpha/numerical characters, - * but simply appends all found chars. Don't make use of that feature! It's - * just a stupid parser to simplify these tests. Make them readable! - */ - -static void parse_attr(char c, term_char_t *ch, term_attr *attr, term_age_t *age) { - switch (c) { - case ' ': - /* ignore */ - break; - case '0' ... '9': - /* increase age */ - *age = *age * 10; - *age = *age + c - '0'; - break; - case 'A' ... 'Z': - case 'a' ... 'z': - /* add to character */ - *ch = term_char_merge(*ch, c); - break; - case '*': - attr->bold = true; - break; - case '/': - attr->italic = true; - break; - default: - assert_se(0); - break; - } -} - -static void cell_assert(MY_ASSERT_ARGS, term_cell *c, term_char_t ch, const term_attr *attr, term_age_t age) { - MY_ASSERT(term_char_equal(c->ch, ch)); - MY_ASSERT(!memcmp(&c->attr, attr, sizeof(*attr))); - MY_ASSERT(c->age == age); -} -#define CELL_ASSERT(_cell, _ch, _attr, _age) cell_assert(MY_ASSERT_VALS, (_cell), (_ch), (_attr), (_age)) - -static void line_assert(MY_ASSERT_ARGS, term_line *l, const char *str, unsigned int fill) { - unsigned int cell_i; - term_char_t ch = TERM_CHAR_NULL; - term_attr attr = { }; - term_age_t age = TERM_AGE_NULL; - char c; - - assert_se(l->fill == fill); - - /* skip leading whitespace */ - while (*str == ' ') - ++str; - - /* skip leading '|' */ - if (*str == '|') - ++str; - - cell_i = 0; - while ((c = *str++)) { - switch (c) { - case '|': - /* end of cell-description; compare it */ - assert_se(cell_i < l->n_cells); - cell_assert(MY_ASSERT_FORW, - &l->cells[cell_i], - ch, - &attr, - age); - - ++cell_i; - ch = term_char_free(ch); - zero(attr); - age = TERM_AGE_NULL; - break; - default: - parse_attr(c, &ch, &attr, &age); - break; - } - } - - assert_se(cell_i == l->n_cells); -} -#define LINE_ASSERT(_line, _str, _fill) line_assert(MY_ASSERT_VALS, (_line), (_str), (_fill)) - -static void line_set(term_line *l, unsigned int pos, const char *str, bool insert_mode) { - term_char_t ch = TERM_CHAR_NULL; - term_attr attr = { }; - term_age_t age = TERM_AGE_NULL; - char c; - - while ((c = *str++)) - parse_attr(c, &ch, &attr, &age); - - term_line_write(l, pos, ch, 1, &attr, age, insert_mode); -} - -static void line_resize(term_line *l, unsigned int width, const term_attr *attr, term_age_t age) { - assert_se(term_line_reserve(l, width, attr, age, width) >= 0); - term_line_set_width(l, width); -} - -static void test_term_line_misc(void) { - term_line *l; - - assert_se(term_line_new(&l) >= 0); - assert_se(!term_line_free(l)); - - assert_se(term_line_new(NULL) < 0); - assert_se(!term_line_free(NULL)); - - assert_se(term_line_new(&l) >= 0); - assert_se(l->n_cells == 0); - assert_se(l->fill == 0); - assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0); - assert_se(l->n_cells == 16); - assert_se(l->fill == 0); - assert_se(term_line_reserve(l, 512, NULL, 0, 0) >= 0); - assert_se(l->n_cells == 512); - assert_se(l->fill == 0); - assert_se(term_line_reserve(l, 16, NULL, 0, 0) >= 0); - assert_se(l->n_cells == 512); - assert_se(l->fill == 0); - assert_se(!term_line_free(l)); -} - -static void test_term_line_ops(void) { - term_line *l; - term_attr attr_regular = { }; - term_attr attr_bold = { .bold = true }; - term_attr attr_italic = { .italic = true }; - - assert_se(term_line_new(&l) >= 0); - line_resize(l, 8, NULL, 0); - assert_se(l->n_cells == 8); - - LINE_ASSERT(l, "| | | | | | | | |", 0); - - term_line_write(l, 4, TERM_CHAR_NULL, 0, NULL, TERM_AGE_NULL, 0); - LINE_ASSERT(l, "| | | | | | | | |", 5); - - term_line_write(l, 1, PACK1('A'), 1, NULL, TERM_AGE_NULL, 0); - LINE_ASSERT(l, "| |A| | | | | | |", 5); - - term_line_write(l, 8, PACK2('A', 'B'), 1, NULL, TERM_AGE_NULL, 0); - LINE_ASSERT(l, "| |A| | | | | | |", 5); - - term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_regular, 10, 0); - LINE_ASSERT(l, "| |A| | | | | | 10 AB |", 8); - - term_line_write(l, 7, PACK2('A', 'B'), 1, &attr_bold, 10, 0); - LINE_ASSERT(l, "| |A| | | | | | 10 *AB* |", 8); - - term_line_reset(l, NULL, TERM_AGE_NULL); - - LINE_ASSERT(l, "| | | | | | | | |", 0); - line_set(l, 2, "*wxyz* 8", 0); - line_set(l, 3, "/wxyz/ 8", 0); - LINE_ASSERT(l, "| | | *wxyz* 8 | /wxyz/ 8 | | | | |", 4); - line_set(l, 2, "*abc* 9", true); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | 9 |", 5); - line_set(l, 7, "*abc* 10", true); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 9 | *abc* 10 |", 8); - - term_line_erase(l, 6, 1, NULL, 11, 0); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 11 | *abc* 10 |", 8); - term_line_erase(l, 6, 2, &attr_italic, 12, 0); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 12 // |", 6); - term_line_erase(l, 7, 2, &attr_regular, 13, 0); - LINE_ASSERT(l, "| | | *abc* 9 | *wxyz* 9 | /wxyz/ 9 | 9 | 12 // | 13 |", 6); - term_line_delete(l, 1, 3, &attr_bold, 14); - LINE_ASSERT(l, "| | /wxyz/ 14 | 14 | 14 // | 14 | 14 ** | 14 ** | 14 ** |", 3); - term_line_insert(l, 2, 2, &attr_regular, 15); - LINE_ASSERT(l, "| | /wxyz/ 14 | 15 | 15 | 15 | 15 // | 15 | 15 ** |", 5); - - assert_se(!term_line_free(l)); -} - -int main(int argc, char *argv[]) { - test_term_char_misc(); - test_term_char_packing(); - test_term_char_allocating(); - - test_term_line_misc(); - test_term_line_ops(); - - return 0; -} diff --git a/src/libsystemd-terminal/test-term-parser.c b/src/libsystemd-terminal/test-term-parser.c deleted file mode 100644 index e40b267b1c..0000000000 --- a/src/libsystemd-terminal/test-term-parser.c +++ /dev/null @@ -1,141 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Terminal Parser Tests - */ - -#include -#include -#include "macro.h" -#include "term-internal.h" -#include "utf8.h" - -static void test_term_utf8_invalid(void) { - term_utf8 p = { }; - uint32_t *res; - size_t len; - - len = term_utf8_decode(NULL, NULL, 0); - assert_se(!len); - - len = term_utf8_decode(&p, NULL, 0); - assert_se(len == 1); - - res = NULL; - len = term_utf8_decode(NULL, &res, 0); - assert_se(!len); - assert_se(res != NULL); - assert_se(!*res); - - len = term_utf8_decode(&p, &res, 0); - assert_se(len == 1); - assert_se(res != NULL); - assert_se(!*res); - - len = term_utf8_decode(&p, &res, 0xCf); - assert_se(len == 0); - assert_se(res != NULL); - assert_se(!*res); - - len = term_utf8_decode(&p, &res, 0); - assert_se(len == 2); - assert_se(res != NULL); - assert_se(res[0] == 0xCf && res[1] == 0); -} - -static void test_term_utf8_range(void) { - term_utf8 p = { }; - uint32_t *res; - char u8[4]; - uint32_t i, j; - size_t ulen, len; - - /* Convert all ucs-4 chars to utf-8 and back */ - - for (i = 0; i < 0x10FFFF; ++i) { - ulen = utf8_encode_unichar(u8, i); - if (!ulen) - continue; - - for (j = 0; j < ulen; ++j) { - len = term_utf8_decode(&p, &res, u8[j]); - if (len < 1) { - assert_se(j + 1 != ulen); - continue; - } - - assert_se(j + 1 == ulen); - assert_se(len == 1 && *res == i); - assert_se(i <= 127 || ulen >= 2); - } - } -} - -static void test_term_utf8_mix(void) { - static const char source[] = { - 0x00, /* normal 0 */ - 0xC0, 0x80, /* overlong 0 */ - 0xC0, 0x81, /* overlong 1 */ - 0xE0, 0x80, 0x81, /* overlong 1 */ - 0xF0, 0x80, 0x80, 0x81, /* overlong 1 */ - 0xC0, 0x00, /* invalid continuation */ - 0xC0, 0xC0, 0x81, /* invalid continuation with a following overlong 1 */ - 0xF8, 0x80, 0x80, 0x80, 0x81, /* overlong 1 with 5 bytes */ - 0xE0, 0x80, 0xC0, 0x81, /* invalid 3-byte followed by valid 2-byte */ - 0xF0, 0x80, 0x80, 0xC0, 0x81, /* invalid 4-byte followed by valid 2-byte */ - }; - static const uint32_t result[] = { - 0x0000, - 0x0000, - 0x0001, - 0x0001, - 0x0001, - 0x00C0, 0x0000, - 0x00C0, 0x0001, - 0x00F8, 0x0080, 0x0080, 0x0080, 0x0081, - 0x00E0, 0x0080, 0x0001, - 0x00F0, 0x0080, 0x0080, 0x0001, - }; - term_utf8 p = { }; - uint32_t *res; - unsigned int i, j; - size_t len; - - for (i = 0, j = 0; i < sizeof(source); ++i) { - len = term_utf8_decode(&p, &res, source[i]); - if (len < 1) - continue; - - assert_se(j + len <= ELEMENTSOF(result)); - assert_se(!memcmp(res, &result[j], sizeof(uint32_t) * len)); - j += len; - } - - assert_se(j == ELEMENTSOF(result)); -} - -int main(int argc, char *argv[]) { - test_term_utf8_invalid(); - test_term_utf8_range(); - test_term_utf8_mix(); - - return 0; -} diff --git a/src/libsystemd-terminal/test-unifont.c b/src/libsystemd-terminal/test-unifont.c deleted file mode 100644 index 2366d38574..0000000000 --- a/src/libsystemd-terminal/test-unifont.c +++ /dev/null @@ -1,125 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Test Unifont Helper - * This tries opening the binary unifont glyph-array and renders some glyphs. - * The glyphs are then compared to hard-coded glyphs. - */ - -#include -#include -#include "macro.h" -#include "unifont-def.h" -#include "unifont.h" - -static void render(char *w, const unifont_glyph *g) { - unsigned int i, j; - const uint8_t *d = g->data; - - for (j = 0; j < 16; ++j) { - for (i = 0; i < 8 * g->cwidth; ++i) { - if (d[i / 8] & (1 << (7 - i % 8))) - *w++ = '#'; - else - *w++ = ' '; - } - *w++ = '\n'; - d += g->stride; - } - - *w++ = 0; -} - -static void test_unifont(void) { - char buf[4096]; - unifont_glyph g; - unifont *u; - - assert_se(unifont_new(&u) >= 0); - - /* lookup invalid font */ - assert_se(unifont_lookup(u, &g, 0xffffffffU) < 0); - - /* lookup and render 'A' */ - assert_se(unifont_lookup(u, &g, 'A') >= 0); - assert_se(g.width == 8); - assert_se(g.height == 16); - assert_se(g.stride >= 1); - assert_se(g.cwidth == 1); - assert_se(g.data != NULL); - render(buf, &g); - assert_se(!strcmp(buf, - " \n" - " \n" - " \n" - " \n" - " ## \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " ###### \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " \n" - " \n" - )); - - /* lookup and render '什' */ - assert_se(unifont_lookup(u, &g, 0x4ec0) >= 0); - assert_se(g.width == 16); - assert_se(g.height == 16); - assert_se(g.stride >= 2); - assert_se(g.cwidth == 2); - assert_se(g.data != NULL); - render(buf, &g); - assert_se(!strcmp(buf, - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " ## # \n" - " ## ########## \n" - " # # # \n" - "# # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - " # # \n" - )); - - unifont_unref(u); -} - -int main(int argc, char **argv) { - if (access(UNIFONT_PATH, F_OK)) - return 77; - - test_unifont(); - - return 0; -} diff --git a/src/libsystemd-terminal/unifont-def.h b/src/libsystemd-terminal/unifont-def.h deleted file mode 100644 index 3847a2cf6c..0000000000 --- a/src/libsystemd-terminal/unifont-def.h +++ /dev/null @@ -1,137 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include -#include -#include -#include "sparse-endian.h" -#include "util.h" - -typedef struct unifont_header unifont_header; -typedef struct unifont_glyph_header unifont_glyph_header; - -/* - * Unifont: On-disk data - * Conventional font-formats have the problem that you have to pre-render each - * glyph before you can use it. If you just need one glyph, you have to parse - * the font-file until you found that glyph. - * GNU-Unifont is a bitmap font with very good Unicode coverage. All glyphs are - * (n*8)x16 bitmaps. Our on-disk data stores all those glyphs pre-compiled with - * fixed offsets. Therefore, the font-file can be mmap()ed and all glyphs can - * be accessed in O(1) (because all glyphs have the same size and thus their - * offsets can be easily computed). This guarantees, that the kernel only loads - * the pages that are really accessed. Thus, we have a far lower overhead than - * traditional font-formats like BDF. Furthermore, the backing file is read-only - * and can be shared in memory between multiple users. - * - * The binary-format starts with a fixed header: - * - * | 2bytes | 2bytes | 2bytes | 2bytes | - * - * +-----------------------------------+ - * | SIGNATURE | 8 bytes - * +-----------------+-----------------+ - * | COMPAT FLAGS | INCOMPAT FLAGS | 8 bytes - * +-----------------+--------+--------+ - * | HEADER SIZE |GH-SIZE |G-STRIDE| 8 bytes - * +-----------------+--------+--------+ - * | GLYPH BODY SIZE | 8 bytes - * +-----------------------------------+ - * - * * The 8 bytes signature must be set to the ASCII string "DVDHRMUF". - * * The 4 bytes compatible-flags field contains flags for new features that - * might be added in the future and which are compatible to older parsers. - * * The 4 bytes incompatible-flags field contains flags for new features that - * might be added in the future and which are incompatible to old parses. - * Thus, if you encounter an unknown bit set, you must abort! - * * The 4 bytes header-size field contains the size of the header in bytes. It - * must be at least 32 (the size of this fixed header). If new features are - * added, it might be increased. It can also be used to add padding to the - * end of the header. - * * The 2 bytes glyph-header-size field specifies the size of each glyph - * header in bytes (see below). - * * The 2 bytes glyph-stride field specifies the stride of each line of glyph - * data in "bytes per line". - * * The 8 byte glyph-body-size field defines the size of each glyph body in - * bytes. - * - * After the header, the file can contain padding bytes, depending on the - * header-size field. Everything beyond the header+padding is treated as a big - * array of glyphs. Each glyph looks like this: - * - * | 1 byte | - * - * +-----------------------------------+ - * | WIDTH | 1 byte - * +-----------------------------------+ - * ~ PADDING ~ - * +-----------------------------------+ - * ~ ~ - * ~ ~ - * ~ DATA ~ - * ~ ~ - * ~ ~ - * +-----------------------------------+ - * - * * The first byte specifies the width of the glyph. If it is 0, the glyph - * must be treated as non-existent. - * All glyphs are "8*n" pixels wide and "16" pixels high. The width-field - * specifies the width multiplier "n". - * * After the width field padding might be added. This depends on the global - * glyph-header-size field. It defines the total size of each glyph-header. - * After the glyph-header+padding, the data-field starts. - * * The data-field contains a byte-array of bitmap data. The array is always - * as big as specified in the global glyph-body-size header field. This might - * include padding. - * The array contains all 16 lines of bitmap information for that glyph. The - * stride is given in the global glyph-stride header field. This can be used - * to add padding after each line. - * Each line is encoded as 1 bit per pixel bitmap. That is, each byte encodes - * data for 8 pixels (left most pixel is encoded in the LSB, right most pixel - * in the MSB). The width field defines the number of bytes valid per line. - * For width==1, you need 1 byte to encode the 8 pixels. The stride defines - * where the encoding of the next line starts. - * Any data beyond the 16th line is padding and must be ignored. - */ - -/* path to binary file */ -#define UNIFONT_PATH "/usr/share/systemd/unifont-glyph-array.bin" - -/* header-size of version 1 */ -#define UNIFONT_HEADER_SIZE_MIN 32 - -struct unifont_header { - /* fields available in version 1 */ - uint8_t signature[8]; - le32_t compatible_flags; - le32_t incompatible_flags; - le32_t header_size; - le16_t glyph_header_size; - le16_t glyph_stride; - le64_t glyph_body_size; -} _packed_; - -struct unifont_glyph_header { - /* fields available in version 1 */ - uint8_t width; -} _packed_; diff --git a/src/libsystemd-terminal/unifont.c b/src/libsystemd-terminal/unifont.c deleted file mode 100644 index 0da81e8ff2..0000000000 --- a/src/libsystemd-terminal/unifont.c +++ /dev/null @@ -1,238 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -/* - * Unifont - * This implements the unifont glyph-array parser and provides it via a simple - * API to the caller. No heavy transformations are performed so glyph-lookups - * stay as fast as possible. - */ - -#include -#include -#include -#include -#include -#include -#include "macro.h" -#include "unifont-def.h" -#include "unifont.h" -#include "util.h" - -struct unifont { - unsigned long ref; - - int fd; - const uint8_t *map; - size_t size; - - unifont_header header; - const void *glyphs; /* unaligned! */ - size_t n_glyphs; - size_t glyphsize; -}; - -static int unifont_fetch_header(unifont *u) { - unifont_header h = { }; - uint64_t glyphsize; - - if (u->size < UNIFONT_HEADER_SIZE_MIN) - return -EBFONT; - - assert_cc(sizeof(h) >= UNIFONT_HEADER_SIZE_MIN); - memcpy(&h, u->map, UNIFONT_HEADER_SIZE_MIN); - - h.compatible_flags = le32toh(h.compatible_flags); - h.incompatible_flags = le32toh(h.incompatible_flags); - h.header_size = le32toh(h.header_size); - h.glyph_header_size = le16toh(h.glyph_header_size); - h.glyph_stride = le16toh(h.glyph_stride); - h.glyph_body_size = le64toh(h.glyph_body_size); - - if (memcmp(h.signature, "DVDHRMUF", 8)) - return -EBFONT; - if (h.incompatible_flags != 0) - return -EBFONT; - if (h.header_size < UNIFONT_HEADER_SIZE_MIN || h.header_size > u->size) - return -EBFONT; - if (h.glyph_header_size + h.glyph_body_size < h.glyph_header_size) - return -EBFONT; - if (h.glyph_stride * 16ULL > h.glyph_body_size) - return -EBFONT; - - glyphsize = h.glyph_header_size + h.glyph_body_size; - - if (glyphsize == 0 || glyphsize > u->size - h.header_size) { - u->n_glyphs = 0; - } else { - u->glyphs = u->map + h.header_size; - u->n_glyphs = (u->size - h.header_size) / glyphsize; - u->glyphsize = glyphsize; - } - - memcpy(&u->header, &h, sizeof(h)); - return 0; -} - -static int unifont_fetch_glyph(unifont *u, unifont_glyph_header *out_header, const void **out_body, uint32_t ucs4) { - unifont_glyph_header glyph_header = { }; - const void *glyph_body = NULL; - const uint8_t *p; - - if (ucs4 >= u->n_glyphs) - return -ENOENT; - - p = u->glyphs; - - /* copy glyph-header data */ - p += ucs4 * u->glyphsize; - memcpy(&glyph_header, p, MIN(sizeof(glyph_header), u->header.glyph_header_size)); - - /* copy glyph-body pointer */ - p += u->header.glyph_header_size; - glyph_body = p; - - if (glyph_header.width < 1) - return -ENOENT; - if (glyph_header.width > u->header.glyph_stride) - return -EBFONT; - - memcpy(out_header, &glyph_header, sizeof(glyph_header)); - *out_body = glyph_body; - return 0; -} - -int unifont_new(unifont **out) { - _cleanup_(unifont_unrefp) unifont *u = NULL; - struct stat st; - int r; - - assert_return(out, -EINVAL); - - u = new0(unifont, 1); - if (!u) - return -ENOMEM; - - u->ref = 1; - u->fd = -1; - u->map = MAP_FAILED; - - u->fd = open(UNIFONT_PATH, O_RDONLY | O_CLOEXEC | O_NOCTTY); - if (u->fd < 0) - return -errno; - - r = fstat(u->fd, &st); - if (r < 0) - return -errno; - - u->size = st.st_size; - u->map = mmap(NULL, u->size, PROT_READ, MAP_PRIVATE, u->fd, 0); - if (u->map == MAP_FAILED) - return -errno; - - r = unifont_fetch_header(u); - if (r < 0) - return r; - - *out = u; - u = NULL; - return 0; -} - -unifont *unifont_ref(unifont *u) { - if (!u || !u->ref) - return NULL; - - ++u->ref; - - return u; -} - -unifont *unifont_unref(unifont *u) { - if (!u || !u->ref || --u->ref) - return NULL; - - if (u->map != MAP_FAILED) - munmap((void*)u->map, u->size); - u->fd = safe_close(u->fd); - free(u); - - return NULL; -} - -unsigned int unifont_get_width(unifont *u) { - assert(u); - - return 8U; -} - -unsigned int unifont_get_height(unifont *u) { - assert(u); - - return 16U; -} - -unsigned int unifont_get_stride(unifont *u) { - assert(u); - - return u->header.glyph_stride; -} - -int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4) { - unifont_glyph_header h = { }; - const void *b = NULL; - unifont_glyph g = { }; - int r; - - assert_return(u, -EINVAL); - - r = unifont_fetch_glyph(u, &h, &b, ucs4); - if (r < 0) - return r; - - g.width = h.width * 8U; - g.height = 16U; - g.stride = u->header.glyph_stride; - g.cwidth = h.width; - g.data = b; - - if (out) - memcpy(out, &g, sizeof(g)); - return 0; -} - -void unifont_fallback(unifont_glyph *out) { - static const uint8_t fallback_data[] = { - /* unifont 0xfffd '�' (unicode replacement character) */ - 0x00, 0x00, 0x00, 0x7e, - 0x66, 0x5a, 0x5a, 0x7a, - 0x76, 0x76, 0x7e, 0x76, - 0x76, 0x7e, 0x00, 0x00, - }; - - assert(out); - - out->width = 8; - out->height = 16; - out->stride = 1; - out->cwidth = 1; - out->data = fallback_data; -} diff --git a/src/libsystemd-terminal/unifont.h b/src/libsystemd-terminal/unifont.h deleted file mode 100644 index 74ee5ecb3c..0000000000 --- a/src/libsystemd-terminal/unifont.h +++ /dev/null @@ -1,54 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 David Herrmann - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#pragma once - -#include - -typedef struct unifont unifont; -typedef struct unifont_glyph unifont_glyph; - -/* - * Unifont - * The unifont API provides a glyph-lookup for bitmap fonts which can be used - * as fallback if no system-font is available or if you don't want to deal with - * full font renderers. - */ - -struct unifont_glyph { - unsigned int width; - unsigned int height; - unsigned int stride; - unsigned int cwidth; - const void *data; /* unaligned! */ -}; - -int unifont_new(unifont **out); -unifont *unifont_ref(unifont *u); -unifont *unifont_unref(unifont *u); - -DEFINE_TRIVIAL_CLEANUP_FUNC(unifont*, unifont_unref); - -unsigned int unifont_get_width(unifont *u); -unsigned int unifont_get_height(unifont *u); -unsigned int unifont_get_stride(unifont *u); -int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4); -void unifont_fallback(unifont_glyph *out); diff --git a/tools/compile-unifont.py b/tools/compile-unifont.py deleted file mode 100755 index 5464c53e7f..0000000000 --- a/tools/compile-unifont.py +++ /dev/null @@ -1,119 +0,0 @@ -# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2013-2014 David Herrmann -# -# systemd is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# systemd is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with systemd; If not, see . - -# -# Parse a unifont.hex file and produce a compressed binary-format. -# - -from __future__ import print_function -import re -import sys -import fileinput -import struct - -# -# Write "bits" array as binary output. -# - - -write = getattr(sys.stdout, 'buffer', sys.stdout).write - -def write_bin_entry(entry): - l = len(entry) - if l != 32 and l != 64: - entry = "0" * 64 - l = 0 - elif l < 64: - entry += "0" * (64 - l) - - write(struct.pack('B', int(l / 32))) # width - write(struct.pack('B', 0)) # padding - write(struct.pack('H', 0)) # padding - write(struct.pack('I', 0)) # padding - - i = 0 - for j in range(0, 16): - for k in range(0, 2): - if l <= k * 16 * 2: - c = 0 - else: - c = int(entry[i:i+2], 16) - i += 2 - - write(struct.pack('B', c)) - -def write_bin(bits): - write(struct.pack('B', 0x44)) # ASCII: 'D' - write(struct.pack('B', 0x56)) # ASCII: 'V' - write(struct.pack('B', 0x44)) # ASCII: 'D' - write(struct.pack('B', 0x48)) # ASCII: 'H' - write(struct.pack('B', 0x52)) # ASCII: 'R' - write(struct.pack('B', 0x4d)) # ASCII: 'M' - write(struct.pack('B', 0x55)) # ASCII: 'U' - write(struct.pack('B', 0x46)) # ASCII: 'F' - write(struct.pack('