Skip to content
50 changes: 49 additions & 1 deletion idevice/src/services/springboardservices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Provides functionality for interacting with the SpringBoard services on iOS devices,
//! which manages home screen and app icon related operations.

use crate::{Idevice, IdeviceError, IdeviceService, obf};
use crate::{obf, Idevice, IdeviceError, IdeviceService};

/// Client for interacting with the iOS SpringBoard services
///
Expand Down Expand Up @@ -70,4 +70,52 @@ impl SpringBoardServicesClient {
_ => Err(IdeviceError::UnexpectedResponse),
}
}

/// Retrieves the current icon state from the device
///
/// The icon state contains the layout and organization of all apps on the home screen,
/// including folder structures and icon positions. This is a read-only operation.
///
/// # Arguments
/// * `format_version` - Optional format version string for the icon state format
///
/// # Returns
/// A plist Value containing the complete icon state structure
///
/// # Errors
/// Returns `IdeviceError` if:
/// - Communication fails
/// - The response is malformed
///
/// # Example
/// ```rust
/// use idevice::services::springboardservices::SpringBoardServicesClient;
///
/// let mut client = SpringBoardServicesClient::connect(&provider).await?;
/// let icon_state = client.get_icon_state(None).await?;
/// println!("Icon state: {:?}", icon_state);
/// ```
///
/// # Notes
/// This method successfully reads the home screen layout on all iOS versions.
/// Note that modifying and setting icon state (setIconState) does not work
/// on iOS 18+ due to Apple's security restrictions. See issue #62 for details.
pub async fn get_icon_state(
&mut self,
format_version: Option<String>,
) -> Result<plist::Value, IdeviceError> {
let mut req = crate::plist!({
"command": "getIconState",
});

if let Some(version) = format_version {
if let Some(dict) = req.as_dictionary_mut() {
dict.insert("formatVersion".to_string(), plist::Value::String(version));
}
}

self.idevice.send_plist(req).await?;
let res = self.idevice.read_plist_value().await?;
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response from the device is not validated for errors before being returned. Unlike get_icon_pngdata which checks if the response contains the expected data field and returns UnexpectedResponse on error, this method returns the raw plist response without any validation. The device might return an error dictionary instead of the expected icon state data. Consider checking if the response contains an error field or validating that it contains the expected structure before returning it to the caller.

Suggested change
let res = self.idevice.read_plist_value().await?;
let res = self.idevice.read_plist_value().await?;
// Some devices may return an error dictionary instead of icon state.
// Detect this and surface it as an UnexpectedResponse, similar to get_icon_pngdata.
if let plist::Value::Dictionary(ref dict) = res {
if dict.contains_key("error") || dict.contains_key("Error") {
return Err(IdeviceError::UnexpectedResponse);
}
}

Copilot uses AI. Check for mistakes.
Ok(res)
}
}
Loading