summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/kernel/src')
-rw-r--r--packages/kernel/src/input.cpp19
-rw-r--r--packages/kernel/src/server.cpp5
-rw-r--r--packages/kernel/src/server_impl.hpp4
-rw-r--r--packages/kernel/src/vt_core.hpp33
4 files changed, 60 insertions, 1 deletions
diff --git a/packages/kernel/src/input.cpp b/packages/kernel/src/input.cpp
index 336947e..6056c53 100644
--- a/packages/kernel/src/input.cpp
+++ b/packages/kernel/src/input.cpp
@@ -1,5 +1,7 @@
#include "server_impl.hpp"
+#include "vt_core.hpp"
+
#include <xkbcommon/xkbcommon.h>
namespace unbox::kernel {
@@ -75,6 +77,23 @@ void Server::Impl::new_keyboard(wlr_input_device* device) {
const std::uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->keyboard);
const bool pressed = event->state == WL_KEYBOARD_KEY_STATE_PRESSED;
+ // SESSION ESCAPE HATCH: Ctrl+Alt+Fn (XF86Switch_VT_1..12) switches the
+ // Linux VT. Handled HERE, kernel-hardwired, BEFORE the key_filter, so no
+ // extension can intercept, consume, or block the only escape from the
+ // real DRM seat (user decision: not config-driven, not rebindable). On
+ // PRESS we change the VT; we CONSUME both press and release (the matching
+ // release carries the same keysym) so the filter never runs and the key
+ // never reaches the focused client. No session (headless/nested =>
+ // session is NULL) is a clean no-op — never crash.
+ for (int i = 0; i < nsyms; ++i) {
+ if (const std::optional<unsigned> vt = vt_for_keysym(syms[i])) {
+ if (pressed && session != nullptr) {
+ wlr_session_change_vt(session, *vt);
+ }
+ return; // consume: no filter, no client forward (press or release)
+ }
+ }
+
// Thread each resolved keysym through the key_filter; a filter link may
// CONSUME the key (set handled=true) — that is how extensions implement
// compositor shortcuts. If any resolution is consumed, suppress the
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp
index a0627f4..a5f8365 100644
--- a/packages/kernel/src/server.cpp
+++ b/packages/kernel/src/server.cpp
@@ -145,7 +145,10 @@ void Server::Impl::init() {
wlr_log_init(WLR_INFO, nullptr);
display = require(wl_display_create(), "wl_display");
- backend = require(wlr_backend_autocreate(wl_display_get_event_loop(display), nullptr),
+ // Capture the session out-param: on the real DRM seat it is the libseat
+ // session the VT-switch escape hatch (input.cpp) drives; NULL under
+ // headless/nested (no real seat), where VT switching no-ops cleanly.
+ backend = require(wlr_backend_autocreate(wl_display_get_event_loop(display), &session),
"wlr_backend");
renderer = require(wlr_renderer_autocreate(backend), "wlr_renderer");
wlr_renderer_init_wl_display(renderer, display);
diff --git a/packages/kernel/src/server_impl.hpp b/packages/kernel/src/server_impl.hpp
index 3407883..ef4a479 100644
--- a/packages/kernel/src/server_impl.hpp
+++ b/packages/kernel/src/server_impl.hpp
@@ -69,6 +69,10 @@ struct Server::Impl : detail::DisableSink {
wl_display* display = nullptr;
wlr_backend* backend = nullptr;
+ // The libseat/logind session, captured from wlr_backend_autocreate's
+ // out-param at init. NULL under headless/nested backends (no real seat) —
+ // the VT-switch escape hatch no-ops cleanly then. Owned by the backend.
+ wlr_session* session = nullptr;
wlr_renderer* renderer = nullptr;
wlr_allocator* allocator = nullptr;
wlr_scene* scene = nullptr;
diff --git a/packages/kernel/src/vt_core.hpp b/packages/kernel/src/vt_core.hpp
new file mode 100644
index 0000000..d08837d
--- /dev/null
+++ b/packages/kernel/src/vt_core.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <optional>
+
+#include <xkbcommon/xkbcommon.h>
+
+// Pure decision core for the kernel's VT-switch escape hatch — NO wlroots / GL,
+// so it is doctest-able with nothing running (AGENTS.md: effects at the edges,
+// pure cores tested hard). The keyboard glue (input.cpp) injects the effect
+// (wlr_session_change_vt) around this.
+//
+// xkbcommon is allowed here: it is a pure value library (a keysym is an
+// integer), not a wlroots/GL/wayland effect surface — the wlroots-include rule
+// targets <wlr/...> + <wayland-server*.h>, and input.cpp already includes
+// <xkbcommon/xkbcommon.h> for keysym resolution.
+
+namespace unbox::kernel {
+
+// Map a resolved keysym to the Linux VT it requests, or nullopt if it is not a
+// VT-switch keysym. Ctrl+Alt+Fn is delivered by xkb as the contiguous range
+// XKB_KEY_XF86Switch_VT_1 (0x1008FE01) .. XKB_KEY_XF86Switch_VT_12
+// (0x1008FE0C); the returned value is the 1-based VT number (1..12). These
+// keysyms are ONLY produced with Ctrl+Alt held, so matching the keysym is
+// sufficient — no separate modifier-mask check. Plain F1..F12 resolve to the
+// ordinary XKB_KEY_F1.. keysyms and are NOT in this range, so they pass through.
+[[nodiscard]] constexpr auto vt_for_keysym(xkb_keysym_t keysym) -> std::optional<unsigned> {
+ if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) {
+ return static_cast<unsigned>(keysym - XKB_KEY_XF86Switch_VT_1) + 1U;
+ }
+ return std::nullopt;
+}
+
+} // namespace unbox::kernel