fix(client-linux): share one GamepadService across app activations
apple / swift (push) Successful in 1m30s
arch / build-publish (push) Successful in 9m24s
apple / screenshots (push) Successful in 6m48s
ci / rust (push) Failing after 36s
ci / web (push) Successful in 58s
android / android (push) Successful in 11m35s
ci / docs-site (push) Successful in 1m2s
ci / bench (push) Successful in 4m59s
decky / build-publish (push) Successful in 26s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m48s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
deb / build-publish (push) Successful in 9m19s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m46s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m24s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 55s
flatpak / build-publish (push) Successful in 4m49s
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled

`build_ui` (the GTK `activate` handler) started a fresh SDL3 gamepad
worker thread on every reactivation of the already-running singleton
(another --connect, the launcher clicked twice, ...). sdl3 only lets
the first thread ever to call sdl3::init() hold "main thread" status,
so every later activation's worker thread failed permanently with
"Cannot initialize `Sdl` from a thread other than the main thread",
silently disabling controller support for the rest of the process.

Start the GamepadService once in run() and clone it into build_ui
instead of starting a new one per activation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 19:11:45 +02:00
parent f94978f820
commit 27d25b5f6d
+11 -3
View File
@@ -154,13 +154,21 @@ pub fn run() -> glib::ExitCode {
builder = builder.flags(gtk::gio::ApplicationFlags::NON_UNIQUE); builder = builder.flags(gtk::gio::ApplicationFlags::NON_UNIQUE);
} }
let app = builder.build(); let app = builder.build();
app.connect_activate(build_ui); // One SDL context for the whole process: `activate` fires again on every subsequent
// launch forwarded to this already-running singleton (another `--connect`, the desktop
// icon clicked twice, …). SDL only ever lets the FIRST thread that calls `sdl3::init()`
// hold the "main thread" — a second `GamepadService::start()` from a later `activate`
// would spawn a new thread that fails that check forever. Starting it once here and
// cloning it into each `build_ui` keeps the worker thread (and its pad state) shared
// across every window instead.
let gamepad = crate::gamepad::GamepadService::start();
app.connect_activate(move |gtk_app| build_ui(gtk_app, gamepad.clone()));
// GTK doesn't see our argv (`--connect` is handled in `build_ui`); an empty argv also // GTK doesn't see our argv (`--connect` is handled in `build_ui`); an empty argv also
// keeps GApplication from rejecting unknown options. // keeps GApplication from rejecting unknown options.
app.run_with_args(&[] as &[&str]) app.run_with_args(&[] as &[&str])
} }
fn build_ui(gtk_app: &adw::Application) { fn build_ui(gtk_app: &adw::Application, gamepad: crate::gamepad::GamepadService) {
let identity = match crate::trust::load_or_create_identity() { let identity = match crate::trust::load_or_create_identity() {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {
@@ -203,7 +211,7 @@ fn build_ui(gtk_app: &adw::Application) {
toasts, toasts,
settings: Rc::new(RefCell::new(Settings::load())), settings: Rc::new(RefCell::new(Settings::load())),
identity, identity,
gamepad: crate::gamepad::GamepadService::start(), gamepad,
busy: std::cell::Cell::new(false), busy: std::cell::Cell::new(false),
fullscreen, fullscreen,
// (`--browse` makes cli_connect_request None — browse mode returns to the // (`--browse` makes cli_connect_request None — browse mode returns to the