The third sibling of the Linux ksni item and the macOS NSStatusItem, wired into the same `tray_qt` seams and dispatchers, so a tray action behaves identically on all three. Close-to-tray needs no QML change: `Main.qml:786` already keys on `trayLive`, which both arms of `init` publish. The menu is the SAME menu. The plan's C ABI carried no labels, which would have shipped an English-only menu in an app with eight locales; Linux and macOS already share five `qbz_i18n` keys, so the translated strings are passed in from Rust and no new string is introduced. Win32 details that are load-bearing rather than incidental: - NIF_SHOWTIP. Version 4 SUPPRESSES the standard tooltip unless asked, so without it the icon appears, every track sets the tooltip, and nothing ever shows on hover. - The NIM_SETVERSION result is checked and creation fails if it is refused. The window procedure decodes callbacks as version 4 and nothing else, so a silent negotiation failure would leave a live icon whose clicks and menu do nothing -- worse than no icon, because close-to-tray would then hide the window behind it. - WS_EX_TOOLWINDOW, not HWND_MESSAGE. A message-only window never receives the broadcast TaskbarCreated, so the icon would never come back after an Explorer restart. The window is on the GUI thread, so Qt's own loop dispatches it. - LoadImageW without LR_SHARED returns an icon this process OWNS; it is destroyed on teardown, while the LoadIconW system fallback is not. - Failed creation gives back the window and the icon instead of parking them for the process lifetime, and create is idempotent. - NIM_SETFOCUS after the menu closes, which the Shell asks for and matters most when it was dismissed with Escape. Every mutation is marshalled through `tray_bridge::ui`, exactly as the macOS theme arm is. The publishers run on whatever thread playback pushed from -- the 1 Hz poll lives in `crate::spawn` -- while this tray keeps process-global C++ state and talks to a GUI-thread window; calling straight through was a data race, not merely a documentation mismatch. Linux needs no hop because ksni's handle is a thread-safe channel. Strings are truncated on the Rust side by UTF-16 CODE UNITS, not by `chars()`. The buffers are sized in `wchar_t`, an astral character costs two, and `wcsncpy_s` with `_TRUNCATE` would happily keep a high surrogate whose partner was cut -- malformed UTF-16 handed to the shell. The tray-icon-variant row is hidden on Windows: the notification area draws the executable's own icon and Windows owns light/dark, so the four choices would render, persist and change nothing. cc
41 lines
1.6 KiB
C
41 lines
1.6 KiB
C
// Windows notification-area icon. C ABI so the Rust seam (src/tray_windows.rs)
|
|
// can drive it exactly as tray_linux.rs drives the StatusNotifierItem and
|
|
// tray_macos.rs drives the NSStatusItem.
|
|
//
|
|
// EVERY function here is GUI-THREAD ONLY. The icon owns a real (hidden) window
|
|
// and a message loop; touching it from tokio would post to a thread that never
|
|
// pumps.
|
|
#pragma once
|
|
|
|
#include <stdbool.h>
|
|
#include <wchar.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
// The five menu labels, already translated by the Rust side (the same
|
|
// qbz_i18n keys Linux and macOS use, so the three menus stay identical).
|
|
// Must be called before qbz_win_tray_create; the strings are copied.
|
|
void qbz_win_tray_set_labels(const wchar_t *play_pause, const wchar_t *next,
|
|
const wchar_t *previous, const wchar_t *show_hide,
|
|
const wchar_t *quit);
|
|
|
|
// Installed once, before create. Each maps 1:1 to a tray_qt dispatcher.
|
|
void qbz_win_tray_set_callbacks(void (*on_left_click)(), void (*on_play_pause)(),
|
|
void (*on_next)(), void (*on_previous)(),
|
|
void (*on_quit)());
|
|
|
|
// NIM_ADD + NIM_SETVERSION(4). False if the message window or the icon could
|
|
// not be created -- the caller must then leave the tray handle empty, or
|
|
// close-to-tray would hide the window with nothing to restore it from.
|
|
bool qbz_win_tray_create(const wchar_t *tooltip);
|
|
|
|
void qbz_win_tray_set_tooltip(const wchar_t *tooltip);
|
|
void qbz_win_tray_set_playing(bool playing);
|
|
void qbz_win_tray_destroy(void);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|