From 0681d4941a36ec3ad70a5153e9c708df76d11295 Mon Sep 17 00:00:00 2001 From: baketnk Date: Sun, 4 Oct 2026 21:48:06 -0400 Subject: [PATCH] Explain missing X display when the focus guard refuses Type --- docs/development.md | 6 ++++++ src/controller.cpp | 7 +++++-- src/controller.hpp | 1 + src/focus_guard.cpp | 4 +++- src/runtime.cpp | 1 + tests/focus_guard_test.cpp | 6 ++++++ 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/development.md b/docs/development.md index e858092..0b0aae4 100644 --- a/docs/development.md +++ b/docs/development.md @@ -44,3 +44,9 @@ SDL3 and OpenVR SDK sources, builds, tests, stages and packages. The release commit sets `RELEASE_VERSION` in `scripts/install_payload.py` (then `python3 scripts/sync-installer.py`); upload the archive, its `.sha256` and `install.sh` to the GitHub release. + +When starting over SSH, supply the verified Gamescope Xwayland `DISPLAY` as well +as `XDG_RUNTIME_DIR` from the user's session. Do not assume `:0` is always the +text destination. Without `DISPLAY`, the focus guard refuses Type and preserves +the review; the status line reports `Input unavailable: DISPLAY missing`. +Launch from Steam/the desktop menu to inherit the normal session environment. diff --git a/src/controller.cpp b/src/controller.cpp index b5eae25..957693d 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -89,7 +89,9 @@ void Controller::delivery_detail(DeliveryResult result, bool submit) { } std::shared_ptr Controller::manual_focus() { auto focus = focus_factory_(); - if (!focus || !focus->arm()) throw std::runtime_error("Current target not verified; review kept"); + if (!focus || !focus->arm()) + throw std::runtime_error(std::string("Current target not verified: ") + + (focus ? focus->failure() : "focus unavailable") + "; review kept"); return std::shared_ptr(std::move(focus)); } void Controller::queue_paced(std::string text, bool enter, bool review, @@ -370,7 +372,8 @@ void Controller::action(UiAction action) { } catch (const std::exception& e) { if (session_.state() == State::Recording || session_.state() == State::Transcribing) { audio_.close(); session_.fail(); armed_focus_.reset(); status_note_.clear(); - } else status_note_ = "Input unavailable - check destination"; + } else status_note_ = std::string(e.what()).find("DISPLAY missing") != std::string::npos ? + "Input unavailable: DISPLAY missing" : "Input unavailable - check destination"; detail_ = e.what(); } } diff --git a/src/controller.hpp b/src/controller.hpp index 694c621..ad79236 100644 --- a/src/controller.hpp +++ b/src/controller.hpp @@ -36,6 +36,7 @@ public: virtual ~ControllerFocus() = default; virtual bool arm() = 0; virtual bool valid() = 0; + virtual const char* failure() const { return "focus unavailable"; } }; using FocusFactory = std::function()>; diff --git a/src/focus_guard.cpp b/src/focus_guard.cpp index c8bef14..48784d8 100644 --- a/src/focus_guard.cpp +++ b/src/focus_guard.cpp @@ -167,9 +167,11 @@ bool FocusGuard::arm() { auto& p = *impl_; if (p.attempted || p.dead) return false; p.attempted = true; + const char* display = std::getenv("DISPLAY"); + if (!display || !*display) { p.reason = "DISPLAY missing"; p.dead = true; return false; } int screen_number = 0; p.connection = xcb_connect(nullptr, &screen_number); - if (!p.connection || xcb_connection_has_error(p.connection)) { p.dead = true; return false; } + if (!p.connection || xcb_connection_has_error(p.connection)) { p.reason = "X display unavailable"; p.dead = true; return false; } const xcb_setup_t* setup = xcb_get_setup(p.connection); auto iter = xcb_setup_roots_iterator(setup); for (int i = 0; i < screen_number && iter.rem; ++i) xcb_screen_next(&iter); diff --git a/src/runtime.cpp b/src/runtime.cpp index b1978fd..cb11813 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -106,6 +106,7 @@ public: trace_(std::string("focus invalid: ") + guard_.failure()); return false; } + const char* failure() const override { return guard_.failure(); } private: FocusGuard guard_; DeliveryTrace& trace_; diff --git a/tests/focus_guard_test.cpp b/tests/focus_guard_test.cpp index 2efaa04..22dfb35 100644 --- a/tests/focus_guard_test.cpp +++ b/tests/focus_guard_test.cpp @@ -33,6 +33,12 @@ private: int main() { try { DisplaySetting isolated_display; + unsetenv("DISPLAY"); + FocusGuard missing_display; + CHECK(!missing_display.arm()); + CHECK(std::string(missing_display.failure()) == "DISPLAY missing"); + CHECK(!missing_display.arm()); + setenv("DISPLAY", "frameyap-no-such-display:9876", 1); FocusGuard guard; // Construction must not connect to X. CHECK(!guard.valid()); CHECK(!guard.arm()); // No server: fail closed.