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