[systemd-commits] 19 commits - .gitignore Makefile.am src/console src/libsystemd-terminal src/shared

David Herrmann dvdhrm at kemper.freedesktop.org
Fri Oct 3 07:08:40 PDT 2014


 .gitignore                                 |    1 
 Makefile.am                                |   22 +
 src/console/Makefile                       |    1 
 src/console/consoled-display.c             |   82 +++++
 src/console/consoled-manager.c             |  288 +++++++++++++++++++
 src/console/consoled-session.c             |  283 ++++++++++++++++++
 src/console/consoled-terminal.c            |  360 +++++++++++++++++++++++
 src/console/consoled-workspace.c           |  168 +++++++++++
 src/console/consoled.c                     |   67 ++++
 src/console/consoled.h                     |  170 +++++++++++
 src/libsystemd-terminal/grdev-drm.c        |  132 ++++----
 src/libsystemd-terminal/grdev.c            |   34 --
 src/libsystemd-terminal/grdev.h            |   23 -
 src/libsystemd-terminal/idev-keyboard.c    |    4 
 src/libsystemd-terminal/idev.h             |   22 +
 src/libsystemd-terminal/modeset.c          |   10 
 src/libsystemd-terminal/subterm.c          |  163 +++++-----
 src/libsystemd-terminal/term-parser.c      |  140 ++++++++-
 src/libsystemd-terminal/term-screen.c      |  436 +++++++++++++++++++++++++++--
 src/libsystemd-terminal/term.h             |   39 ++
 src/libsystemd-terminal/test-term-parser.c |   49 +--
 src/libsystemd-terminal/unifont.c          |   18 +
 src/libsystemd-terminal/unifont.h          |    1 
 src/shared/pty.c                           |    8 
 24 files changed, 2268 insertions(+), 253 deletions(-)

New commits:
commit ce7b9f50c3fadbad22feeb28e4429ad9bee02bcc
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 15:58:44 2014 +0200

    console: add user console daemon
    
    This adds a first draft of systemd-consoled. This is still missing a lot
    of features and does some rather primitive rendering. However, it shows
    the direction this code is going and serves as basis for further testing.
    
    The systemd-consoled binary should be run as `systemd --user' unit. It
    automatically picks up any session marked as Desktop=SYSTEMD-CONSOLE.
    Therefore, you can use any login-manager you want (ranging from /bin/login
    to gdm) to create sessions for systemd-consoled. However, the sessions
    managers must be prepared to set the Desktop= variable properly.
    
    The user-session is called `systemd-console', only the daemon providing
    the terminal environment is called `systemd-consoled' (mind the 'd').
    
    So far, only a single terminal session is provided on each opened
    user-session. However, we support multiple user-sessions (even across
    multiple seats) just fine. In the future, the workspace logic will get
    extended so you can have multiple terminal sessions in a single
    user-session for easier access.
    
    Note that this is still experimental! Instructions on how to run it will
    follow shortly.

diff --git a/.gitignore b/.gitignore
index cb1af8d..f119b57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,7 @@
 /systemd-cgls
 /systemd-cgroups-agent
 /systemd-cgtop
+/systemd-consoled
 /systemd-coredump
 /systemd-cryptsetup
 /systemd-cryptsetup-generator
diff --git a/Makefile.am b/Makefile.am
index 5033028..60011b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3020,6 +3020,9 @@ if ENABLE_TERMINAL
 noinst_LTLIBRARIES += \
 	libsystemd-terminal.la
 
+bin_PROGRAMS += \
+	systemd-consoled
+
 noinst_PROGRAMS += \
 	systemd-evcat \
 	systemd-modeset \
@@ -3068,6 +3071,25 @@ libsystemd_terminal_la_LIBADD = \
 	libsystemd-shared.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 \
+	libsystemd-internal.la \
+	libsystemd-shared.la \
+	$(TERMINAL_LIBS)
+
 systemd_evcat_CFLAGS = \
 	$(AM_CFLAGS) \
 	$(TERMINAL_CFLAGS)
diff --git a/src/console/Makefile b/src/console/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/console/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/console/consoled-display.c b/src/console/consoled-display.c
new file mode 100644
index 0000000..a30a2f1
--- /dev/null
+++ b/src/console/consoled-display.c
@@ -0,0 +1,82 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <inttypes.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);
+        }
+}
diff --git a/src/console/consoled-manager.c b/src/console/consoled-manager.c
new file mode 100644
index 0000000..8f3823f
--- /dev/null
+++ b/src/console/consoled-manager.c
@@ -0,0 +1,288 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <libudev.h>
+#include <stdlib.h>
+#include <string.h>
+#include "consoled.h"
+#include "grdev.h"
+#include "idev.h"
+#include "log.h"
+#include "sd-bus.h"
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-login.h"
+#include "sysview.h"
+#include "unifont.h"
+#include "util.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, 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) {
+                log_error("Cannot request session control on '%s': %s",
+                          sysview_session_get_name(session), strerror(-r));
+                return r;
+        }
+
+        r = session_new(&s, m, session);
+        if (r < 0) {
+                log_error("Cannot create session on '%s': %s",
+                          sysview_session_get_name(session), strerror(-r));
+                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("Cannot take session control on '%s': %s",
+                          sysview_session_get_name(session), strerror(-error));
+                session_free(s);
+                sysview_session_set_userdata(session, NULL);
+                return -error;
+        }
+
+        return 0;
+}
+
+static int manager_sysview_fn(sysview_context *sysview, void *userdata, sysview_event *event) {
+        Manager *m = userdata;
+        int r;
+
+        assert(m);
+
+        switch (event->type) {
+        case SYSVIEW_EVENT_SESSION_FILTER:
+                r = manager_sysview_session_filter(m, event);
+                break;
+        case SYSVIEW_EVENT_SESSION_ADD:
+                r = manager_sysview_session_add(m, event);
+                break;
+        case SYSVIEW_EVENT_SESSION_REMOVE:
+                r = manager_sysview_session_remove(m, event);
+                break;
+        case SYSVIEW_EVENT_SESSION_ATTACH:
+                r = manager_sysview_session_attach(m, event);
+                break;
+        case SYSVIEW_EVENT_SESSION_DETACH:
+                r = manager_sysview_session_detach(m, event);
+                break;
+        case SYSVIEW_EVENT_SESSION_REFRESH:
+                r = manager_sysview_session_refresh(m, event);
+                break;
+        case SYSVIEW_EVENT_SESSION_CONTROL:
+                r = manager_sysview_session_control(m, event);
+                break;
+        default:
+                r = 0;
+                break;
+        }
+
+        return r;
+}
+
+int manager_run(Manager *m) {
+        int r;
+
+        assert(m);
+
+        r = sysview_context_start(m->sysview, manager_sysview_fn, m);
+        if (r < 0)
+                return r;
+
+        r = sd_event_loop(m->event);
+
+        sysview_context_stop(m->sysview);
+        return r;
+}
diff --git a/src/console/consoled-session.c b/src/console/consoled-session.c
new file mode 100644
index 0000000..8bacaca
--- /dev/null
+++ b/src/console/consoled-session.c
@@ -0,0 +1,283 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <inttypes.h>
+#include <libudev.h>
+#include <stdlib.h>
+#include "consoled.h"
+#include "grdev.h"
+#include "hashmap.h"
+#include "idev.h"
+#include "list.h"
+#include "macro.h"
+#include "sd-bus.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("Cannot create display '%s' on '%s': %s",
+                                  grdev_display_get_name(display), sysview_session_get_name(s->sysview), strerror(-r));
+                        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("Cannot enable redraw-source: %s", strerror(-r));
+}
+
+void session_add_device(Session *s, sysview_device *device) {
+        unsigned int type;
+
+        assert(s);
+        assert(device);
+
+        type = sysview_device_get_type(device);
+        switch (type) {
+        case SYSVIEW_DEVICE_DRM:
+                grdev_session_add_drm(s->grdev, sysview_device_get_ud(device));
+                break;
+        case SYSVIEW_DEVICE_EVDEV:
+                idev_session_add_evdev(s->idev, sysview_device_get_ud(device));
+                break;
+        }
+}
+
+void session_remove_device(Session *s, sysview_device *device) {
+        unsigned int type;
+
+        assert(s);
+        assert(device);
+
+        type = sysview_device_get_type(device);
+        switch (type) {
+        case SYSVIEW_DEVICE_DRM:
+                grdev_session_remove_drm(s->grdev, sysview_device_get_ud(device));
+                break;
+        case SYSVIEW_DEVICE_EVDEV:
+                idev_session_remove_evdev(s->idev, sysview_device_get_ud(device));
+                break;
+        }
+}
+
+void session_refresh_device(Session *s, sysview_device *device, struct udev_device *ud) {
+        unsigned int type;
+
+        assert(s);
+        assert(device);
+
+        type = sysview_device_get_type(device);
+        switch (type) {
+        case SYSVIEW_DEVICE_DRM:
+                grdev_session_hotplug_drm(s->grdev, sysview_device_get_ud(device));
+                break;
+        }
+}
diff --git a/src/console/consoled-terminal.c b/src/console/consoled-terminal.c
new file mode 100644
index 0000000..d091579
--- /dev/null
+++ b/src/console/consoled-terminal.c
@@ -0,0 +1,360 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <inttypes.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("Cannot update screen state: %s", strerror(-r));
+
+                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("Cannot resize pty: %s", strerror(-r));
+        }
+
+        r = term_screen_resize(t->screen, width, height);
+        if (r < 0)
+                log_error("Cannot resize screen: %s", strerror(-r));
+}
+
+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("Cannot fork PTY: %s", strerror(-pid));
+                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("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("Cannot feed keyboard data to screen: %s",
+                                  strerror(-r));
+        }
+}
+
+void terminal_feed(Terminal *t, idev_data *data) {
+        switch (data->type) {
+        case IDEV_DATA_KEYBOARD:
+                terminal_feed_keyboard(t, data);
+                break;
+        }
+}
+
+static void terminal_fill(uint8_t *dst,
+                          uint32_t width,
+                          uint32_t height,
+                          uint32_t stride,
+                          uint32_t value) {
+        uint32_t i, j, *px;
+
+        for (j = 0; j < height; ++j) {
+                px = (uint32_t*)dst;
+
+                for (i = 0; i < width; ++i)
+                        *px++ = value;
+
+                dst += stride;
+        }
+}
+
+static void terminal_blend(uint8_t *dst,
+                           uint32_t width,
+                           uint32_t height,
+                           uint32_t dst_stride,
+                           const uint8_t *src,
+                           uint32_t src_stride,
+                           uint32_t fg,
+                           uint32_t bg) {
+        uint32_t i, j, *px;
+
+        for (j = 0; j < height; ++j) {
+                px = (uint32_t*)dst;
+
+                for (i = 0; i < width; ++i) {
+                        if (!src || src[i / 8] & (1 << (7 - i % 8)))
+                                *px = fg;
+                        else
+                                *px = bg;
+
+                        ++px;
+                }
+
+                src += src_stride;
+                dst += dst_stride;
+        }
+}
+
+typedef struct {
+        const grdev_display_target *target;
+        unifont *uf;
+        uint32_t cell_width;
+        uint32_t cell_height;
+        bool dirty;
+} TerminalDrawContext;
+
+static int terminal_draw_cell(term_screen *screen,
+                              void *userdata,
+                              unsigned int x,
+                              unsigned int y,
+                              const term_attr *attr,
+                              const uint32_t *ch,
+                              size_t n_ch,
+                              unsigned int ch_width) {
+        TerminalDrawContext *ctx = userdata;
+        const grdev_display_target *target = ctx->target;
+        grdev_fb *fb = target->back;
+        uint32_t xpos, ypos, width, height;
+        uint32_t fg, bg;
+        unifont_glyph g;
+        uint8_t *dst;
+        int r;
+
+        if (n_ch > 0) {
+                r = unifont_lookup(ctx->uf, &g, *ch);
+                if (r < 0)
+                        r = unifont_lookup(ctx->uf, &g, 0xfffd);
+                if (r < 0)
+                        unifont_fallback(&g);
+        }
+
+        xpos = x * ctx->cell_width;
+        ypos = y * ctx->cell_height;
+
+        if (xpos >= fb->width || ypos >= fb->height)
+                return 0;
+
+        width = MIN(fb->width - xpos, ctx->cell_width * ch_width);
+        height = MIN(fb->height - ypos, ctx->cell_height);
+
+        term_attr_to_argb32(attr, &fg, &bg, NULL);
+
+        ctx->dirty = true;
+
+        dst = fb->maps[0];
+        dst += fb->strides[0] * ypos + sizeof(uint32_t) * xpos;
+
+        if (n_ch < 1) {
+                terminal_fill(dst,
+                              width,
+                              height,
+                              fb->strides[0],
+                              bg);
+        } else {
+                if (width > g.width)
+                        terminal_fill(dst + sizeof(uint32_t) * g.width,
+                                      width - g.width,
+                                      height,
+                                      fb->strides[0],
+                                      bg);
+                if (height > g.height)
+                        terminal_fill(dst + fb->strides[0] * g.height,
+                                      width,
+                                      height - g.height,
+                                      fb->strides[0],
+                                      bg);
+
+                terminal_blend(dst,
+                               width,
+                               height,
+                               fb->strides[0],
+                               g.data,
+                               g.stride,
+                               fg,
+                               bg);
+        }
+
+        return 0;
+}
+
+bool terminal_draw(Terminal *t, const grdev_display_target *target) {
+        TerminalDrawContext ctx = { };
+        uint64_t age;
+
+        assert(t);
+        assert(target);
+
+        /* start up terminal on first frame */
+        terminal_run(t);
+
+        ctx.target = target;
+        ctx.uf = t->workspace->manager->uf;
+        ctx.cell_width = unifont_get_width(ctx.uf);
+        ctx.cell_height = unifont_get_height(ctx.uf);
+        ctx.dirty = false;
+
+        if (target->front) {
+                /* if the frontbuffer is new enough, no reason to redraw */
+                age = term_screen_get_age(t->screen);
+                if (age != 0 && age <= target->front->data.u64)
+                        return false;
+        } else {
+                /* force flip if no frontbuffer is set, yet */
+                ctx.dirty = true;
+        }
+
+        term_screen_draw(t->screen, terminal_draw_cell, &ctx, &target->back->data.u64);
+
+        return ctx.dirty;
+}
diff --git a/src/console/consoled-workspace.c b/src/console/consoled-workspace.c
new file mode 100644
index 0000000..56344ef
--- /dev/null
+++ b/src/console/consoled-workspace.c
@@ -0,0 +1,168 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <inttypes.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);
+}
diff --git a/src/console/consoled.c b/src/console/consoled.c
new file mode 100644
index 0000000..b0c9eda
--- /dev/null
+++ b/src/console/consoled.c
@@ -0,0 +1,67 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <string.h>
+#include "consoled.h"
+#include "log.h"
+#include "sd-daemon.h"
+#include "util.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("Could not create manager: %s", strerror(-r));
+                goto out;
+        }
+
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
+        r = manager_run(m);
+        if (r < 0) {
+                log_error("Cannot run manager: %s", strerror(-r));
+                goto out;
+        }
+
+out:
+        sd_notify(false,
+                  "STATUS=Shutting down...");
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/console/consoled.h b/src/console/consoled.h
new file mode 100644
index 0000000..f8a3df4
--- /dev/null
+++ b/src/console/consoled.h
@@ -0,0 +1,170 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 David Herrmann <dh.herrmann at 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 <inttypes.h>
+#include <libudev.h>
+#include <stdlib.h>
+#include "grdev.h"
+#include "hashmap.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"
+#include "util.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);

commit 48fed5c55b5183e6d44702dfdccd3b5325d8689c
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 15:54:21 2014 +0200

    pty: optimize read loop
    
    As it turns out, I can actually send data to the pty faster than the
    terminal can read. Therefore, make sure we read as much data as possible
    but bail out early enough to not cause starvation.
    
    Kernel TTY buffers are 4k, so reduce the overall buffer size, but read
    more than once if possible (up to 8 times sounds reasonable).

diff --git a/src/shared/pty.c b/src/shared/pty.c
index adcb32d..52a426c 100644
--- a/src/shared/pty.c
+++ b/src/shared/pty.c
@@ -67,7 +67,7 @@
 #include "ring.h"
 #include "util.h"
 
-#define PTY_BUFSIZE 16384
+#define PTY_BUFSIZE 4096
 
 enum {
         PTY_ROLE_UNKNOWN,
@@ -305,11 +305,11 @@ static int pty_dispatch_read(Pty *pty) {
         /*
          * We're edge-triggered, means we need to read the whole queue. This,
          * however, might cause us to stall if the writer is faster than we
-         * are. Therefore, we read twice and if the second read still returned
-         * data, we reschedule.
+         * are. Therefore, try reading as much as 8 times (32KiB) and only
+         * bail out then.
          */
 
-        for (i = 0; i < 2; ++i) {
+        for (i = 0; i < 8; ++i) {
                 len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf) - 1);
                 if (len < 0) {
                         if (errno == EINTR)

commit ce04e2335ab80eda5674de3399aa16b5aea2657f
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 15:27:25 2014 +0200

    terminal/screen: adjust screen age only on update
    
    Instead of increasing the screen-age on redraw, we now increase it only on
    real updates. This is effectively the same, but avoids increased age
    counters on backbuffer rendering. Therefore, we can now check age counters
    against fronbuffers safely, while rendering frames in background.

diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index 3f7ef1c..145dcda 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -3735,6 +3735,12 @@ unsigned int term_screen_get_height(term_screen *screen) {
         return screen->page->height;
 }
 
+uint64_t term_screen_get_age(term_screen *screen) {
+        assert_return(screen, 0);
+
+        return screen->age;
+}
+
 int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
         uint32_t *ucs4_str;
         size_t i, j, ucs4_len;
@@ -3743,6 +3749,8 @@ int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
 
         assert_return(screen, -EINVAL);
 
+        ++screen->age;
+
         /* Feed bytes into utf8 decoder and handle parsed ucs4 chars. We always
          * treat data as UTF-8, but the parser makes sure to fall back to raw
          * 8bit mode if the stream is not valid UTF-8. This should be more than
@@ -4258,7 +4266,7 @@ int term_screen_draw(term_screen *screen,
         }
 
         if (fb_age)
-                *fb_age = screen->age++;
+                *fb_age = screen->age;
 
         return 0;
 }
diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h
index 8efd48b..eae6c63 100644
--- a/src/libsystemd-terminal/term.h
+++ b/src/libsystemd-terminal/term.h
@@ -156,6 +156,7 @@ 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,

commit 56dec05d29098b151421625c68525c2c3961e574
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 14:44:41 2014 +0200

    terminal/screen: add color converter
    
    Terminals use pseudo color-codes mixed with 8bit and 24bit colors. Provide
    a color-converter so external renderers only have to deal with ARGB32
    colors.
    
    This requires a color-palette as input as there's no fixed mapping. We
    provide a default, but maybe we wanna support external palettes in the
    future.

diff --git a/src/libsystemd-terminal/term-parser.c b/src/libsystemd-terminal/term-parser.c
index f9326d5..8ec6345 100644
--- a/src/libsystemd-terminal/term-parser.c
+++ b/src/libsystemd-terminal/term-parser.c
@@ -35,6 +35,122 @@
 #include "term-internal.h"
 #include "util.h"
 
+static const uint8_t default_palette[18][3] = {
+        {   0,   0,   0 }, /* black */
+        { 205,   0,   0 }, /* red */
+        {   0, 205,   0 }, /* green */
+        { 205, 205,   0 }, /* yellow */
+        {   0,   0, 238 }, /* blue */
+        { 205,   0, 205 }, /* magenta */
+        {   0, 205, 205 }, /* cyan */
+        { 229, 229, 229 }, /* light grey */
+        { 127, 127, 127 }, /* dark grey */
+        { 255,   0,   0 }, /* light red */
+        {   0, 255,   0 }, /* light green */
+        { 255, 255,   0 }, /* light yellow */
+        {  92,  92, 255 }, /* light blue */
+        { 255,   0, 255 }, /* light magenta */
+        {   0, 255, 255 }, /* light cyan */
+        { 255, 255, 255 }, /* white */
+
+        { 229, 229, 229 }, /* light grey */
+        {   0,   0,   0 }, /* black */
+};
+
+static uint32_t term_color_to_argb32(const term_color *color, const term_attr *attr, const uint8_t *palette) {
+        static const uint8_t bval[] = {
+                0x00, 0x5f, 0x87,
+                0xaf, 0xd7, 0xff,
+        };
+        uint8_t r, g, b, t;
+
+        assert(color);
+
+        if (!palette)
+                palette = (void*)default_palette;
+
+        switch (color->ccode) {
+        case TERM_CCODE_RGB:
+                r = color->red;
+                g = color->green;
+                b = color->blue;
+
+                break;
+        case TERM_CCODE_256:
+                t = color->c256;
+                if (t < 16) {
+                        r = palette[t * 3 + 0];
+                        g = palette[t * 3 + 1];
+                        b = palette[t * 3 + 2];
+                } else if (t < 232) {
+                        t -= 16;
+                        b = bval[t % 6];
+                        t /= 6;
+                        g = bval[t % 6];
+                        t /= 6;
+                        r = bval[t % 6];
+                } else {
+                        t = (t - 232) * 10 + 8;
+                        r = t;
+                        g = t;
+                        b = t;
+                }
+
+                break;
+        case TERM_CCODE_BLACK ... TERM_CCODE_LIGHT_WHITE:
+                t = color->ccode - TERM_CCODE_BLACK;
+
+                /* bold causes light colors */
+                if (t < 8 && attr->bold)
+                        t += 8;
+
+                r = palette[t * 3 + 0];
+                g = palette[t * 3 + 1];
+                b = palette[t * 3 + 2];
+                break;
+        case TERM_CCODE_DEFAULT:
+                /* fallthrough */
+        default:
+                t = 16 + !(color == &attr->fg);
+                r = palette[t * 3 + 0];
+                g = palette[t * 3 + 1];
+                b = palette[t * 3 + 2];
+                break;
+        }
+
+        return (0xff << 24) | (r << 16) | (g << 8) | b;
+}
+
+/**
+ * term_attr_to_argb32() - Encode terminal colors as native ARGB32 value
+ * @color: Terminal attributes to work on
+ * @fg: Storage for foreground color (or NULL)
+ * @bg: Storage for background color (or NULL)
+ * @palette: The color palette to use (or NULL for default)
+ *
+ * This encodes the colors attr->fg and attr->bg as native-endian ARGB32 values
+ * and returns them. Any color conversions are automatically applied.
+ */
+void term_attr_to_argb32(const term_attr *attr, uint32_t *fg, uint32_t *bg, const uint8_t *palette) {
+        uint32_t f, b, t;
+
+        assert(attr);
+
+        f = term_color_to_argb32(&attr->fg, attr, palette);
+        b = term_color_to_argb32(&attr->bg, attr, palette);
+
+        if (attr->inverse) {
+                t = f;
+                f = b;
+                b = t;
+        }
+
+        if (fg)
+                *fg = f;
+        if (bg)
+                *bg = b;
+}
+
 /**
  * term_utf8_encode() - Encode single UCS-4 character as UTF-8
  * @out_utf8: output buffer of at least 4 bytes or NULL
diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index ccfb9a4..3f7ef1c 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -2944,31 +2944,9 @@ static int screen_SGR(term_screen *screen, const term_seq *seq) {
                                 if (i >= seq->n_args || seq->args[i] < 0)
                                         break;
 
+                                dst->ccode = TERM_CCODE_256;
                                 code = seq->args[i];
-                                if (code < 16) {
-                                        dst->ccode = code;
-                                } else if (code < 232) {
-                                        static const uint8_t bval[] = {
-                                                0x00, 0x5f, 0x87,
-                                                0xaf, 0xd7, 0xff,
-                                        };
-
-                                        dst->ccode = TERM_CCODE_256;
-                                        dst->c256 = code;
-                                        code -= 16;
-                                        dst->blue = bval[code % 6];
-                                        code /= 6;
-                                        dst->green = bval[code % 6];
-                                        code /= 6;
-                                        dst->red = bval[code % 6];
-                                } else if (code < 256) {
-                                        dst->ccode = TERM_CCODE_256;
-                                        dst->c256 = code;
-                                        code = (code - 232) * 10 + 8;
-                                        dst->red = code;
-                                        dst->green = code;
-                                        dst->blue = code;
-                                }
+                                dst->c256 = code < 256 ? code : 0;
 
                                 break;
                         }
diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h
index 5228ce0..8efd48b 100644
--- a/src/libsystemd-terminal/term.h
+++ b/src/libsystemd-terminal/term.h
@@ -97,6 +97,8 @@ struct term_attr {
         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
  */

commit cad8fe9a2b2ac340ef69233dd32e1bb1e45dae48
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 14:42:42 2014 +0200

    terminal/screen: add cursor rendering
    
    This is the most simple way to render cursors: flip attr->inverse of the
    cursor cell. This causes the background and foreground colors of the
    cursor-cell to be inversed.
    
    Now that we render cursors ourselves, make subterm not call into the
    parent terminal to render cursors.

diff --git a/src/libsystemd-terminal/subterm.c b/src/libsystemd-terminal/subterm.c
index 563cbf0..321cd35 100644
--- a/src/libsystemd-terminal/subterm.c
+++ b/src/libsystemd-terminal/subterm.c
@@ -286,6 +286,8 @@ 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);
@@ -317,6 +319,11 @@ static int output_new(Output **out, int fd) {
         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;
@@ -539,10 +546,6 @@ static void output_draw(Output *o, bool menu, term_screen *screen) {
         else
                 output_draw_screen(o, screen);
 
-        /* show cursor */
-        if (!(screen->flags & TERM_FLAG_HIDE_CURSOR))
-                output_printf(o, "\e[?25h");
-
         /*
          * Hack: sd-term was not written to support TTY as output-objects, thus
          * expects callers to use term_screen_feed_keyboard(). However, we
diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index 2c881ca..ccfb9a4 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -4246,6 +4246,8 @@ int term_screen_draw(term_screen *screen,
                 line_age = MAX(line->age, page->age);
 
                 for (i = 0; i < page->width; ++i) {
+                        term_attr attr;
+
                         cell = &line->cells[i];
                         cell_age = MAX(cell->age, line_age);
 
@@ -4259,11 +4261,16 @@ int term_screen_draw(term_screen *screen,
                          * renderers can assume ch_width is set properpy. */
                         cw = MAX(cell->cwidth, 1U);
 
+                        attr = cell->attr;
+                        if (i == screen->cursor_x && j == screen->cursor_y &&
+                            !(screen->flags & TERM_FLAG_HIDE_CURSOR))
+                                attr.inverse ^= 1;
+
                         r = draw_fn(screen,
                                     userdata,
                                     i,
                                     j,
-                                    &cell->attr,
+                                    &attr,
                                     ch_str,
                                     ch_n,
                                     cw);

commit 2ea8d19b210b62a02ebcb38f035e074dcba66426
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 14:30:37 2014 +0200

    terminal/screen: mark cursor dirty on enabled/disable
    
    If we hide or show the cursor, we change visual attributes and have to
    mark the underlying cell as dirty. Otherwise, the terminal will not be
    redrawn.

diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index 5b0562e..2c881ca 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -419,6 +419,7 @@ static void screen_mode_change(term_screen *screen, unsigned int mode, bool dec,
                          * TODO
                          */
                         set_reset(screen, TERM_FLAG_HIDE_CURSOR, !set);
+                        screen_age_cursor(screen);
                 }
 
                 break;

commit 884964a9639649422d3613500cdacea48a4ccc91
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 13:11:08 2014 +0200

    terminal/idev: add helper to match keyboard shortcuts
    
    Matching keyboard shortcuts on internationalized keyboards is actually
    non-trivial. Matching the actual key is easy, but the modifiers can be
    used by both, the matching and the translation step. Therefore, XKB
    exports "consumed-modifiers" that we use to figure out whether a modifier
    was already used by the translation step.
    
    The new IDEV_KBDMATCH() helper can be used to match on any keyboard
    shortcut and it will do the right thing.

diff --git a/src/libsystemd-terminal/idev.h b/src/libsystemd-terminal/idev.h
index 0ae044c..ea79bb6 100644
--- a/src/libsystemd-terminal/idev.h
+++ b/src/libsystemd-terminal/idev.h
@@ -110,6 +110,28 @@ struct idev_data_keyboard {
         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 & ~kdata->consumed_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
  */

commit f8958c3495edf6d1563a5309e84bd68931a46213
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 12:50:41 2014 +0200

    terminal/screen: add keyboard mapping
    
    Implement the feed_keyboard() handling by mapping XKB keys according to
    DEC-VT behavior.
    
    Public information on terminal key-mappings is pretty scarce. We only
    implement the most basic mapping for now. Further improvements welcome!

diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index b442b96..5b0562e 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -47,6 +47,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <xkbcommon/xkbcommon-keysyms.h>
 #include "macro.h"
 #include "term-internal.h"
 #include "util.h"
@@ -3784,12 +3785,329 @@ int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
         return 0;
 }
 
-int term_screen_feed_keyboard(term_screen *screen, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods) {
+static char *screen_map_key(term_screen *screen,
+                            char *p,
+                            const uint32_t *keysyms,
+                            size_t n_syms,
+                            uint32_t ascii,
+                            const uint32_t *ucs4,
+                            unsigned int mods) {
+        char ch, ch2, ch_mods;
+        uint32_t v;
+        size_t i;
+
+        /* TODO: All these key-mappings need to be verified. Public information
+         * on those mappings is pretty scarce and every emulator seems to do it
+         * slightly differently.
+         * A lot of mappings are also missing. */
+
+        if (n_syms < 1)
+                return p;
+
+        if (n_syms == 1)
+                v = keysyms[0];
+        else
+                v = XKB_KEY_NoSymbol;
+
+        /* In some mappings, the modifiers are encoded as CSI parameters. The
+         * encoding is rather arbitrary, but seems to work. */
+        ch_mods = 0;
+        switch (mods & (TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT | TERM_KBDMOD_CTRL)) {
+        case TERM_KBDMOD_SHIFT:
+                ch_mods = '2';
+                break;
+        case TERM_KBDMOD_ALT:
+                ch_mods = '3';
+                break;
+        case TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT:
+                ch_mods = '4';
+                break;
+        case TERM_KBDMOD_CTRL:
+                ch_mods = '5';
+                break;
+        case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT:
+                ch_mods = '6';
+                break;
+        case TERM_KBDMOD_CTRL | TERM_KBDMOD_ALT:
+                ch_mods = '7';
+                break;
+        case TERM_KBDMOD_CTRL | TERM_KBDMOD_SHIFT | TERM_KBDMOD_ALT:
+                ch_mods = '8';
+                break;
+        }
+
+        /* A user might actually use multiple layouts for keyboard
+         * input. @keysyms[0] contains the actual keysym that the user
+         * used. But if this keysym is not in the ascii range, the
+         * input handler does check all other layouts that the user
+         * specified whether one of them maps the key to some ASCII
+         * keysym and provides this via @ascii. We always use the real
+         * keysym except when handling CTRL+<XY> shortcuts we use the
+         * ascii keysym. This is for compatibility to xterm et. al. so
+         * ctrl+c always works regardless of the currently active
+         * keyboard layout. But if no ascii-sym is found, we still use
+         * the real keysym. */
+        if (ascii == XKB_KEY_NoSymbol)
+                ascii = v;
+
+        /* map CTRL+<ascii> */
+        if (mods & TERM_KBDMOD_CTRL) {
+                switch (ascii) {
+                case 0x60 ... 0x7e:
+                        /* Right hand side is mapped to the left and then
+                         * treated equally. Fall through to left-hand side.. */
+                        ascii -= 0x20;
+                case 0x20 ... 0x5f:
+                        /* Printable ASCII is mapped 1-1 in XKB and in
+                         * combination with CTRL bit 7 is flipped. This
+                         * is equivalent to the caret-notation. */
+                        *p++ = ascii ^ 0x40;
+                        return p;
+                }
+        }
+
+        /* map cursor keys */
+        ch = 0;
+        switch (v) {
+        case XKB_KEY_Up:
+                ch = 'A';
+                break;
+        case XKB_KEY_Down:
+                ch = 'B';
+                break;
+        case XKB_KEY_Right:
+                ch = 'C';
+                break;
+        case XKB_KEY_Left:
+                ch = 'D';
+                break;
+        case XKB_KEY_Home:
+                ch = 'H';
+                break;
+        case XKB_KEY_End:
+                ch = 'F';
+                break;
+        }
+        if (ch) {
+                *p++ = 0x1b;
+                if (screen->flags & TERM_FLAG_CURSOR_KEYS)
+                        *p++ = 'O';
+                else
+                        *p++ = '[';
+                if (ch_mods) {
+                        *p++ = '1';
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                }
+                *p++ = ch;
+                return p;
+        }
+
+        /* map action keys */
+        ch = 0;
+        switch (v) {
+        case XKB_KEY_Find:
+                ch = '1';
+                break;
+        case XKB_KEY_Insert:
+                ch = '2';
+                break;
+        case XKB_KEY_Delete:
+                ch = '3';
+                break;
+        case XKB_KEY_Select:
+                ch = '4';
+                break;
+        case XKB_KEY_Page_Up:
+                ch = '5';
+                break;
+        case XKB_KEY_Page_Down:
+                ch = '6';
+                break;
+        }
+        if (ch) {
+                *p++ = 0x1b;
+                *p++ = '[';
+                *p++ = ch;
+                if (ch_mods) {
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                }
+                *p++ = '~';
+                return p;
+        }
+
+        /* map lower function keys */
+        ch = 0;
+        switch (v) {
+        case XKB_KEY_F1:
+                ch = 'P';
+                break;
+        case XKB_KEY_F2:
+                ch = 'Q';
+                break;
+        case XKB_KEY_F3:
+                ch = 'R';
+                break;
+        case XKB_KEY_F4:
+                ch = 'S';
+                break;
+        }
+        if (ch) {
+                if (ch_mods) {
+                        *p++ = 0x1b;
+                        *p++ = '[';
+                        *p++ = '1';
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                        *p++ = ch;
+                } else {
+                        *p++ = 0x1b;
+                        *p++ = 'O';
+                        *p++ = ch;
+                }
+
+                return p;
+        }
+
+        /* map upper function keys */
+        ch = 0;
+        ch2 = 0;
+        switch (v) {
+        case XKB_KEY_F5:
+                ch = '1';
+                ch2 = '5';
+                break;
+        case XKB_KEY_F6:
+                ch = '1';
+                ch2 = '7';
+                break;
+        case XKB_KEY_F7:
+                ch = '1';
+                ch2 = '8';
+                break;
+        case XKB_KEY_F8:
+                ch = '1';
+                ch2 = '9';
+                break;
+        case XKB_KEY_F9:
+                ch = '2';
+                ch2 = '0';
+                break;
+        case XKB_KEY_F10:
+                ch = '2';
+                ch2 = '1';
+                break;
+        case XKB_KEY_F11:
+                ch = '2';
+                ch2 = '2';
+                break;
+        case XKB_KEY_F12:
+                ch = '2';
+                ch2 = '3';
+                break;
+        }
+        if (ch) {
+                *p++ = 0x1b;
+                *p++ = '[';
+                *p++ = ch;
+                if (ch2)
+                        *p++ = ch2;
+                if (ch_mods) {
+                        *p++ = ';';
+                        *p++ = ch_mods;
+                }
+                *p++ = '~';
+                return p;
+        }
+
+        /* map special keys */
+        switch (v) {
+        case 0xff08: /* XKB_KEY_BackSpace */
+        case 0xff09: /* XKB_KEY_Tab */
+        case 0xff0a: /* XKB_KEY_Linefeed */
+        case 0xff0b: /* XKB_KEY_Clear */
+        case 0xff15: /* XKB_KEY_Sys_Req */
+        case 0xff1b: /* XKB_KEY_Escape */
+        case 0xffff: /* XKB_KEY_Delete */
+                *p++ = v - 0xff00;
+                return p;
+        case 0xff13: /* XKB_KEY_Pause */
+                /* TODO: What should we do with this key?
+                 * Sending XOFF is awful as there is no simple
+                 * way on modern keyboards to send XON again.
+                 * If someone wants this, we can re-eanble
+                 * optionally. */
+                return p;
+        case 0xff14: /* XKB_KEY_Scroll_Lock */
+                /* TODO: What should we do on scroll-lock?
+                 * Sending 0x14 is what the specs say but it is
+                 * not used today the way most users would
+                 * expect so we disable it. If someone wants
+                 * this, we can re-enable it (optionally). */
+                return p;
+        case XKB_KEY_Return:
+                *p++ = 0x0d;
+                if (screen->flags & TERM_FLAG_NEWLINE_MODE)
+                        *p++ = 0x0a;
+                return p;
+        case XKB_KEY_ISO_Left_Tab:
+                *p++ = 0x09;
+                return p;
+        }
+
+        /* map unicode keys */
+        for (i = 0; i < n_syms; ++i)
+                p += term_utf8_encode(p, ucs4[i]);
+
+        return p;
+}
+
+int term_screen_feed_keyboard(term_screen *screen,
+                              const uint32_t *keysyms,
+                              size_t n_syms,
+                              uint32_t ascii,
+                              const uint32_t *ucs4,
+                              unsigned int mods) {
+        _cleanup_free_ char *dyn = NULL;
+        static const size_t padding = 1;
+        char buf[128], *start, *p = buf;
+
         assert_return(screen, -EINVAL);
 
-        /* TODO */
+        /* allocate buffer if too small */
+        start = buf;
+        if (4 * n_syms + padding > sizeof(buf)) {
+                dyn = malloc(4 * n_syms + padding);
+                if (!dyn)
+                        return -ENOMEM;
 
-        return 0;
+                start = dyn;
+        }
+
+        /* reserve prefix space */
+        start += padding;
+        p = start;
+
+        p = screen_map_key(screen, p, keysyms, n_syms, ascii, ucs4, mods);
+        if (!p || p - start < 1)
+                return 0;
+
+        /* The ALT modifier causes ESC to be prepended to any key-stroke. We
+         * already accounted for that buffer space above, so simply prepend it
+         * here.
+         * TODO: is altSendsEscape a suitable default? What are the semantics
+         * exactly? Is it used in C0/C1 conversion? Is it prepended if there
+         * already is an escape character? */
+        if (mods & TERM_KBDMOD_ALT && *start != 0x1b)
+                *--start = 0x1b;
+
+        /* turn C0 into C1 */
+        if (!(screen->flags & TERM_FLAG_7BIT_MODE) && p - start >= 2)
+                if (start[0] == 0x1b && start[1] >= 0x40 && start[1] <= 0x5f)
+                        *++start ^= 0x40;
+
+        return screen_write(screen, start, p - start);
 }
 
 int term_screen_resize(term_screen *screen, unsigned int x, unsigned int y) {
diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h
index a3ca252..5228ce0 100644
--- a/src/libsystemd-terminal/term.h
+++ b/src/libsystemd-terminal/term.h
@@ -128,6 +128,21 @@ 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);
 
@@ -141,7 +156,12 @@ unsigned int term_screen_get_width(term_screen *screen);
 unsigned int term_screen_get_height(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, uint32_t keysym, uint32_t ascii, uint32_t ucs4, unsigned int mods);
+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);

commit fe741a85c1912ead26c1a78251e1d490a8a432b3
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Fri Oct 3 12:48:36 2014 +0200

    terminal/idev: don't map XKB_KEY_NoSymbol as ASCII 0
    
    XKB_KEY_NoSymbol is defined as 0 but does not correspond to a VT key with
    ASCII value 0. No such key exists, so don't try to find such a key.

diff --git a/src/libsystemd-terminal/idev-keyboard.c b/src/libsystemd-terminal/idev-keyboard.c
index d5936b7..8dc1c20 100644
--- a/src/libsystemd-terminal/idev-keyboard.c
+++ b/src/libsystemd-terminal/idev-keyboard.c
@@ -575,7 +575,7 @@ static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_sym
         const xkb_keysym_t *s;
         int num;
 
-        if (n_syms == 1 && syms[0] < 128)
+        if (n_syms == 1 && syms[0] < 128 && syms[0] > 0)
                 return syms[0];
 
         keymap = xkb_state_get_keymap(state);
@@ -584,7 +584,7 @@ static int8_t guess_ascii(struct xkb_state *state, uint32_t code, uint32_t n_sym
         for (lo = 0; lo < n_lo; ++lo) {
                 lv = xkb_state_key_get_level(state, code + KBDXKB_SHIFT, lo);
                 num = xkb_keymap_key_get_syms_by_level(keymap, code + KBDXKB_SHIFT, lo, lv, &s);
-                if (num == 1 && s[0] < 128)
+                if (num == 1 && s[0] < 128 && s[0] > 0)
                         return s[0];
         }
 

commit 61d0326a5b1c11a8f2e8e31ec9093e81daa26588
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 19:47:21 2014 +0200

    terminal/unifont: add built-in fallback glyph
    
    In case we cannot render a glyph, we want a fallback we can display
    instead. If we rely on the font itself to provide the fallback character,
    we have nothing to display if that character is not available. Therefore,
    add a static fallback that we can use at any time.

diff --git a/src/libsystemd-terminal/unifont.c b/src/libsystemd-terminal/unifont.c
index 7520015..2acfa98 100644
--- a/src/libsystemd-terminal/unifont.c
+++ b/src/libsystemd-terminal/unifont.c
@@ -221,3 +221,21 @@ int unifont_lookup(unifont *u, unifont_glyph *out, uint32_t ucs4) {
                 memcpy(out, &g, sizeof(g));
         return 0;
 }
+
+void unifont_fallback(unifont_glyph *out) {
+        static const uint8_t fallback_data[] = {
+                /* unifont 0xfffd '�' (unicode replacement character) */
+                0x00, 0x00, 0x00, 0x7e,
+                0x66, 0x5a, 0x5a, 0x7a,
+                0x76, 0x76, 0x7e, 0x76,
+                0x76, 0x7e, 0x00, 0x00,
+        };
+
+        assert(out);
+
+        out->width = 8;
+        out->height = 16;
+        out->stride = 1;
+        out->cwidth = 1;
+        out->data = fallback_data;
+}
diff --git a/src/libsystemd-terminal/unifont.h b/src/libsystemd-terminal/unifont.h
index 0ded614..30527cb 100644
--- a/src/libsystemd-terminal/unifont.h
+++ b/src/libsystemd-terminal/unifont.h
@@ -54,3 +54,4 @@ 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);

commit cb51a41fa632790ea839aa126844dfc2d74eb341
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 19:34:14 2014 +0200

    terminal/subterm: use screen renderer
    
    Don't hard-code the screen renderer but use the newly introduced
    term_screen_draw() helper.

diff --git a/src/libsystemd-terminal/subterm.c b/src/libsystemd-terminal/subterm.c
index adc4caa..563cbf0 100644
--- a/src/libsystemd-terminal/subterm.c
+++ b/src/libsystemd-terminal/subterm.c
@@ -392,85 +392,89 @@ static void output_draw_menu(Output *o) {
         output_frame_printl(o, "     ^C: send ^C to the PTY");
 }
 
-static void output_draw_screen(Output *o, term_screen *s) {
-        unsigned int i, j;
-        bool first = true;
-
-        assert(o);
-        assert(s);
-
-        for (j = 0; j < s->page->height && j < o->in_height; ++j) {
-                if (!first)
-                        output_printf(o, "\e[m\r\n" BORDER_VERT);
-                first = false;
-
-                for (i = 0; i < s->page->width && i < o->in_width; ++i) {
-                        term_charbuf_t buf;
-                        term_cell *cell = &s->page->lines[j]->cells[i];
-                        size_t k, len, ulen;
-                        const uint32_t *str;
-                        char utf8[4];
-
-                        switch (cell->attr.fg.ccode) {
-                        case TERM_CCODE_DEFAULT:
-                                output_printf(o, "\e[39m");
-                                break;
-                        case TERM_CCODE_256:
-                                output_printf(o, "\e[38;5;%um", cell->attr.fg.c256);
-                                break;
-                        case TERM_CCODE_RGB:
-                                output_printf(o, "\e[38;2;%u;%u;%um", cell->attr.fg.red, cell->attr.fg.green, cell->attr.fg.blue);
-                                break;
-                        case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
-                                if (cell->attr.bold)
-                                        output_printf(o, "\e[%um", cell->attr.fg.ccode - TERM_CCODE_BLACK + 90);
-                                else
-                                        output_printf(o, "\e[%um", cell->attr.fg.ccode - TERM_CCODE_BLACK + 30);
-                                break;
-                        case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
-                                output_printf(o, "\e[%um", cell->attr.fg.ccode - TERM_CCODE_LIGHT_BLACK + 90);
-                                break;
-                        }
+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;
 
-                        switch (cell->attr.bg.ccode) {
-                        case TERM_CCODE_DEFAULT:
-                                output_printf(o, "\e[49m");
-                                break;
-                        case TERM_CCODE_256:
-                                output_printf(o, "\e[48;5;%um", cell->attr.bg.c256);
-                                break;
-                        case TERM_CCODE_RGB:
-                                output_printf(o, "\e[48;2;%u;%u;%um", cell->attr.bg.red, cell->attr.bg.green, cell->attr.bg.blue);
-                                break;
-                        case TERM_CCODE_BLACK ... TERM_CCODE_WHITE:
-                                output_printf(o, "\e[%um", cell->attr.bg.ccode - TERM_CCODE_BLACK + 40);
-                                break;
-                        case TERM_CCODE_LIGHT_BLACK ... TERM_CCODE_LIGHT_WHITE:
-                                output_printf(o, "\e[%um", cell->attr.bg.ccode - TERM_CCODE_LIGHT_BLACK + 100);
-                                break;
-                        }
+        if (x == 0 && y != 0)
+                output_printf(o, "\e[m\r\n" BORDER_VERT);
 
-                        output_printf(o, "\e[%u;%u;%u;%u;%u;%um",
-                                      cell->attr.bold ? 1 : 22,
-                                      cell->attr.italic ? 3 : 23,
-                                      cell->attr.underline ? 4 : 24,
-                                      cell->attr.inverse ? 7 : 27,
-                                      cell->attr.blink ? 5 : 25,
-                                      cell->attr.hidden ? 8 : 28);
+        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:
+                if (attr->bold)
+                        output_printf(o, "\e[%um", attr->fg.ccode - TERM_CCODE_BLACK + 90);
+                else
+                        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;
+        }
 
-                        str = term_char_resolve(cell->ch, &len, &buf);
+        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;
+        }
 
-                        if (len < 1) {
-                                output_printf(o, " ");
-                        } else {
-                                for (k = 0; k < len; ++k) {
-                                        ulen = term_utf8_encode(utf8, str[k]);
-                                        output_write(o, utf8, ulen);
-                                }
-                        }
+        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 = term_utf8_encode(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_move_to(o, s->cursor_x + 1, s->cursor_y + 3);
         output_printf(o, "\e[m");
 }

commit be5022138495d2e509735dec7486a040d3e2eb2d
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 19:31:43 2014 +0200

    terminal: add screen renderer
    
    We don't want to expose the term_screen internals for rendering.
    Therefore, provide an iterator that allows external renderers to draw
    terminals.

diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index 2f3f6f9..b442b96 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -3892,3 +3892,69 @@ int term_screen_set_answerback(term_screen *screen, const char *answerback) {
 
         return 0;
 }
+
+int term_screen_draw(term_screen *screen,
+                     int (*draw_fn) (term_screen *screen,
+                                     void *userdata,
+                                     unsigned int x,
+                                     unsigned int y,
+                                     const term_attr *attr,
+                                     const uint32_t *ch,
+                                     size_t n_ch,
+                                     unsigned int ch_width),
+                     void *userdata,
+                     uint64_t *fb_age) {
+        uint64_t cell_age, line_age, age = 0;
+        term_charbuf_t ch_buf;
+        const uint32_t *ch_str;
+        unsigned int i, j, cw;
+        term_page *page;
+        term_line *line;
+        term_cell *cell;
+        size_t ch_n;
+        int r;
+
+        assert(screen);
+        assert(draw_fn);
+
+        if (fb_age)
+                age = *fb_age;
+
+        page = screen->page;
+
+        for (j = 0; j < page->height; ++j) {
+                line = page->lines[j];
+                line_age = MAX(line->age, page->age);
+
+                for (i = 0; i < page->width; ++i) {
+                        cell = &line->cells[i];
+                        cell_age = MAX(cell->age, line_age);
+
+                        if (age != 0 && cell_age <= age)
+                                continue;
+
+                        ch_str = term_char_resolve(cell->ch, &ch_n, &ch_buf);
+
+                        /* Character-width of 0 is used for cleared cells.
+                         * Always treat this as single-cell character, so
+                         * renderers can assume ch_width is set properpy. */
+                        cw = MAX(cell->cwidth, 1U);
+
+                        r = draw_fn(screen,
+                                    userdata,
+                                    i,
+                                    j,
+                                    &cell->attr,
+                                    ch_str,
+                                    ch_n,
+                                    cw);
+                        if (r != 0)
+                                return r;
+                }
+        }
+
+        if (fb_age)
+                *fb_age = screen->age++;
+
+        return 0;
+}
diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h
index d5b934f..a3ca252 100644
--- a/src/libsystemd-terminal/term.h
+++ b/src/libsystemd-terminal/term.h
@@ -147,3 +147,15 @@ 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);

commit 1bfa594cf26a9880c489cdcb5911bfb3440aa566
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 18:51:29 2014 +0200

    terminal/drm: clear 'applied' flag when changing state
    
    If a pipe is enabled/disabled, we have to clear crtc->applied of the
    linked CRTC. Otherwise, we will not run a deep modeset, but leave the crtc
    in the pre-configured state.

diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c
index f01df1d..232321c 100644
--- a/src/libsystemd-terminal/grdev-drm.c
+++ b/src/libsystemd-terminal/grdev-drm.c
@@ -1549,9 +1549,23 @@ static grdev_fb *grdrm_pipe_target(grdev_pipe *basepipe) {
         return basepipe->back;
 }
 
+static void grdrm_pipe_enable(grdev_pipe *basepipe) {
+        grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe);
+
+        pipe->crtc->applied = false;
+}
+
+static void grdrm_pipe_disable(grdev_pipe *basepipe) {
+        grdrm_pipe *pipe = grdrm_pipe_from_base(basepipe);
+
+        pipe->crtc->applied = false;
+}
+
 static const grdev_pipe_vtable grdrm_pipe_vtable = {
         .free                   = grdrm_pipe_free,
         .target                 = grdrm_pipe_target,
+        .enable                 = grdrm_pipe_enable,
+        .disable                = grdrm_pipe_disable,
 };
 
 /*

commit 66695cc343647dcbf654fbb4b3f38bd1ee092a0d
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 18:47:01 2014 +0200

    terminal/grdev: allow arbitrary fb-age contexts
    
    Instead of limiting fb-aging to 64bit integers, allow any arbitrary
    context together with a release function to free it once the FB is
    destroyed.

diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c
index 57b930b..f01df1d 100644
--- a/src/libsystemd-terminal/grdev-drm.c
+++ b/src/libsystemd-terminal/grdev-drm.c
@@ -1442,6 +1442,9 @@ grdrm_fb *grdrm_fb_free(grdrm_fb *fb) {
 
         assert(fb->card);
 
+        if (fb->base.free_fn)
+                fb->base.free_fn(fb->base.data.ptr);
+
         if (fb->id > 0 && fb->card->fd >= 0) {
                 r = ioctl(fb->card->fd, DRM_IOCTL_MODE_RMFB, fb->id);
                 if (r < 0)
diff --git a/src/libsystemd-terminal/grdev.c b/src/libsystemd-terminal/grdev.c
index a700a73..0c21eac 100644
--- a/src/libsystemd-terminal/grdev.c
+++ b/src/libsystemd-terminal/grdev.c
@@ -393,28 +393,19 @@ const grdev_display_target *grdev_display_next_target(grdev_display *display, co
         return NULL;
 }
 
-void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target, uint64_t age) {
+void grdev_display_flip_target(grdev_display *display, const grdev_display_target *target) {
         grdev_display_cache *cache;
-        size_t i;
 
         assert(display);
         assert(!display->modified);
         assert(display->enabled);
         assert(target);
-        assert(target->back);
 
         cache = container_of(target, grdev_display_cache, target);
 
         assert(cache->pipe);
         assert(cache->pipe->tile->display == display);
 
-        /* reset age of all FB on overflow */
-        if (age < target->back->age)
-                for (i = 0; i < cache->pipe->max_fbs; ++i)
-                        if (cache->pipe->fbs[i])
-                                cache->pipe->fbs[i]->age = 0;
-
-        ((grdev_fb*)target->back)->age = age;
         cache->pipe->flip = true;
 }
 
diff --git a/src/libsystemd-terminal/grdev.h b/src/libsystemd-terminal/grdev.h
index ca0373e..35d6eb2 100644
--- a/src/libsystemd-terminal/grdev.h
+++ b/src/libsystemd-terminal/grdev.h
@@ -93,9 +93,15 @@ struct grdev_fb {
         uint32_t width;
         uint32_t height;
         uint32_t format;
-        uint64_t age;
         int32_t strides[4];
         void *maps[4];
+
+        union {
+                void *ptr;
+                uint64_t u64;
+        } data;
+
+        void (*free_fn) (void *ptr);
 };
 
 struct grdev_display_target {
@@ -105,8 +111,8 @@ struct grdev_display_target {
         uint32_t height;
         unsigned int rotate;
         unsigned int flip;
-        const grdev_fb *front;
-        const grdev_fb *back;
+        grdev_fb *front;
+        grdev_fb *back;
 };
 
 void grdev_display_set_userdata(grdev_display *display, void *userdata);
@@ -121,7 +127,7 @@ 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, uint64_t age);
+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);        \
diff --git a/src/libsystemd-terminal/modeset.c b/src/libsystemd-terminal/modeset.c
index 2f8860d..f5be38e 100644
--- a/src/libsystemd-terminal/modeset.c
+++ b/src/libsystemd-terminal/modeset.c
@@ -258,7 +258,7 @@ static void modeset_render(Modeset *m, grdev_display *d) {
 
         GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
                 modeset_draw(m, t);
-                grdev_display_flip_target(d, t, 1);
+                grdev_display_flip_target(d, t);
         }
 
         grdev_session_commit(m->grdev_session);

commit 51cff8bdedbc283b2403ab4a688903d8b1f2fab5
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 18:26:06 2014 +0200

    terminal/grdev: provide front and back buffer to renderers
    
    We really want more sophisticated aging than just 64bit integers. So
    always provide front *and* back buffers to renderers so they can compare
    arbitrary aging information and decide whether to re-render.

diff --git a/src/libsystemd-terminal/grdev.c b/src/libsystemd-terminal/grdev.c
index bbc45af..a700a73 100644
--- a/src/libsystemd-terminal/grdev.c
+++ b/src/libsystemd-terminal/grdev.c
@@ -343,7 +343,7 @@ void grdev_display_disable(grdev_display *display) {
         }
 }
 
-const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev, uint64_t minage) {
+const grdev_display_target *grdev_display_next_target(grdev_display *display, const grdev_display_target *prev) {
         grdev_display_cache *cache;
         size_t idx;
 
@@ -374,26 +374,19 @@ const grdev_display_target *grdev_display_next_target(grdev_display *display, co
                 if (!pipe->running || !pipe->enabled)
                         continue;
 
-                /* if front-buffer is up-to-date, there's nothing to do */
-                if (minage > 0 && pipe->front && pipe->front->age >= minage)
-                        continue;
-
                 /* find suitable back-buffer */
-                if (!(fb = pipe->back)) {
-                        if (!pipe->vtable->target || !(fb = pipe->vtable->target(pipe)))
+                if (!pipe->back) {
+                        if (!pipe->vtable->target)
+                                continue;
+                        if (!(fb = pipe->vtable->target(pipe)))
                                 continue;
 
                         assert(fb == pipe->back);
                 }
 
-                /* if back-buffer is up-to-date, schedule flip */
-                if (minage > 0 && fb->age >= minage) {
-                        grdev_display_flip_target(display, target, fb->age);
-                        continue;
-                }
+                target->front = pipe->front;
+                target->back = pipe->back;
 
-                /* we have an out-of-date back-buffer; return for redraw */
-                target->fb = fb;
                 return target;
         }
 
@@ -408,7 +401,7 @@ void grdev_display_flip_target(grdev_display *display, const grdev_display_targe
         assert(!display->modified);
         assert(display->enabled);
         assert(target);
-        assert(target->fb);
+        assert(target->back);
 
         cache = container_of(target, grdev_display_cache, target);
 
@@ -416,12 +409,12 @@ void grdev_display_flip_target(grdev_display *display, const grdev_display_targe
         assert(cache->pipe->tile->display == display);
 
         /* reset age of all FB on overflow */
-        if (age < target->fb->age)
+        if (age < target->back->age)
                 for (i = 0; i < cache->pipe->max_fbs; ++i)
                         if (cache->pipe->fbs[i])
                                 cache->pipe->fbs[i]->age = 0;
 
-        ((grdev_fb*)target->fb)->age = age;
+        ((grdev_fb*)target->back)->age = age;
         cache->pipe->flip = true;
 }
 
diff --git a/src/libsystemd-terminal/grdev.h b/src/libsystemd-terminal/grdev.h
index 6ca8a76..ca0373e 100644
--- a/src/libsystemd-terminal/grdev.h
+++ b/src/libsystemd-terminal/grdev.h
@@ -105,7 +105,8 @@ struct grdev_display_target {
         uint32_t height;
         unsigned int rotate;
         unsigned int flip;
-        const grdev_fb *fb;
+        const grdev_fb *front;
+        const grdev_fb *back;
 };
 
 void grdev_display_set_userdata(grdev_display *display, void *userdata);
@@ -119,13 +120,13 @@ 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, uint64_t minage);
+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, uint64_t age);
 
-#define GRDEV_DISPLAY_FOREACH_TARGET(_display, _t, _minage)                     \
-        for ((_t) = grdev_display_next_target((_display), NULL, (_minage));     \
-             (_t);                                                              \
-             (_t) = grdev_display_next_target((_display), (_t), (_minage)))
+#define GRDEV_DISPLAY_FOREACH_TARGET(_display, _t)                      \
+        for ((_t) = grdev_display_next_target((_display), NULL);        \
+             (_t);                                                      \
+             (_t) = grdev_display_next_target((_display), (_t)))
 
 /*
  * Events
diff --git a/src/libsystemd-terminal/modeset.c b/src/libsystemd-terminal/modeset.c
index f564fa0..2f8860d 100644
--- a/src/libsystemd-terminal/modeset.c
+++ b/src/libsystemd-terminal/modeset.c
@@ -234,18 +234,18 @@ static void modeset_draw(Modeset *m, const grdev_display_target *t) {
         uint32_t j, k, *b;
         uint8_t *l;
 
-        assert(t->fb->format == DRM_FORMAT_XRGB8888 || t->fb->format == DRM_FORMAT_ARGB8888);
+        assert(t->back->format == DRM_FORMAT_XRGB8888 || t->back->format == DRM_FORMAT_ARGB8888);
         assert(!t->rotate);
         assert(!t->flip);
 
-        l = t->fb->maps[0];
+        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->fb->strides[0];
+                l += t->back->strides[0];
         }
 }
 
@@ -256,7 +256,7 @@ static void modeset_render(Modeset *m, grdev_display *d) {
         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, 0) {
+        GRDEV_DISPLAY_FOREACH_TARGET(d, t) {
                 modeset_draw(m, t);
                 grdev_display_flip_target(d, t, 1);
         }

commit aec3f44651998211d559b474bb830aad65680a62
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 17:59:26 2014 +0200

    terminal/drm: provide pipe->target() callback
    
    Instead of looking for available back-buffers on each operation, set it to
    NULL and wait for the next frame request. It will call back into the pipe
    to request the back-buffer via ->target(), where we can do the same and
    look for an available backbuffer.
    
    This simplifies the code and avoids double lookups if we run short of
    buffers.

diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c
index 6b13011..57b930b 100644
--- a/src/libsystemd-terminal/grdev-drm.c
+++ b/src/libsystemd-terminal/grdev-drm.c
@@ -1095,19 +1095,19 @@ static void grdrm_crtc_expose(grdrm_crtc *crtc) {
         grdev_pipe_ready(&crtc->pipe->base, true);
 }
 
-static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb **slot) {
+static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb *basefb) {
         struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id };
         grdrm_card *card = crtc->object.card;
         grdrm_pipe *pipe = crtc->pipe;
-        grdrm_fb *fb = fb_from_base(*slot);
-        size_t i;
+        grdrm_fb *fb;
         int r;
 
         assert(crtc);
-        assert(slot);
-        assert(*slot);
+        assert(basefb);
         assert(pipe);
 
+        fb = fb_from_base(basefb);
+
         set_crtc.set_connectors_ptr = PTR_TO_UINT64(crtc->set.connectors);
         set_crtc.count_connectors = crtc->set.n_connectors;
         set_crtc.fb_id = fb->id;
@@ -1132,7 +1132,7 @@ static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb **slot) {
                 crtc->applied = true;
         }
 
-        *slot = NULL;
+        pipe->base.back = NULL;
         pipe->base.front = &fb->base;
         fb->flipid = 0;
         ++pipe->counter;
@@ -1144,40 +1144,25 @@ static void grdrm_crtc_commit_deep(grdrm_crtc *crtc, grdev_fb **slot) {
          * To avoid duplicating that everywhere, we schedule our own
          * timer and raise a fake FRAME event when it fires. */
         grdev_pipe_schedule(&pipe->base, 1);
-
-        if (!pipe->base.back) {
-                for (i = 0; i < pipe->base.max_fbs; ++i) {
-                        if (!pipe->base.fbs[i])
-                                continue;
-
-                        fb = fb_from_base(pipe->base.fbs[i]);
-                        if (&fb->base == pipe->base.front)
-                                continue;
-
-                        fb->flipid = 0;
-                        pipe->base.back = &fb->base;
-                        break;
-                }
-        }
 }
 
-static int grdrm_crtc_commit_flip(grdrm_crtc *crtc, grdev_fb **slot) {
+static int grdrm_crtc_commit_flip(grdrm_crtc *crtc, grdev_fb *basefb) {
         struct drm_mode_crtc_page_flip page_flip = { .crtc_id = crtc->object.id };
         grdrm_card *card = crtc->object.card;
         grdrm_pipe *pipe = crtc->pipe;
-        grdrm_fb *fb = fb_from_base(*slot);
+        grdrm_fb *fb;
         uint32_t cnt;
-        size_t i;
         int r;
 
         assert(crtc);
-        assert(slot);
-        assert(*slot);
+        assert(basefb);
         assert(pipe);
 
         if (!crtc->applied && !grdrm_modes_compatible(&crtc->kern.mode, &crtc->set.mode))
                 return 0;
 
+        fb = fb_from_base(basefb);
+
         cnt = ++pipe->counter ? : ++pipe->counter;
         page_flip.fb_id = fb->id;
         page_flip.flags = DRM_MODE_PAGE_FLIP_EVENT;
@@ -1209,29 +1194,13 @@ static int grdrm_crtc_commit_flip(grdrm_crtc *crtc, grdev_fb **slot) {
         pipe->base.flip = false;
         pipe->counter = cnt;
         fb->flipid = cnt;
-        *slot = NULL;
+        pipe->base.back = NULL;
 
         /* Raise fake FRAME event if it takes longer than 2
          * frames to receive the pageflip event. We assume the
          * queue ran over or some other error happened. */
         grdev_pipe_schedule(&pipe->base, 2);
 
-        if (!pipe->base.back) {
-                for (i = 0; i < pipe->base.max_fbs; ++i) {
-                        if (!pipe->base.fbs[i])
-                                continue;
-
-                        fb = fb_from_base(pipe->base.fbs[i]);
-                        if (&fb->base == pipe->base.front)
-                                continue;
-                        if (fb->flipid)
-                                continue;
-
-                        pipe->base.back = &fb->base;
-                        break;
-                }
-        }
-
         return 1;
 }
 
@@ -1239,7 +1208,7 @@ static void grdrm_crtc_commit(grdrm_crtc *crtc) {
         struct drm_mode_crtc set_crtc = { .crtc_id = crtc->object.id };
         grdrm_card *card = crtc->object.card;
         grdrm_pipe *pipe;
-        grdev_fb **slot;
+        grdev_fb *fb;
         int r;
 
         assert(crtc);
@@ -1280,19 +1249,19 @@ static void grdrm_crtc_commit(grdrm_crtc *crtc) {
         assert(crtc->set.n_connectors > 0);
 
         if (pipe->base.flip)
-                slot = &pipe->base.back;
+                fb = pipe->base.back;
         else if (!crtc->applied)
-                slot = &pipe->base.front;
+                fb = pipe->base.front;
         else
                 return;
 
-        if (!*slot)
+        if (!fb)
                 return;
 
-        r = grdrm_crtc_commit_flip(crtc, slot);
+        r = grdrm_crtc_commit_flip(crtc, fb);
         if (r == 0) {
                 /* in case we couldn't page-flip, perform deep modeset */
-                grdrm_crtc_commit_deep(crtc, slot);
+                grdrm_crtc_commit_deep(crtc, fb);
         }
 }
 
@@ -1335,7 +1304,6 @@ static void grdrm_crtc_restore(grdrm_crtc *crtc) {
 static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct drm_event_vblank *event) {
         bool flipped = false;
         grdrm_pipe *pipe;
-        grdrm_fb *back = NULL;
         size_t i;
 
         assert(crtc);
@@ -1366,15 +1334,9 @@ static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct
                         flipped = true;
                 } else if (counter - fb->flipid < UINT16_MAX) {
                         fb->flipid = 0;
-                        back = fb;
-                } else if (fb->flipid == 0) {
-                        back = fb;
                 }
         }
 
-        if (!pipe->base.back && back)
-                pipe->base.back = &back->base;
-
         if (flipped) {
                 crtc->pipe->base.flipping = false;
                 grdev_pipe_frame(&pipe->base);
@@ -1561,8 +1523,32 @@ static void grdrm_pipe_free(grdev_pipe *basepipe) {
         free(pipe);
 }
 
+static grdev_fb *grdrm_pipe_target(grdev_pipe *basepipe) {
+        grdrm_fb *fb;
+        size_t i;
+
+        if (!basepipe->back) {
+                for (i = 0; i < basepipe->max_fbs; ++i) {
+                        if (!basepipe->fbs[i])
+                                continue;
+
+                        fb = fb_from_base(basepipe->fbs[i]);
+                        if (&fb->base == basepipe->front)
+                                continue;
+                        if (basepipe->flipping && fb->flipid)
+                                continue;
+
+                        basepipe->back = &fb->base;
+                        break;
+                }
+        }
+
+        return basepipe->back;
+}
+
 static const grdev_pipe_vtable grdrm_pipe_vtable = {
         .free                   = grdrm_pipe_free,
+        .target                 = grdrm_pipe_target,
 };
 
 /*
diff --git a/src/libsystemd-terminal/grdev.c b/src/libsystemd-terminal/grdev.c
index aaac06e..bbc45af 100644
--- a/src/libsystemd-terminal/grdev.c
+++ b/src/libsystemd-terminal/grdev.c
@@ -382,6 +382,8 @@ const grdev_display_target *grdev_display_next_target(grdev_display *display, co
                 if (!(fb = pipe->back)) {
                         if (!pipe->vtable->target || !(fb = pipe->vtable->target(pipe)))
                                 continue;
+
+                        assert(fb == pipe->back);
                 }
 
                 /* if back-buffer is up-to-date, schedule flip */

commit 6a15ce2b3eb852023d77787f96c6a4a72eb4d60d
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 17:09:05 2014 +0200

    terminal/grdev: simplify DRM event parsing
    
    Coverity complained about this code and is partially right. We are not
    really protected against integer overflows. Sure, unlikely, but lets just
    avoid any overflows and properly protect our parser loop.

diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c
index 7a6e1d9..6b13011 100644
--- a/src/libsystemd-terminal/grdev-drm.c
+++ b/src/libsystemd-terminal/grdev-drm.c
@@ -2195,7 +2195,8 @@ static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *
         uint32_t id, counter;
         grdrm_object *object;
         char buf[4096];
-        ssize_t l, i;
+        size_t len;
+        ssize_t l;
 
         if (revents & (EPOLLHUP | EPOLLERR)) {
                 /* Immediately close device on HUP; no need to flush pending
@@ -2214,15 +2215,12 @@ static int grdrm_card_io_fn(sd_event_source *s, int fd, uint32_t revents, void *
                         log_debug("grdrm: %s/%s: read error: %m", card->base.session->name, card->base.name);
                         grdrm_card_close(card);
                         return 0;
-                } else if ((size_t)l < sizeof(*event)) {
-                        log_debug("grdrm: %s/%s: short read of %zd bytes", card->base.session->name, card->base.name, l);
-                        return 0;
                 }
 
-                for (i = 0; i < l; i += event->length) {
-                        event = (void*)&buf[i];
+                for (len = l; len > 0; len -= event->length) {
+                        event = (void*)buf;
 
-                        if (i + (ssize_t)sizeof(*event) > l || i + (ssize_t)event->length > l) {
+                        if (len < sizeof(*event) || len < event->length) {
                                 log_debug("grdrm: %s/%s: truncated event", card->base.session->name, card->base.name);
                                 break;
                         }

commit f1f5b2a3bdc3178d57c4088a7cd7758afaeba9cb
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 16:36:09 2014 +0200

    terminal: make utf8 decoder return length
    
    Lets return the parsed length in term_utf8_decode() instead of a buffer
    pointer. Store the pointer in the passed argument.
    
    This makes it adhere to the systemd coding-style, were we always avoid
    returning pointers, but store them in output arguments. In this case, the
    storage is not allocated, so it doesn't fit 100% to this idiom, but still
    looks much nicer.

diff --git a/src/libsystemd-terminal/subterm.c b/src/libsystemd-terminal/subterm.c
index 3990fb3..adc4caa 100644
--- a/src/libsystemd-terminal/subterm.c
+++ b/src/libsystemd-terminal/subterm.c
@@ -716,10 +716,10 @@ static int terminal_io_fn(sd_event_source *source, int fd, uint32_t revents, voi
 
         for (i = 0; i < len; ++i) {
                 const term_seq *seq;
-                const uint32_t *str;
+                uint32_t *str;
                 size_t n_str, j;
 
-                str = term_utf8_decode(&t->utf8, &n_str, buf[i]);
+                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) {
diff --git a/src/libsystemd-terminal/term-parser.c b/src/libsystemd-terminal/term-parser.c
index c8c1d13..f9326d5 100644
--- a/src/libsystemd-terminal/term-parser.c
+++ b/src/libsystemd-terminal/term-parser.c
@@ -81,15 +81,16 @@ size_t term_utf8_encode(char *out_utf8, uint32_t g) {
 /**
  * term_utf8_decode() - Try decoding the next UCS-4 character
  * @p: decoder object to operate on or NULL
- * @out_len: output buffer for length of decoded UCS-4 string or NULL
+ * @out_len: output storage for pointer to decoded UCS-4 string or NULL
  * @c: next char to push into decoder
  *
  * This decodes a UTF-8 stream. It must be called for each input-byte of the
- * UTF-8 stream and returns a UCS-4 stream. The length of the returned UCS-4
- * string (number of parsed characters) is stored in @out_len if non-NULL. A
- * pointer to the string is returned (or NULL if none was parsed). The string
- * is not zero-terminated! Furthermore, the string is only valid until the next
- * invokation of this function. It is also bound to the parser-state @p.
+ * UTF-8 stream and returns a UCS-4 stream. A pointer to the parsed UCS-4
+ * string is stored in @out_buf if non-NULL. The length of this string (number
+ * of parsed UCS4 characters) is returned as result. The string is not
+ * zero-terminated! Furthermore, the string is only valid until the next
+ * invocation of this function. It is also bound to the parser state @p and
+ * must not be freed nor written to by the caller.
  *
  * This function is highly optimized to work with terminal-emulators. Instead
  * of being strict about UTF-8 validity, this tries to perform a fallback to
@@ -100,9 +101,10 @@ size_t term_utf8_encode(char *out_utf8, uint32_t g) {
  * no helpers to do that for you. To initialize it, simply reset it to all
  * zero. You can reset or free the object at any point in time.
  *
- * Returns: Pointer to the UCS-4 string or NULL.
+ * Returns: Number of parsed UCS4 characters
  */
-const uint32_t *term_utf8_decode(term_utf8 *p, size_t *out_len, char c) {
+size_t term_utf8_decode(term_utf8 *p, uint32_t **out_buf, char c) {
+        static uint32_t ucs4_null = 0;
         uint32_t t, *res = NULL;
         uint8_t byte;
         size_t len = 0;
@@ -246,9 +248,9 @@ const uint32_t *term_utf8_decode(term_utf8 *p, size_t *out_len, char c) {
         p->n_bytes = 0;
 
 out:
-        if (out_len)
-                *out_len = len;
-        return len > 0 ? res : NULL;
+        if (out_buf)
+                *out_buf = res ? : &ucs4_null;
+        return len;
 }
 
 /*
diff --git a/src/libsystemd-terminal/term-screen.c b/src/libsystemd-terminal/term-screen.c
index 14c32ac..2f3f6f9 100644
--- a/src/libsystemd-terminal/term-screen.c
+++ b/src/libsystemd-terminal/term-screen.c
@@ -3756,7 +3756,7 @@ unsigned int term_screen_get_height(term_screen *screen) {
 }
 
 int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
-        const uint32_t *ucs4_str;
+        uint32_t *ucs4_str;
         size_t i, j, ucs4_len;
         const term_seq *seq;
         int r;
@@ -3768,7 +3768,7 @@ int term_screen_feed_text(term_screen *screen, const uint8_t *in, size_t size) {
          * 8bit mode if the stream is not valid UTF-8. This should be more than
          * enough to support old 7bit/8bit modes. */
         for (i = 0; i < size; ++i) {
-                ucs4_str = term_utf8_decode(&screen->utf8, &ucs4_len, in[i]);
+                ucs4_len = term_utf8_decode(&screen->utf8, &ucs4_str, in[i]);
                 for (j = 0; j < ucs4_len; ++j) {
                         r = term_parser_feed(screen->parser, &seq, ucs4_str[j]);
                         if (r < 0) {
diff --git a/src/libsystemd-terminal/term.h b/src/libsystemd-terminal/term.h
index 021cf1c..d5b934f 100644
--- a/src/libsystemd-terminal/term.h
+++ b/src/libsystemd-terminal/term.h
@@ -111,7 +111,7 @@ struct term_utf8 {
 };
 
 size_t term_utf8_encode(char *out_utf8, uint32_t g);
-const uint32_t *term_utf8_decode(term_utf8 *p, size_t *out_len, char c);
+size_t term_utf8_decode(term_utf8 *p, uint32_t **out_buf, char c);
 
 /*
  * Parsers
diff --git a/src/libsystemd-terminal/test-term-parser.c b/src/libsystemd-terminal/test-term-parser.c
index ed16f5f..e8d5dcf 100644
--- a/src/libsystemd-terminal/test-term-parser.c
+++ b/src/libsystemd-terminal/test-term-parser.c
@@ -33,39 +33,40 @@
 
 static void test_term_utf8_invalid(void) {
         term_utf8 p = { };
-        const uint32_t *res;
+        uint32_t *res;
         size_t len;
 
-        res = term_utf8_decode(NULL, NULL, 0);
-        assert_se(res == NULL);
+        len = term_utf8_decode(NULL, NULL, 0);
+        assert_se(!len);
 
-        res = term_utf8_decode(&p, NULL, 0);
-        assert_se(res != NULL);
-
-        len = 5;
-        res = term_utf8_decode(NULL, &len, 0);
-        assert_se(res == NULL);
-        assert_se(len == 0);
+        len = term_utf8_decode(&p, NULL, 0);
+        assert_se(len == 1);
 
-        len = 5;
-        res = term_utf8_decode(&p, &len, 0);
+        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 = 5;
-        res = term_utf8_decode(&p, &len, 0xCf);
-        assert_se(res == NULL);
+        len = term_utf8_decode(&p, &res, 0xCf);
         assert_se(len == 0);
-
-        len = 5;
-        res = term_utf8_decode(&p, &len, 0x0);
         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 = { };
-        const uint32_t *res;
+        uint32_t *res;
         char u8[4];
         uint32_t i, j;
         size_t ulen, len;
@@ -78,8 +79,8 @@ static void test_term_utf8_range(void) {
                         continue;
 
                 for (j = 0; j < ulen; ++j) {
-                        res = term_utf8_decode(&p, &len, u8[j]);
-                        if (!res) {
+                        len = term_utf8_decode(&p, &res, u8[j]);
+                        if (len < 1) {
                                 assert_se(j + 1 != ulen);
                                 continue;
                         }
@@ -117,13 +118,13 @@ static void test_term_utf8_mix(void) {
                 0x00F0, 0x0080, 0x0080, 0x0001,
         };
         term_utf8 p = { };
-        const uint32_t *res;
+        uint32_t *res;
         unsigned int i, j;
         size_t len;
 
         for (i = 0, j = 0; i < sizeof(source); ++i) {
-                res = term_utf8_decode(&p, &len, source[i]);
-                if (!res)
+                len = term_utf8_decode(&p, &res, source[i]);
+                if (len < 1)
                         continue;
 
                 assert_se(j + len <= ELEMENTSOF(result));

commit db1a606610e5a528903a4380f30c9934a0c5a134
Author: David Herrmann <dh.herrmann at gmail.com>
Date:   Thu Oct 2 13:11:53 2014 +0200

    terminal: fix back-buffer selection on DRM page-flip
    
    We currently select front-buffers as new back-buffer if they happen to be
    the last buffer in our framebuffer-array. Fix this by never selecting a
    new front buffer as back buffer.

diff --git a/src/libsystemd-terminal/grdev-drm.c b/src/libsystemd-terminal/grdev-drm.c
index 5393ebf..7a6e1d9 100644
--- a/src/libsystemd-terminal/grdev-drm.c
+++ b/src/libsystemd-terminal/grdev-drm.c
@@ -1362,10 +1362,9 @@ static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct
                 fb = fb_from_base(pipe->base.fbs[i]);
                 if (counter != 0 && counter == pipe->counter && fb->flipid == counter) {
                         pipe->base.front = &fb->base;
+                        fb->flipid = 0;
                         flipped = true;
-                }
-
-                if (counter - fb->flipid < UINT16_MAX) {
+                } else if (counter - fb->flipid < UINT16_MAX) {
                         fb->flipid = 0;
                         back = fb;
                 } else if (fb->flipid == 0) {
@@ -1373,7 +1372,7 @@ static void grdrm_crtc_flip_complete(grdrm_crtc *crtc, uint32_t counter, struct
                 }
         }
 
-        if (!pipe->base.back)
+        if (!pipe->base.back && back)
                 pipe->base.back = &back->base;
 
         if (flipped) {



More information about the systemd-commits mailing list