Skip to content

Commit a1e8f7f

Browse files
Windows MPV Hang Fix
1 parent 4c54e37 commit a1e8f7f

3 files changed

Lines changed: 90 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ tokio = { version = "1", features = ["full"] }
1111
rand = "0.9"
1212

1313
[target.'cfg(unix)'.dependencies]
14-
libc = "0.2"
14+
libc = "0.2"
15+
16+
[target.'cfg(windows)'.dependencies]
17+
windows-sys = { version = "0.59", features = [
18+
"Win32_System_JobObjects",
19+
"Win32_Foundation",
20+
"Win32_Security",
21+
] }

src/audio/player.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use std::os::unix::net::UnixStream;
77
#[cfg(unix)]
88
use std::os::unix::process::CommandExt;
99

10+
#[cfg(windows)]
11+
use std::os::windows::io::AsRawHandle;
12+
1013
#[cfg(unix)]
1114
use crate::audio::pipe as audio_pipe;
1215
use crate::audio::pipe::SharedAnalysis;
@@ -89,6 +92,9 @@ pub struct Player {
8992
has_parec: bool,
9093
/// Real-time stream information from mpv
9194
pub stream_info: StreamInfo,
95+
/// Windows Job Object handle — kills mpv automatically if AetherTune is closed via X button
96+
#[cfg(windows)]
97+
job_handle: Option<windows_sys::Win32::Foundation::HANDLE>,
9298
}
9399

94100
impl Player {
@@ -107,6 +113,9 @@ impl Player {
107113
#[cfg(windows)]
108114
let has_parec = false;
109115

116+
#[cfg(windows)]
117+
let job_handle = create_kill_on_close_job();
118+
110119
Self {
111120
process: None,
112121
#[cfg(unix)]
@@ -124,6 +133,8 @@ impl Player {
124133
request_counter: 0,
125134
has_parec,
126135
stream_info: StreamInfo::new(),
136+
#[cfg(windows)]
137+
job_handle,
127138
}
128139
}
129140

@@ -154,6 +165,15 @@ impl Player {
154165

155166
match cmd.spawn() {
156167
Ok(c) => {
168+
// On Windows, assign mpv to our Job Object so it gets killed
169+
// automatically if the terminal window is closed via the X button.
170+
#[cfg(windows)]
171+
{
172+
if let Some(job) = self.job_handle {
173+
assign_process_to_job(job, c.as_raw_handle());
174+
}
175+
}
176+
157177
self.process = Some(c);
158178
self.media_title = None;
159179
self.stream_info.reset();
@@ -499,10 +519,61 @@ impl Player {
499519
impl Drop for Player {
500520
fn drop(&mut self) {
501521
self.stop();
522+
523+
#[cfg(windows)]
524+
{
525+
if let Some(job) = self.job_handle.take() {
526+
unsafe { windows_sys::Win32::Foundation::CloseHandle(job) };
527+
}
528+
}
502529
}
503530
}
504531

505532
#[cfg(unix)]
506533
fn shell_escape(s: &str) -> String {
507534
format!("'{}'", s.replace('\'', "'\\''"))
535+
}
536+
537+
/// Create a Windows Job Object configured to kill all assigned processes
538+
/// when the last handle to the job is closed (i.e. when AetherTune exits).
539+
#[cfg(windows)]
540+
fn create_kill_on_close_job() -> Option<windows_sys::Win32::Foundation::HANDLE> {
541+
use windows_sys::Win32::System::JobObjects::*;
542+
543+
unsafe {
544+
let job = CreateJobObjectW(std::ptr::null(), std::ptr::null());
545+
if job == 0 {
546+
return None;
547+
}
548+
549+
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
550+
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
551+
552+
let ok = SetInformationJobObject(
553+
job,
554+
JobObjectExtendedLimitInformation,
555+
&info as *const _ as *const _,
556+
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
557+
);
558+
559+
if ok == 0 {
560+
windows_sys::Win32::Foundation::CloseHandle(job);
561+
return None;
562+
}
563+
564+
Some(job)
565+
}
566+
}
567+
568+
/// Assign a spawned process to a Job Object so it inherits the kill-on-close behavior.
569+
#[cfg(windows)]
570+
fn assign_process_to_job(
571+
job: windows_sys::Win32::Foundation::HANDLE,
572+
process: std::os::windows::io::RawHandle,
573+
) {
574+
use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject;
575+
576+
unsafe {
577+
AssignProcessToJobObject(job, process as isize);
578+
}
508579
}

0 commit comments

Comments
 (0)