Skip to content

Commit

Permalink
[Windows]: Create dummy window to handle media control (#261)
Browse files Browse the repository at this point in the history
Closes #259 

The code is from the example in Souvlaki repository: https://github.com/Sinono3/souvlaki/blob/master/examples/print_events.rs
  • Loading branch information
rashil2000 authored Oct 1, 2023
1 parent a073031 commit f4d4501
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion spotify_player/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ regex = "1.9.5"
daemonize = { version = "0.5.0", optional = true }
ttl_cache = "0.5.1"

[target.'cfg(target_os = "windows")'.dependencies.windows]
version = "0.44"
features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_System_LibraryLoader",
"Win32_UI_WindowsAndMessaging"
]
optional = true

[features]
alsa-backend = ["streaming", "librespot-playback/alsa-backend"]
pulseaudio-backend = ["streaming", "librespot-playback/pulseaudio-backend"]
Expand All @@ -58,7 +68,7 @@ sdl-backend = ["streaming", "librespot-playback/sdl-backend"]
gstreamer-backend = ["streaming", "librespot-playback/gstreamer-backend"]
streaming = ["librespot-playback", "librespot-connect"]
lyric-finder = ["lyric_finder"]
media-control = ["souvlaki", "winit"]
media-control = ["souvlaki", "winit", "windows"]
image = ["viuer", "dep:image"]
sixel = ["image", "viuer/sixel"]
notify = ["notify-rust"]
Expand Down
121 changes: 121 additions & 0 deletions spotify_player/src/media_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ pub fn start_event_watcher(
) -> Result<(), souvlaki::Error> {
tracing::info!("Initializing application's media control event watcher...");

#[cfg(not(target_os = "windows"))]
let hwnd = None;

#[cfg(target_os = "windows")]
let (hwnd, _dummy_window) = {
let dummy_window = windows::DummyWindow::new().unwrap();
let handle = Some(dummy_window.handle.0 as _);
(handle, dummy_window)
};

let config = PlatformConfig {
dbus_name: "spotify_player",
display_name: "Spotify Player",
Expand Down Expand Up @@ -105,5 +114,117 @@ pub fn start_event_watcher(
loop {
update_control_metadata(&state, &mut controls, &mut track_info)?;
std::thread::sleep(refresh_duration);

// this must be run repeatedly to ensure that
// the Windows event queue is processed by the app
#[cfg(target_os = "windows")]
windows::pump_event_queue();
}
}

// demonstrates how to make a minimal window to allow use of media keys on the command line
// ref: https://github.com/Sinono3/souvlaki/blob/master/examples/print_events.rs
#[cfg(target_os = "windows")]
mod windows {
use std::io::Error;
use std::mem;

use windows::core::PCWSTR;
use windows::w;
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetAncestor,
IsDialogMessageW, PeekMessageW, RegisterClassExW, TranslateMessage, GA_ROOT, MSG,
PM_REMOVE, WINDOW_EX_STYLE, WINDOW_STYLE, WM_QUIT, WNDCLASSEXW,
};

pub struct DummyWindow {
pub handle: HWND,
}

impl DummyWindow {
pub fn new() -> Result<DummyWindow, String> {
let class_name = w!("SimpleTray");

let handle_result = unsafe {
let instance = GetModuleHandleW(None)
.map_err(|e| (format!("Getting module handle failed: {e}")))?;

let wnd_class = WNDCLASSEXW {
cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
hInstance: instance,
lpszClassName: class_name,
lpfnWndProc: Some(Self::wnd_proc),
..Default::default()
};

if RegisterClassExW(&wnd_class) == 0 {
return Err(format!(
"Registering class failed: {}",
Error::last_os_error()
));
}

let handle = CreateWindowExW(
WINDOW_EX_STYLE::default(),
class_name,
w!(""),
WINDOW_STYLE::default(),
0,
0,
0,
0,
None,
None,
instance,
None,
);

if handle.0 == 0 {
Err(format!(
"Message only window creation failed: {}",
Error::last_os_error()
))
} else {
Ok(handle)
}
};

handle_result.map(|handle| DummyWindow { handle })
}
extern "system" fn wnd_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
}
}

impl Drop for DummyWindow {
fn drop(&mut self) {
unsafe {
DestroyWindow(self.handle);
}
}
}

pub fn pump_event_queue() -> bool {
unsafe {
let mut msg: MSG = std::mem::zeroed();
let mut has_message = PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool();
while msg.message != WM_QUIT && has_message {
if !IsDialogMessageW(GetAncestor(msg.hwnd, GA_ROOT), &msg).as_bool() {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}

has_message = PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool();
}

msg.message == WM_QUIT
}
}
}

0 comments on commit f4d4501

Please sign in to comment.