public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/kmscon] rawhide: Add localectl runtime support
@ 2026-06-05 11:19 Jocelyn Falempe
0 siblings, 0 replies; only message in thread
From: Jocelyn Falempe @ 2026-06-05 11:19 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : rpms/kmscon
Branch : rawhide
Commit : acf9652b0f61ec90ae0042e92b6282c65f89d0d0
Author : Jocelyn Falempe <jfalempe@redhat.com>
Date : 2026-06-05T13:17:24+02:00
Stats : +2810/-3 in 2 file(s)
URL : https://src.fedoraproject.org/rpms/kmscon/c/acf9652b0f61ec90ae0042e92b6282c65f89d0d0?branch=rawhide
Log:
Add localectl runtime support
This allows to change the keyboard layout at runtime using localectl.
OpenQA scripts use loadkeys on fbcon VT, and need a similar
functionality on kmscon
Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
---
diff --git a/kmscon.spec b/kmscon.spec
index 94e94f5..7875ca9 100644
--- a/kmscon.spec
+++ b/kmscon.spec
@@ -21,7 +21,7 @@ BuildRequires: pkgconfig(freetype2)
BuildRequires: pkgconfig(gbm)
BuildRequires: pkgconfig(glesv2)
BuildRequires: pkgconfig(libdrm)
-BuildRequires: pkgconfig(libsystemd)
+BuildRequires: pkgconfig(dbus-1)
BuildRequires: pkgconfig(libudev) >= 172
BuildRequires: pkgconfig(pango)
BuildRequires: pkgconfig(pangoft2)
@@ -29,6 +29,8 @@ BuildRequires: pkgconfig(systemd)
BuildRequires: pkgconfig(xkbcommon) >= 0.5.0
BuildRequires: pkgconfig(zlib)
+Patch: localectl_support.patch
+
%description
Kmscon is a simple terminal emulator based on linux kernel mode setting (KMS).
It is an attempt to replace the in-kernel VT implementation with a userspace
@@ -91,8 +93,6 @@ mod-gltex.so
%{_bindir}/%{name}
%{_bindir}/kmscon-launch-gui
%{_libdir}/kmscon/mod-unifont.so
-%dir %{_libexecdir}/kmscon
-%{_libexecdir}/kmscon/kmscon
%{_mandir}/man1/kmscon.1*
%{_mandir}/man5/kmscon.conf.5*
%{_unitdir}/kmscon.service
diff --git a/localectl_support.patch b/localectl_support.patch
new file mode 100644
index 0000000..3fd517d
--- /dev/null
+++ b/localectl_support.patch
@@ -0,0 +1,2807 @@
+From 519a57b8546977d0378ac491065797d3040f914a Mon Sep 17 00:00:00 2001
+From: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Fri, 5 Jun 2026 12:08:56 +0200
+Subject: [PATCH] Squashed commit of the following:
+
+commit 21a7f41266a881169609452d3d3d7623c10404ae
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Fri Jun 5 11:11:42 2026 +0200
+
+ dbus: Listen to locale1 property changes
+
+ And update the keyboard layout accordingly.
+
+ Don't listen to locale1 property changes if the keyboard layout was
+ set in the config file or in command line argument.
+
+ This allows to use `localectl set-keymap` to change the keyboard
+ layout at runtime.
+ One difference from "loadkeys" is that this is a system-wide setting,
+ and that it will affect all kmscon instance on all tty.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 99b5e7e840035f9f6a437cb4d1c506be085cbd92
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Fri Jun 5 10:48:21 2026 +0200
+
+ conf: Use NULL for default xkb config variable
+
+ This better fit libxkbcommon API
+ Remove the workaround in input, as it's no longer necessary.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 3010008c5d810a794b47e38befbd780f406c4904
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Fri Jun 5 10:22:01 2026 +0200
+
+ input: Add input_update_keymap() to change the keymap at runtime
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit d716d72cae6331e681a0f0a37dfa4ad2cc49366d
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Fri Jun 5 10:16:09 2026 +0200
+
+ input: split keyboard layout and compose initialization
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 0dffbd7a27db7f23806cfab0ecd5369e5f7b15ca
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Thu Jun 4 00:38:20 2026 +0200
+
+ session: Simplify session management
+
+ Directly switch from one session to another.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit fa42d6d0adf806a05349efedceaa801df77aca6d
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Wed Jun 3 17:37:47 2026 +0200
+
+ terminal: pass directly the seat input, conf, name and eloop
+
+ This avoid to use kmscon_seat_get_xxx() for each.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 21011b1561c9199fdae2a1566d27e43b67bcc0b6
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Wed Jun 3 16:24:24 2026 +0200
+
+ session: Remove session callback
+
+ As terminal sessions are the only session available, remove the
+ callbacks, and call directly the corresponding terminal functions.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 12c1072d254e6b04819850630def9028e0cf8d19
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Wed Jun 3 12:26:13 2026 +0200
+
+ dummy_session: Remove dummy sessions
+
+ Kmscon wanted to provide different type of session, but only the
+ terminal session is used.
+ Dummy session is just a black screen, so has no real usage.
+
+ Let's focus on terminal sessions, and remove the dummy session.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit a6e78153a5690659d99e587fe770eca4b56d09ad
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Mon Jun 1 23:41:32 2026 +0200
+
+ Remove startup script, as it is no more necessary
+
+ kmscon now retrieve xkb keyboard configuration directly form dbus,
+ so there is no need for the startup script.
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 06bb7413292d7c556ffc817bc3075c3c3c96fb0c
+Author: Jocelyn Falempe <jfalempe@redhat.com>
+Date: Thu May 21 18:09:51 2026 +0200
+
+ Use libdbus directly to get locale
+
+ Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
+
+commit 84501684db7dda606209c4c7ae04f7e66d964809
+Author: Fabian Vogt <fvogt@suse.de>
+Date: Tue Jun 2 12:31:05 2026 +0200
+
+ freetype: Enable light hinting
+
+ Not hinting at all results in blurry edges, especially visible with straight
+ lines in common characters such as "-[]".
+
+ Light hinting in FreeType has the nice property that it only tries to
+ align edges vertically, so the width of glyphs does not change, preserving
+ monospaceness.
+
+commit d789d1c3517d997f1271a38b4594c29898eaf199
+Author: Fabian Vogt <fvogt@suse.de>
+Date: Mon Jun 1 17:03:47 2026 +0200
+
+ Define default issue search path in a single place
+
+ It was hardcoded in four different places, of which only one has an actual
+ effect. Consolidate it.
+
+commit 44c0df12ff202eb84394ea0c8e059894563fe501
+Author: Adam Duskett <adam.duskett@amarulasolutions.com>
+Date: Thu May 21 16:24:23 2026 +0200
+
+ fix zlib cross-compiling errors
+
+ zlib compression was introduced in version 9.3.3. However, when
+ cross-compiling kmscon for an architecture other than x86_64, the
+ genunifont executable tries to use the build-systems zlib instead
+ of the hosts zlib.
+
+ This leads to the following error during compiling:
+ libz.so: error adding symbols: file in wrong format
+
+ Fix this by adding a new native zlib dependency specifically for the
+ genunifont executable.
+---
+ README.md | 2 -
+ docs/man/kmscon.1.xml.in | 29 +-
+ docs/man/kmscon.conf.5.xml.in | 14 -
+ meson.build | 10 +-
+ meson.options | 8 +-
+ scripts/etc/kmscon.conf.example | 1 -
+ scripts/kmscon.in | 63 ----
+ src/dbus.c | 368 +++++++++++++++++++
+ src/{kmscon_dummy.h => dbus.h} | 53 +--
+ src/font/font_freetype.c | 4 +-
+ src/font/meson.build | 4 +-
+ src/input/input.c | 60 +++-
+ src/input/input.h | 7 +-
+ src/input/input_internal.h | 10 +-
+ src/input/input_uxkb.c | 33 +-
+ src/kmscon_conf.c | 26 +-
+ src/kmscon_conf.h | 4 -
+ src/kmscon_dummy.c | 154 --------
+ src/kmscon_issue.c | 10 -
+ src/kmscon_issue.h | 9 +
+ src/kmscon_main.c | 33 ++
+ src/kmscon_seat.c | 602 +++++++-------------------------
+ src/kmscon_seat.h | 41 +--
+ src/kmscon_terminal.c | 98 ++----
+ src/kmscon_terminal.h | 34 +-
+ src/meson.build | 25 +-
+ src/shl/dlist.h | 10 +
+ tests/test_input.c | 7 +-
+ 28 files changed, 751 insertions(+), 968 deletions(-)
+ delete mode 100755 scripts/kmscon.in
+ create mode 100644 src/dbus.c
+ rename src/{kmscon_dummy.h => dbus.h} (50%)
+ delete mode 100644 src/kmscon_dummy.c
+
+diff --git a/README.md b/README.md
+index 8675413..3bf4912 100644
+--- a/README.md
++++ b/README.md
+@@ -82,8 +82,6 @@ explicitly enable it via command line:
+ |`font_freetype`| `auto` | Freetype2 based scalable font renderer, also handle bitmap fonts |
+ |`font_pango`| `auto` | Pango based scalable font renderer |
+ |`renderer_gltex`| `auto` | OpenGLESv2 accelerated renderer |
+-|`session_dummy`| `auto` | Dummy fallback session |
+-|`session_terminal`| `auto` | Terminal-emulator sessions |
+ |`docs`|`auto`| Build manpages and documentation |
+
+
+diff --git a/docs/man/kmscon.1.xml.in b/docs/man/kmscon.1.xml.in
+index 0538186..25cf29a 100644
+--- a/docs/man/kmscon.1.xml.in
++++ b/docs/man/kmscon.1.xml.in
+@@ -187,14 +187,6 @@
+ sessions via keyboard input. (default: off)</para>
+ </listitem>
+ </varlistentry>
+-
+- <varlistentry>
+- <term><option>--terminal-session</option></term>
+- <listitem>
+- <para>Start a terminal session after setup is done.
+- (default: on)</para>
+- </listitem>
+- </varlistentry>
+ </variablelist>
+
+ <para>Terminal Options:</para>
+@@ -463,14 +455,6 @@
+ </listitem>
+ </varlistentry>
+
+- <varlistentry>
+- <term><option>--grab-session-dummy {grab}</option></term>
+- <listitem>
+- <para>Switch to a dummy session (blank screen).
+- (default: <Ctrl><Logo>Escape)</para>
+- </listitem>
+- </varlistentry>
+-
+ <varlistentry>
+ <term><option>--grab-session-close {grab}</option></term>
+ <listitem>
+@@ -886,16 +870,11 @@
+ <refsect1>
+ <title>Sessions</title>
+ <para>If KMSCON is active on a seat, the internal session-manager is woken
+- up. At most times, only the terminal-session is active, but KMSCON can
+- also support any other session type. You can switch between sessions
+- with keyboard-shortcuts and you can create/destroy sessions during
+- runtime.</para>
+-
+- <para>Dummy sessions simply show a black screen and are used if no other
+- session is available. Otherwise, dummy sessions are hidden from the
+- user so they cannot switch to it.</para>
++ up. At most times, only the terminal-session is active. You can switch
++ between sessions with keyboard-shortcuts and you can create/destroy
++ terminal sessions during runtime.</para>
+
+- <para>Terminal sessions provide a terminal emulator. They are the main
++ <para>Terminal sessions provide a terminal emulator. They are the only
+ session type and provide all the terminal-emulation
+ functionality.</para>
+ </refsect1>
+diff --git a/docs/man/kmscon.conf.5.xml.in b/docs/man/kmscon.conf.5.xml.in
+index dca056d..8af4580 100644
+--- a/docs/man/kmscon.conf.5.xml.in
++++ b/docs/man/kmscon.conf.5.xml.in
+@@ -161,13 +161,6 @@ font-name=Ubuntu Mono
+ </listitem>
+ </varlistentry>
+
+- <varlistentry>
+- <term><option>terminal-session</option></term>
+- <listitem>
+- <para>Enable terminal session. (default: on)</para>
+- </listitem>
+- </varlistentry>
+-
+ <varlistentry>
+ <term><option>login</option></term>
+ <listitem>
+@@ -368,13 +361,6 @@ font-name=Ubuntu Mono
+ </listitem>
+ </varlistentry>
+
+- <varlistentry>
+- <term><option>grab-session-dummy</option></term>
+- <listitem>
+- <para>Switch to dummy session. (default: <Ctrl><Logo>Escape)</para>
+- </listitem>
+- </varlistentry>
+-
+ <varlistentry>
+ <term><option>grab-session-close</option></term>
+ <listitem>
+diff --git a/meson.build b/meson.build
+index de89d72..1aa72f2 100644
+--- a/meson.build
++++ b/meson.build
+@@ -53,6 +53,7 @@ libudev_deps = dependency('libudev', version: '>=172')
+ dl_deps = dependency('dl')
+ threads_deps = dependency('threads')
+ zlib_deps = dependency('zlib')
++zlib_deps_native = dependency('zlib', native: true)
+
+ python = find_program('python3')
+
+@@ -75,6 +76,7 @@ glesv2_deps = dependency('glesv2', disabler: true, required: require_glesv2)
+ pango_deps = dependency('pangoft2', disabler: true, required: get_option('font_pango'))
+ freetype_deps = dependency('freetype2', disabler: true, required: get_option('font_freetype'))
+ fontconfig_deps = dependency('fontconfig', disabler: true, required: get_option('font_freetype'))
++dbus_deps = dependency('dbus-1', disabler: true, required: get_option('dbus'))
+ xsltproc = find_program('xsltproc', native: true, disabler: true, required: get_option('docs'))
+ check_deps = dependency('check', disabler: true, required: get_option('tests'))
+ tic = find_program('tic', required: false)
+@@ -89,7 +91,6 @@ sections = {
+ 'video': 'Video Backends',
+ 'font': 'Font Backends',
+ 'renderer': 'Renderers',
+- 'session': 'Session Types',
+ }
+ config = configuration_data()
+ # Note: keep this in sync with the dependencies above
+@@ -102,8 +103,7 @@ foreach name, reqs : {
+ 'font_unifont': [],
+ 'font_freetype': [freetype_deps, fontconfig_deps],
+ 'font_pango': [pango_deps],
+- 'session_dummy': [],
+- 'session_terminal': [],
++ 'dbus': [dbus_deps],
+ }
+ found = true
+ foreach req : reqs
+@@ -195,10 +195,6 @@ else
+ dirs_info.set('PAM_CFG', '')
+ endif
+ foreach filename, kwargs : {
+- 'scripts/kmscon.in': {
+- 'install_dir': bindir,
+- 'install_mode': 'rwxr-xr-x',
+- },
+ 'scripts/kmscon-launch-gui.sh': {
+ 'install_dir': bindir,
+ 'install_mode': 'rwxr-xr-x',
+diff --git a/meson.options b/meson.options
+index 5cafa60..afa5b32 100644
+--- a/meson.options
++++ b/meson.options
+@@ -29,8 +29,6 @@ option('font_freetype', type: 'feature', value: 'auto',
+ option('font_pango', type: 'feature', value: 'auto',
+ description: 'pango font backend')
+
+-# kmscon sessions
+-option('session_dummy', type: 'feature', value: 'auto',
+- description: 'dummy session')
+-option('session_terminal', type: 'feature', value: 'auto',
+- description: 'terminal session')
++# dbus integration
++option('dbus', type: 'feature', value: 'auto',
++ description: 'dbus integration')
+diff --git a/scripts/etc/kmscon.conf.example b/scripts/etc/kmscon.conf.example
+index ca2232e..be45a4a 100644
+--- a/scripts/etc/kmscon.conf.example
++++ b/scripts/etc/kmscon.conf.example
+@@ -66,7 +66,6 @@
+ #grab-zoom-out=<Ctrl>Minus
+ #grab-session-next=<Ctrl><Logo>Right
+ #grab-session-prev=<Ctrl><Logo>Left
+-#grab-session-dummy=<Ctrl><Logo>Escape
+ #grab-session-close=<Ctrl><Logo>BackSpace
+ #grab-terminal-new=<Ctrl><Logo>Return
+ #grab-rotate-cw=<Logo>Plus
+diff --git a/scripts/kmscon.in b/scripts/kmscon.in
+deleted file mode 100755
+index 6545399..0000000
+--- a/scripts/kmscon.in
++++ /dev/null
+@@ -1,63 +0,0 @@
+-#!/bin/sh
+-#
+-# Copyright (c) 2018 Aetf <aetf@unlimitedcodeworks.xyz>
+-# Copyright (c) 2018 Fabian Vogt <fvogt@suse.com>
+-#
+-# Permission is hereby granted, free of charge, to any person obtaining a copy
+-# of this software and associated documentation files (the "Software"), to deal
+-# in the Software without restriction, including without limitation the rights
+-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+-# copies of the Software, and to permit persons to whom the Software is
+-# furnished to do so, subject to the following conditions:
+-#
+-# The above copyright notice and this permission notice shall be included in all
+-# copies or substantial portions of the Software.
+-#
+-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-# SOFTWARE.
+-
+-# configuration path
+-helperdir=@libexecdir@
+-
+-# Get a property from org.freedesktop.locale1
+-queryLocaledbus() {
+- dbus-send --system --print-reply=literal --dest=org.freedesktop.locale1 /org/freedesktop/locale1 org.freedesktop.DBus.Properties.Get "string:org.freedesktop.locale1" "string:X11$1" 2>/dev/null | awk '{print $2}'
+-}
+-
+-queryLocalectl() {
+- localectl status | awk "/X11 $1/ {print \$3}"
+-}
+-
+-# Query and setup system locale settings before start kmscon
+-setupLocale() {
+- # Don't override existing values. Also there is no point in setting only some of them
+- # as then they would not match anymore.
+- if test -n "${XKB_DEFAULT_MODEL}" -o -n "${XKB_DEFAULT_LAYOUT}" -o -n "${XKB_DEFAULT_VARIANT}" -o -n "${XKB_DEFAULT_OPTIONS}"; then
+- return
+- fi
+-
+- if command -v dbus-send >/dev/null 2>/dev/null; then
+- querycmd="queryLocaledbus"
+- elif command -v localectl >/dev/null 2>/dev/null; then
+- querycmd="queryLocalectl"
+- else
+- return
+- fi
+-
+- X11MODEL="$(${querycmd} Model)"
+- X11LAYOUT="$(${querycmd} Layout)"
+- X11VARIANT="$(${querycmd} Variant)"
+- X11OPTIONS="$(${querycmd} Options)"
+- [ -n "${X11MODEL}" ] && export XKB_DEFAULT_MODEL="${X11MODEL}"
+- [ -n "${X11LAYOUT}" ] && export XKB_DEFAULT_LAYOUT="${X11LAYOUT}"
+- [ -n "${X11VARIANT}" ] && export XKB_DEFAULT_VARIANT="${X11VARIANT}"
+- [ -n "${X11OPTIONS}" ] && export XKB_DEFAULT_OPTIONS="${X11OPTIONS}"
+-}
+-
+-setupLocale
+-exec ${helperdir}/kmscon "$@"
+diff --git a/src/dbus.c b/src/dbus.c
+new file mode 100644
+index 0000000..84c467d
+--- /dev/null
++++ b/src/dbus.c
+@@ -0,0 +1,368 @@
++/*
++ * dbus - D-Bus support
++ *
++ * Copyright (c) 2026 Red Hat.
++ * Author: Jocelyn Falempe <jfalempe@redhat.com>
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining
++ * a copy of this software and associated documentation files
++ * (the "Software"), to deal in the Software without restriction, including
++ * without limitation the rights to use, copy, modify, merge, publish,
++ * distribute, sublicense, and/or sell copies of the Software, and to
++ * permit persons to whom the Software is furnished to do so, subject to
++ * the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included
++ * in all copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
++ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
++ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
++ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
++ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
++ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
++ */
++
++#include <dbus/dbus.h>
++#include <errno.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include "dbus.h"
++#include "shl/eloop.h"
++#include "shl/log.h"
++
++#define LOG_SUBSYSTEM "dbus"
++
++struct rmlvo {
++ const char *model;
++ const char *layout;
++ const char *variant;
++ const char *options;
++};
++
++struct kmscon_dbus {
++ DBusConnection *conn;
++ struct ev_eloop *eloop;
++ struct ev_fd *watch_fd;
++ dbus_update_xkb_layout_cb cb;
++ void *data;
++};
++
++static void set_env_from_locale1_properties(DBusConnection *conn, const char *property_name,
++ const char *env_name)
++{
++ DBusError error;
++ DBusMessage *msg;
++ DBusMessage *reply;
++ DBusMessageIter args, variant_iter;
++ const char *interface_name = "org.freedesktop.locale1";
++ char *value = NULL;
++
++ dbus_error_init(&error);
++
++ msg = dbus_message_new_method_call("org.freedesktop.locale1", "/org/freedesktop/locale1",
++ "org.freedesktop.DBus.Properties", "Get");
++ if (!msg)
++ return;
++
++ dbus_message_iter_init_append(msg, &args);
++ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &interface_name) ||
++ !dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &property_name)) {
++ dbus_message_unref(msg);
++ return;
++ }
++
++ reply = dbus_connection_send_with_reply_and_block(conn, msg, 2000, &error);
++ dbus_message_unref(msg);
++
++ if (dbus_error_has_name(&error, DBUS_ERROR_UNKNOWN_INTERFACE) ||
++ dbus_error_has_name(&error, DBUS_ERROR_UNKNOWN_PROPERTY)) {
++ /* This is normal if the interface is not supported by the system */
++ dbus_error_free(&error);
++ return;
++ } else if (dbus_error_is_set(&error)) {
++ log_warning("dbus error: %s / %s", error.name, error.message);
++ dbus_error_free(&error);
++ return;
++ } else if (!reply) {
++ log_warning("no reply from dbus");
++ return;
++ }
++
++ if (!dbus_message_iter_init(reply, &args)) {
++ log_debug("Reply message has no arguments.\n");
++ } else if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_VARIANT) {
++ log_debug("Reply argument is not a variant.\n");
++ } else {
++ dbus_message_iter_recurse(&args, &variant_iter);
++
++ if (dbus_message_iter_get_arg_type(&variant_iter) == DBUS_TYPE_STRING) {
++ dbus_message_iter_get_basic(&variant_iter, &value);
++ if (strlen(value) > 0) {
++ setenv(env_name, value, 0);
++ log_debug("Set %s to %s\n", env_name, value);
++ }
++ } else {
++ log_debug("Unexpected inner data type in variant.\n");
++ }
++ }
++
++ dbus_message_unref(reply);
++ return;
++}
++
++static bool is_xkb_env_set(void)
++{
++ return getenv("XKB_DEFAULT_MODEL") || getenv("XKB_DEFAULT_LAYOUT") ||
++ getenv("XKB_DEFAULT_VARIANT") || getenv("XKB_DEFAULT_OPTIONS");
++}
++
++/* This is called once at startup to set the XKB environment variables from the locale1 properties.
++ */
++void kmscon_dbus_set_xkb_env_from_locale1(struct kmscon_dbus *dbus)
++{
++ DBusError error;
++
++ if (is_xkb_env_set())
++ return;
++
++ dbus_error_init(&error);
++
++ set_env_from_locale1_properties(dbus->conn, "X11Model", "XKB_DEFAULT_MODEL");
++ set_env_from_locale1_properties(dbus->conn, "X11Layout", "XKB_DEFAULT_LAYOUT");
++ set_env_from_locale1_properties(dbus->conn, "X11Variant", "XKB_DEFAULT_VARIANT");
++ set_env_from_locale1_properties(dbus->conn, "X11Options", "XKB_DEFAULT_OPTIONS");
++}
++
++struct kmscon_dbus *kmscon_dbus_new(struct ev_eloop *eloop)
++{
++ struct kmscon_dbus *dbus;
++ DBusError error;
++
++ dbus = malloc(sizeof(*dbus));
++ if (!dbus)
++ return NULL;
++ memset(dbus, 0, sizeof(*dbus));
++
++ dbus->eloop = eloop;
++
++ dbus_error_init(&error);
++ dbus->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
++ if (dbus_error_is_set(&error)) {
++ log_debug("Connection Error: %s\n", error.message);
++ dbus_error_free(&error);
++ return NULL;
++ }
++ return dbus;
++}
++
++void kmscon_dbus_free(struct kmscon_dbus *dbus)
++{
++ if (!dbus)
++ return;
++ dbus_connection_unref(dbus->conn);
++ free(dbus);
++}
++
++static void set_rmlvo_property(struct rmlvo *rmlvo, const char *property_name,
++ const char *property_value)
++{
++ if (!strcmp(property_name, "X11Model")) {
++ rmlvo->model = property_value;
++ } else if (!strcmp(property_name, "X11Layout")) {
++ rmlvo->layout = property_value;
++ } else if (!strcmp(property_name, "X11Variant")) {
++ rmlvo->variant = property_value;
++ } else if (!strcmp(property_name, "X11Options")) {
++ rmlvo->options = property_value;
++ }
++}
++
++/* Parse the locale1 properties changed message and update the rmlvo struct */
++void parse_locale_properties(DBusMessage *msg, struct rmlvo *rmlvo)
++{
++ DBusMessageIter root_iter;
++ DBusMessageIter dict_iter;
++ DBusMessageIter entry_iter;
++ DBusMessageIter variant_iter;
++ char *property_name;
++ char *property_value;
++
++ if (!dbus_message_iter_init(msg, &root_iter)) {
++ log_debug("Message has no arguments.\n");
++ return;
++ }
++
++ dbus_message_iter_next(&root_iter);
++ if (dbus_message_iter_get_arg_type(&root_iter) != DBUS_TYPE_ARRAY) {
++ log_debug("Not the container type we expect\n");
++ return;
++ }
++
++ dbus_message_iter_recurse(&root_iter, &dict_iter);
++ while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) {
++
++ dbus_message_iter_recurse(&dict_iter, &entry_iter);
++
++ dbus_message_iter_get_basic(&entry_iter, &property_name);
++
++ dbus_message_iter_next(&entry_iter);
++
++ if (dbus_message_iter_get_arg_type(&entry_iter) == DBUS_TYPE_VARIANT) {
++
++ dbus_message_iter_recurse(&entry_iter, &variant_iter);
++
++ if (dbus_message_iter_get_arg_type(&variant_iter) == DBUS_TYPE_STRING) {
++ dbus_message_iter_get_basic(&variant_iter, &property_value);
++ set_rmlvo_property(rmlvo, property_name, property_value);
++ log_debug("property %s: %s\n", property_name, property_value);
++ }
++ }
++ dbus_message_iter_next(&dict_iter);
++ }
++}
++
++static void handle_dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *data)
++{
++ struct kmscon_dbus *dbus = data;
++ struct rmlvo rmlvo = {NULL, NULL, NULL, NULL};
++
++ if (status == DBUS_DISPATCH_DATA_REMAINS) {
++ DBusMessage *msg = dbus_connection_pop_message(dbus->conn);
++ if (msg) {
++ log_debug("Received message %s\n", dbus_message_get_signature(msg));
++ if (dbus_message_is_signal(msg, "org.freedesktop.DBus.Properties",
++ "PropertiesChanged")) {
++ parse_locale_properties(msg, &rmlvo);
++ if (dbus->cb)
++ dbus->cb(rmlvo.model, rmlvo.layout, rmlvo.variant,
++ rmlvo.options, dbus->data);
++ }
++ dbus_message_unref(msg);
++ }
++ }
++}
++
++static void locale1_properties_changed(struct ev_fd *fd, int mask, void *data)
++{
++ struct DBusWatch *watch = data;
++ struct kmscon_dbus *dbus = dbus_watch_get_data(watch);
++
++ unsigned int flags = 0;
++
++ log_debug("locale1 property changed\n");
++
++ if (mask & EV_READABLE)
++ flags |= DBUS_WATCH_READABLE;
++ if (mask & EV_WRITEABLE)
++ flags |= DBUS_WATCH_WRITABLE;
++
++ dbus_watch_handle(watch, flags);
++
++ while (dbus_connection_get_dispatch_status(dbus->conn) == DBUS_DISPATCH_DATA_REMAINS) {
++ dbus_connection_dispatch(dbus->conn);
++ // DBUS-1 API is a pain, but at least that works for this simple use case
++ handle_dispatch_status(dbus->conn, DBUS_DISPATCH_DATA_REMAINS, dbus);
++ }
++}
++
++/* Only register one read watch, as we're not interested in writes */
++static unsigned int dbus_add_watch(DBusWatch *watch, void *data)
++{
++ struct kmscon_dbus *dbus = data;
++ int fd = dbus_watch_get_unix_fd(watch);
++ unsigned int flags = dbus_watch_get_flags(watch);
++ unsigned int ev_flags = 0;
++ int ret = 0;
++
++ log_debug("Adding watch for fd %d with flags %d %p %d\n", fd, flags, watch,
++ dbus_watch_get_enabled(watch));
++
++ if (!dbus_watch_get_enabled(watch))
++ return TRUE;
++
++ if (dbus->watch_fd) {
++ log_warning("dbus watch already registered");
++ return TRUE;
++ }
++ if (flags & DBUS_WATCH_WRITABLE)
++ ev_flags |= EV_WRITEABLE;
++ if (flags & DBUS_WATCH_READABLE)
++ ev_flags |= EV_READABLE;
++
++ ret = ev_eloop_new_fd(dbus->eloop, &dbus->watch_fd, fd, ev_flags,
++ locale1_properties_changed, watch);
++
++ dbus_watch_set_data(watch, dbus, NULL);
++
++ if (ret)
++ log_error("cannot add watch for fd %d: %d %d %p", fd, ret, ev_flags, watch);
++ return TRUE;
++}
++
++static void dbus_remove_watch(DBusWatch *watch, void *data)
++{
++ struct kmscon_dbus *dbus = data;
++ log_debug("Removing watch for fd %d\n", dbus_watch_get_unix_fd(watch));
++
++ if (dbus_watch_get_data(watch) != dbus)
++ return;
++
++ ev_eloop_rm_fd(dbus->watch_fd);
++ dbus->watch_fd = NULL;
++ dbus_watch_set_data(watch, NULL, NULL);
++ return;
++}
++
++static void dbus_toggle_watch(DBusWatch *watch, void *data)
++{
++ log_debug("Toggling watch for fd %d\n", dbus_watch_get_unix_fd(watch));
++
++ if (dbus_watch_get_enabled(watch))
++ dbus_add_watch(watch, data);
++ else
++ dbus_remove_watch(watch, data);
++}
++
++/* Listen to locale1 properties changed, and update the XKB layout if needed */
++int kmscon_dbus_listen_locale1(struct kmscon_dbus *dbus, dbus_update_xkb_layout_cb cb, void *data)
++{
++ DBusError error;
++
++ log_debug("Listening to locale1\n");
++
++ if (!cb)
++ return -EINVAL;
++
++ dbus->cb = cb;
++ dbus->data = data;
++
++ dbus_error_init(&error);
++
++ dbus_connection_set_watch_functions(dbus->conn, dbus_add_watch, dbus_remove_watch,
++ dbus_toggle_watch, dbus, NULL);
++
++ dbus_connection_set_dispatch_status_function(dbus->conn, handle_dispatch_status, dbus,
++ NULL);
++
++ const char *match_rule = "type='signal',"
++ "sender='org.freedesktop.locale1',"
++ "path='/org/freedesktop/locale1',"
++ "interface='org.freedesktop.DBus.Properties',"
++ "member='PropertiesChanged'";
++
++ dbus_bus_add_match(dbus->conn, match_rule, &error);
++
++ if (dbus_error_is_set(&error)) {
++ log_debug("Error adding match: %s\n", error.message);
++ dbus_error_free(&error);
++ return -EIO;
++ }
++ dbus_connection_flush(dbus->conn);
++
++ log_info("Listening to locale1 changes");
++
++ return 0;
++}
+diff --git a/src/kmscon_dummy.h b/src/dbus.h
+similarity index 50%
+rename from src/kmscon_dummy.h
+rename to src/dbus.h
+index b4f3aa2..272434f 100644
+--- a/src/kmscon_dummy.h
++++ b/src/dbus.h
+@@ -1,7 +1,8 @@
+ /*
+- * kmscon - Dummy Session
++ * dbus - D-Bus support
+ *
+- * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
++ * Copyright (c) 2026 Red Hat.
++ * Author: Jocelyn Falempe <jfalempe@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+@@ -23,28 +24,38 @@
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+-/*
+- * Dummy Session
+- */
+-
+-#ifndef KMSCON_DUMMY_H
+-#define KMSCON_DUMMY_H
++#ifndef _KMSCON_DBUS_H
++#define _KMSCON_DBUS_H
+
+-#include <errno.h>
+-#include <stdlib.h>
+-#include "kmscon_seat.h"
++struct ev_eloop;
++struct kmscon_dbus;
+
+-#ifdef BUILD_ENABLE_SESSION_DUMMY
++typedef void (*dbus_update_xkb_layout_cb)(const char *model, const char *layout,
++ const char *variant, const char *options, void *data);
+
+-int kmscon_dummy_register(struct kmscon_session **out, struct kmscon_seat *seat);
++#ifdef BUILD_ENABLE_DBUS
++void kmscon_dbus_set_xkb_env_from_locale1(struct kmscon_dbus *dbus);
++struct kmscon_dbus *kmscon_dbus_new(struct ev_eloop *eloop);
++void kmscon_dbus_free(struct kmscon_dbus *dbus);
++int kmscon_dbus_listen_locale1(struct kmscon_dbus *dbus, dbus_update_xkb_layout_cb cb, void *data);
+
+-#else /* !BUILD_ENABLE_SESSION_DUMMY */
+-
+-static inline int kmscon_dummy_register(struct kmscon_session **out, struct kmscon_seat *seat)
++#else
++static inline void kmscon_dbus_set_xkb_env_from_locale1(struct kmscon_dbus *dbus)
+ {
+- return -EOPNOTSUPP;
++ return;
+ }
+-
+-#endif /* BUILD_ENABLE_SESSION_DUMMY */
+-
+-#endif /* KMSCON_DUMMY_H */
++static inline struct kmscon_dbus *kmscon_dbus_new(struct ev_eloop *eloop)
++{
++ return NULL;
++}
++static inline void kmscon_dbus_free(struct kmscon_dbus *dbus)
++{
++ return;
++}
++static inline int kmscon_dbus_listen_locale1(struct kmscon_dbus *dbus, dbus_update_xkb_layout_cb cb,
++ void *data)
++{
++ return 0;
++}
++#endif /* BUILD_ENABLE_DBUS */
++#endif /* _KMSCON_DBUS_H */
+\ No newline at end of file
+diff --git a/src/font/font_freetype.c b/src/font/font_freetype.c
+index a879d64..d0e3ee9 100644
+--- a/src/font/font_freetype.c
++++ b/src/font/font_freetype.c
+@@ -114,7 +114,7 @@ static int font_get_width(FT_Face face)
+ {
+ FT_UInt glyph_index = FT_Get_Char_Index(face, 'M');
+
+- if (FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT))
++ if (FT_Load_Glyph(face, glyph_index, FT_LOAD_TARGET_LIGHT))
+ return -1;
+
+ if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL))
+@@ -377,7 +377,7 @@ static struct kmscon_glyph *render_glyph(FT_Face face, FT_UInt index, const uint
+ if (!cwidth)
+ return NULL;
+
+- if (FT_Load_Glyph(face, index, FT_LOAD_NO_HINTING)) {
++ if (FT_Load_Glyph(face, index, FT_LOAD_TARGET_LIGHT)) {
+ log_err("Failed to load glyph\n");
+ return NULL;
+ }
+diff --git a/src/font/meson.build b/src/font/meson.build
+index dd5d9dc..d265bb0 100644
+--- a/src/font/meson.build
++++ b/src/font/meson.build
+@@ -22,7 +22,7 @@ font_deps = declare_dependency(
+ # Unifont Generator
+ # This generates the unifont sources from raw hex-encoded font data.
+ #
+-genunifont = executable('genunifont', 'genunifont.c', dependencies: [zlib_deps], native: true)
++genunifont = executable('genunifont', 'genunifont.c', dependencies: [zlib_deps_native], native: true)
+
+ unifont_bin = custom_target('unifont-bin',
+ input: ['font_unifont_data.hex'],
+@@ -68,4 +68,4 @@ if enable_font_pango
+ install: true,
+ install_dir: moduledir,
+ )
+-endif
+\ No newline at end of file
++endif
+diff --git a/src/input/input.c b/src/input/input.c
+index 65c1d6f..71a053b 100644
+--- a/src/input/input.c
++++ b/src/input/input.c
+@@ -504,23 +504,48 @@ err_free:
+
+ SHL_EXPORT
+ int input_set_keymap(struct input *input, const char *model, const char *layout,
+- const char *variant, const char *options, const char *locale,
+- const char *keymap, const char *compose_file, size_t compose_file_len)
+-{
+- /* xkbcommon won't use the XKB_DEFAULT_OPTIONS environment
+- * variable if options is an empty string.
+- * So if all variables are empty, use NULL instead.
+- */
+- if (model && *model == 0 && layout && *layout == 0 && variant && *variant == 0 && options &&
+- *options == 0) {
+- model = NULL;
+- layout = NULL;
+- variant = NULL;
+- options = NULL;
+- }
++ const char *variant, const char *options, const char *keymap)
++{
++ return uxkb_layout_init(input, model, layout, variant, options, keymap);
++}
+
+- return uxkb_desc_init(input, model, layout, variant, options, locale, keymap, compose_file,
+- compose_file_len);
++SHL_EXPORT
++void input_set_compose(struct input *input, const char *locale, const char *compose_file,
++ size_t compose_file_len)
++{
++ uxkb_compose_table_init(input, compose_file, compose_file_len, locale);
++}
++
++SHL_EXPORT
++int input_update_keymap(struct input *input, const char *model, const char *layout,
++ const char *variant, const char *options)
++{
++ struct shl_dlist *iter;
++ struct input_dev *dev;
++
++ if (input->ctx) {
++ shl_dlist_for_each(iter, &input->devices)
++ {
++ dev = shl_dlist_entry(iter, struct input_dev, list);
++ if (dev->capabilities & DEVICE_HAS_KEYS) {
++ input_sleep_dev(dev);
++ input_exit_keyboard(dev);
++ }
++ }
++ }
++ uxkb_compose_table_destroy(input);
++ uxkb_layout_destroy(input);
++ uxkb_layout_init(input, model, layout, variant, options, NULL);
++ shl_dlist_for_each(iter, &input->devices)
++ {
++ dev = shl_dlist_entry(iter, struct input_dev, list);
++ if (dev->capabilities & DEVICE_HAS_KEYS) {
++ input_init_keyboard(dev);
++ if (input->awake)
++ input_wake_up_dev(dev);
++ }
++ }
++ return 0;
+ }
+
+ SHL_EXPORT
+@@ -565,7 +590,8 @@ void input_unref(struct input *input)
+ input_free_dev(dev);
+ }
+
+- uxkb_desc_destroy(input);
++ uxkb_compose_table_destroy(input);
++ uxkb_layout_destroy(input);
+ shl_hook_free(input->key_hook);
+ shl_hook_free(input->pointer_hook);
+ ev_eloop_rm_timer(input->hide_pointer);
+diff --git a/src/input/input.h b/src/input/input.h
+index 8b81e00..9f269ff 100644
+--- a/src/input/input.h
++++ b/src/input/input.h
+@@ -92,8 +92,11 @@ typedef void (*uterm_close_cb)(int fd, int fd_id, void *data);
+
+ int input_new(struct input **out, struct ev_eloop *eloop);
+ int input_set_keymap(struct input *input, const char *model, const char *layout,
+- const char *variant, const char *options, const char *locale,
+- const char *keymap, const char *compose_file, size_t compose_file_len);
++ const char *variant, const char *options, const char *keymap);
++void input_set_compose(struct input *input, const char *locale, const char *compose_file,
++ size_t compose_file_len);
++int input_update_keymap(struct input *input, const char *model, const char *layout,
++ const char *variant, const char *options);
+ void input_set_conf(struct input *input, unsigned int repeat_delay, unsigned int repeat_rate,
+ bool mouse_enabled);
+ void input_ref(struct input *input);
+diff --git a/src/input/input_internal.h b/src/input/input_internal.h
+index 559e8ec..fc932fa 100644
+--- a/src/input/input_internal.h
++++ b/src/input/input_internal.h
+@@ -140,10 +140,12 @@ static inline bool input_bit_is_set(const unsigned long *array, int bit)
+ return !!(array[bit / LONG_BIT] & (1UL << (bit % LONG_BIT)));
+ }
+
+-int uxkb_desc_init(struct input *input, const char *model, const char *layout, const char *variant,
+- const char *options, const char *locale, const char *keymap,
+- const char *compose_file, size_t compose_file_len);
+-void uxkb_desc_destroy(struct input *input);
++int uxkb_layout_init(struct input *input, const char *model, const char *layout,
++ const char *variant, const char *options, const char *keymap);
++void uxkb_compose_table_init(struct input *input, const char *compose_file, size_t compose_file_len,
++ const char *locale);
++void uxkb_layout_destroy(struct input *input);
++void uxkb_compose_table_destroy(struct input *input);
+
+ int uxkb_dev_init(struct input_dev *dev);
+ void uxkb_dev_destroy(struct input_dev *dev);
+diff --git a/src/input/input_uxkb.c b/src/input/input_uxkb.c
+index 377e935..1ae2565 100644
+--- a/src/input/input_uxkb.c
++++ b/src/input/input_uxkb.c
+@@ -69,11 +69,10 @@ static void uxkb_log(struct xkb_context *context, enum xkb_log_level level, cons
+ log_submit(LOG_DEFAULT, sev, format, args);
+ }
+
+-int uxkb_desc_init(struct input *input, const char *model, const char *layout, const char *variant,
+- const char *options, const char *locale, const char *keymap,
+- const char *compose_file, size_t compose_file_len)
++int uxkb_layout_init(struct input *input, const char *model, const char *layout,
++ const char *variant, const char *options, const char *keymap)
+ {
+- int ret;
++ int ret = 0;
+ struct xkb_rule_names rmlvo = {
+ .rules = "evdev",
+ .model = model,
+@@ -140,7 +139,15 @@ int uxkb_desc_init(struct input *input, const char *model, const char *layout, c
+ log_debug("new keyboard description (%s, %s, %s, %s)", model, layout, variant,
+ options);
+ }
++ return 0;
++err_ctx:
++ xkb_context_unref(input->ctx);
++ return ret;
++}
+
++void uxkb_compose_table_init(struct input *input, const char *compose_file, size_t compose_file_len,
++ const char *locale)
++{
+ if (compose_file && *compose_file) {
+ input->compose_table = xkb_compose_table_new_from_buffer(
+ input->ctx, compose_file, compose_file_len, locale,
+@@ -161,19 +168,20 @@ int uxkb_desc_init(struct input *input, const char *model, const char *layout, c
+ "table, disabling compose support");
+ }
+ }
+-
+- return 0;
+-
+-err_ctx:
+- xkb_context_unref(input->ctx);
+- return ret;
+ }
+
+-void uxkb_desc_destroy(struct input *input)
++void uxkb_compose_table_destroy(struct input *input)
+ {
+ xkb_compose_table_unref(input->compose_table);
++ input->compose_table = NULL;
++}
++
++void uxkb_layout_destroy(struct input *input)
++{
+ xkb_keymap_unref(input->keymap);
+ xkb_context_unref(input->ctx);
++ input->keymap = NULL;
++ input->ctx = NULL;
+ }
+
+ static void timer_event(struct ev_timer *timer, uint64_t num, void *data)
+@@ -218,6 +226,9 @@ void uxkb_dev_destroy(struct input_dev *dev)
+ xkb_compose_state_unref(dev->compose_state);
+ xkb_state_unref(dev->state);
+ ev_eloop_rm_timer(dev->repeat_timer);
++ dev->compose_state = NULL;
++ dev->state = NULL;
++ dev->repeat_timer = NULL;
+ }
+
+ #define EVDEV_KEYCODE_OFFSET 8
+diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c
+index 6a8728a..4b01d4a 100644
+--- a/src/kmscon_conf.c
++++ b/src/kmscon_conf.c
+@@ -34,6 +34,7 @@
+ #include <xkbcommon/xkbcommon-keysyms.h>
+ #include "conf.h"
+ #include "kmscon_conf.h"
++#include "kmscon_issue.h"
+ #include "shl/githead.h"
+ #include "shl/log.h"
+ #include "shl/misc.h"
+@@ -78,14 +79,13 @@ static void print_help()
+ "Session Options:\n"
+ "\t --session-max <max> [50] Maximum number of sessions\n"
+ "\t --session-control [off] Allow keyboard session-control\n"
+- "\t --terminal-session [on] Enable terminal session\n"
+ "\n"
+ "Terminal Options:\n"
+ "\t --issue [on]\n"
+ "\t Display issue files before the\n"
+ "\t login prompt. Use --no-issue to\n"
+ "\t let the login process handle it.\n"
+- "\t --issue-path <path> [/etc/issue:/etc/issue.d]\n"
++ "\t --issue-path <path> [" ISSUE_DEFAULT_PATH "]\n"
+ "\t Colon-separated list of issue\n"
+ "\t files and directories to read\n"
+ "\t-l, --login [/bin/login -p]\n"
+@@ -148,8 +148,6 @@ static void print_help()
+ "\t Switch to the next session\n"
+ "\t --grab-session-prev <grab> [<Ctrl><Logo>Left]\n"
+ "\t Switch to the previous session\n"
+- "\t --grab-session-dummy <grab> [<Ctrl><Logo>Escape]\n"
+- "\t Switch to a dummy session\n"
+ "\t --grab-session-close <grab> [<Ctrl><Logo>BackSpace]\n"
+ "\t Close current session\n"
+ "\t --grab-terminal-new <grab> [<Ctrl><Logo>Return]\n"
+@@ -659,9 +657,6 @@ static struct conf_grab def_grab_session_next =
+ static struct conf_grab def_grab_session_prev =
+ CONF_SINGLE_GRAB(SHL_CONTROL_MASK | SHL_LOGO_MASK, XKB_KEY_Left);
+
+-static struct conf_grab def_grab_session_dummy =
+- CONF_SINGLE_GRAB(SHL_CONTROL_MASK | SHL_LOGO_MASK, XKB_KEY_Escape);
+-
+ static struct conf_grab def_grab_session_close =
+ CONF_SINGLE_GRAB(SHL_CONTROL_MASK | SHL_LOGO_MASK, XKB_KEY_BackSpace);
+
+@@ -728,11 +723,10 @@ int kmscon_conf_new(struct conf_ctx **out)
+ /* Session Options */
+ CONF_OPTION_UINT(0, "session-max", &conf->session_max, 50),
+ CONF_OPTION_BOOL(0, "session-control", &conf->session_control, false),
+- CONF_OPTION_BOOL(0, "terminal-session", &conf->terminal_session, true),
+
+ /* Terminal Options */
+ CONF_OPTION_BOOL(0, "issue", &conf->issue, true),
+- CONF_OPTION_STRING(0, "issue-path", &conf->issue_path, "/etc/issue:/etc/issue.d"),
++ CONF_OPTION_STRING(0, "issue-path", &conf->issue_path, ISSUE_DEFAULT_PATH),
+ CONF_OPTION(0, 'l', "login", &conf_login, aftercheck_login, NULL, file_login,
+ &conf->login, false),
+ CONF_OPTION_STRING('t', "term", &conf->term, "kmscon"),
+@@ -742,12 +736,12 @@ int kmscon_conf_new(struct conf_ctx **out)
+ CONF_OPTION_BOOL(0, "bell", &conf->bell, false),
+
+ /* Input Options */
+- CONF_OPTION_STRING(0, "xkb-model", &conf->xkb_model, ""),
+- CONF_OPTION_STRING(0, "xkb-layout", &conf->xkb_layout, ""),
+- CONF_OPTION_STRING(0, "xkb-variant", &conf->xkb_variant, ""),
+- CONF_OPTION_STRING(0, "xkb-options", &conf->xkb_options, ""),
+- CONF_OPTION_STRING(0, "xkb-keymap", &conf->xkb_keymap, ""),
+- CONF_OPTION_STRING(0, "xkb-compose-file", &conf->xkb_compose_file, ""),
++ CONF_OPTION_STRING(0, "xkb-model", &conf->xkb_model, NULL),
++ CONF_OPTION_STRING(0, "xkb-layout", &conf->xkb_layout, NULL),
++ CONF_OPTION_STRING(0, "xkb-variant", &conf->xkb_variant, NULL),
++ CONF_OPTION_STRING(0, "xkb-options", &conf->xkb_options, NULL),
++ CONF_OPTION_STRING(0, "xkb-keymap", &conf->xkb_keymap, NULL),
++ CONF_OPTION_STRING(0, "xkb-compose-file", &conf->xkb_compose_file, NULL),
+ CONF_OPTION_UINT(0, "xkb-repeat-delay", &conf->xkb_repeat_delay, 250),
+ CONF_OPTION_UINT(0, "xkb-repeat-rate", &conf->xkb_repeat_rate, 50),
+ CONF_OPTION_BOOL(0, "mouse", &conf->mouse, true),
+@@ -767,8 +761,6 @@ int kmscon_conf_new(struct conf_ctx **out)
+ &def_grab_session_next),
+ CONF_OPTION_GRAB(0, "grab-session-prev", &conf->grab_session_prev,
+ &def_grab_session_prev),
+- CONF_OPTION_GRAB(0, "grab-session-dummy", &conf->grab_session_dummy,
+- &def_grab_session_dummy),
+ CONF_OPTION_GRAB(0, "grab-session-close", &conf->grab_session_close,
+ &def_grab_session_close),
+ CONF_OPTION_GRAB(0, "grab-terminal-new", &conf->grab_terminal_new,
+diff --git a/src/kmscon_conf.h b/src/kmscon_conf.h
+index 8dc7afd..377f8a3 100644
+--- a/src/kmscon_conf.h
++++ b/src/kmscon_conf.h
+@@ -79,8 +79,6 @@ struct kmscon_conf_t {
+ unsigned int session_max;
+ /* allow keyboard session control */
+ bool session_control;
+- /* run terminal session */
+- bool terminal_session;
+
+ /* Terminal Options */
+ /* display /etc/issue before login prompt */
+@@ -145,8 +143,6 @@ struct kmscon_conf_t {
+ struct conf_grab *grab_session_next;
+ /* session-prev grab */
+ struct conf_grab *grab_session_prev;
+- /* session-dummy grab */
+- struct conf_grab *grab_session_dummy;
+ /* session-close grab */
+ struct conf_grab *grab_session_close;
+ /* terminal-new grab */
+diff --git a/src/kmscon_dummy.c b/src/kmscon_dummy.c
+deleted file mode 100644
+index fcafb6c..0000000
+--- a/src/kmscon_dummy.c
++++ /dev/null
+@@ -1,154 +0,0 @@
+-/*
+- * kmscon - Dummy Session
+- *
+- * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+- *
+- * Permission is hereby granted, free of charge, to any person obtaining
+- * a copy of this software and associated documentation files
+- * (the "Software"), to deal in the Software without restriction, including
+- * without limitation the rights to use, copy, modify, merge, publish,
+- * distribute, sublicense, and/or sell copies of the Software, and to
+- * permit persons to whom the Software is furnished to do so, subject to
+- * the following conditions:
+- *
+- * The above copyright notice and this permission notice shall be included
+- * in all copies or substantial portions of the Software.
+- *
+- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+- */
+-
+-/*
+- * Dummy Session
+- */
+-
+-#include <errno.h>
+-#include <stdlib.h>
+-#include <string.h>
+-#include "kmscon_dummy.h"
+-#include "kmscon_seat.h"
+-#include "shl/dlist.h"
+-#include "shl/log.h"
+-#include "video/video.h"
+-
+-#define LOG_SUBSYSTEM "dummy"
+-
+-struct display {
+- struct shl_dlist list;
+- struct display *disp;
+-};
+-
+-struct kmscon_dummy {
+- struct kmscon_session *session;
+- struct shl_dlist displays;
+- bool active;
+-};
+-
+-static void dummy_redraw(struct kmscon_dummy *dummy, struct display *d)
+-{
+- display_clear(d->disp, 0, 0, 0);
+- display_swap(d->disp);
+-}
+-
+-static int dummy_session_event(struct kmscon_session *session, struct kmscon_session_event *ev,
+- void *data)
+-{
+- struct kmscon_dummy *dummy = data;
+- struct display *d;
+- struct shl_dlist *iter;
+-
+- switch (ev->type) {
+- case KMSCON_SESSION_DISPLAY_NEW:
+- d = malloc(sizeof(*d));
+- if (!d) {
+- log_error("cannot allocate memory for new display");
+- break;
+- }
+- memset(d, 0, sizeof(*d));
+- d->disp = ev->disp;
+- shl_dlist_link_tail(&dummy->displays, &d->list);
+- if (dummy->active)
+- dummy_redraw(dummy, d);
+- break;
+- case KMSCON_SESSION_DISPLAY_GONE:
+- shl_dlist_for_each(iter, &dummy->displays)
+- {
+- d = shl_dlist_entry(iter, struct display, list);
+- if (d->disp != ev->disp)
+- continue;
+-
+- shl_dlist_unlink(&d->list);
+- free(d);
+- break;
+- }
+- break;
+- case KMSCON_SESSION_DISPLAY_REFRESH:
+- shl_dlist_for_each(iter, &dummy->displays)
+- {
+- d = shl_dlist_entry(iter, struct display, list);
+- if (d->disp != ev->disp)
+- continue;
+-
+- if (dummy->active)
+- dummy_redraw(dummy, d);
+- break;
+- }
+- break;
+- case KMSCON_SESSION_ACTIVATE:
+- dummy->active = true;
+- shl_dlist_for_each(iter, &dummy->displays)
+- {
+- d = shl_dlist_entry(iter, struct display, list);
+- dummy_redraw(dummy, d);
+- }
+- break;
+- case KMSCON_SESSION_DEACTIVATE:
+- dummy->active = false;
+- break;
+- case KMSCON_SESSION_UNREGISTER:
+- while (!shl_dlist_empty(&dummy->displays)) {
+- d = shl_dlist_entry(dummy->displays.prev, struct display, list);
+- shl_dlist_unlink(&d->list);
+- free(d);
+- }
+-
+- free(dummy);
+- break;
+- }
+-
+- return 0;
+-}
+-
+-int kmscon_dummy_register(struct kmscon_session **out, struct kmscon_seat *seat)
+-{
+- struct kmscon_dummy *dummy;
+- int ret;
+-
+- if (!out || !seat)
+- return -EINVAL;
+-
+- dummy = malloc(sizeof(*dummy));
+- if (!dummy)
+- return -ENOMEM;
+- memset(dummy, 0, sizeof(*dummy));
+- shl_dlist_init(&dummy->displays);
+-
+- ret = kmscon_seat_register_session(seat, &dummy->session, dummy_session_event, dummy);
+- if (ret) {
+- log_error("cannot register session for dummy: %d", ret);
+- goto err_free;
+- }
+-
+- *out = dummy->session;
+- log_debug("new dummy object %p", dummy);
+- return 0;
+-
+-err_free:
+- free(dummy);
+- return ret;
+-}
+diff --git a/src/kmscon_issue.c b/src/kmscon_issue.c
+index 3846f89..bffb792 100644
+--- a/src/kmscon_issue.c
++++ b/src/kmscon_issue.c
+@@ -36,16 +36,6 @@
+ /* Cap total collected issue text to prevent runaway allocation. */
+ #define ISSUE_MAX_SIZE (256u * 1024u)
+
+-/*
+- * Default search path, matching agetty's default:
+- * /etc/issue:/etc/issue.d
+- *
+- * Distros can override this with --issue-path or the issue-path config
+- * option using a colon-separated list. Plain files are read directly;
+- * directories are scanned for *.issue files in lexicographic order.
+- */
+-#define ISSUE_DEFAULT_PATH "/etc/issue:/etc/issue.d"
+-
+ /* Color escape codes */
+ // clang-format off
+ #define UL_COLOR_RESET "[0m"
+diff --git a/src/kmscon_issue.h b/src/kmscon_issue.h
+index f74382a..8900c95 100644
+--- a/src/kmscon_issue.h
++++ b/src/kmscon_issue.h
+@@ -29,6 +29,15 @@
+ #include <libtsm.h>
+ #include "pty.h"
+
++/*
++ * Default search path, matching agetty's default.
++ *
++ * Distros can override this with --issue-path or the issue-path config
++ * option using a colon-separated list. Plain files are read directly;
++ * directories are scanned for *.issue files in lexicographic order.
++ */
++#define ISSUE_DEFAULT_PATH "/etc/issue:/etc/issue.d"
++
+ void kmscon_issue_write(struct tsm_vte *vte, struct kmscon_pty *pty, const char *search_path);
+
+ #endif /* KMSCON_ISSUE_H */
+diff --git a/src/kmscon_main.c b/src/kmscon_main.c
+index 6fb57a8..81343f4 100644
+--- a/src/kmscon_main.c
++++ b/src/kmscon_main.c
+@@ -31,6 +31,7 @@
+ #include <string.h>
+ #include <sys/signalfd.h>
+ #include "conf.h"
++#include "dbus.h"
+ #include "kmscon_conf.h"
+ #include "kmscon_seat.h"
+ #include "render/text.h"
+@@ -39,6 +40,8 @@
+ #include "shl/module.h"
+ #include "video/video.h"
+
++#define LOG_SUBSYSTEM "kmscon"
++
+ struct kmscon_app {
+ struct conf_ctx *conf_ctx;
+ struct kmscon_conf_t *conf;
+@@ -49,6 +52,7 @@ struct kmscon_app {
+ struct conf_ctx *seat_ctx;
+ struct kmscon_conf_t *seat_conf;
+ struct kmscon_seat *seat;
++ struct kmscon_dbus *dbus;
+ };
+
+ static int app_seat_event(struct kmscon_seat *s, unsigned int event, void *data)
+@@ -112,6 +116,30 @@ static void destroy_app(struct kmscon_app *app)
+ ev_eloop_unregister_signal_cb(app->eloop, SIGINT, app_sig_generic, app);
+ ev_eloop_unregister_signal_cb(app->eloop, SIGTERM, app_sig_generic, app);
+ ev_eloop_unref(app->eloop);
++ kmscon_dbus_free(app->dbus);
++}
++
++static void update_xkb_layout(const char *model, const char *layout, const char *variant,
++ const char *options, void *data)
++{
++ struct kmscon_app *app = data;
++
++ log_debug("updating xkb layout: %s, %s, %s, %s", model, layout, variant, options);
++ if (kmscon_seat_update_xkb_layout(app->seat, model, layout, variant, options))
++ log_error("cannot update xkb layout");
++}
++
++static void listen_to_dbus_locale1(struct kmscon_app *app)
++{
++ int ret;
++
++ if (app->conf->xkb_keymap || app->conf->xkb_model || app->conf->xkb_layout ||
++ app->conf->xkb_variant || app->conf->xkb_options)
++ return;
++
++ ret = kmscon_dbus_listen_locale1(app->dbus, update_xkb_layout, app);
++ if (ret)
++ log_warning("cannot listen to locale1: %d", ret);
+ }
+
+ static int setup_app(struct kmscon_app *app)
+@@ -142,6 +170,11 @@ static int setup_app(struct kmscon_app *app)
+ goto err_app;
+ }
+
++ app->dbus = kmscon_dbus_new(app->eloop);
++ if (app->dbus) {
++ kmscon_dbus_set_xkb_env_from_locale1(app->dbus);
++ listen_to_dbus_locale1(app);
++ }
+ return 0;
+
+ err_app:
+diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c
+index 7b6759a..91b8db8 100644
+--- a/src/kmscon_seat.c
++++ b/src/kmscon_seat.c
+@@ -39,7 +39,6 @@
+ #include "conf.h"
+ #include "input/input.h"
+ #include "kmscon_conf.h"
+-#include "kmscon_dummy.h"
+ #include "kmscon_seat.h"
+ #include "kmscon_terminal.h"
+ #include "shl/dlist.h"
+@@ -53,17 +52,12 @@
+
+ struct kmscon_session {
+ struct shl_dlist list;
+- unsigned long ref;
+ struct kmscon_seat *seat;
+
+- bool enabled;
+ bool foreground;
+- bool deactivating;
+
+ struct ev_timer *timer;
+-
+- kmscon_session_cb_t cb;
+- void *data;
++ struct kmscon_terminal *term;
+ };
+
+ struct kmscon_display {
+@@ -85,12 +79,6 @@ struct kmscon_video {
+ bool awake;
+ };
+
+-enum kmscon_async_schedule {
+- SCHEDULE_SWITCH,
+- SCHEDULE_VT,
+- SCHEDULE_UNREGISTER,
+-};
+-
+ struct kmscon_seat {
+ struct ev_eloop *eloop;
+ struct conf_ctx *conf_ctx;
+@@ -110,10 +98,6 @@ struct kmscon_seat {
+ bool awake;
+ bool foreground;
+ struct kmscon_session *current_sess;
+- struct kmscon_session *scheduled_sess;
+- struct kmscon_session *dummy_sess;
+-
+- unsigned int async_schedule;
+
+ /* DPMS timeout management */
+ struct ev_timer *dpms_timer;
+@@ -133,48 +117,10 @@ static int kmscon_seat_add_video(struct kmscon_seat *seat, enum uterm_monitor_de
+ static void kmscon_seat_remove_video(struct kmscon_seat *seat, void *data);
+ static void kmscon_seat_poll_video(void *data);
+
+-static int session_call(struct kmscon_session *sess, unsigned int event, struct display *disp)
+-{
+- struct kmscon_session_event ev;
+-
+- if (!sess->cb)
+- return 0;
+-
+- memset(&ev, 0, sizeof(ev));
+- ev.type = event;
+- ev.disp = disp;
+- return sess->cb(sess, &ev, sess->data);
+-}
+-
+-static int session_call_activate(struct kmscon_session *sess)
+-{
+- log_debug("activate session %p", sess);
+- return session_call(sess, KMSCON_SESSION_ACTIVATE, NULL);
+-}
+-
+-static int session_call_deactivate(struct kmscon_session *sess)
+-{
+- log_debug("deactivate session %p", sess);
+- return session_call(sess, KMSCON_SESSION_DEACTIVATE, NULL);
+-}
+-
+-static void session_call_display_new(struct kmscon_session *sess, struct display *disp)
+-{
+- session_call(sess, KMSCON_SESSION_DISPLAY_NEW, disp);
+-}
+-
+-static void session_call_display_gone(struct kmscon_session *sess, struct display *disp)
+-{
+- session_call(sess, KMSCON_SESSION_DISPLAY_GONE, disp);
+-}
+-
+-static void session_call_display_refresh(struct kmscon_session *sess, struct display *disp)
+-{
+- session_call(sess, KMSCON_SESSION_DISPLAY_REFRESH, disp);
+-}
+-
+ /* Forward declaration */
+ static void seat_dpms_reset_timer(struct kmscon_seat *seat);
++static struct kmscon_session *kmscon_seat_new_session(struct kmscon_seat *seat);
++static void kmscon_session_unregister(struct kmscon_session *sess);
+
+ static void activate_display(struct kmscon_display *d)
+ {
+@@ -203,12 +149,12 @@ static void activate_display(struct kmscon_display *d)
+ shl_dlist_for_each_safe(iter, tmp, &seat->sessions)
+ {
+ s = shl_dlist_entry(iter, struct kmscon_session, list);
+- session_call_display_new(s, d->disp);
++ terminal_add_display(s->term, d->disp);
+ }
+ }
+ }
+
+-static int seat_go_foreground(struct kmscon_seat *seat, bool force)
++static int seat_go_foreground(struct kmscon_seat *seat)
+ {
+ struct shl_dlist *iter, *tmp;
+ struct kmscon_video *vid;
+@@ -217,7 +163,7 @@ static int seat_go_foreground(struct kmscon_seat *seat, bool force)
+
+ if (seat->foreground)
+ return 0;
+- if (!seat->awake || (!force && seat->current_sess))
++ if (!seat->awake)
+ return -EBUSY;
+
+ seat->foreground = true;
+@@ -251,14 +197,14 @@ static int seat_go_foreground(struct kmscon_seat *seat, bool force)
+ return 0;
+ }
+
+-static int seat_go_background(struct kmscon_seat *seat, bool force)
++static int seat_go_background(struct kmscon_seat *seat)
+ {
+ struct shl_dlist *iter;
+ struct kmscon_video *vid;
+
+ if (!seat->foreground)
+ return 0;
+- if (!seat->awake || (!force && seat->current_sess))
++ if (!seat->awake)
+ return -EBUSY;
+
+ shl_dlist_for_each(iter, &seat->videos)
+@@ -271,25 +217,15 @@ static int seat_go_background(struct kmscon_seat *seat, bool force)
+ return 0;
+ }
+
+-static int seat_go_asleep(struct kmscon_seat *seat, bool force)
++static int seat_go_asleep(struct kmscon_seat *seat)
+ {
+- int err = 0;
+-
+ if (!seat->awake)
+ return 0;
+- if (seat->current_sess || seat->foreground) {
+- if (force) {
+- seat->foreground = false;
+- seat->current_sess = NULL;
+- err = -EBUSY;
+- } else {
+- return -EBUSY;
+- }
+- }
++
+ seat->awake = false;
+ input_sleep(seat->input);
+
+- return err;
++ return 0;
+ }
+
+ static int seat_go_awake(struct kmscon_seat *seat)
+@@ -313,196 +249,52 @@ static int seat_go_awake(struct kmscon_seat *seat)
+ return 0;
+ }
+
+-static int seat_run(struct kmscon_seat *seat)
++static void seat_switch(struct kmscon_seat *seat, struct kmscon_session *new)
+ {
+- int ret;
+- struct kmscon_session *session;
+-
+- if (!seat->awake)
+- return -EBUSY;
+- if (seat->current_sess)
+- return 0;
+-
+- if (!seat->scheduled_sess) {
+- log_debug("no session scheduled to run (num %zu)", seat->session_count);
+- return -ENOENT;
+- }
+- session = seat->scheduled_sess;
+-
+- if (session->foreground && !seat->foreground) {
+- ret = seat_go_foreground(seat, false);
+- if (ret) {
+- log_warning("cannot put seat %s into foreground for session %p", seat->name,
+- session);
+- return ret;
+- }
+- } else if (!session->foreground && seat->foreground) {
+- ret = seat_go_background(seat, false);
+- if (ret) {
+- log_warning("cannot put seat %s into background for session %p", seat->name,
+- session);
+- return ret;
+- }
+- }
+-
+- ret = session_call_activate(session);
+- if (ret) {
+- log_warning("cannot activate session %p: %d", session, ret);
+- return ret;
+- }
+-
+- seat->current_sess = session;
+-
+- return 0;
+-}
+-
+-static void session_deactivate(struct kmscon_session *sess)
+-{
+- if (sess->seat->current_sess != sess)
++ if (seat->current_sess == new)
+ return;
+
+- sess->seat->async_schedule = SCHEDULE_SWITCH;
+- sess->deactivating = false;
+- sess->seat->current_sess = NULL;
+-}
+-
+-static int seat_pause(struct kmscon_seat *seat, bool force)
+-{
+- int ret;
+-
+- if (!seat->current_sess)
+- return 0;
+-
+- seat->current_sess->deactivating = true;
+- ret = session_call_deactivate(seat->current_sess);
+- if (ret) {
+- if (ret == -EINPROGRESS)
+- log_debug("pending deactivation for session %p", seat->current_sess);
+- else
+- log_warning("cannot deactivate session %p: %d", seat->current_sess, ret);
+- if (!force)
+- return ret;
+- }
+-
+- session_deactivate(seat->current_sess);
+-
+- return ret;
+-}
+-
+-static void seat_reschedule(struct kmscon_seat *seat)
+-{
+- struct shl_dlist *iter, *start;
+- struct kmscon_session *sess;
+-
+- if (seat->scheduled_sess && seat->scheduled_sess->enabled)
+- return;
+-
+- if (seat->current_sess && seat->current_sess->enabled) {
+- seat->scheduled_sess = seat->current_sess;
+- return;
+- }
++ log_debug("switch session from %p to %p on seat %s", seat->current_sess, new, seat->name);
+
+ if (seat->current_sess)
+- start = &seat->current_sess->list;
+- else
+- start = &seat->sessions;
+-
+- shl_dlist_for_each_but_one(iter, start, &seat->sessions)
+- {
+- sess = shl_dlist_entry(iter, struct kmscon_session, list);
+- if (sess == seat->dummy_sess || !sess->enabled)
+- continue;
+- seat->scheduled_sess = sess;
+- return;
++ terminal_deactivate(seat->current_sess->term);
++ if (new) {
++ if (new->foreground && !seat->foreground) {
++ seat_go_foreground(seat);
++ } else if (!new->foreground && seat->foreground) {
++ seat_go_background(seat);
++ }
++ terminal_activate(new->term);
+ }
+-
+- if (seat->dummy_sess && seat->dummy_sess->enabled)
+- seat->scheduled_sess = seat->dummy_sess;
+- else
+- seat->scheduled_sess = NULL;
+-}
+-
+-static bool seat_has_schedule(struct kmscon_seat *seat)
+-{
+- return seat->scheduled_sess && seat->scheduled_sess != seat->current_sess;
+-}
+-
+-static int seat_switch(struct kmscon_seat *seat)
+-{
+- int ret;
+-
+- seat->async_schedule = SCHEDULE_SWITCH;
+- ret = seat_pause(seat, false);
+- if (ret)
+- return ret;
+-
+- return seat_run(seat);
++ seat->current_sess = new;
+ }
+
+ static void seat_next(struct kmscon_seat *seat)
+ {
+- struct shl_dlist *cur, *iter;
+- struct kmscon_session *s, *next;
++ struct kmscon_session *next = NULL;
+
+ if (seat->current_sess)
+- cur = &seat->current_sess->list;
+- else if (seat->session_count)
+- cur = &seat->sessions;
+- else
+- return;
+-
+- next = NULL;
+- if (!seat->current_sess && seat->dummy_sess && seat->dummy_sess->enabled)
+- next = seat->dummy_sess;
+-
+- shl_dlist_for_each_but_one(iter, cur, &seat->sessions)
+- {
+- s = shl_dlist_entry(iter, struct kmscon_session, list);
+- if (!s->enabled || seat->dummy_sess == s)
+- continue;
+-
+- next = s;
+- break;
+- }
+-
++ next = shl_dlist_next(seat->current_sess, &seat->sessions, list);
+ if (!next)
+- return;
++ next = shl_dlist_first(&seat->sessions, struct kmscon_session, list);
++ if (next && next == seat->current_sess)
++ next = NULL;
+
+- seat->scheduled_sess = next;
+- seat_switch(seat);
++ seat_switch(seat, next);
+ }
+
+ static void seat_prev(struct kmscon_seat *seat)
+ {
+- struct shl_dlist *cur, *iter;
+- struct kmscon_session *s, *prev;
++ struct kmscon_session *prev = NULL;
+
+ if (seat->current_sess)
+- cur = &seat->current_sess->list;
+- else if (seat->session_count)
+- cur = &seat->sessions;
+- else
+- return;
+-
+- prev = NULL;
+- if (!seat->current_sess && seat->dummy_sess && seat->dummy_sess->enabled)
+- prev = seat->dummy_sess;
+-
+- shl_dlist_for_each_reverse_but_one(iter, cur, &seat->sessions)
+- {
+- s = shl_dlist_entry(iter, struct kmscon_session, list);
+- if (!s->enabled || seat->dummy_sess == s)
+- continue;
+-
+- prev = s;
+- break;
+- }
+-
++ prev = shl_dlist_prev(seat->current_sess, &seat->sessions, list);
+ if (!prev)
+- return;
++ prev = shl_dlist_last(&seat->sessions, struct kmscon_session, list);
++ if (prev && prev == seat->current_sess)
++ prev = NULL;
+
+- seat->scheduled_sess = prev;
+- seat_switch(seat);
++ seat_switch(seat, prev);
+ }
+
+ static void seat_add_display(struct kmscon_seat *seat, struct display *disp)
+@@ -536,7 +328,7 @@ static void seat_remove_display(struct kmscon_seat *seat, struct kmscon_display
+ shl_dlist_for_each_safe(iter, tmp, &seat->sessions)
+ {
+ s = shl_dlist_entry(iter, struct kmscon_session, list);
+- session_call_display_gone(s, d->disp);
++ terminal_rm_display(s->term, d->disp);
+ }
+ }
+
+@@ -569,7 +361,7 @@ static void seat_refresh_display(struct kmscon_seat *seat, struct kmscon_display
+ shl_dlist_for_each(iter, &seat->sessions)
+ {
+ s = shl_dlist_entry(iter, struct kmscon_session, list);
+- session_call_display_refresh(s, d->disp);
++ terminal_refresh_displays(s->term);
+ }
+ }
+ }
+@@ -578,9 +370,14 @@ static void seat_vt_activate(struct uterm_vt *vt, void *data)
+ {
+ struct kmscon_seat *seat = data;
+
++ log_debug("VT %d activated on seat %s", uterm_vt_get_num(vt), seat->name);
++
+ if (seat_go_awake(seat))
+ return;
+- seat_run(seat);
++ if (seat_go_foreground(seat))
++ return;
++ if (seat->current_sess)
++ terminal_activate(seat->current_sess->term);
+ }
+
+ static int seat_vt_deactivate(struct uterm_vt *vt, bool force, void *data)
+@@ -588,14 +385,13 @@ static int seat_vt_deactivate(struct uterm_vt *vt, bool force, void *data)
+ struct kmscon_seat *seat = data;
+ int ret;
+
+- seat->async_schedule = SCHEDULE_VT;
+- ret = seat_pause(seat, false);
+- if (ret)
+- return ret;
+- ret = seat_go_background(seat, false);
++ if (seat->current_sess)
++ terminal_deactivate(seat->current_sess->term);
++
++ ret = seat_go_background(seat);
+ if (ret)
+ return ret;
+- ret = seat_go_asleep(seat, false);
++ ret = seat_go_asleep(seat);
+ if (ret)
+ return ret;
+ return 0;
+@@ -726,7 +522,6 @@ static void seat_input_event(struct input *input, struct input_key_event *ev, vo
+ {
+ struct kmscon_seat *seat = data;
+ struct kmscon_session *s;
+- int ret;
+
+ /* Reset DPMS timer on any input event */
+ seat_dpms_reset_timer(seat);
+@@ -736,49 +531,27 @@ static void seat_input_event(struct input *input, struct input_key_event *ev, vo
+
+ if (conf_grab_matches(seat->conf->grab_session_next, ev->mods, ev->num_syms, ev->keysyms)) {
+ ev->handled = true;
+- if (!seat->conf->session_control)
++ if (!seat->conf->session_control || seat->session_count < 2)
+ return;
+ seat_next(seat);
+ return;
+ }
+ if (conf_grab_matches(seat->conf->grab_session_prev, ev->mods, ev->num_syms, ev->keysyms)) {
+ ev->handled = true;
+- if (!seat->conf->session_control)
++ if (!seat->conf->session_control || seat->session_count < 2)
+ return;
+ seat_prev(seat);
+ return;
+ }
+- if (conf_grab_matches(seat->conf->grab_session_dummy, ev->mods, ev->num_syms,
+- ev->keysyms)) {
+- ev->handled = true;
+- if (!seat->conf->session_control)
+- return;
+- seat->scheduled_sess = seat->dummy_sess;
+- seat_switch(seat);
+- return;
+- }
++
+ if (conf_grab_matches(seat->conf->grab_session_close, ev->mods, ev->num_syms,
+ ev->keysyms)) {
+ ev->handled = true;
+- if (!seat->conf->session_control)
++ if (!seat->conf->session_control || seat->session_count < 2)
+ return;
+ s = seat->current_sess;
+ if (!s)
+ return;
+- if (s == seat->dummy_sess)
+- return;
+-
+- /* First time this is invoked on a session, we simply try
+- * unloading it. If it fails, we give it some time. If this is
+- * invoked a second time, we notice that we already tried
+- * removing it and so we go straight to unregistering the
+- * session unconditionally. */
+- if (!s->deactivating) {
+- seat->async_schedule = SCHEDULE_UNREGISTER;
+- ret = seat_pause(seat, false);
+- if (ret)
+- return;
+- }
+
+ kmscon_session_unregister(s);
+ return;
+@@ -787,16 +560,9 @@ static void seat_input_event(struct input *input, struct input_key_event *ev, vo
+ ev->handled = true;
+ if (!seat->conf->session_control)
+ return;
+- ret = kmscon_terminal_register(&s, seat, uterm_vt_get_num(seat->vt));
+- if (ret == -EOPNOTSUPP) {
+- log_notice("terminal support not compiled in");
+- } else if (ret) {
+- log_error("cannot register terminal session: %d", ret);
+- } else {
+- s->enabled = true;
+- seat->scheduled_sess = s;
+- seat_switch(seat);
+- }
++ s = kmscon_seat_new_session(seat);
++ if (s)
++ seat_switch(seat, s);
+ return;
+ }
+ if (seat->conf->grab_reboot &&
+@@ -829,12 +595,9 @@ static const char *find_locale(void)
+
+ static int kmscon_seat_set_keymap(struct kmscon_seat *seat)
+ {
+- const char *locale;
+- char *keymap, *compose_file;
+- size_t compose_file_len;
+- int ret;
+
+- locale = find_locale();
++ char *keymap;
++ int ret;
+
+ /* TODO: The XKB-API currently requires zero-terminated strings as
+ * keymap input. Hence, we have to read it in instead of using mmap().
+@@ -846,21 +609,49 @@ static int kmscon_seat_set_keymap(struct kmscon_seat *seat)
+ log_error("cannot read keymap file %s: %d", seat->conf->xkb_keymap, ret);
+ }
+
++ ret = input_set_keymap(seat->input, seat->conf->xkb_model, seat->conf->xkb_layout,
++ seat->conf->xkb_variant, seat->conf->xkb_options, keymap);
++ if (ret)
++ log_error("cannot set keymap: %d", ret);
++
++ free(keymap);
++ return ret;
++}
++
++static void kmscon_seat_set_compose(struct kmscon_seat *seat)
++{
++ const char *locale;
++ char *compose_file;
++ size_t compose_file_len;
++ int ret;
++
++ locale = find_locale();
++
+ compose_file = NULL;
+ compose_file_len = 0;
+ if (seat->conf->xkb_compose_file && *seat->conf->xkb_compose_file) {
+ ret = shl_read_file(seat->conf->xkb_compose_file, &compose_file, &compose_file_len);
+- if (ret)
++ if (ret) {
+ log_error("cannot read compose file %s: %d", seat->conf->xkb_compose_file,
+ ret);
++ return;
++ }
+ }
+- ret = input_set_keymap(seat->input, seat->conf->xkb_model, seat->conf->xkb_layout,
+- seat->conf->xkb_variant, seat->conf->xkb_options, locale, keymap,
+- compose_file, compose_file_len);
+- if (ret)
+- log_error("cannot set keymap: %d", ret);
++ input_set_compose(seat->input, locale, compose_file, compose_file_len);
++}
+
+- free(keymap);
++int kmscon_seat_update_xkb_layout(struct kmscon_seat *seat, const char *model, const char *layout,
++ const char *variant, const char *options)
++{
++ int ret;
++
++ ret = input_update_keymap(seat->input, model, layout, variant, options);
++ if (ret) {
++ log_error("cannot update keymap: %d", ret);
++ return ret;
++ }
++ /* Compose table is lost when updating the keymap, so we need to set it again */
++ kmscon_seat_set_compose(seat);
+ return ret;
+ }
+
+@@ -993,6 +784,8 @@ int kmscon_seat_new(struct kmscon_seat **out, struct conf_ctx *main_conf,
+ if (ret)
+ goto err_conf;
+
++ kmscon_seat_set_compose(seat);
++
+ ret = uterm_monitor_new(&seat->mon, seat->eloop, &seat_monitor_cb, seat);
+ if (ret) {
+ log_error("cannot create device monitor: %d", ret);
+@@ -1063,12 +856,9 @@ void kmscon_seat_free(struct kmscon_seat *seat)
+ if (!seat)
+ return;
+
+- ret = seat_pause(seat, true);
+- if (ret)
+- log_warning("destroying seat %s while session %p is active", seat->name,
+- seat->current_sess);
++ seat_switch(seat, NULL);
+
+- ret = seat_go_asleep(seat, true);
++ ret = seat_go_asleep(seat);
+ if (ret)
+ log_warning("destroying seat %s while still awake: %d", seat->name, ret);
+
+@@ -1306,31 +1096,18 @@ static void kmscon_seat_poll_video(void *data)
+
+ void kmscon_seat_startup(struct kmscon_seat *seat)
+ {
+- int ret;
+ struct kmscon_session *s;
+
+ if (!seat)
+ return;
+
+- ret = kmscon_dummy_register(&s, seat);
+- if (ret == -EOPNOTSUPP) {
+- log_notice("dummy sessions not compiled in");
+- } else if (ret) {
+- log_error("cannot register dummy session: %d", ret);
+- } else {
+- seat->dummy_sess = s;
+- kmscon_session_enable(s);
+- }
++ seat_go_awake(seat);
+
+- if (seat->conf->terminal_session) {
+- ret = kmscon_terminal_register(&s, seat, uterm_vt_get_num(seat->vt));
+- if (ret == -EOPNOTSUPP)
+- log_notice("terminal support not compiled in");
+- else if (ret)
+- log_error("cannot register terminal session");
+- else
+- kmscon_session_enable(s);
+- }
++ s = kmscon_seat_new_session(seat);
++ if (s)
++ seat_switch(seat, s);
++ else
++ log_error("cannot create new session on seat %s", seat->name);
+
+ if (seat->conf->switchvt || uterm_vt_get_num(seat->vt) == 0)
+ uterm_vt_activate(seat->vt);
+@@ -1339,30 +1116,6 @@ void kmscon_seat_startup(struct kmscon_seat *seat)
+ uterm_monitor_scan(seat->mon, seat->name);
+ }
+
+-const char *kmscon_seat_get_name(struct kmscon_seat *seat)
+-{
+- if (!seat)
+- return NULL;
+-
+- return seat->name;
+-}
+-
+-struct input *kmscon_seat_get_input(struct kmscon_seat *seat)
+-{
+- if (!seat)
+- return NULL;
+-
+- return seat->input;
+-}
+-
+-struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat)
+-{
+- if (!seat)
+- return NULL;
+-
+- return seat->eloop;
+-}
+-
+ struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat)
+ {
+ if (!seat)
+@@ -1371,36 +1124,39 @@ struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat)
+ return seat->conf_ctx;
+ }
+
+-int kmscon_seat_register_session(struct kmscon_seat *seat, struct kmscon_session **out,
+- kmscon_session_cb_t cb, void *data)
++static struct kmscon_session *kmscon_seat_new_session(struct kmscon_seat *seat)
+ {
+ struct kmscon_session *sess;
+ struct shl_dlist *iter;
+ struct kmscon_display *d;
+
+- if (!seat || !out)
+- return -EINVAL;
++ if (!seat)
++ return NULL;
+
+ if (seat->conf->session_max && seat->session_count >= seat->conf->session_max) {
+ log_warning("maximum number of sessions reached (%d), dropping new session",
+ seat->conf->session_max);
+- return -EOVERFLOW;
++ return NULL;
+ }
+
+ sess = malloc(sizeof(*sess));
+ if (!sess) {
+ log_error("cannot allocate memory for new session on seat %s", seat->name);
+- return -ENOMEM;
++ return NULL;
+ }
+
+ log_debug("register session %p", sess);
+
+ memset(sess, 0, sizeof(*sess));
+- sess->ref = 1;
+ sess->seat = seat;
+- sess->cb = cb;
+- sess->data = data;
+ sess->foreground = true;
++ sess->term = terminal_new(sess, uterm_vt_get_num(seat->vt), seat->conf_ctx, seat->eloop,
++ seat->input, seat->name);
++ if (!sess->term) {
++ log_error("cannot create terminal for new session on seat %s", seat->name);
++ free(sess);
++ return NULL;
++ }
+
+ /* register new sessions next to the current one */
+ if (seat->current_sess)
+@@ -1409,39 +1165,18 @@ int kmscon_seat_register_session(struct kmscon_seat *seat, struct kmscon_session
+ shl_dlist_link_tail(&seat->sessions, &sess->list);
+
+ ++seat->session_count;
+- *out = sess;
+
+ shl_dlist_for_each(iter, &seat->displays)
+ {
+ d = shl_dlist_entry(iter, struct kmscon_display, list);
+- session_call_display_new(sess, d->disp);
++ terminal_add_display(sess->term, d->disp);
+ }
+-
+- return 0;
+-}
+-
+-void kmscon_session_ref(struct kmscon_session *sess)
+-{
+- if (!sess || !sess->ref)
+- return;
+-
+- ++sess->ref;
++ return sess;
+ }
+
+-void kmscon_session_unref(struct kmscon_session *sess)
+-{
+- if (!sess || !sess->ref || --sess->ref)
+- return;
+-
+- kmscon_session_unregister(sess);
+- free(sess);
+-}
+-
+-void kmscon_session_unregister(struct kmscon_session *sess)
++static void kmscon_session_unregister(struct kmscon_session *sess)
+ {
+ struct kmscon_seat *seat;
+- int ret;
+- bool forced = false;
+
+ if (!sess || !sess->seat)
+ return;
+@@ -1449,47 +1184,18 @@ void kmscon_session_unregister(struct kmscon_session *sess)
+ log_debug("unregister session %p", sess);
+
+ seat = sess->seat;
+- sess->enabled = false;
+- if (seat->dummy_sess == sess)
+- seat->dummy_sess = NULL;
+- seat_reschedule(seat);
+
+ if (seat->current_sess == sess) {
+- ret = seat_pause(seat, true);
+- if (ret) {
+- forced = true;
+- log_warning("unregistering active session %p; skipping automatic "
+- "session-switch",
+- sess);
+- }
++ terminal_deactivate(seat->current_sess->term);
++ seat_next(seat);
+ }
+
+ shl_dlist_unlink(&sess->list);
+ --seat->session_count;
+ sess->seat = NULL;
+
+- session_call(sess, KMSCON_SESSION_UNREGISTER, NULL);
+- kmscon_session_unref(sess);
+-
+- /* If this session was active and we couldn't deactivate it, then it
+- * might still have resources allocated that couldn't get freed. In this
+- * case we should not automatically switch to the next session as it is
+- * very likely that it will not be able to start.
+- * Instead, we stay inactive and wait for user/external input to switch
+- * to another session. This delay will then hopefully be long enough so
+- * all resources got freed. */
+- if (!forced)
+- seat_run(seat);
+-}
+-
+-bool kmscon_session_is_registered(struct kmscon_session *sess)
+-{
+- return sess && sess->seat;
+-}
+-
+-bool kmscon_session_is_active(struct kmscon_session *sess)
+-{
+- return sess && sess->seat && sess->seat->current_sess == sess;
++ terminal_destroy(sess->term);
++ free(sess);
+ }
+
+ int kmscon_session_set_foreground(struct kmscon_session *sess)
+@@ -1508,7 +1214,7 @@ int kmscon_session_set_foreground(struct kmscon_session *sess)
+ if (ret)
+ return ret;
+
+- ret = seat_go_foreground(seat, true);
++ ret = seat_go_foreground(seat);
+ if (ret)
+ return ret;
+ }
+@@ -1529,7 +1235,7 @@ int kmscon_session_set_background(struct kmscon_session *sess)
+
+ seat = sess->seat;
+ if (seat && seat->current_sess == sess && seat->foreground) {
+- ret = seat_go_background(seat, true);
++ ret = seat_go_background(seat);
+ if (ret)
+ return ret;
+ }
+@@ -1543,35 +1249,6 @@ bool kmscon_session_get_foreground(struct kmscon_session *sess)
+ return sess->foreground;
+ }
+
+-void kmscon_session_enable(struct kmscon_session *sess)
+-{
+- if (!sess || sess->enabled)
+- return;
+-
+- log_debug("enable session %p", sess);
+- sess->enabled = true;
+- if (sess->seat &&
+- (!sess->seat->current_sess || sess->seat->current_sess == sess->seat->dummy_sess)) {
+- sess->seat->scheduled_sess = sess;
+- if (seat_has_schedule(sess->seat))
+- seat_switch(sess->seat);
+- }
+-}
+-
+-void kmscon_session_disable(struct kmscon_session *sess)
+-{
+- if (!sess || !sess->enabled)
+- return;
+-
+- log_debug("disable session %p", sess);
+- sess->enabled = false;
+-}
+-
+-bool kmscon_session_is_enabled(struct kmscon_session *sess)
+-{
+- return sess && sess->enabled;
+-}
+-
+ void kmscon_session_bell(struct kmscon_session *sess)
+ {
+ if (!sess || !sess->seat)
+@@ -1588,36 +1265,3 @@ void kmscon_session_set_leds(struct kmscon_session *sess, unsigned int scroll_lo
+
+ input_set_leds(sess->seat->input, scroll_lock, num_lock, caps_lock);
+ }
+-
+-void kmscon_session_notify_deactivated(struct kmscon_session *sess)
+-{
+- struct kmscon_seat *seat;
+- int ret;
+- unsigned int sched;
+-
+- if (!sess || !sess->seat)
+- return;
+-
+- seat = sess->seat;
+- if (seat->current_sess != sess)
+- return;
+-
+- sched = seat->async_schedule;
+- log_debug("session %p notified core about deactivation (schedule: %u)", sess, sched);
+- session_deactivate(sess);
+- seat_reschedule(seat);
+-
+- if (sched == SCHEDULE_VT) {
+- ret = seat_go_background(seat, false);
+- if (ret)
+- return;
+- ret = seat_go_asleep(seat, false);
+- if (ret)
+- return;
+- uterm_vt_retry(seat->vt);
+- } else if (sched == SCHEDULE_UNREGISTER) {
+- kmscon_session_unregister(sess);
+- } else {
+- seat_switch(seat);
+- }
+-}
+diff --git a/src/kmscon_seat.h b/src/kmscon_seat.h
+index c3fa707..31e8808 100644
+--- a/src/kmscon_seat.h
++++ b/src/kmscon_seat.h
+@@ -33,14 +33,9 @@
+ #define KMSCON_SEAT_H
+
+ #include <stdbool.h>
+-#include <stdlib.h>
+ #include "conf.h"
+-#include "input/input.h"
+ #include "kmscon_conf.h"
+ #include "shl/eloop.h"
+-#include "uterm_monitor.h"
+-#include "uterm_vt.h"
+-#include "video/video.h"
+
+ struct kmscon_seat;
+ struct kmscon_session;
+@@ -53,54 +48,22 @@ enum kmscon_seat_event {
+
+ typedef int (*kmscon_seat_cb_t)(struct kmscon_seat *seat, unsigned int event, void *data);
+
+-enum kmscon_session_event_type {
+- KMSCON_SESSION_DISPLAY_NEW,
+- KMSCON_SESSION_DISPLAY_GONE,
+- KMSCON_SESSION_DISPLAY_REFRESH,
+- KMSCON_SESSION_ACTIVATE,
+- KMSCON_SESSION_DEACTIVATE,
+- KMSCON_SESSION_UNREGISTER,
+-};
+-
+-struct kmscon_session_event {
+- unsigned int type;
+- struct display *disp;
+-};
+-
+-typedef int (*kmscon_session_cb_t)(struct kmscon_session *session,
+- struct kmscon_session_event *event, void *data);
+-
+ int kmscon_seat_new(struct kmscon_seat **out, struct conf_ctx *main_conf,
+ struct kmscon_conf_t *conf, struct ev_eloop *eloop, kmscon_seat_cb_t cb,
+ void *data);
+ void kmscon_seat_free(struct kmscon_seat *seat);
+ void kmscon_seat_startup(struct kmscon_seat *seat);
++int kmscon_seat_update_xkb_layout(struct kmscon_seat *seat, const char *model, const char *layout,
++ const char *variant, const char *options);
+
+-const char *kmscon_seat_get_name(struct kmscon_seat *seat);
+-struct input *kmscon_seat_get_input(struct kmscon_seat *seat);
+-struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat);
+ struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat);
+
+-int kmscon_seat_register_session(struct kmscon_seat *seat, struct kmscon_session **out,
+- kmscon_session_cb_t cb, void *data);
+-
+-void kmscon_session_ref(struct kmscon_session *sess);
+-void kmscon_session_unref(struct kmscon_session *sess);
+-void kmscon_session_unregister(struct kmscon_session *sess);
+-bool kmscon_session_is_registered(struct kmscon_session *sess);
+-
+-bool kmscon_session_is_active(struct kmscon_session *sess);
+ int kmscon_session_set_foreground(struct kmscon_session *sess);
+ int kmscon_session_set_background(struct kmscon_session *sess);
+ bool kmscon_session_get_foreground(struct kmscon_session *sess);
+
+-void kmscon_session_enable(struct kmscon_session *sess);
+-void kmscon_session_disable(struct kmscon_session *sess);
+-bool kmscon_session_is_enabled(struct kmscon_session *sess);
+ void kmscon_session_bell(struct kmscon_session *sess);
+ void kmscon_session_set_leds(struct kmscon_session *sess, unsigned int scroll_lock,
+ unsigned int num_lock, unsigned int caps_lock);
+
+-void kmscon_session_notify_deactivated(struct kmscon_session *sess);
+-
+ #endif /* KMSCON_SEAT_H */
+diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c
+index 391cd53..c5562f6 100644
+--- a/src/kmscon_terminal.c
++++ b/src/kmscon_terminal.c
+@@ -645,7 +645,7 @@ static void rotate_ccw_all(struct kmscon_terminal *term)
+ update_pointer_max_all(term);
+ }
+
+-static int add_display(struct kmscon_terminal *term, struct display *disp)
++int terminal_add_display(struct kmscon_terminal *term, struct display *disp)
+ {
+ struct shl_dlist *iter;
+ struct screen *scr;
+@@ -741,7 +741,7 @@ static void free_screen(struct screen *scr, bool update)
+ terminal_update_size_notify(term);
+ }
+
+-static void rm_display(struct kmscon_terminal *term, struct display *disp)
++void terminal_rm_display(struct kmscon_terminal *term, struct display *disp)
+ {
+ struct shl_dlist *iter;
+ struct screen *scr;
+@@ -1080,7 +1080,30 @@ static void terminal_close(struct kmscon_terminal *term)
+ term->opened = false;
+ }
+
+-static void terminal_destroy(struct kmscon_terminal *term)
++void terminal_refresh_displays(struct kmscon_terminal *term)
++{
++ if (term->pointer.visible)
++ hw_cursor_show(term, term->pointer.x, term->pointer.y);
++ redraw_all_text(term);
++}
++
++void terminal_activate(struct kmscon_terminal *term)
++{
++ term->awake = true;
++ if (!term->opened)
++ terminal_open(term);
++ if (term->pointer.visible)
++ hw_cursor_show(term, term->pointer.x, term->pointer.y);
++ redraw_all_text(term);
++}
++
++void terminal_deactivate(struct kmscon_terminal *term)
++{
++ term->awake = false;
++ hw_cursor_hide(term);
++}
++
++void terminal_destroy(struct kmscon_terminal *term)
+ {
+ log_debug("free terminal object %p", term);
+
+@@ -1099,43 +1122,6 @@ static void terminal_destroy(struct kmscon_terminal *term)
+ free(term);
+ }
+
+-static int session_event(struct kmscon_session *session, struct kmscon_session_event *ev,
+- void *data)
+-{
+- struct kmscon_terminal *term = data;
+-
+- switch (ev->type) {
+- case KMSCON_SESSION_DISPLAY_NEW:
+- add_display(term, ev->disp);
+- break;
+- case KMSCON_SESSION_DISPLAY_GONE:
+- rm_display(term, ev->disp);
+- break;
+- case KMSCON_SESSION_DISPLAY_REFRESH:
+- if (term->pointer.visible)
+- hw_cursor_show(term, term->pointer.x, term->pointer.y);
+- redraw_all_text(term);
+- break;
+- case KMSCON_SESSION_ACTIVATE:
+- term->awake = true;
+- if (!term->opened)
+- terminal_open(term);
+- if (term->pointer.visible)
+- hw_cursor_show(term, term->pointer.x, term->pointer.y);
+- redraw_all_text(term);
+- break;
+- case KMSCON_SESSION_DEACTIVATE:
+- term->awake = false;
+- hw_cursor_hide(term);
+- break;
+- case KMSCON_SESSION_UNREGISTER:
+- terminal_destroy(term);
+- break;
+- }
+-
+- return 0;
+-}
+-
+ static void pty_input(struct kmscon_pty *pty, const char *u8, size_t len, void *data)
+ {
+ struct kmscon_terminal *term = data;
+@@ -1163,26 +1149,25 @@ static void write_event(struct tsm_vte *vte, const char *u8, size_t len, void *d
+ kmscon_pty_write(term->pty, u8, len);
+ }
+
+-int kmscon_terminal_register(struct kmscon_session **out, struct kmscon_seat *seat,
+- unsigned int vtnr)
++struct kmscon_terminal *terminal_new(struct kmscon_session *session, unsigned int vtnr,
++ struct conf_ctx *conf_ctx, struct ev_eloop *eloop,
++ struct input *input, const char *seat_name)
+ {
+ struct kmscon_terminal *term;
+ int ret;
+
+- if (!out || !seat)
+- return -EINVAL;
+-
+ term = malloc(sizeof(*term));
+ if (!term)
+- return -ENOMEM;
++ return NULL;
+
+ memset(term, 0, sizeof(*term));
+ term->ref = 1;
+- term->eloop = kmscon_seat_get_eloop(seat);
+- term->input = kmscon_seat_get_input(seat);
++ term->session = session;
++ term->eloop = eloop;
++ term->input = input;
+ shl_dlist_init(&term->screens);
+
+- term->conf_ctx = kmscon_seat_get_conf(seat);
++ term->conf_ctx = conf_ctx;
+ term->conf = conf_ctx_get_mem(term->conf_ctx);
+
+ strncpy(term->font_attr.name, term->conf->font_name, KMSCON_FONT_MAX_NAME - 1);
+@@ -1221,7 +1206,7 @@ int kmscon_terminal_register(struct kmscon_session **out, struct kmscon_seat *se
+ goto err_font;
+
+ ret = kmscon_pty_set_conf(term->pty, term->conf->term, "truecolor", term->conf->argv,
+- kmscon_seat_get_name(seat), vtnr, term->conf->reset_env,
++ seat_name, vtnr, term->conf->reset_env,
+ term->conf->backspace_delete);
+ if (ret)
+ goto err_pty;
+@@ -1241,20 +1226,11 @@ int kmscon_terminal_register(struct kmscon_session **out, struct kmscon_seat *se
+ goto err_input;
+ }
+
+- ret = kmscon_seat_register_session(seat, &term->session, session_event, term);
+- if (ret) {
+- log_error("cannot register session for terminal: %d", ret);
+- goto err_pointer;
+- }
+-
+ ev_eloop_ref(term->eloop);
+ input_ref(term->input);
+- *out = term->session;
+ log_debug("new terminal object %p", term);
+- return 0;
++ return term;
+
+-err_pointer:
+- input_unregister_pointer_cb(term->input, pointer_event, term);
+ err_input:
+ input_unregister_key_cb(term->input, input_event, term);
+ err_ptyfd:
+@@ -1269,5 +1245,5 @@ err_con:
+ tsm_screen_unref(term->console);
+ err_free:
+ free(term);
+- return ret;
++ return NULL;
+ }
+diff --git a/src/kmscon_terminal.h b/src/kmscon_terminal.h
+index cd74bab..552242c 100644
+--- a/src/kmscon_terminal.h
++++ b/src/kmscon_terminal.h
+@@ -33,23 +33,21 @@
+ #ifndef KMSCON_TERMINAL_H
+ #define KMSCON_TERMINAL_H
+
+-#include <errno.h>
+-#include <stdlib.h>
+-#include "kmscon_seat.h"
+-
+-#ifdef BUILD_ENABLE_SESSION_TERMINAL
+-
+-int kmscon_terminal_register(struct kmscon_session **out, struct kmscon_seat *seat,
+- unsigned int vtnr);
+-
+-#else /* !BUILD_ENABLE_SESSION_TERMINAL */
+-
+-static inline int kmscon_terminal_register(struct kmscon_session **out, struct kmscon_seat *seat,
+- unsigned int vtnr)
+-{
+- return -EOPNOTSUPP;
+-}
+-
+-#endif /* BUILD_ENABLE_SESSION_TERMINAL */
++struct conf_ctx;
++struct display;
++struct ev_eloop;
++struct kmscon_terminal;
++struct kmscon_session;
++struct input;
++
++struct kmscon_terminal *terminal_new(struct kmscon_session *session, unsigned int vtnr,
++ struct conf_ctx *conf_ctx, struct ev_eloop *eloop,
++ struct input *input, const char *seat_name);
++void terminal_destroy(struct kmscon_terminal *term);
++int terminal_add_display(struct kmscon_terminal *term, struct display *disp);
++void terminal_rm_display(struct kmscon_terminal *term, struct display *disp);
++void terminal_refresh_displays(struct kmscon_terminal *term);
++void terminal_activate(struct kmscon_terminal *term);
++void terminal_deactivate(struct kmscon_terminal *term);
+
+ #endif /* KMSCON_TERMINAL_H */
+diff --git a/src/meson.build b/src/meson.build
+index 9547fe1..1c4ba12 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -88,16 +88,27 @@ kmscon_srcs = [
+ 'kmscon_conf.c',
+ 'kmscon_issue.c',
+ 'kmscon_main.c',
++ 'kmscon_terminal.c',
+ ]
+-if enable_session_dummy
+- kmscon_srcs += 'kmscon_dummy.c'
+-endif
+-if enable_session_terminal
+- kmscon_srcs += 'kmscon_terminal.c'
++kmscon_dep = [xkbcommon_deps,
++ libtsm_deps,
++ threads_deps,
++ dl_deps,
++ conf_deps,
++ shl_deps,
++ eloop_deps,
++ uterm_deps,
++ render_deps,
++ font_deps,
++]
++
++if enable_dbus
++ kmscon_srcs += 'dbus.c'
++ kmscon_dep += dbus_deps
+ endif
+ kmscon = executable('kmscon', kmscon_srcs,
+- dependencies: [xkbcommon_deps, libtsm_deps, threads_deps, dl_deps, conf_deps, shl_deps, eloop_deps, uterm_deps, render_deps, font_deps],
++ dependencies: kmscon_dep,
+ export_dynamic: true,
+ install: true,
+- install_dir: libexecdir,
++ install_dir: bindir,
+ )
+diff --git a/src/shl/dlist.h b/src/shl/dlist.h
+index 83cd233..0af5094 100644
+--- a/src/shl/dlist.h
++++ b/src/shl/dlist.h
+@@ -102,6 +102,16 @@ static inline bool shl_dlist_empty(struct shl_dlist *head)
+
+ #define shl_dlist_last(head, type, member) shl_dlist_entry((head)->prev, type, member)
+
++#define shl_dlist_next(iter, head, member) \
++ ((iter)->member.next == (head) \
++ ? NULL \
++ : shl_dlist_entry((iter)->member.next, typeof(*iter), list))
++
++#define shl_dlist_prev(iter, head, member) \
++ ((iter)->member.prev == (head) \
++ ? NULL \
++ : shl_dlist_entry((iter)->member.prev, typeof(*iter), list))
++
+ #define shl_dlist_for_each(iter, head) for (iter = (head)->next; iter != (head); iter = iter->next)
+
+ #define shl_dlist_for_each_but_one(iter, start, head) \
+diff --git a/tests/test_input.c b/tests/test_input.c
+index 0ecefc0..b218edc 100644
+--- a/tests/test_input.c
++++ b/tests/test_input.c
+@@ -128,11 +128,12 @@ static void setup_input(struct uterm_monitor *mon)
+ if (ret)
+ return;
+ ret = input_set_keymap(input, input_conf.xkb_model, input_conf.xkb_layout,
+- input_conf.xkb_variant, input_conf.xkb_options, input_conf.locale,
+- input_conf.xkb_keymap, input_conf.xkb_compose_file,
+- compose_file_len);
++ input_conf.xkb_variant, input_conf.xkb_options,
++ input_conf.xkb_keymap);
+ if (ret)
+ return;
++
++ input_set_compose(input, input_conf.locale, compose_file, compose_file_len);
+ input_set_conf(input, 250, 50, true);
+ ret = input_register_key_cb(input, input_arrived, NULL);
+ if (ret)
+--
+2.54.0
+
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-06-05 11:19 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-05 11:19 [rpms/kmscon] rawhide: Add localectl runtime support Jocelyn Falempe
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox