fix(windows): create the tray when the Qt thread is actually registered
The icon never appeared, and the log said NOTHING -- not even "[tray] disabled by user setting", which is what `init_gates` prints when it refuses. That absence is what identified it: the gates had passed and the work itself had vanished. `tray_bridge::ui` silently discards its closure until `QbzTray::boot` registers the Qt thread, and `tray_qt::init` runs from `on_session_entered`, which reaches it first. Every log line in that arm lives inside the closure, so nothing was left to see. `init` now records the intent and still tries the hop; `boot` calls the same idempotent `create_now()` right after registering the thread. Whichever arrives SECOND does the work. No polling and nothing to time out. Verified on the box by differential capture rather than by the log: with QBZ running the notification-area overflow holds eight icons, without it seven, and the one that disappears is QBZ's. `HKCU\Control Panel\NotifyIconSettings` has the entry with `InitialTooltip: QBZ`. It sits in the overflow because `IsPromoted` is unset, which is what Windows 11 does with every newly seen app. The macOS arm has the same shape and is NOT touched here: if `init` wins that race there, the NSStatusItem is never created, nothing logs, and `tray_live` keeps its derived false so close-to-tray stops working too. Recorded for the owner rather than changed from a Windows branch, since the structural fix (buffering early work in `tray_bridge::ui`) would also move Linux, where early calls are dropped by design. cc
This commit is contained in:
2 files changed
+46
-14
No files matched your search
@@ -180,6 +180,11 @@ impl qbz_tray::QbzTray {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] tray Qt thread already registered");
|
||||
}
|
||||
// If `tray_qt::init` ran before this point its queued closure was
|
||||
// dropped on the floor -- `ui()` above is a no-op until the line
|
||||
// right here. Pick the work up now; we are already on the GUI thread.
|
||||
#[cfg(target_os = "windows")]
|
||||
crate::tray_qt::create_now();
|
||||
#[cfg(target_os = "linux")]
|
||||
crate::single_instance_qt::bind_ui();
|
||||
}
|
||||
|
||||
@@ -416,23 +416,24 @@ pub(crate) fn init(theme_override: String, enabled: bool) {
|
||||
|
||||
// Windows: Shell_NotifyIconW owns a hidden window with a message loop,
|
||||
// so the icon must be created on the thread that pumps it -- the Qt GUI
|
||||
// thread. Same hop as macOS, and for the same class of reason.
|
||||
// thread.
|
||||
//
|
||||
// The hop ALONE is not enough, and the first version of this shipped with
|
||||
// exactly that bug: `tray_bridge::ui` silently discards work until
|
||||
// `QbzTray::boot` registers the Qt thread, and this runs from
|
||||
// `on_session_entered`, which reaches it first. The symptom was a tray that
|
||||
// never appeared and a log with no `[tray]` line at all -- not even the
|
||||
// "disabled by user setting" the gates print when they refuse, which is
|
||||
// what pinned it down.
|
||||
//
|
||||
// So: record the intent, then try. Whichever of the two arrives SECOND --
|
||||
// this call or `boot` -- does the work, and `create_now` is idempotent.
|
||||
// No polling, and nothing to time out.
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let _ = theme_override;
|
||||
crate::tray_bridge::ui(move |_b| {
|
||||
let ok = crate::tray_windows::create();
|
||||
// Both arms publish, exactly as Linux and macOS do: a failure MUST
|
||||
// report false, or close-to-tray hides the window into a tray that
|
||||
// is not there.
|
||||
if ok {
|
||||
let _ = TRAY.set(TrayHandle { windows: true });
|
||||
push_tray_live(true);
|
||||
} else {
|
||||
log::error!("[tray] Windows notification-area icon could not be created");
|
||||
push_tray_live(false);
|
||||
}
|
||||
});
|
||||
TRAY_WANTED.store(true, Ordering::SeqCst);
|
||||
crate::tray_bridge::ui(|_b| create_now());
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
@@ -442,6 +443,32 @@ pub(crate) fn init(theme_override: String, enabled: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set once `init` has decided a tray SHOULD exist. Read by
|
||||
/// `QbzTray::boot`, which creates it if `init` ran before the Qt thread was
|
||||
/// registered and its queued closure went nowhere.
|
||||
#[cfg(target_os = "windows")]
|
||||
static TRAY_WANTED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Create the Windows tray. GUI THREAD ONLY, and idempotent: both `init`'s
|
||||
/// hop and `QbzTray::boot` call it, and whichever runs first wins.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn create_now() {
|
||||
if !TRAY_WANTED.load(Ordering::SeqCst) || TRAY.get().is_some() {
|
||||
return;
|
||||
}
|
||||
let ok = crate::tray_windows::create();
|
||||
// Both arms publish, exactly as Linux and macOS do: a failure MUST report
|
||||
// false, or close-to-tray hides the window into a tray that is not there.
|
||||
if ok {
|
||||
let _ = TRAY.set(TrayHandle { windows: true });
|
||||
push_tray_live(true);
|
||||
log::info!("[tray] Windows notification-area icon created");
|
||||
} else {
|
||||
log::error!("[tray] Windows notification-area icon could not be created");
|
||||
push_tray_live(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// True when running under gamescope (Steam Deck Game Mode). Stateless, and a
|
||||
/// verbatim port of `crates/qbz/src/main.rs:8333-8336` — the raw
|
||||
/// `GAMESCOPE_WAYLAND_DISPLAY` env is inherited by a re-spawn, and
|
||||
|
||||
Reference in new issue
Block a user