Skip to content

Commit 7d30a3b

Browse files
committed
feat: Support mpris on MacOS.
1 parent 6c3256a commit 7d30a3b

File tree

5 files changed

+124
-17
lines changed

5 files changed

+124
-17
lines changed

Cargo.lock

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ytermusic/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,13 @@ common-structs.workspace = true
5050
# -- Cookies auto retreival --
5151
rookie = "0.5.2"
5252

53+
ctrlc = "3.5.0"
54+
5355
[target."cfg(target_os = \"windows\")".dependencies]
5456
raw-window-handle = "0.4.3"
5557
winit = "0.26.1"
58+
[target."cfg(target_os = \"macos\")".dependencies]
59+
winit = "0.26.1"
5660

5761
[profile.release]
5862
codegen-units = 1

crates/ytermusic/src/main.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ use std::{
1010
future::Future,
1111
panic,
1212
path::{Path, PathBuf},
13-
process::exit,
1413
str::FromStr,
1514
sync::RwLock,
1615
};
1716
use systems::{logger::init, player::player_system};
1817

1918
use crate::{
2019
consts::HEADER_TUTORIAL,
21-
structures::sound_action::download_manager_handler,
20+
structures::{media::run_window_handler, sound_action::download_manager_handler},
2221
systems::{logger::get_log_file_path, DOWNLOAD_MANAGER},
2322
utils::get_project_dirs,
2423
};
@@ -57,10 +56,11 @@ where
5756
}
5857

5958
fn shutdown() {
59+
info!("Shutdown signal sending");
6060
for _ in 0..1000 {
6161
SIGNALING_STOP.0.send(()).unwrap();
6262
}
63-
exit(0);
63+
info!("Shutdown signal sent");
6464
}
6565

6666
static COOKIES: Lazy<RwLock<Option<String>>> = Lazy::new(|| RwLock::new(None));
@@ -70,8 +70,7 @@ pub fn try_get_cookies() -> Option<String> {
7070
cookies.clone()
7171
}
7272

73-
#[tokio::main]
74-
async fn main() {
73+
fn main() {
7574
// Check if the first param is --files
7675
if let Some(arg) = std::env::args().nth(1) {
7776
match arg.as_str() {
@@ -137,15 +136,7 @@ async fn main() {
137136
error!("{e}");
138137
shutdown();
139138
}));
140-
select! {
141-
_ = async {
142-
app_start().await
143-
} => {},
144-
_ = SIGNALING_STOP.1.recv_async() => {},
145-
_ = tokio::signal::ctrl_c() => {
146-
shutdown();
147-
},
148-
};
139+
app_start();
149140
}
150141

151142
fn cookies(specific_browser: Option<String>) -> Option<String> {
@@ -229,7 +220,8 @@ fn get_header_file() -> Result<(String, PathBuf), (std::io::Error, PathBuf)> {
229220
let fp = fp.join("headers.txt");
230221
std::fs::read_to_string(&fp).map_or_else(|e| Err((e, fp.clone())), |e| Ok((e, fp.clone())))
231222
}
232-
async fn app_start() {
223+
224+
async fn app_start_main(updater_r: Receiver<ManagerMessage>, updater_s: Sender<ManagerMessage>) {
233225
STARTUP_TIME.log("Init");
234226

235227
std::fs::create_dir_all(CACHE_DIR.join("downloads")).unwrap();
@@ -248,7 +240,6 @@ async fn app_start() {
248240
STARTUP_TIME.log("Startup");
249241

250242
// Spawn the clean task
251-
let (updater_s, updater_r) = flume::unbounded::<ManagerMessage>();
252243
tasks::clean::spawn_clean_task();
253244

254245
STARTUP_TIME.log("Spawned clean task");
@@ -272,3 +263,26 @@ async fn app_start() {
272263
let mut manager = Manager::new(sa, player).await;
273264
manager.run(&updater_r).unwrap();
274265
}
266+
fn app_start() {
267+
let (updater_s, updater_r) = flume::unbounded::<ManagerMessage>();
268+
let updater_s_c = updater_s.clone();
269+
ctrlc::set_handler(move || {
270+
info!("CTRL-C received");
271+
shutdown()
272+
})
273+
.expect("Error setting Ctrl-C handler");
274+
std::thread::spawn(move || {
275+
tokio::runtime::Builder::new_multi_thread()
276+
.enable_all()
277+
.build()
278+
.expect("Failed to build runtime")
279+
.block_on(async move {
280+
select! {
281+
_ = app_start_main(updater_r, updater_s) => {},
282+
_ = SIGNALING_STOP.1.recv_async() => {},
283+
};
284+
});
285+
info!("Runtime closed");
286+
});
287+
run_window_handler(&updater_s_c);
288+
}

crates/ytermusic/src/structures/media.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,56 @@ fn get_handle(updater: &Sender<ManagerMessage>) -> Option<MediaControls> {
154154
.map_err(|e| format!("{e:?}")),
155155
)
156156
}
157+
#[cfg(not(target_os = "macos"))]
158+
pub fn run_window_handler(_updater: &Sender<ManagerMessage>) -> Option<()> {
159+
use crate::SIGNALING_STOP;
160+
loop {
161+
if SIGNALING_STOP.1.try_recv() == Ok(()) {
162+
use std::process::exit;
163+
164+
info!("event loop closed");
165+
SIGNALING_STOP.0.send(()).unwrap();
166+
exit(0);
167+
}
168+
}
169+
}
170+
171+
#[cfg(target_os = "macos")]
172+
pub fn run_window_handler(updater: &Sender<ManagerMessage>) -> Option<()> {
173+
use std::process::exit;
174+
175+
use winit::event_loop::EventLoop;
176+
use winit::platform::macos::{ActivationPolicy, EventLoopExtMacOS};
177+
use winit::window::WindowBuilder;
178+
179+
use crate::errors::handle_error_option;
180+
let thread = std::thread::current();
181+
info!("Current Thread Name: {:?}", thread.name());
182+
info!("Current Thread ID: {:?}", thread.id());
183+
184+
// On macOS, winit requires the EventLoop to be created on the main thread.
185+
// Unlike Windows, we cannot use `new_any_thread`.
186+
// We create a hidden window to ensure NSApplication is active and capable of receiving events.
187+
let mut event_loop = EventLoop::new();
188+
event_loop.set_activation_policy(ActivationPolicy::Regular);
189+
190+
// Create a hidden window. While souvlaki doesn't need the handle in the config,
191+
// the existence of the window helps keep the event loop and application state valid.
192+
let _window = handle_error_option(
193+
updater,
194+
"OS Error while creating media hook window",
195+
WindowBuilder::new().with_visible(false).build(&event_loop),
196+
)?;
197+
event_loop.run(move |_event, _window_target, ctrl_flow| {
198+
use crate::SIGNALING_STOP;
199+
if SIGNALING_STOP.1.try_recv() == Ok(()) {
200+
info!("event loop closed");
201+
SIGNALING_STOP.0.send(()).unwrap();
202+
*ctrl_flow = winit::event_loop::ControlFlow::Exit;
203+
exit(0);
204+
}
205+
});
206+
}
157207

158208
#[cfg(target_os = "windows")]
159209
fn get_handle(updater: &Sender<ManagerMessage>) -> Option<MediaControls> {

crates/ytermusic/src/term/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ use flume::{Receiver, Sender};
2424
use ratatui::{backend::CrosstermBackend, layout::Rect, Frame, Terminal};
2525
use ytpapi2::YoutubeMusicVideoRef;
2626

27-
use crate::{structures::sound_action::SoundAction, systems::player::PlayerState, SIGNALING_STOP};
27+
use crate::{
28+
shutdown, structures::sound_action::SoundAction, systems::player::PlayerState, SIGNALING_STOP,
29+
};
2830

2931
use self::{device_lost::DeviceLost, item_list::ListItem, playlist::Chooser, search::Search};
3032

@@ -255,6 +257,8 @@ impl Manager {
255257
)?;
256258
terminal.show_cursor()?;
257259

260+
shutdown();
261+
258262
Ok(())
259263
}
260264
}

0 commit comments

Comments
 (0)