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}