Skip to content

Commit 672fa6e

Browse files
committed
MTP: Add connection management and ptpcamerad handling
Phase 2 implementation: - Add MtpConnectionManager with global device registry - Add connect_mtp_device, disconnect_mtp_device, get_mtp_device_info commands - Add macOS ptpcamerad workaround with ioreg-based detection - Add PtpcameradDialog with Terminal command and copy button - Add TypeScript wrappers and event listeners for MTP errors - Wire up error dialog in main layout
1 parent 938e87c commit 672fa6e

12 files changed

Lines changed: 1224 additions & 20 deletions

File tree

apps/desktop/coverage-allowlist.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"icon-cache.ts": { "reason": "Depends on Tauri APIs" },
2727
"licensing-store.svelte.ts": { "reason": "Depends on Tauri store APIs" },
2828
"logger.ts": { "reason": "LogTape config, initialization code only" },
29+
"mtp/PtpcameradDialog.svelte": { "reason": "UI modal for macOS MTP workaround" },
2930
"licensing/AboutWindow.svelte": { "reason": "UI component" },
3031
"licensing/CommercialReminderModal.svelte": { "reason": "UI component" },
3132
"licensing/ExpirationModal.svelte": { "reason": "UI component" },

apps/desktop/src-tauri/src/commands/mtp.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Tauri commands for MTP (Android device) operations.
22
3-
use crate::mtp::{self, MtpDeviceInfo};
3+
use crate::mtp::{self, ConnectedDeviceInfo, MtpConnectionError, MtpDeviceInfo, MtpStorageInfo};
4+
use tauri::AppHandle;
45

56
/// Lists all connected MTP devices.
67
///
@@ -14,3 +15,73 @@ use crate::mtp::{self, MtpDeviceInfo};
1415
pub fn list_mtp_devices() -> Vec<MtpDeviceInfo> {
1516
mtp::list_mtp_devices()
1617
}
18+
19+
/// Connects to an MTP device by ID.
20+
///
21+
/// Opens an MTP session to the device and retrieves storage information.
22+
/// If another process (like ptpcamerad on macOS) has exclusive access,
23+
/// an `mtp-exclusive-access-error` event is emitted to the frontend.
24+
///
25+
/// # Arguments
26+
///
27+
/// * `device_id` - The device ID from `list_mtp_devices` (format: "mtp-{bus}-{address}")
28+
///
29+
/// # Returns
30+
///
31+
/// Information about the connected device including available storages.
32+
#[tauri::command]
33+
pub async fn connect_mtp_device(app: AppHandle, device_id: String) -> Result<ConnectedDeviceInfo, MtpConnectionError> {
34+
mtp::connection_manager().connect(&device_id, Some(&app)).await
35+
}
36+
37+
/// Disconnects from an MTP device.
38+
///
39+
/// Closes the MTP session gracefully. The device remains available in
40+
/// `list_mtp_devices` for reconnection.
41+
///
42+
/// # Arguments
43+
///
44+
/// * `device_id` - The device ID to disconnect from
45+
#[tauri::command]
46+
pub async fn disconnect_mtp_device(app: AppHandle, device_id: String) -> Result<(), MtpConnectionError> {
47+
mtp::connection_manager().disconnect(&device_id, Some(&app)).await
48+
}
49+
50+
/// Gets information about a connected MTP device.
51+
///
52+
/// Returns device metadata and storage information for a currently connected device.
53+
/// Returns `None` if the device is not connected.
54+
///
55+
/// # Arguments
56+
///
57+
/// * `device_id` - The device ID to query
58+
#[tauri::command]
59+
pub fn get_mtp_device_info(device_id: String) -> Option<ConnectedDeviceInfo> {
60+
mtp::connection_manager().get_device_info(&device_id)
61+
}
62+
63+
/// Gets the ptpcamerad workaround command for macOS.
64+
///
65+
/// Returns the Terminal command that users can run to work around
66+
/// ptpcamerad blocking MTP device access.
67+
#[tauri::command]
68+
pub fn get_ptpcamerad_workaround_command() -> String {
69+
mtp::PTPCAMERAD_WORKAROUND_COMMAND.to_string()
70+
}
71+
72+
/// Gets storage information for all storages on a connected device.
73+
///
74+
/// # Arguments
75+
///
76+
/// * `device_id` - The connected device ID
77+
///
78+
/// # Returns
79+
///
80+
/// A vector of storage info, or empty if device is not connected.
81+
#[tauri::command]
82+
pub fn get_mtp_storages(device_id: String) -> Vec<MtpStorageInfo> {
83+
mtp::connection_manager()
84+
.get_device_info(&device_id)
85+
.map(|info| info.storages)
86+
.unwrap_or_default()
87+
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,28 @@ pub fn run() {
351351
// MTP commands (macOS only - Android device support)
352352
#[cfg(target_os = "macos")]
353353
commands::mtp::list_mtp_devices,
354+
#[cfg(target_os = "macos")]
355+
commands::mtp::connect_mtp_device,
356+
#[cfg(target_os = "macos")]
357+
commands::mtp::disconnect_mtp_device,
358+
#[cfg(target_os = "macos")]
359+
commands::mtp::get_mtp_device_info,
360+
#[cfg(target_os = "macos")]
361+
commands::mtp::get_ptpcamerad_workaround_command,
362+
#[cfg(target_os = "macos")]
363+
commands::mtp::get_mtp_storages,
354364
#[cfg(not(target_os = "macos"))]
355365
stubs::mtp::list_mtp_devices,
366+
#[cfg(not(target_os = "macos"))]
367+
stubs::mtp::connect_mtp_device,
368+
#[cfg(not(target_os = "macos"))]
369+
stubs::mtp::disconnect_mtp_device,
370+
#[cfg(not(target_os = "macos"))]
371+
stubs::mtp::get_mtp_device_info,
372+
#[cfg(not(target_os = "macos"))]
373+
stubs::mtp::get_ptpcamerad_workaround_command,
374+
#[cfg(not(target_os = "macos"))]
375+
stubs::mtp::get_mtp_storages,
356376
// Volume commands (platform-specific)
357377
#[cfg(target_os = "macos")]
358378
commands::volumes::list_volumes,

0 commit comments

Comments
 (0)