diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 3cab160..0afbdde 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -60,7 +60,8 @@ MediaPreviewDialog::~MediaPreviewDialog() { // Release the streamer if it was used for video if (m_isVideo) { - MediaStreamerManager::sharedInstance()->releaseStreamer(m_filePath); + MediaStreamerManager::sharedInstance()->releaseStreamer(m_device->udid, + m_filePath); } } diff --git a/src/mediastreamermanager.cpp b/src/mediastreamermanager.cpp index 2a6aa70..290505a 100644 --- a/src/mediastreamermanager.cpp +++ b/src/mediastreamermanager.cpp @@ -69,7 +69,8 @@ QUrl MediaStreamerManager::getStreamUrl( return QUrl(rustUrl); } -void MediaStreamerManager::releaseStreamer(const QString &filePath) +void MediaStreamerManager::releaseStreamer(const QString &udid, + const QString &filePath) { QMutexLocker locker(&m_streamersMutex); auto it = m_streamers.find(filePath); @@ -83,7 +84,7 @@ void MediaStreamerManager::releaseStreamer(const QString &filePath) << filePath; // delete it->streamer; AppContext::sharedInstance()->ioManager->release_video_streamer( - it.value().rustUrl); + udid, it.value().rustUrl); m_streamers.erase(it); } } diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h index 246198c..f1358c5 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -37,7 +37,7 @@ public: std::optional> hause_arrest, bool useAfc2, const QString &filePath); - void releaseStreamer(const QString &filePath); + void releaseStreamer(const QString &udid, const QString &filePath); void cleanup(); diff --git a/src/rust/src/afc2_services.rs b/src/rust/src/afc2_services.rs index d730a54..8560960 100644 --- a/src/rust/src/afc2_services.rs +++ b/src/rust/src/afc2_services.rs @@ -1,7 +1,7 @@ use cxx_qt::Threading; use cxx_qt_lib::{QByteArray, QList, QMap, QMapPair_QString_QVariant, QString}; -use crate::{APP_DEVICE_STATE, RUNTIME, VIDEO_STREAMS, afc, run_sync}; +use crate::{APP_DEVICE_STATE, RUNTIME, afc, run_sync}; use idevice::afc::{AfcClient, opcode::AfcFopenMode}; use once_cell::sync::Lazy; use regex::Regex; @@ -700,10 +700,27 @@ impl qobject::Afc2Backend { let url = format!("http://127.0.0.1:{}/{}", port, encoded); let url_clone = url.clone(); let url_clone_for_log = url.clone(); + let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); - { - let mut map = VIDEO_STREAMS.lock().unwrap(); - map.insert(url.clone(), shutdown_tx); + let udid_for_insert = udid_str.clone(); + let url_for_insert = url.clone(); + let inserted = run_sync(async move { + let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_for_insert).cloned(); + let device = match maybe_device { + Some(d) => d, + None => return false, + }; + + let mut video_streams = device.video_streams.lock().await; + video_streams.insert(url_for_insert, shutdown_tx); + true + }); + if !inserted { + eprintln!( + "start_video_stream: failed to insert video stream for udid={} path={}", + udid_str, cloned_path + ); + return QString::default(); } eprintln!( "start_video_stream: serving {} for udid={} path={}", diff --git a/src/rust/src/afc_services.rs b/src/rust/src/afc_services.rs index a83088b..bd6e236 100644 --- a/src/rust/src/afc_services.rs +++ b/src/rust/src/afc_services.rs @@ -3,7 +3,7 @@ use cxx_qt_lib::{ QByteArray, QDateTime, QList, QMap, QMapPair_QString_QVariant, QString, QTimeZone, QVariant, }; -use crate::{APP_DEVICE_STATE, RUNTIME, VIDEO_STREAMS, afc, run_sync}; +use crate::{APP_DEVICE_STATE, RUNTIME, afc, run_sync}; use idevice::{ IdeviceService, afc::{AfcClient, opcode::AfcFopenMode}, @@ -628,10 +628,27 @@ impl qobject::AfcBackend { let url_clone = url.clone(); let url_clone_for_log = url.clone(); let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); - { - let mut map = VIDEO_STREAMS.lock().unwrap(); - map.insert(url.clone(), shutdown_tx); + let udid_for_insert = udid_str.clone(); + let url_for_insert = url.clone(); + let inserted = run_sync(async move { + let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_for_insert).cloned(); + let device = match maybe_device { + Some(d) => d, + None => return false, + }; + + let mut video_streams = device.video_streams.lock().await; + video_streams.insert(url_for_insert, shutdown_tx); + true + }); + if !inserted { + eprintln!( + "start_video_stream: failed to insert video stream for udid={} path={}", + udid_str, cloned_path + ); + return QString::default(); } + eprintln!( "start_video_stream: serving {} for udid={} path={}", url_clone, udid_str, cloned_path diff --git a/src/rust/src/hause_arrest.rs b/src/rust/src/hause_arrest.rs index 2724f5c..deebf11 100644 --- a/src/rust/src/hause_arrest.rs +++ b/src/rust/src/hause_arrest.rs @@ -1,4 +1,4 @@ -use crate::{APP_DEVICE_STATE, RUNTIME, VIDEO_STREAMS, afc, run_sync, utils}; +use crate::{APP_DEVICE_STATE, RUNTIME, afc, run_sync, utils}; use cxx_qt::{CxxQtType, Threading}; use cxx_qt_lib::{QByteArray, QMap, QMapPair_QString_QVariant, QString}; use idevice::afc::{AfcClient, opcode::AfcFopenMode}; @@ -292,9 +292,22 @@ impl qobject::HauseArrest { let url_clone = url.clone(); let url_clone_for_log = url.clone(); let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); - { - let mut map = VIDEO_STREAMS.lock().unwrap(); - map.insert(url.clone(), shutdown_tx); + let udid_for_insert = udid_str.clone(); + let url_for_insert = url.clone(); + let inserted = run_sync(async move { + let maybe_device = APP_DEVICE_STATE.lock().await.get(&udid_for_insert).cloned(); + let device = match maybe_device { + Some(d) => d, + None => return false, + }; + + let mut video_streams = device.video_streams.lock().await; + video_streams.insert(url_for_insert, shutdown_tx); + true + }); + if !inserted { + eprintln!("start_video_stream: failed to insert video stream for udid={} path={}", udid_str, cloned_path); + return QString::default(); } eprintln!( "start_video_stream: serving {} for udid={} path={}", diff --git a/src/rust/src/io_manager.rs b/src/rust/src/io_manager.rs index 7c1435c..be04455 100644 --- a/src/rust/src/io_manager.rs +++ b/src/rust/src/io_manager.rs @@ -1,4 +1,4 @@ -use crate::{APP_DEVICE_STATE, RUNTIME, VIDEO_STREAMS, utils}; +use crate::{APP_DEVICE_STATE, RUNTIME, run_sync, utils}; use cxx_qt::{CxxQtType, Threading}; use cxx_qt_lib::QUuid; use idevice::{IdeviceService, afc::AfcClient, services::afc::opcode::AfcFopenMode}; @@ -144,7 +144,7 @@ mod qobject { ); #[qinvokable] - fn release_video_streamer(self: &IOManager, url: &QString); + fn release_video_streamer(self: &IOManager, udid: &QString, url: &QString); } impl cxx_qt::Threading for IOManager {} @@ -695,20 +695,26 @@ impl qobject::IOManager { } } - fn release_video_streamer(&self, url: &qobject::QString) { - let s = url.to_string(); + fn release_video_streamer(&self, udid: &qobject::QString, url: &qobject::QString) { + let udid_str = udid.to_string(); + let url_str = url.to_string(); + let url_str_clone = url_str.clone(); + let tx_opt = run_sync(async move { + let mut state = APP_DEVICE_STATE.lock().await; + let Some(device) = state.get_mut(&udid_str) else { + eprintln!("release_streamer: device {udid_str} not found"); + return None; + }; - let tx_opt = { - let mut map = VIDEO_STREAMS.lock().unwrap(); - map.remove(&s) - }; + let mut streams = device.video_streams.lock().await; + streams.remove(&url_str) + }); if let Some(tx) = tx_opt { - eprintln!("release_streamer: sending shutdown for URL: {s}"); - // ignore if receiver is already gone + eprintln!("release_streamer: sending shutdown for URL: {url_str_clone}"); let _ = tx.send(()); } else { - eprintln!("release_streamer: no streamer found for URL: {s}"); + eprintln!("release_streamer: no streamer found for URL: {url_str_clone}"); } } } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 424a0a5..8bdbca1 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -10,7 +10,6 @@ use idevice::{ IdeviceError, usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdConnection, UsbmuxdListenEvent}, }; -use std::sync::atomic::{AtomicBool, Ordering}; use std::{any::type_name, sync::Arc}; use std::{collections::HashMap, net::IpAddr}; use tokio::sync::Mutex; @@ -51,7 +50,7 @@ pub struct DeviceServices { pub afc2: Option>>, pub diag: Arc>, pub heartbeat_task: Option>>, - pub video_streams: Arc>>>, + pub video_streams: Arc>>>, pub provider: Arc>>, pub lockdown: Arc>, } @@ -67,9 +66,6 @@ static RUNTIME: Lazy = Lazy::new(|| { .unwrap() }); -static VIDEO_STREAMS: Lazy>>> = - Lazy::new(|| std::sync::Mutex::new(HashMap::new())); - pub fn run_sync(fut: F) -> R where F: Future + Send + 'static, @@ -463,7 +459,7 @@ impl qobject::Core { let result = tokio::select! { res = init_idescriptor_device(t, qt_t.clone()) => res, // timeout - _ = tokio::time::sleep(tokio::time::Duration::from_secs(10)) => { + _ = tokio::time::sleep(tokio::time::Duration::from_secs(20)) => { eprintln!("Timeout collecting device info for wireless device mac address: {mac_address_owned}"); None } @@ -574,9 +570,13 @@ impl qobject::Core { async fn clean_device_from_app_state(udid: &str) { let mut state = APP_DEVICE_STATE.lock().await; if let Some(svc) = state.remove(udid) { - let streams = svc.video_streams.lock().await; - for (_path, flag) in streams.iter() { - flag.store(true, Ordering::Relaxed); + if let Some(t) = &svc.heartbeat_task { + t.abort(); + } + + let mut streams = svc.video_streams.lock().await; + for (_url, tx) in streams.drain() { + let _ = tx.send(()); } println!("Removed device with UDID {}", udid); } else { @@ -605,14 +605,14 @@ async fn init_idescriptor_device< let mut lc = match LockdownClient::connect(&provider).await { Ok(lc) => lc, Err(e) => { - eprintln!("wireless: LockdownClient::connect failed : {e:?}"); + eprintln!("LockdownClient::connect failed : {e:?}"); return None; } }; eprintln!("init_idescriptor_device: Attempting to start Lockdown session."); if let Err(e) = lc.start_session(&pf).await { - eprintln!("wireless: start_session failed: {e:?}"); + eprintln!("start_session failed: {e:?}"); return None; } eprintln!("init_idescriptor_device: Lockdown session started."); @@ -621,7 +621,7 @@ async fn init_idescriptor_device< let mut def_vals = match lc.get_value(None, None).await { Ok(v) => v, Err(e) => { - eprintln!("wireless: get_value(None, None) failed : {e:?}"); + eprintln!("get_value(None, None) failed : {e:?}"); return None; } }; @@ -651,24 +651,10 @@ async fn init_idescriptor_device< eprintln!("init_idescriptor_device: Connected to HeartbeatClient."); } - // FIXME: this cannot be done when paired wirelessly - // lockdownd_set_value(device->lockdown, "EnableWifiConnections", - // // value, "com.apple.mobile.wireless_lockdown"); - - // let value = Value::Boolean(true); - // match lc.set_value("EnableWifiConnections", value, Some("com.apple.mobile.wireless_lockdown")).await { - // Ok(_) => {}, - // Err(e) => { - // eprintln!("wireless: LockdownClient::set_value failed: {e:?}"); - // return None; - // // continue anyway, as this might not be critical - // } - // } - let disk_vals = match lc.get_value(None, Some("com.apple.disk_usage")).await { Ok(v) => v, Err(e) => { - eprintln!("wireless: get_value(com.apple.disk_usage) failed: {e:?}"); + eprintln!("get_value(com.apple.disk_usage) failed: {e:?}"); return None; } }; @@ -692,7 +678,6 @@ async fn init_idescriptor_device< } }; eprintln!("init_idescriptor_device: Connected to DiagnosticsRelayClient."); - // afc_client.set_timeout(Some(5000)).await; let afc2 = match AfcClient::new_afc2(&provider).await { Ok(c) => Some(Arc::new(Mutex::new(c))), diff --git a/src/rust/src/service_manager.rs b/src/rust/src/service_manager.rs index 39601dd..369ebd6 100644 --- a/src/rust/src/service_manager.rs +++ b/src/rust/src/service_manager.rs @@ -130,6 +130,13 @@ mod qobject { #[qsignal] fn install_ipa_progress(self: Pin<&mut ServiceManager>, progress: f64, state: QString); + + + #[qinvokable] + fn enable_wifi_connections(&self); + + #[qsignal] + fn enable_wifi_connections_result(self: Pin<&mut ServiceManager>, success: bool); } impl cxx_qt::Threading for ServiceManager {} @@ -793,7 +800,7 @@ impl qobject::ServiceManager { } _ = tokio::time::sleep(Duration::from_secs(10)) => { eprintln!("set_location: timed out"); - -71 + idevice::IdeviceError::Timeout.code() } } }) @@ -1133,6 +1140,56 @@ impl qobject::ServiceManager { } }); } + + fn enable_wifi_connections(&self) { + let qt_t = self.qt_thread(); + let udid = self.get_udid().to_string(); + + + RUNTIME.spawn(async move { + let qt_thread = qt_t.clone(); + + let lc_arc = { + let maybe_device = APP_DEVICE_STATE + .lock() + .await + .get(udid.as_str()) + .cloned(); + + let device = match maybe_device { + Some(d) => d, + None => { + eprintln!("enable_wifi_connections: device {udid} not found"); + let _ = qt_thread.queue(|t| { + t.enable_wifi_connections_result(false); + }).ok(); + return; + } + }; + + device.lockdown.clone() + }; + + let mut lc = lc_arc.lock().await; + + let value = Value::Boolean(true); + match lc.set_value("EnableWifiConnections", value, Some("com.apple.mobile.wireless_lockdown")).await { + Ok(_) => { + let _ = qt_thread.queue(|t| { + t.enable_wifi_connections_result(true); + }).ok(); + }, + Err(e) => { + eprintln!("wireless: LockdownClient::set_value failed: {e:?}"); + let _ = qt_thread.queue(|t| { + t.enable_wifi_connections_result(false); + }).ok(); + } + } + + }); + + } } async fn set_device_location_lockdown(