diff --git a/ffi/src/springboardservices.rs b/ffi/src/springboardservices.rs index 50d56d5..4145ebd 100644 --- a/ffi/src/springboardservices.rs +++ b/ffi/src/springboardservices.rs @@ -137,6 +137,96 @@ pub unsafe extern "C" fn springboard_services_get_icon( } } +/// Gets the home screen wallpaper preview as PNG image +/// +/// # Arguments +/// * `client` - A valid SpringBoardServicesClient handle +/// * `out_result` - On success, will be set to point to newly allocated png image +/// * `out_result_len` - On success, will contain the size of the data in bytes +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `out_result` and `out_result_len` must be valid, non-null pointers +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_get_home_screen_wallpaper_preview( + client: *mut SpringBoardServicesClientHandle, + out_result: *mut *mut c_void, + out_result_len: *mut libc::size_t, +) -> *mut IdeviceFfiError { + if client.is_null() || out_result.is_null() || out_result_len.is_null() { + tracing::error!("Invalid arguments: {client:?}, {out_result:?}"); + return ffi_err!(IdeviceError::FfiInvalidArg); + } + let client = unsafe { &mut *client }; + + let res: Result, IdeviceError> = + run_sync(async { client.0.get_home_screen_wallpaper_preview_pngdata().await }); + + match res { + Ok(r) => { + let len = r.len(); + let boxed_slice = r.into_boxed_slice(); + let ptr = boxed_slice.as_ptr(); + std::mem::forget(boxed_slice); + + unsafe { + *out_result = ptr as *mut c_void; + *out_result_len = len; + } + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Gets the lock screen wallpaper preview as PNG image +/// +/// # Arguments +/// * `client` - A valid SpringBoardServicesClient handle +/// * `out_result` - On success, will be set to point to newly allocated png image +/// * `out_result_len` - On success, will contain the size of the data in bytes +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `out_result` and `out_result_len` must be valid, non-null pointers +#[unsafe(no_mangle)] +pub unsafe extern "C" fn springboard_services_get_lock_screen_wallpaper_preview( + client: *mut SpringBoardServicesClientHandle, + out_result: *mut *mut c_void, + out_result_len: *mut libc::size_t, +) -> *mut IdeviceFfiError { + if client.is_null() || out_result.is_null() || out_result_len.is_null() { + tracing::error!("Invalid arguments: {client:?}, {out_result:?}"); + return ffi_err!(IdeviceError::FfiInvalidArg); + } + let client = unsafe { &mut *client }; + + let res: Result, IdeviceError> = + run_sync(async { client.0.get_lock_screen_wallpaper_preview_pngdata().await }); + + match res { + Ok(r) => { + let len = r.len(); + let boxed_slice = r.into_boxed_slice(); + let ptr = boxed_slice.as_ptr(); + std::mem::forget(boxed_slice); + + unsafe { + *out_result = ptr as *mut c_void; + *out_result_len = len; + } + null_mut() + } + Err(e) => ffi_err!(e), + } +} + /// Frees an SpringBoardServicesClient handle /// /// # Arguments diff --git a/idevice/src/services/springboardservices.rs b/idevice/src/services/springboardservices.rs index 15177d1..11b4b9d 100644 --- a/idevice/src/services/springboardservices.rs +++ b/idevice/src/services/springboardservices.rs @@ -198,4 +198,71 @@ impl SpringBoardServicesClient { self.idevice.send_plist(req).await?; Ok(()) } + /// Gets the home screen wallpaper preview as PNG data + /// + /// This gets a rendered preview of the home screen wallpaper. + /// + /// # Returns + /// The raw PNG data of the home screen wallpaper preview + /// + /// # Errors + /// Returns `IdeviceError` if: + /// - Communication fails + /// - The device rejects the request + /// - The image is malformed/corupted + /// + /// # Example + /// ```rust + /// let wallpaper = client.get_home_screen_wallpaper_preview_pngdata().await?; + /// std::fs::write("home.png", wallpaper)?; + /// ``` + pub async fn get_home_screen_wallpaper_preview_pngdata( + &mut self, + ) -> Result, IdeviceError> { + let req = crate::plist!({ + "command": "getWallpaperPreviewImage", + "wallpaperName": "homescreen", + }); + self.idevice.send_plist(req).await?; + + let mut res = self.idevice.read_plist().await?; + match res.remove("pngData") { + Some(plist::Value::Data(res)) => Ok(res), + _ => Err(IdeviceError::UnexpectedResponse), + } + } + + /// Gets the lock screen wallpaper preview as PNG data + /// + /// This gets a rendered preview of the lock screen wallpaper. + /// + /// # Returns + /// The raw PNG data of the lock screen wallpaper preview + /// + /// # Errors + /// Returns `IdeviceError` if: + /// - Communication fails + /// - The device rejects the request + /// - The image is malformed/corupted + /// + /// # Example + /// ```rust + /// let wallpaper = client.get_lock_screen_wallpaper_preview_pngdata().await?; + /// std::fs::write("lock.png", wallpaper)?; + /// ``` + pub async fn get_lock_screen_wallpaper_preview_pngdata( + &mut self, + ) -> Result, IdeviceError> { + let req = crate::plist!({ + "command": "getWallpaperPreviewImage", + "wallpaperName": "lockscreen", + }); + self.idevice.send_plist(req).await?; + + let mut res = self.idevice.read_plist().await?; + match res.remove("pngData") { + Some(plist::Value::Data(res)) => Ok(res), + _ => Err(IdeviceError::UnexpectedResponse), + } + } } diff --git a/tools/src/springboardservices.rs b/tools/src/springboardservices.rs index 5f0fb56..f997c2f 100644 --- a/tools/src/springboardservices.rs +++ b/tools/src/springboardservices.rs @@ -32,6 +32,19 @@ pub fn register() -> JkCommand { .required(true), ), ) + .with_subcommand( + "get_wallpaper_preview", + JkCommand::new() + .help("Gets wallpaper preview") + .with_subcommand("homescreen", JkCommand::new()) + .with_subcommand("lockscreen", JkCommand::new()) + .subcommand_required(true) + .with_flag( + JkFlag::new("save") + .with_help("Path to save the wallpaper PNG file, or preview.png by default") + .with_argument(JkArgument::new().required(true)), + ), + ) .subcommand_required(true) } @@ -71,6 +84,24 @@ pub async fn main(arguments: &CollectedArguments, provider: Box { + let (wallpaper_type, _) = sub_args.first_subcommand().unwrap(); + + let wallpaper = match wallpaper_type.as_str() { + "homescreen" => sbc.get_home_screen_wallpaper_preview_pngdata().await, + "lockscreen" => sbc.get_lock_screen_wallpaper_preview_pngdata().await, + _ => panic!("Invalid wallpaper type. Use 'homescreen' or 'lockscreen'"), + } + .expect("Failed to get wallpaper preview"); + + let save_path = sub_args + .get_flag::("save") + .unwrap_or("preview.png".to_string()); + + tokio::fs::write(&save_path, wallpaper) + .await + .expect("Failed to save wallpaper"); + } _ => unreachable!(), } }