Merge pull request #747 from dvdhrm/consoled

terminal: drop unfinished code
This commit is contained in:
David Herrmann
2015-07-29 12:03:34 +02:00
44 changed files with 0 additions and 24559 deletions
-7
View File
@@ -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
View File
@@ -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
-3
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
../Makefile
-81
View File
@@ -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);
}
}
-284
View File
@@ -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;
}
-279
View File
@@ -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;
}
}
-358
View File
@@ -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;
}
-167
View File
@@ -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);
}
-66
View File
@@ -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;
}
-164
View File
@@ -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
View File
@@ -1 +0,0 @@
/unifont-glyph-array.bin
-488
View File
@@ -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
-251
View File
@@ -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
-199
View File
@@ -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);
-859
View File
@@ -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);
}
-188
View File
@@ -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
-799
View File
@@ -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;
}
-221
View File
@@ -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);
-482
View File
@@ -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;
}
-981
View File
@@ -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;
}
-144
View File
@@ -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
-162
View File
@@ -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);
-488
View File
@@ -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 */
};
-650
View File
@@ -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
-312
View File
@@ -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;
}
-183
View File
@@ -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);
-459
View File
@@ -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;
}
-141
View File
@@ -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;
}
-125
View File
@@ -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;
}
-137
View File
@@ -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_;
-238
View File
@@ -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 '�' (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;
}
-54
View File
@@ -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);
-119
View File
@@ -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
View File
@@ -1,3 +1,2 @@
/systemd-exit.service
/systemd-bus-proxyd.service
/systemd-consoled.service
-15
View File
@@ -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