mirror of
https://github.com/systemd/systemd.git
synced 2024-11-23 18:23:32 +08:00
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.
This commit is contained in:
parent
2d5c8a2756
commit
d537694a98
7
.gitignore
vendored
7
.gitignore
vendored
@ -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
|
||||
|
140
Makefile.am
140
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
|
||||
|
@ -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
|
||||
|
22
configure.ac
22
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}
|
||||
|
@ -1 +0,0 @@
|
||||
../Makefile
|
@ -1,81 +0,0 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#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);
|
||||
}
|
||||
}
|
@ -1,284 +0,0 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
||||
}
|
@ -1,358 +0,0 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#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);
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
|
||||
|
||||
/***
|
||||
This file is part of systemd.
|
||||
|
||||
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#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);
|
1
src/libsystemd-terminal/.gitignore
vendored
1
src/libsystemd-terminal/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/unifont-glyph-array.bin
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <linux/kd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#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) ? : "<unknown>");
|
||||
|
||||
/* 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 ? : "<none>");
|
||||
|
||||
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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <libudev.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <libudev.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#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);
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <libevdev/libevdev.h>
|
||||
#include <libudev.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#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);
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <libudev.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#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;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#include <libudev.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* IDev
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libudev.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdbool.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#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);
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <drm_fourcc.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/kd.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#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 ? : "<none>");
|
||||
|
||||
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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <libudev.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#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);
|
File diff suppressed because it is too large
Load Diff
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <stdbool.h>
|
||||
#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);
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
};
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#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);
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* Terminal Parser Tests
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#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_;
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
/*
|
||||
* 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 <endian.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#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 '<27>' (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;
|
||||
}
|
@ -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 <dh.herrmann@gmail.com>
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
***/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
@ -1,119 +0,0 @@
|
||||
# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# Copyright 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# 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('<I', 0)) # compatible-flags
|
||||
write(struct.pack('<I', 0)) # incompatible-flags
|
||||
write(struct.pack('<I', 32)) # header-size
|
||||
write(struct.pack('<H', 8)) # glyph-header-size
|
||||
write(struct.pack('<H', 2)) # glyph-stride
|
||||
write(struct.pack('<Q', 32)) # glyph-body-size
|
||||
|
||||
# write glyphs
|
||||
for idx in range(len(bits)):
|
||||
write_bin_entry(bits[idx])
|
||||
|
||||
#
|
||||
# Parse hex file into "bits" array
|
||||
#
|
||||
|
||||
def parse_hex_line(bits, line):
|
||||
m = re.match(r"^([0-9A-Fa-f]+):([0-9A-Fa-f]+)$", line)
|
||||
if m == None:
|
||||
return
|
||||
|
||||
idx = int(m.group(1), 16)
|
||||
val = m.group(2)
|
||||
|
||||
# insert skipped lines
|
||||
for i in range(len(bits), idx):
|
||||
bits.append("")
|
||||
|
||||
bits.insert(idx, val)
|
||||
|
||||
def parse_hex():
|
||||
bits = []
|
||||
|
||||
for line in sys.stdin:
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("#"):
|
||||
continue
|
||||
|
||||
parse_hex_line(bits, line)
|
||||
|
||||
return bits
|
||||
|
||||
#
|
||||
# In normal mode we simply read line by line from standard-input and write the
|
||||
# binary-file to standard-output.
|
||||
#
|
||||
|
||||
if __name__ == "__main__":
|
||||
bits = parse_hex()
|
||||
write_bin(bits)
|
1
units/user/.gitignore
vendored
1
units/user/.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
/systemd-exit.service
|
||||
/systemd-bus-proxyd.service
|
||||
/systemd-consoled.service
|
||||
|
@ -1,15 +0,0 @@
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=Console Manager and Terminal Emulator
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
Restart=always
|
||||
RestartSec=0
|
||||
ExecStart=@rootlibexecdir@/systemd-consoled
|
Loading…
Reference in New Issue
Block a user