media_remote/utils/helpers.rs
1use std::{io::Cursor, ptr::NonNull};
2
3use block2::RcBlock;
4use image::ImageReader;
5use objc2::rc::{autoreleasepool, Retained};
6use objc2_app_kit::NSWorkspace;
7use objc2_foundation::{NSFileManager, NSNotification, NSNotificationCenter, NSString};
8
9#[allow(unused_imports)]
10use crate::{register_for_now_playing_notifications, BundleInfo, Notification, Observer};
11
12// Retrieves information about an application based on its bundle identifier.
13///
14///
15/// If the application is found, its name and icon are returned as a `BundleInfo` struct.
16/// Otherwise, `None` is returned if the application cannot be located or an error occurs
17/// during data retrieval.
18///
19/// # Arguments
20/// - `id`: A string slice representing the application's bundle identifier.
21///
22/// # Returns
23/// - `Option<BundleInfo>`:
24/// - `Some(BundleInfo)` containing the application's name and icon if retrieval is successful.
25/// - `None` if the application cannot be found or if an error occurs during processing.
26///
27/// # Example
28/// ```rust
29/// use media_remote::{get_now_playing_client_parent_app_bundle_identifier, get_bundle_info};
30///
31/// let bundle_id = get_now_playing_client_parent_app_bundle_identifier();
32///
33/// if let Some(id) = bundle_id {
34/// if let Some(bundle) = get_bundle_info(id.as_str()) {
35/// println!("App Name: {}", bundle.name);
36/// } else {
37/// println!("Application not found.");
38/// }
39/// }
40/// ```
41pub fn get_bundle_info(id: &str) -> Option<BundleInfo> {
42 autoreleasepool(|_| {
43 let workspace = NSWorkspace::sharedWorkspace();
44 let url = workspace.URLForApplicationWithBundleIdentifier(&NSString::from_str(id))?;
45
46 let path = &url.path()?;
47
48 let file_manager = NSFileManager::defaultManager();
49 let name = file_manager.displayNameAtPath(path);
50
51 let icon = workspace.iconForFile(path);
52 let icon_data = icon.TIFFRepresentation()?;
53 let icon = ImageReader::new(Cursor::new(icon_data.to_vec()))
54 .with_guessed_format()
55 .ok()?
56 .decode()
57 .ok()?;
58
59 Some(BundleInfo {
60 name: name.to_string(),
61 icon,
62 })
63 })
64}
65
66/// Adds an observer for a specific media notification.
67///
68/// This function registers a closure to be executed when the specified `notification`
69/// is posted. It listens for notifications related to media playback state changes.
70///
71/// **Note:** [`register_for_now_playing_notifications`] **must** be called before using
72/// this function to ensure notifications are received.
73///
74/// # Arguments
75/// - `notification`: The [`Notification`] type representing the event to observe.
76/// - `closure`: A closure to execute when the notification is received.
77///
78/// # Returns
79/// - An [`Observer`] handle that can be used to remove the observer later.
80///
81/// # Example
82/// ```rust
83/// use media_remote::{register_for_now_playing_notifications, add_observer, Notification};
84///
85/// register_for_now_playing_notifications();
86///
87/// let observer = add_observer(Notification::NowPlayingApplicationIsPlayingDidChange, || {
88/// println!("Now playing status changed.");
89/// });
90/// ```
91pub fn add_observer<F: Fn() + 'static>(notification: Notification, closure: F) -> Observer {
92 unsafe {
93 let observer = NSNotificationCenter::defaultCenter()
94 .addObserverForName_object_queue_usingBlock(
95 Some(NSString::from_str(notification.as_str()).as_ref()),
96 None,
97 None,
98 &RcBlock::new(move |_: NonNull<NSNotification>| closure()),
99 );
100
101 Retained::cast_unchecked(observer)
102 }
103}
104
105/// Removes a previously added observer.
106///
107/// This function removes an observer registered with [`add_observer`], preventing further
108/// notifications from being received.
109///
110/// # Arguments
111/// - `observer`: The [`Observer`] handle returned from [`add_observer`].
112///
113/// # Example
114/// ```rust
115/// use media_remote::{register_for_now_playing_notifications, add_observer, remove_observer, Notification};
116///
117/// register_for_now_playing_notifications();
118///
119/// let observer = add_observer(Notification::NowPlayingApplicationIsPlayingDidChange, || {
120/// println!("Now playing status changed.");
121/// });
122///
123/// // Later, when no longer needed:
124/// remove_observer(observer);
125/// ```
126pub fn remove_observer(observer: Observer) {
127 unsafe {
128 NSNotificationCenter::defaultCenter().removeObserver(&observer);
129 }
130}