Merge pull request #747 from dvdhrm/consoled
terminal: drop unfinished code
This commit is contained in:
commit
48579c4b6a
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 a new issue