implement interval to try connecting to wireless devices and add device sleep warning dialog

This commit is contained in:
uncor3
2026-04-08 10:23:28 +00:00
parent fd508acfe2
commit 0c50cf4982
16 changed files with 321 additions and 95 deletions
+1
View File
@@ -76,6 +76,7 @@
<file>resources/keychain.mp4</file>
<file>resources/wireless-gallery-import.mp4</file>
<file>resources/dev-mode.mp4</file>
<file>resources/unlock.mp4</file>
<file>resources/trust.png</file>
</qresource>
</RCC>
BIN
View File
Binary file not shown.
+16 -4
View File
@@ -181,6 +181,12 @@ void AppContext::removeDevice(iDescriptor::Uniq uniq, bool ask_backend)
auto device = m_devices[q_udid];
m_devices.remove(q_udid);
qDebug() << "Removed device with UUID " + q_udid << "macAddress:"
<< QString::fromStdString(device->deviceInfo.wifiMacAddress)
<< "ipAddress:"
<< QString::fromStdString(device->deviceInfo.ipAddress)
<< "wasWireless:" << device->deviceInfo.isWireless;
emit deviceRemoved(q_udid, device->deviceInfo.wifiMacAddress,
device->deviceInfo.ipAddress,
device->deviceInfo.isWireless);
@@ -350,13 +356,17 @@ void AppContext::heartbeatFailed(const QString &macAddress, int tries)
emit deviceHeartbeatFailed(macAddress, tries);
}
void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device)
void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device,
bool forceCache,
bool setSelectionIfExists)
{
qDebug() << "Trying to connect to network device with MAC:"
<< device.macAddress << "IP:" << device.address;
// force refresh macAddress-pairing file mapping
cachePairedDevices();
if (forceCache) {
cachePairedDevices();
}
const iDescriptorDevice *existingDevice = nullptr;
existingDevice = getDeviceByMacAddress(device.macAddress);
@@ -365,8 +375,10 @@ void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device)
emit deviceAlreadyExistsMAC(
iDescriptor::Uniq(existingDevice->deviceInfo.wifiMacAddress, true));
// TODO: add a setting for this
setCurrentDeviceSelection(DeviceSelection(existingDevice->udid), true);
if (setSelectionIfExists) {
setCurrentDeviceSelection(DeviceSelection(existingDevice->udid),
true);
}
return;
}
+3 -1
View File
@@ -45,7 +45,9 @@ public:
const QString getCachedPairingFile(const QString &udid) const;
CXX::Core *core = new CXX::Core(this);
CXX::IOManager *ioManager = new CXX::IOManager(this);
void tryToConnectToNetworkDevice(const NetworkDevice &device);
void tryToConnectToNetworkDevice(const NetworkDevice &device,
bool forceCache = true,
bool setSelectionIfExists = true);
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
QList<iDescriptorRecoveryDevice *> getAllRecoveryDevices();
#endif
+6 -6
View File
@@ -70,12 +70,12 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
connect(AppContext::sharedInstance(), &AppContext::devicePaired, this,
[this](const std::shared_ptr<iDescriptorDevice> device) {
addPairedDevice(device);
// SettingsManager::sharedInstance()->doIfEnabled(
// SettingsManager::Setting::SwitchToNewDevice,
// [this, device]() {
// AppContext::sharedInstance()->setCurrentDeviceSelection(
// DeviceSelection(device->udid));
// });
SettingsManager::sharedInstance()->doIfEnabled(
SettingsManager::Setting::SwitchToNewDevice,
[this, device]() {
AppContext::sharedInstance()->setCurrentDeviceSelection(
DeviceSelection(device->udid));
});
updateUI();
});
+76
View File
@@ -0,0 +1,76 @@
#include "devicesleepwarningwidget.h"
DeviceSleepWarningWidget::DeviceSleepWarningWidget(QWidget *parent)
: QDialog{parent}
{
#ifdef WIN32
setupWinWindow(this);
#endif
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 20, 0, 0);
mainLayout->setSpacing(0);
m_loadingWidget = new ZLoadingWidget(false, this);
mainLayout->addWidget(m_loadingWidget);
QWidget *contentContainer = new QWidget(this);
QVBoxLayout *contentLayout = new QVBoxLayout(contentContainer);
contentLayout->setContentsMargins(0, 0, 0, 0);
contentLayout->setSpacing(0);
m_loadingWidget->setupContentWidget(contentContainer);
m_mediaPlayer = new QMediaPlayer(this);
m_videoWidget = new QVideoWidget(this);
m_videoWidget->setMinimumSize(300, 500);
m_videoWidget->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
m_videoWidget->setAspectRatioMode(Qt::KeepAspectRatio);
m_mediaPlayer->setVideoOutput(m_videoWidget);
connect(m_mediaPlayer,
QOverload<QMediaPlayer::MediaStatus>::of(
&QMediaPlayer::mediaStatusChanged),
[this](QMediaPlayer::MediaStatus status) {
if (status == QMediaPlayer::EndOfMedia) {
m_mediaPlayer->setPosition(0);
m_mediaPlayer->play();
}
});
QLabel *title = new QLabel("Keep Your Device Awake", this);
title->setAlignment(Qt::AlignCenter);
title->setStyleSheet("QLabel { font-size: 18px; font-weight: bold; }");
QLabel *description = new QLabel(
R"(Please keep your device awake or unlocked while connected wirelessly to avoid disconnection)",
this);
description->setAlignment(Qt::AlignCenter);
description->setWordWrap(true);
QVBoxLayout *textLayout = new QVBoxLayout();
textLayout->setContentsMargins(20, 20, 20, 20);
textLayout->setSpacing(10);
contentLayout->addWidget(title);
textLayout->addWidget(description);
contentLayout->addLayout(textLayout);
contentLayout->addWidget(m_videoWidget);
contentLayout->addSpacing(20);
QCheckBox *dontShowAgain = new QCheckBox("Don't show this again", this);
connect(dontShowAgain, &QCheckBox::toggled, this, [this](bool checked) {
SettingsManager::sharedInstance()->setIsSleepyDeviceWarningDismissed(
checked);
});
contentLayout->addWidget(dontShowAgain, 0, Qt::AlignCenter);
m_mediaPlayer->setSource(QUrl("qrc:/resources/unlock.mp4"));
QTimer::singleShot(500, this, &DeviceSleepWarningWidget::init);
}
void DeviceSleepWarningWidget::init()
{
m_mediaPlayer->play();
m_loadingWidget->stop();
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef DEVICESLEEPWARNINGWIDGET_H
#define DEVICESLEEPWARNINGWIDGET_H
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "zloadingwidget.h"
#include <QCheckBox>
#include <QDialog>
#include <QMediaPlayer>
#include <QTimer>
#include <QVBoxLayout>
#include <QVideoWidget>
#include <QWidget>
class DeviceSleepWarningWidget : public QDialog
{
Q_OBJECT
public:
explicit DeviceSleepWarningWidget(QWidget *parent = nullptr);
private:
ZLoadingWidget *m_loadingWidget;
QMediaPlayer *m_mediaPlayer;
QVideoWidget *m_videoWidget;
void init();
};
#endif // DEVICESLEEPWARNINGWIDGET_H
+30 -12
View File
@@ -26,23 +26,13 @@
#include "releasechangelogdialog.h"
#include "toolboxwidget.h"
#include "welcomewidget.h"
#include <QHBoxLayout>
#include <QStack>
#include <QStackedWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
#include <unistd.h>
#include "appcontext.h"
#include "networkdeviceswidget.h"
#include "settingsmanager.h"
#include "statusballoon.h"
#include <QApplication>
#include <QDesktopServices>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#ifdef WIN32
#include "platform/windows/win_common.h"
#endif
@@ -129,11 +119,16 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
auto mainLayout = new QVBoxLayout(centralWidget);
mainLayout->setContentsMargins(0, 0, 0, 0);
connect(AppContext::sharedInstance()->core, &CXX::Core::device_event, this,
auto core = AppContext::sharedInstance()->core;
connect(core, &CXX::Core::device_event, this,
[this](uint32_t code, const QString &udid, const QString &info) {
handleCallback(code, udid, info);
});
connect(core, &CXX::Core::sleepy_time_detected, this,
&MainWindow::handleShowSleepyDeviceWarning);
AppContext::sharedInstance()->core->init();
m_ZTabWidget = new ZTabWidget(this);
@@ -491,4 +486,27 @@ MainWindow::~MainWindow()
#endif
delete m_updater;
// sleep(2);
}
void MainWindow::handleShowSleepyDeviceWarning()
{
/* one minute cooldown to prevent spamming */
static const int kMinIntervalMs = 60 * 1000;
static QDateTime lastShown;
const auto now = QDateTime::currentDateTimeUtc();
if (lastShown.isValid() && lastShown.msecsTo(now) < kMinIntervalMs) {
qDebug() << "Ignoring sleepy device warning dialog.";
return;
}
lastShown = now;
if (SettingsManager::sharedInstance()->isSleepyDeviceWarningDismissed()) {
return;
}
DeviceSleepWarningWidget(this).exec();
}
+13
View File
@@ -21,13 +21,25 @@
#define MAINWINDOW_H
#include "ZDownloader.h"
#include "ZUpdater.h"
#include "devicesleepwarningwidget.h"
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "iomanagerclient.h"
#include "ztabwidget.h"
#include <QApplication>
#include <QDateTime>
#include <QDesktopServices>
#include <QHBoxLayout>
#include <QLabel>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QStack>
#include <QStackedWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
class DeviceManagerWidget;
@@ -43,6 +55,7 @@ public:
void raiseDeviceTab();
void showConnectedDevicesTab();
void showWelcomeTab();
void handleShowSleepyDeviceWarning();
public slots:
void updateNoDevicesConnected();
+23 -2
View File
@@ -30,7 +30,7 @@
NetworkDeviceCard::NetworkDeviceCard(const NetworkDevice &device,
QWidget *parent)
: QWidget(parent)
: QWidget(parent), m_device(device)
{
QVBoxLayout *cardLayout = new QVBoxLayout(this);
cardLayout->setContentsMargins(12, 10, 12, 10);
@@ -182,6 +182,12 @@ NetworkDevicesToConnectWidget::NetworkDevicesToConnectWidget(QWidget *parent)
&NetworkDevicesToConnectWidget::onDeviceAdded);
connect(AppContext::sharedInstance(), &AppContext::deviceAlreadyExistsMAC,
this, &NetworkDevicesToConnectWidget::onDeviceAlreadyExists);
// eval interval
QTimer *evalTimer = new QTimer(this);
connect(evalTimer, &QTimer::timeout, this,
&NetworkDevicesToConnectWidget::eval);
evalTimer->start(30000); // evaluate every 30 seconds
}
NetworkDevicesToConnectWidget::~NetworkDevicesToConnectWidget() {}
@@ -313,6 +319,21 @@ void NetworkDevicesToConnectWidget::onWirelessDeviceRemoved(
}
}
void NetworkDevicesToConnectWidget::eval()
{
if (QCoreApplication::closingDown())
return;
bool forceCache = true;
for (const auto &card : m_deviceCards) {
if (card) {
NetworkDevice device = card->getDevice();
AppContext::sharedInstance()->tryToConnectToNetworkDevice(
device, forceCache, false);
forceCache = false; // force cache once
}
}
}
void NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice(
const QString &macAddress)
{
@@ -367,4 +388,4 @@ void NetworkDevicesToConnectWidget::onDeviceAlreadyExists(
return;
}
qDebug() << "No device card found for" << uniq.get();
}
}
+3 -1
View File
@@ -39,6 +39,7 @@ public:
private:
QPushButton *m_connectButton = nullptr;
NetworkDevice m_device;
public:
void failed();
@@ -46,6 +47,7 @@ public:
void initStarted();
void connected();
void alreadyExists();
NetworkDevice getDevice() { return m_device; };
};
class NetworkDevicesToConnectWidget : public QWidget
@@ -70,7 +72,7 @@ private:
void createDeviceCard(const NetworkDevice &device);
void clearDeviceCards();
void updateDeviceList();
void eval();
QGroupBox *m_deviceGroup = nullptr;
QScrollArea *m_scrollArea = nullptr;
QWidget *m_scrollContent = nullptr;
+4 -1
View File
@@ -306,7 +306,10 @@ impl qobject::HauseArrest {
true
});
if !inserted {
eprintln!("start_video_stream: failed to insert video stream for udid={} path={}", udid_str, cloned_path);
eprintln!(
"start_video_stream: failed to insert video stream for udid={} path={}",
udid_str, cloned_path
);
return QString::default();
}
eprintln!(
+31 -7
View File
@@ -1,13 +1,12 @@
use futures::StreamExt;
use idevice::{
IdeviceService,
IdeviceError, IdeviceService,
afc::AfcClient,
diagnostics_relay::DiagnosticsRelayClient,
heartbeat,
lockdown::LockdownClient,
pairing_file::PairingFile,
provider::{TcpProvider},
IdeviceError,
provider::TcpProvider,
usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdConnection, UsbmuxdListenEvent},
};
use std::{any::type_name, sync::Arc};
@@ -29,8 +28,8 @@ use crate::qobject::Core;
use once_cell::sync::Lazy;
use plist::Value;
mod afc;
mod afc_services;
mod afc2_services;
mod afc_services;
mod hause_arrest;
mod io_manager;
mod screenshot;
@@ -55,7 +54,6 @@ pub struct DeviceServices {
pub lockdown: Arc<Mutex<LockdownClient>>,
}
pub static APP_DEVICE_STATE: Lazy<Mutex<HashMap<String, DeviceServices>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
@@ -122,6 +120,9 @@ mod qobject {
#[qsignal]
fn no_pairing_file(self: Pin<&mut Core>, mac_address: &QString);
#[qsignal]
fn sleepy_time_detected(self: Pin<&mut Core>);
}
impl cxx_qt::Threading for Core {}
}
@@ -638,7 +639,7 @@ async fn init_idescriptor_device<
return None;
}
let mut hb = None;
if is_wireless {
eprintln!("init_idescriptor_device: Attempting to connect to HeartbeatClient.");
hb = match heartbeat::HeartbeatClient::connect(&provider).await {
@@ -735,7 +736,6 @@ async fn init_idescriptor_device<
);
}
eprintln!("init_idescriptor_device: Storing device services.");
let device_services = DeviceServices {
afc: Arc::new(Mutex::new(afc_client)),
@@ -810,6 +810,19 @@ async fn spawn_heartbeat_task(
Err(e) => {
fails += 1;
eprintln!("heartbeat: get_marco failed (fail count: {fails}): {e:?}");
match e {
IdeviceError::Heartbeat(idevice::HeartbeatError::SleepyTime) => {
println!("heartbeat: Sleepy time");
qt_thread
.queue(move |core_qobj| {
core_qobj.sleepy_time_detected();
})
.ok();
}
_ => {}
};
if fails >= 3 {
eprintln!("heartbeat: too many failures for giving up");
clean_device_from_app_state(&udid_for_hb).await;
@@ -832,6 +845,17 @@ async fn spawn_heartbeat_task(
if let Err(e) = hb_client.send_polo().await {
fails += 1;
eprintln!("heartbeat: send_polo failed (fail count: {fails}): {e:?}");
match e {
IdeviceError::Heartbeat(idevice::HeartbeatError::SleepyTime) => {
println!("heartbeat: Sleepy time");
qt_thread
.queue(move |core_qobj| {
core_qobj.sleepy_time_detected();
})
.ok();
}
_ => {}
};
if fails >= 3 {
eprintln!("heartbeat: too many failures for , giving up");
clean_device_from_app_state(&udid_for_hb).await;
+62 -60
View File
@@ -75,7 +75,7 @@ mod qobject {
);
#[qsignal]
fn dev_image_mounted(self: Pin<&mut ServiceManager>, success: bool,is_locked: bool);
fn dev_image_mounted(self: Pin<&mut ServiceManager>, success: bool, is_locked: bool);
#[qsignal]
fn developer_mode_option_revealed(self: Pin<&mut ServiceManager>, success: bool);
@@ -131,7 +131,6 @@ mod qobject {
#[qsignal]
fn install_ipa_progress(self: Pin<&mut ServiceManager>, progress: f64, state: QString);
#[qinvokable]
fn enable_wifi_connections(&self);
@@ -908,11 +907,7 @@ impl qobject::ServiceManager {
let udid = self.get_udid().to_string();
run_sync(async move {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
@@ -924,7 +919,7 @@ impl qobject::ServiceManager {
if let Err(e) = device.diag.lock().await.restart().await {
eprintln!("restart: Failed to restart device {udid}: {e}");
return false
return false;
}
return true;
})
@@ -934,11 +929,7 @@ impl qobject::ServiceManager {
let udid = self.get_udid().to_string();
run_sync(async move {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
@@ -950,7 +941,7 @@ impl qobject::ServiceManager {
if let Err(e) = device.diag.lock().await.shutdown().await {
eprintln!("shutdown: Failed to shutdown device {udid}: {e}");
return false
return false;
}
return true;
})
@@ -959,11 +950,7 @@ impl qobject::ServiceManager {
let udid = self.get_udid().to_string();
run_sync(async move {
let maybe_device = APP_DEVICE_STATE
.lock()
.await
.get(udid.as_str())
.cloned();
let maybe_device = APP_DEVICE_STATE.lock().await.get(udid.as_str()).cloned();
let device = match maybe_device {
Some(d) => d,
@@ -974,8 +961,10 @@ impl qobject::ServiceManager {
};
if let Err(e) = device.lockdown.lock().await.enter_recovery().await {
eprintln!("enter_recovery_mode: Failed to enter recovery mode for device {udid}: {e}");
return false
eprintln!(
"enter_recovery_mode: Failed to enter recovery mode for device {udid}: {e}"
);
return false;
}
return true;
})
@@ -985,8 +974,15 @@ impl qobject::ServiceManager {
let udid = self.get_udid().to_string();
let qt_t = self.qt_thread();
let local_ipa_path_owned = local_ipa_path.clone().to_string();
// FIXME: this is a bit hacky
let ipa_path_on_device = format!("/PublicStaging/{}", local_ipa_path.to_string().split('/').last().unwrap_or("app.ipa"));
// FIXME: this is a bit hacky
let ipa_path_on_device = format!(
"/PublicStaging/{}",
local_ipa_path
.to_string()
.split('/')
.last()
.unwrap_or("app.ipa")
);
RUNTIME.spawn(async move {
let qt_thread = qt_t.clone();
@@ -1145,24 +1141,21 @@ impl qobject::ServiceManager {
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 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();
let _ = qt_thread
.queue(|t| {
t.enable_wifi_connections_result(false);
})
.ok();
return;
}
};
@@ -1173,22 +1166,31 @@ impl qobject::ServiceManager {
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 {
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();
},
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();
let _ = qt_thread
.queue(|t| {
t.enable_wifi_connections_result(false);
})
.ok();
}
}
});
}
}
@@ -1202,21 +1204,21 @@ async fn set_device_location_lockdown(
}
// iOS 17+:
async fn set_device_location_rsd(
proxy: CoreDeviceProxy,
latitude: f64,
longitude: f64,
) -> Result<(), idevice::IdeviceError> {
async fn set_device_location_rsd(
proxy: CoreDeviceProxy,
latitude: f64,
longitude: f64,
) -> Result<(), idevice::IdeviceError> {
let rsd_port = proxy.tunnel_info().server_rsd_port;
let adapter = proxy.create_software_tunnel()?;
let mut adapter = adapter.to_async_handle();
let stream = adapter.connect(rsd_port).await?;
let mut handshake = RsdHandshake::new(stream).await?;
let mut remote_server = RemoteServerClient::connect_rsd(&mut adapter, &mut handshake).await?;
remote_server.read_message(0).await?;
let mut location_client = LocationSimulationClient::new(&mut remote_server).await?;
location_client.set(latitude, longitude).await
}
let adapter = proxy.create_software_tunnel()?;
let mut adapter = adapter.to_async_handle();
let stream = adapter.connect(rsd_port).await?;
let mut handshake = RsdHandshake::new(stream).await?;
let mut remote_server = RemoteServerClient::connect_rsd(&mut adapter, &mut handshake).await?;
remote_server.read_message(0).await?;
let mut location_client = LocationSimulationClient::new(&mut remote_server).await?;
location_client.set(latitude, longitude).await
}
+19 -1
View File
@@ -265,6 +265,7 @@ void SettingsManager::resetToDefaults()
#ifdef __linux__
setShowV4L2(false);
#endif
setIsSleepyDeviceWarningDismissed(false);
}
void SettingsManager::saveFavoritePlace(const QString &path,
@@ -502,4 +503,21 @@ WIN_BACKDROP SettingsManager::winBackdropType() const
return static_cast<WIN_BACKDROP>(
m_settings->value("winBackdropType", static_cast<int>(MICA)).toInt());
}
#endif
#endif
bool SettingsManager::isSleepyDeviceWarningDismissed() const
{
return m_settings->value("sleepyDeviceWarningDismissed", false).toBool();
}
void SettingsManager::dismissSleepyDeviceWarning()
{
m_settings->setValue("sleepyDeviceWarningDismissed", true);
m_settings->sync();
}
void SettingsManager::setIsSleepyDeviceWarningDismissed(bool dismissed)
{
m_settings->setValue("sleepyDeviceWarningDismissed", dismissed);
m_settings->sync();
}
+5
View File
@@ -138,6 +138,11 @@ public:
void setWinBackdropType(WIN_BACKDROP type);
WIN_BACKDROP winBackdropType() const;
#endif
bool isSleepyDeviceWarningDismissed() const;
void setIsSleepyDeviceWarningDismissed(bool dismissed);
void dismissSleepyDeviceWarning();
signals:
void favoritePlacesChanged();
void recentLocationsChanged();