summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-13 15:49:32 +0900
committerAdam Malczewski <[email protected]>2026-06-13 15:49:32 +0900
commit11812b0374d5de395e2c17532c6bf89a903ee043 (patch)
tree029f1d222aadf0d43f96fa071c313e4cdd119202 /packages/kernel/src
parent803fd2687a5f6ead0644f9c952bed6e3e4ef7ed9 (diff)
downloadunbox-11812b0374d5de395e2c17532c6bf89a903ee043.tar.gz
unbox-11812b0374d5de395e2c17532c6bf89a903ee043.zip
Slice 5b: config-driven keybindings — Super→fuzzel, Alt+Tab; kernel exports WAYLAND_DISPLAY
ext-keybindings (new core ext) reads unbox.toml: tap-Super spawns fuzzel, Alt+Tab/Alt+Shift+Tab rotate focus across all toplevels, plus Alt+F1 and Ctrl+Alt+Backspace (quit). ext-xdg-shell's hardcoded keybinds removed (migrated to the toml). Kernel setenv()s WAYLAND_DISPLAY at startup so extension-spawned clients connect to unbox, not the launching session — fixes fuzzel "no monitors" on the real seat. build + build-asan green: third-party Mesa/EGL/DRM + vendored-RmlUi sanitizer noise suppressed (suppressions/), our code stays leak-checked; a real libwayland leak in the layer-shell client test fixed. Harness: spawn-env + sanitizer-noise rules, diagnose-real-seat skill, GLOSSARY keybinding/action/tap-binding. Real-seat verified on the CF-AX3.
Diffstat (limited to 'packages/kernel/src')
-rw-r--r--packages/kernel/src/server.cpp62
1 files changed, 54 insertions, 8 deletions
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp
index d02cf78..a0627f4 100644
--- a/packages/kernel/src/server.cpp
+++ b/packages/kernel/src/server.cpp
@@ -206,6 +206,15 @@ void Server::Impl::init() {
}
socket = socket_cstr;
+ // Advertise OUR socket in the PROCESS environment. The process inherited
+ // WAYLAND_DISPLAY from its parent (e.g. labwc's wayland-0), but our real
+ // socket is whatever wl_display_add_socket_auto just picked. Without this,
+ // children spawned by extensions (ext-keybindings forks fuzzel) inherit the
+ // stale parent value and connect to the WRONG compositor -> "no monitors".
+ // setenv here makes every child (the -s startup spawn AND extension spawns)
+ // reach unbox by default. tinywl/sway do exactly this.
+ setenv("WAYLAND_DISPLAY", socket.c_str(), 1);
+
if (!wlr_backend_start(backend)) {
throw std::runtime_error("failed to start the wlr_backend");
}
@@ -406,14 +415,50 @@ void Server::Impl::shutdown() {
void Server::Impl::handle_new_output(wlr_output* wlr_output) {
wlr_output_init_render(wlr_output, allocator, renderer);
- wlr_output_state state;
- wlr_output_state_init(&state);
- wlr_output_state_set_enabled(&state, true);
- if (wlr_output_mode* mode = wlr_output_preferred_mode(wlr_output)) {
- wlr_output_state_set_mode(&state, mode);
+ // Enable + set a mode, then commit. The committed mode is what gives the
+ // output a non-zero width/height — and wlr_output_layout (below) advertises
+ // the client-facing wl_output global ONLY for an output whose width/height
+ // are > 0 (see output_update_global in wlroots' wlr_output_layout.c). So a
+ // FAILED modeset leaves size 0 and the output silently global-less: clients
+ // that need an output (layer-shell: fuzzel et al.) see "no monitors".
+ //
+ // Headless commits trivially succeed; the DRM modeset can fail for the
+ // preferred mode. tinywl ignores the commit result (single-mode demo); we
+ // must not. Try the preferred mode, then any other reported mode, then a
+ // mode-less enable, so every backend ends up with a committed, advertisable
+ // output where the hardware allows one at all.
+ auto try_commit = [&](wlr_output_mode* mode) -> bool {
+ wlr_output_state state;
+ wlr_output_state_init(&state);
+ wlr_output_state_set_enabled(&state, true);
+ if (mode != nullptr) {
+ wlr_output_state_set_mode(&state, mode);
+ }
+ const bool ok = wlr_output_commit_state(wlr_output, &state);
+ wlr_output_state_finish(&state);
+ return ok;
+ };
+
+ bool committed = try_commit(wlr_output_preferred_mode(wlr_output));
+ if (!committed) {
+ wlr_output_mode* mode = nullptr;
+ wl_list_for_each(mode, &wlr_output->modes, link) {
+ if (try_commit(mode)) {
+ committed = true;
+ break;
+ }
+ }
+ }
+ if (!committed) {
+ // Mode-less enable (modeless backends, or hardware that rejected every
+ // mode). On a modeful backend this generally won't yield a usable size,
+ // but it is the last resort and keeps the output enabled.
+ committed = try_commit(nullptr);
+ }
+ if (!committed) {
+ wlr_log(WLR_ERROR, "output %s: every commit failed; no wl_output global",
+ wlr_output->name);
}
- wlr_output_commit_state(wlr_output, &state);
- wlr_output_state_finish(&state);
auto owned = std::make_unique<Output>();
Output* output = owned.get();
@@ -443,11 +488,12 @@ void Server::Impl::handle_new_output(wlr_output* wlr_output) {
outputs.remove_if([output](const auto& owned) { return owned.get() == output; });
});
+ // Adding to the layout auto-advertises the wl_output global for an output
+ // with a committed size (output_update_global in wlr_output_layout.c).
wlr_output_layout_output* layout_output = wlr_output_layout_add_auto(output_layout, wlr_output);
wlr_scene_output* scene_output = wlr_scene_output_create(scene, wlr_output);
wlr_scene_output_layout_add_output(scene_layout, layout_output, scene_output);
- wlr_log(WLR_INFO, "new output %s", wlr_output->name);
const OutputEvent ev{wlr_output};
ev_output_added.emit(ev);
}