From f4ffbcaf5414789242e2d9dec63393e63a2b73b5 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Wed, 25 Mar 2026 10:52:24 +0000 Subject: [PATCH] Refactor device event handling and improve icon loading in InstalledAppsWidget - Updated DeviceInfoWidget to include necessary headers for UI components. - Modified DiskUsageWidget to enable debug mode for gallery usage. - Enhanced iDescriptor.h by adding new AddType for pairing failures. - Replaced QLabel with IDLoadingIconLabel in InstalledAppsWidget for better icon handling. - Implemented asynchronous icon loading in InstalledAppsWidget using a queue. - Improved event handling in main window to differentiate between connection states. - Added alreadyExists method in NetworkDeviceCard to handle already connected devices. - Updated Rust dependencies and added new header for device event subscription. - Refactored Rust code to improve device pairing logic and event callback handling. --- src/appcontext.cpp | 254 ++++--------- src/appcontext.h | 10 +- src/core/services/init_device.cpp | 12 +- src/devdiskimagehelper.cpp | 98 ++--- src/devdiskimagehelper.h | 5 +- src/deviceinfowidget.cpp | 79 ++-- src/deviceinfowidget.h | 15 + src/diskusagewidget.cpp | 9 +- src/iDescriptor.h | 10 +- src/installedappswidget.cpp | 169 ++++++--- src/installedappswidget.h | 19 +- src/livescreenwidget.cpp | 2 - src/mainwindow.cpp | 53 ++- src/mainwindow.h | 2 + src/networkdevicestoconnectwidget.cpp | 16 +- src/networkdevicestoconnectwidget.h | 1 + src/rust/Cargo.lock | 347 +++++++++++++++++- src/rust/Cargo.toml | 1 + .../{idecriptor_rust.h => idescriptor_rust.h} | 3 +- src/rust/src/lib.rs | 136 ++++++- 20 files changed, 843 insertions(+), 398 deletions(-) rename src/rust/include/{idecriptor_rust.h => idescriptor_rust.h} (70%) diff --git a/src/appcontext.cpp b/src/appcontext.cpp index d16cbd5..3d5ea4f 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -73,12 +73,7 @@ void AppContext::cachePairedDevices() if (!isCompatible) { continue; } - qDebug() << "Found pairing file for MAC" - << QString::fromStdString(wifiMacAddress); - qDebug() << "Caching pairing file for MAC" - << QString::fromStdString(wifiMacAddress) << "Local Path" - << lockdowndir.filePath(fileName); m_pairingFileCache[QString::fromStdString(wifiMacAddress)] = lockdowndir.filePath(fileName); } @@ -187,8 +182,28 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, } emit initStarted(uniq); - if (auto device = getDevice(uniq)) { + const iDescriptorDevice *existingDevice = nullptr; + existingDevice = getDeviceByMacAddress(uniq.get()); + if (!existingDevice) { + existingDevice = getDevice(uniq.get().toStdString()); + } + + if (existingDevice) { emit deviceAlreadyExists(uniq); + // TODO: add a setting for this + + setCurrentDeviceSelection(DeviceSelection(existingDevice->udid), true); + return; + } + + if (addType == AddType::Pairing) { + handlePairing(uniq, true); + return; + } + + if (addType == AddType::FailedToPair) { + // FIXME: no widget is listening for this signal for now + emit pairingFailed(uniq); return; } @@ -199,6 +214,8 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, addType, wifiMacAddress, ipAddress, initResult]() { if (addType == AddType::UpgradeToWireless) { + // FIXME: wireless pairing is supported iOS 14+ + // we can ignore devices that don't support it, qDebug() << "AddType::UpgradeToWireless"; const QString _pairingFilePath = getCachedPairingFile(uniq); @@ -243,12 +260,29 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, watcher->deleteLater(); qDebug() << "init_idescriptor_device success ?: " << initResult->success; - if (!initResult->success) { - return handlePairing(initResult.get(), addType, uniq); + emit initFailed(uniq); + if (initResult->error) { + // Handle if it's a pairing related error + const auto code = initResult->error->code; + if (code == PairingDialogResponsePending || + code == InvalidHostID || + code == PasswordProtected) { + handlePairing(uniq, true); + } + return; + } + // FIXME: warn + return; } qDebug() << "Device initialized: " << uniq; + if (m_pendingDevices.contains(uniq)) { + qDebug() << "Removing from pending devices: " << uniq; + m_pendingDevices.removeAll(uniq); + emit devicePairingExpired(uniq); + } + iDescriptorDevice *device = new iDescriptorDevice{ .udid = initResult->deviceInfo.UniqueDeviceID, .conn_type = conn_type, @@ -447,17 +481,25 @@ AppContext::~AppContext() #endif } -void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) +void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection, + bool showConnectedDevices) { if (m_currentSelection.type == selection.type && m_currentSelection.udid == selection.udid && m_currentSelection.ecid == selection.ecid && m_currentSelection.section == selection.section) { qDebug() << "setCurrentDeviceSelection: No change in selection"; + if (showConnectedDevices) { + MainWindow::sharedInstance()->showConnectedDevicesTab(); + } return; // No change } m_currentSelection = selection; emit currentDeviceSelectionChanged(m_currentSelection); + + if (showConnectedDevices) { + MainWindow::sharedInstance()->showConnectedDevicesTab(); + } } const DeviceSelection &AppContext::getCurrentDeviceSelection() const @@ -499,7 +541,7 @@ void AppContext::heartbeatFailed(const QString &macAddress, int tries) void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device) { - // force refresh macAddress-udid mapping + // force refresh macAddress-pairing file mapping cachePairedDevices(); QMetaObject::invokeMethod( @@ -536,185 +578,23 @@ void AppContext::freeDevice(iDescriptorDevice *device) device = nullptr; } -void AppContext::handlePairing(iDescriptorInitDeviceResult *initResult, - AddType addType, iDescriptor::Uniq uniq) +void AppContext::handlePairing(iDescriptor::Uniq uniq, bool timeout) { - qDebug() << "[handlePairing] for device" << uniq; - emit initFailed(uniq); - - if (!initResult || !initResult->error) { - qDebug() << "[handlePairing] initResult->error is null for" << uniq - << "- skipping pairing handling"; - return; - } - - const auto code = initResult->error->code; - if (code == PairingDialogResponsePending || code == InvalidHostID || - code == PasswordProtected) { - if (addType == AddType::Regular) { - m_pendingDevices.append(uniq); - emit devicePasswordProtected(uniq); - emit deviceChange(); - QTimer::singleShot( - SettingsManager::sharedInstance()->connectionTimeout() * 1000, - this, [this, uniq]() { - if (m_pendingDevices.contains(uniq)) { - qDebug() << "Pairing expired for " - "device UDID:" - << uniq; - m_pendingDevices.removeAll(uniq); - emit devicePairingExpired(uniq); - emit deviceChange(); - } - }); - // FIXME: free properly and move to a better place - QThreadPool::globalInstance()->start([uniq, this]() { - UsbmuxdConnectionHandle *usbmuxd_conn = nullptr; - UsbmuxdAddrHandle *addr_handle = nullptr; - IdeviceProviderHandle *provider = nullptr; - LockdowndClientHandle *lockdown = nullptr; - IdevicePairingFile *pairing_file = nullptr; - - IdeviceFfiError *err = - idevice_usbmuxd_new_default_connection(0, &usbmuxd_conn); - if (err) { - // if (!isWireless) { - qDebug() << "Failed to connect to usbmuxd"; - // goto cleanup; - return; - // } - } - - err = idevice_usbmuxd_default_addr_new(&addr_handle); - if (err) { - qDebug() << "Failed to create address handle"; - // goto cleanup; - return; - } - - UsbmuxdDeviceHandle **devices; - int device_count; - int actual_device_id = -1; - err = idevice_usbmuxd_get_devices(usbmuxd_conn, &devices, - &device_count); - - for (size_t i = 0; i < device_count; i++) { - const char *device_udid = - idevice_usbmuxd_device_get_udid(devices[i]); - if (strcmp(device_udid, uniq.get().toUtf8().constData()) == - 0) { - actual_device_id = - idevice_usbmuxd_device_get_device_id(devices[i]); - break; - } - } - - err = usbmuxd_provider_new( - addr_handle, 0, uniq.get().toUtf8().constData(), - actual_device_id, APP_LABEL, &provider); - - err = lockdownd_connect(provider, &lockdown); - if (err) { - qDebug() << "Failed to connect to lockdown"; - return; - } - - QString hostId = QUuid::createUuid() - .toString() - .remove("{") - .remove("}") - .toUpper(); - char *buid = nullptr; - idevice_usbmuxd_get_buid(usbmuxd_conn, &buid); - - bool ok = false; - while (true) { - // if (const auto dev = - // AppContext::sharedInstance() - // ->getDevice( - // uniq.get().toStdString())) - // { - - if (ok) { - qDebug() << "Successfully paired with " - "device, "; - break; - }; - err = lockdownd_pair(lockdown, hostId.toStdString().c_str(), - buid, nullptr, &pairing_file); - if (err) { - qDebug() - << "Failed to pair with device" << err->message; - - std::this_thread::sleep_for(std::chrono::seconds(5)); - // return; - // goto cleanup; - } else { - - qDebug() << "There was no error, pairing " - "successful"; - uint8_t *data = nullptr; - size_t size = 0; - - // Serialize pairing file to bytes - IdeviceFfiError *err = idevice_pairing_file_serialize( - pairing_file, &data, &size); - if (err) { - qDebug() << "Failed to serialize " - "pairing file:" - << err->message; - // idevice_error_free(err); - // goto cleanup; - } - - err = idevice_usbmuxd_save_pair_record( - usbmuxd_conn, uniq.get().toUtf8().constData(), data, - size); - - QMetaObject::invokeMethod( - AppContext::sharedInstance(), "addDevice", - Qt::QueuedConnection, - Q_ARG(iDescriptor::Uniq, uniq), - Q_ARG(iDescriptor::IdeviceConnectionType, - iDescriptor::IdeviceConnectionType:: - CONNECTION_NETWORK), - Q_ARG(AddType, AddType::Regular)); - ok = true; - } + m_pendingDevices.append(uniq); + emit devicePasswordProtected(uniq); + emit deviceChange(); + if (timeout) { + QTimer::singleShot( + SettingsManager::sharedInstance()->connectionTimeout() * 1000, this, + [this, uniq]() { + if (m_pendingDevices.contains(uniq)) { + qDebug() << "Pairing expired for " + "device UDID:" + << uniq; + m_pendingDevices.removeAll(uniq); + emit devicePairingExpired(uniq); + emit deviceChange(); } }); - } } - - // else if (initResult.error == - // LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING - // || - // initResult.error == - // LOCKDOWN_E_INVALID_HOST_ID) { - // m_pendingDevices.append(udid); - // emit devicePairPending(udid); - // emit deviceChange(); - // QTimer::singleShot( - // SettingsManager::sharedInstance()->connectionTimeout() - // * - // 1000, - // this, [this, udid]() { - // qDebug() - // << "Pairing timer fired for device - // UDID: " << udid; - // if (m_pendingDevices.contains(udid)) { - // qDebug() - // << "Pairing expired for device - // UDID: " << udid; - // m_pendingDevices.removeAll(udid); - // emit devicePairingExpired(udid); - // emit deviceChange(); - // } - // }); - // } else { - // qDebug() << "Unhandled error for device UDID: " - // << udid - // << " Error code: " << initResult.error; - // } - return; } \ No newline at end of file diff --git a/src/appcontext.h b/src/appcontext.h index 3656c74..56d5b11 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -52,7 +52,8 @@ public: ~AppContext(); int getConnectedDeviceCount() const; - void setCurrentDeviceSelection(const DeviceSelection &selection); + void setCurrentDeviceSelection(const DeviceSelection &selection, + bool showConnectedDevices = false); const DeviceSelection &getCurrentDeviceSelection() const; const iDescriptorDevice * getDeviceByMacAddress(const QString &macAddress) const; @@ -68,8 +69,10 @@ private: void cachePairedDevices(); void emitNoPairingFileForWirelessDevice(const QString &udid); void freeDevice(iDescriptorDevice *device); - void handlePairing(iDescriptorInitDeviceResult *initResult, AddType addType, - iDescriptor::Uniq uniq); + void handlePairing(iDescriptor::Uniq uniq, bool timeout); + // void handlePairing(iDescriptorInitDeviceResult *initResult, AddType + // addType, + // iDescriptor::Uniq uniq); signals: void deviceAdded(const iDescriptorDevice *device); void deviceRemoved(const std::string &udid, const std::string &macAddress, @@ -87,6 +90,7 @@ signals: void noPairingFileForWirelessDevice(const QString &macAddress); void initFailed(const QString &udid); void initStarted(const QString &udid); + void pairingFailed(const QString &udid); void systemSleepStarting(); void systemWakeup(); diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index a1c8a1d..787a47d 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -65,7 +65,6 @@ std::string safeGetXML(const char *key, pugi::xml_node dict) return ""; } -// this is reused in the ui in deviceinfowidget void parseOldDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d) { d.batteryInfo.isCharging = ioreg["IsCharging"].getBool(); @@ -317,8 +316,6 @@ void fullDeviceInfo(const pugi::xml_document &doc, AfcClientHandle *afcClient, d.oldDevice = !ioreg["BatteryData"]; if (d.oldDevice) { parseOldDevice(ioreg, d); - plist_free(diagnostics); - diagnostics = nullptr; return; } @@ -354,8 +351,6 @@ void fullDeviceInfo(const pugi::xml_document &doc, AfcClientHandle *afcClient, : "Error retrieving serial number"; qDebug() << "Cycle count: " << cycleCount; parseDeviceBattery(ioreg, d); - plist_free(diagnostics); - diagnostics = nullptr; return; } catch (const std::exception &e) { @@ -485,9 +480,8 @@ void init_idescriptor_device(const iDescriptor::Uniq &uniq, err = idevice_provider_get_pairing_file(provider, &pairing_file); if (err) { - idevice_error_free(err); - err = new IdeviceFfiError{.code = PairingDialogResponsePending, - .message = "Pairing dialog response pending"}; + // TODO: maybe unnecessary + err->code = PairingDialogResponsePending; qDebug() << "Waiting for user to respond to pairing dialog on device"; goto cleanup; @@ -570,6 +564,8 @@ cleanup: afc_client_free(afc2_client); if (afc_client) afc_client_free(afc_client); + if (diagnostics_relay) + diagnostics_relay_client_free(diagnostics_relay); if (lockdown) lockdownd_client_free(lockdown); if (addr_handle) diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index 57326d0..ac0233f 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -20,9 +20,9 @@ #include "devdiskimagehelper.h" #include "appcontext.h" #include "devdiskmanager.h" -#include "qprocessindicator.h" #include "servicemanager.h" #include "settingsmanager.h" +#include "zloadingwidget.h" #include #include #include @@ -52,38 +52,32 @@ void DevDiskImageHelper::setupUI() mainLayout->setContentsMargins(20, 20, 20, 20); mainLayout->setSpacing(15); - // Loading indicator - auto *indicatorLayout = new QHBoxLayout(); - indicatorLayout->addStretch(); - m_loadingIndicator = new QProcessIndicator(); - m_loadingIndicator->setType(QProcessIndicator::line_rotate); - m_loadingIndicator->setFixedSize(64, 32); - indicatorLayout->addWidget(m_loadingIndicator); - indicatorLayout->addStretch(); - mainLayout->addLayout(indicatorLayout); + // ZLoadingWidget handles the spinner + state switching + m_loadingWidget = new ZLoadingWidget(true, this); + mainLayout->addWidget(m_loadingWidget); - // Status label - m_statusLabel = new QLabel("Checking developer disk image..."); + // Custom error layout: message + Retry + auto *errorLayout = new QHBoxLayout(); + errorLayout->addStretch(); + + m_statusLabel = new QLabel("An error occurred."); m_statusLabel->setWordWrap(true); m_statusLabel->setAlignment(Qt::AlignCenter); - mainLayout->addWidget(m_statusLabel); - - // Button layout - auto *buttonLayout = new QHBoxLayout(); - buttonLayout->addStretch(); - - m_mountButton = new QPushButton("Mount"); - m_mountButton->setDefault(true); - m_mountButton->setVisible(false); - connect(m_mountButton, &QPushButton::clicked, this, - &DevDiskImageHelper::onMountButtonClicked); - buttonLayout->addWidget(m_mountButton); + errorLayout->addWidget(m_statusLabel); m_retryButton = new QPushButton("Retry"); - m_retryButton->setVisible(false); connect(m_retryButton, &QPushButton::clicked, this, &DevDiskImageHelper::onRetryButtonClicked); - buttonLayout->addWidget(m_retryButton); + errorLayout->addWidget(m_retryButton); + + errorLayout->addStretch(); + + // Register custom error layout with ZLoadingWidget + m_loadingWidget->setupErrorWidget(errorLayout); + + // Bottom button row (Cancel / Close) + auto *buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(); m_cancelButton = new QPushButton("Cancel"); connect(m_cancelButton, &QPushButton::clicked, this, &QDialog::reject); @@ -98,7 +92,12 @@ void DevDiskImageHelper::setupUI() void DevDiskImageHelper::start() { - m_loadingIndicator->start(); + if (m_cancelButton) { + m_cancelButton->setText("Cancel"); + } + if (m_loadingWidget) { + m_loadingWidget->showLoading(); + } showStatus("Please wait..."); unsigned int deviceMajorVersion = @@ -151,8 +150,10 @@ void DevDiskImageHelper::checkAndMount() void DevDiskImageHelper::onMountButtonClicked() { QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); - m_mountButton->setVisible(false); - m_loadingIndicator->start(); + + if (m_loadingWidget) { + m_loadingWidget->showLoading(); + } // Check if we need to download first unsigned int deviceMajorVersion = @@ -250,23 +251,37 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version, void DevDiskImageHelper::showRetryUI(const QString &errorMessage) { - m_loadingIndicator->stop(); - showStatus(errorMessage, true); - m_mountButton->setVisible(false); - m_retryButton->setVisible(true); - m_cancelButton->setText("Close"); + if (m_statusLabel) { + m_statusLabel->setText(errorMessage); + } + if (m_loadingWidget) { + m_loadingWidget->showError(); + } + if (m_cancelButton) { + m_cancelButton->setText("Close"); + } } void DevDiskImageHelper::onRetryButtonClicked() { - m_retryButton->setVisible(false); - m_cancelButton->setText("Cancel"); - start(); + if (m_cancelButton) { + m_cancelButton->setText("Cancel"); + } + if (m_loadingWidget) { + m_loadingWidget->showLoading(); + } + QTimer::singleShot(200, this, &DevDiskImageHelper::start); } void DevDiskImageHelper::showStatus(const QString &message, bool isError) { - m_statusLabel->setText(message); + if (isError) { + showRetryUI(message); + } else { + if (m_statusLabel) { + m_statusLabel->setText(message); + } + } show(); } @@ -279,7 +294,9 @@ void DevDiskImageHelper::showStatus(const QString &message, bool isError) void DevDiskImageHelper::finishWithSuccess(bool wait) { auto handler = [this]() { - m_loadingIndicator->stop(); + if (m_loadingWidget) { + m_loadingWidget->stop(false); + } accept(); }; if (wait) { @@ -290,6 +307,5 @@ void DevDiskImageHelper::finishWithSuccess(bool wait) void DevDiskImageHelper::finishWithError(const QString &errorMessage) { - m_loadingIndicator->stop(); - showStatus(errorMessage, true); + showRetryUI(errorMessage); } diff --git a/src/devdiskimagehelper.h b/src/devdiskimagehelper.h index a6e425f..9b7c20f 100644 --- a/src/devdiskimagehelper.h +++ b/src/devdiskimagehelper.h @@ -27,7 +27,7 @@ #include #include -class QProcessIndicator; +class ZLoadingWidget; class DevDiskImageHelper : public QDialog { @@ -62,8 +62,7 @@ private: const iDescriptorDevice *m_device; QLabel *m_statusLabel; - QProcessIndicator *m_loadingIndicator; - QPushButton *m_mountButton; + ZLoadingWidget *m_loadingWidget; QPushButton *m_retryButton; QPushButton *m_cancelButton; diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 88b89e2..d4a631f 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -20,30 +20,11 @@ #include "deviceinfowidget.h" #include "batterywidget.h" #include "diskusagewidget.h" -// #include "fileexplorerwidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "infolabel.h" #include "privateinfolabel.h" #include "toolboxwidget.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include DeviceInfoWidget::DeviceInfoWidget(const iDescriptorDevice *device, QWidget *parent) @@ -391,35 +372,41 @@ void DeviceInfoWidget::onBatteryMoreClicked() void DeviceInfoWidget::updateBatteryInfo() { qDebug() << "Updating battery info..."; - plist_t diagnostics = nullptr; - // DONT BLOCK - get_battery_info(m_device->diagRelay.get(), diagnostics); + QThreadPool::globalInstance()->start([this]() { + std::lock_guard lock(m_device->mutex); + if (!m_device || QCoreApplication::closingDown()) + return; - if (!diagnostics) { - qDebug() << "Failed to get diagnostics plist."; - return; - } - /*DATA*/ - DeviceInfo &d = const_cast(m_device->deviceInfo); - qDebug() << "old device" << d.oldDevice; - PlistNavigator ioreg = PlistNavigator(diagnostics); - if (d.oldDevice) - ServiceManager::safeParseOldDeviceBattery(m_device, ioreg, d); - else - ServiceManager::safeParseDeviceBattery(m_device, ioreg, d); - /*UI*/ - updateChargingStatusIcon(); - m_chargingWattsWithCableTypeLabel->setText( - QString::number(d.batteryInfo.watts) + "W" + "/" + - (d.batteryInfo.usbConnectionType == BatteryInfo::ConnectionType::USB - ? "USB" - : "USB-C")); + /* diagnostics will be freed by c++ wrapper */ + plist_t diagnostics = nullptr; + get_battery_info(m_device->diagRelay.get(), diagnostics); + QMetaObject::invokeMethod(this, [this, diagnostics]() { + if (!diagnostics) { + qDebug() << "Failed to get diagnostics plist."; + return; + } + /*DATA*/ + DeviceInfo &d = const_cast(m_device->deviceInfo); + qDebug() << "old device" << d.oldDevice; + PlistNavigator ioreg = PlistNavigator(diagnostics); + if (d.oldDevice) + ServiceManager::safeParseOldDeviceBattery(m_device, ioreg, d); + else + ServiceManager::safeParseDeviceBattery(m_device, ioreg, d); + /*UI*/ + updateChargingStatusIcon(); + m_chargingWattsWithCableTypeLabel->setText( + QString::number(d.batteryInfo.watts) + "W" + "/" + + (d.batteryInfo.usbConnectionType == + BatteryInfo::ConnectionType::USB + ? "USB" + : "USB-C")); - m_batteryWidget->updateContext( - d.batteryInfo.isCharging, - qBound(1, d.batteryInfo.currentBatteryLevel, 100)); - // FIXME: does diag c++ wrappers free this already ? - // plist_free(diagnostics); + m_batteryWidget->updateContext( + d.batteryInfo.isCharging, + qBound(1, d.batteryInfo.currentBatteryLevel, 100)); + }); + }); } void DeviceInfoWidget::updateChargingStatusIcon() diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 932fbd9..109074d 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -24,9 +24,24 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "servicemanager.h" +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include class DeviceInfoWidget : public QWidget { diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index a7a4f29..8788401 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -516,8 +516,13 @@ void DiskUsageWidget::fetchData() return result; } - result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); - return result; + // FIXME: + // bool debug = qgetenv("ID_DESCRIPTOR_DEBUG_GALLERY_USAGE") == "1"; + bool debug = true; + if (debug) { + result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); + return result; + } const size_t CHUNK_SIZE = 256 * 1024; uint8_t *db_data = nullptr; diff --git a/src/iDescriptor.h b/src/iDescriptor.h index 943a7ec..7b1dbac 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -98,7 +98,7 @@ // rust codebase extern "C" { -#include "idecriptor_rust.h" +#include "idescriptor_rust.h" } namespace iDescriptor { @@ -295,7 +295,13 @@ struct TakeScreenshotResult { void warn(const QString &message, const QString &title = "Warning", QWidget *parent = nullptr); -enum class AddType { Regular, Pairing, Wireless, UpgradeToWireless }; +enum class AddType { + Regular, + Pairing, + FailedToPair, + Wireless, + UpgradeToWireless +}; class PlistNavigator { diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 5c6f993..f7cb165 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -55,18 +55,11 @@ void AppTabWidget::setupUI(const QPixmap &icon) mainLayout->setContentsMargins(10, 8, 10, 8); mainLayout->setSpacing(10); - // Icon label - m_iconLabel = new QLabel(); + m_iconLabel = new IDLoadingIconLabel(this); m_iconLabel->setFixedSize(32, 32); - m_iconLabel->setScaledContents(true); if (!icon.isNull()) { - m_iconLabel->setPixmap(icon); - } else { - QPixmap placeholderIcon = QApplication::style() - ->standardIcon(QStyle::SP_ComputerIcon) - .pixmap(32, 32); - m_iconLabel->setPixmap(placeholderIcon); + m_iconLabel->setLoadedPixmap(icon); } mainLayout->addWidget(m_iconLabel); @@ -103,6 +96,18 @@ void AppTabWidget::setupUI(const QPixmap &icon) updateStyles(); } +void AppTabWidget::setIcon(const QPixmap &icon) +{ + if (!m_iconLabel) + return; + + if (!icon.isNull()) { + m_iconLabel->setLoadedPixmap(icon); + } else { + m_iconLabel->setLoadFailed(); + } +} + void AppTabWidget::mousePressEvent(QMouseEvent *event) { Q_UNUSED(event) @@ -180,10 +185,6 @@ InstalledAppsWidget::~InstalledAppsWidget() m_containerWatcher->waitForFinished(); } cleanupHouseArrestClients(); - if (m_springboardClient) { - springboard_services_free(m_springboardClient); - m_springboardClient = nullptr; - } } void InstalledAppsWidget::setupUI() @@ -313,17 +314,6 @@ void InstalledAppsWidget::fetchInstalledApps() std::lock_guard lock(m_device->mutex); QVariantList apps; - // fetch icon from springboard service - IdeviceFfiError *err = nullptr; - if (!m_springboardClient) { - err = springboard_services_connect(m_device->provider, - &m_springboardClient); - if (err) { - qDebug() << "Error connecting to SpringBoard services:" - << QString::fromUtf8(err->message); - idevice_error_free(err); - } - } try { InstallationProxyClientHandle *installationProxyClientHandle = @@ -426,34 +416,6 @@ void InstalledAppsWidget::fetchInstalledApps() appData["type"] = appType; QString bundleId = appData["bundleId"].toString(); - - if (m_springboardClient && !bundleId.isEmpty()) { - void *out_result; - size_t out_result_len; - - // FIXME: free out_result - // there is no springboard_services_free_data or - // similar, create an issue - err = springboard_services_get_icon( - m_springboardClient, - bundleId.toUtf8().constData(), &out_result, - &out_result_len); - if (err != nullptr) { - qWarning() << "Error getting icon for" - << appData.value("bundleId") << ":" - << QString::fromUtf8(err->message); - idevice_error_free(err); - } else { - QByteArray byteArray( - reinterpret_cast(out_result), - static_cast(out_result_len)); - QImage image; - image.loadFromData(byteArray); - QPixmap pixmap = QPixmap::fromImage(image); - appData["icon"] = pixmap; - } - } - if (!bundleId.isEmpty()) { apps.append(appData); } @@ -508,6 +470,8 @@ void InstalledAppsWidget::onAppsDataReady() qDeleteAll(m_appTabs); m_appTabs.clear(); m_selectedTab = nullptr; + m_iconLoadQueue.clear(); + m_iconLoading = false; // Create tabs for each app for (const QVariant &appVariant : apps) { @@ -516,7 +480,6 @@ void InstalledAppsWidget::onAppsDataReady() QString bundleId = appData.value("bundleId").toString(); QString version = appData.value("version").toString(); QString appType = appData.value("type").toString(); - QPixmap icon = appData.value("icon").value(); bool fileSharingEnabled = appData.value("fileSharingEnabled", false).toBool(); @@ -536,7 +499,9 @@ void InstalledAppsWidget::onAppsDataReady() tabName += " (System)"; } - createAppTab(tabName, bundleId, version, icon); + createAppTab(tabName, bundleId, version, QPixmap()); + + enqueueIconLoad(bundleId); // Select first tab if available if (!m_appTabs.isEmpty()) { @@ -556,7 +521,7 @@ void InstalledAppsWidget::createAppTab(const QString &appName, &InstalledAppsWidget::onAppTabClicked); // Remove the stretch before adding the new tab - m_tabLayout->removeItem(m_tabLayout->itemAt(m_tabLayout->count() - 1)); + m_tabLayout->removeItem(m_tabLayout->itemAt(m_tabLayout->count() - 1)); // m_tabLayout->addWidget(tabWidget); m_tabLayout->addStretch(); // Add stretch back at the end @@ -844,4 +809,98 @@ void InstalledAppsWidget::disableTabs(bool disable) for (AppTabWidget *tab : m_appTabs) { tab->setEnabled(!disable); } +} + +void InstalledAppsWidget::enqueueIconLoad(const QString &bundleId) +{ + if (bundleId.isEmpty()) + return; + + if (!m_iconLoadQueue.contains(bundleId)) { + m_iconLoadQueue.enqueue(bundleId); + } + + if (!m_iconLoading) { + startNextIconLoad(); + } +} + +void InstalledAppsWidget::startNextIconLoad() +{ + if (!m_device || QCoreApplication::closingDown()) { + m_iconLoading = false; + return; + } + + if (m_iconLoadQueue.isEmpty()) { + m_iconLoading = false; + return; + } + + m_iconLoading = true; + const QString bundleId = m_iconLoadQueue.dequeue(); + + QtConcurrent::run([this, bundleId]() { + if (QCoreApplication::closingDown() || !m_device) + return; + + QPixmap iconPixmap; + + { + std::lock_guard lock(m_device->mutex); + + IdeviceFfiError *err = nullptr; + SpringBoardServicesClientHandle *springboardClient = nullptr; + + err = springboard_services_connect(m_device->provider, + &springboardClient); + if (err != nullptr) { + qWarning() << "Error connecting to SpringBoard services for" + << bundleId << ":" + << QString::fromUtf8(err->message); + idevice_error_free(err); + } else { + void *out_result = nullptr; + size_t out_result_len = 0; + + err = springboard_services_get_icon( + springboardClient, bundleId.toUtf8().constData(), + &out_result, &out_result_len); + if (err != nullptr) { + qWarning() << "Error getting icon for" << bundleId << ":" + << QString::fromUtf8(err->message); + idevice_error_free(err); + } else if (out_result && out_result_len > 0) { + QByteArray byteArray( + reinterpret_cast(out_result), + static_cast(out_result_len)); + QImage image; + image.loadFromData(byteArray); + iconPixmap = QPixmap::fromImage(image); + springboard_services_free_icon_result(out_result, + out_result_len); + } + + springboard_services_free(springboardClient); + } + } + + QMetaObject::invokeMethod( + this, + [this, bundleId, iconPixmap]() { + if (QCoreApplication::closingDown()) + return; + + for (AppTabWidget *tab : m_appTabs) { + if (tab->getBundleId() == bundleId) { + tab->setIcon(iconPixmap); + break; + } + } + + m_iconLoading = false; + startNextIconLoad(); + }, + Qt::QueuedConnection); + }); } \ No newline at end of file diff --git a/src/installedappswidget.h b/src/installedappswidget.h index 1ed7e00..7470207 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -20,6 +20,7 @@ #ifndef INSTALLEDAPPSWIDGET_H #define INSTALLEDAPPSWIDGET_H +#include "iDescriptor-ui.h" #include "iDescriptor.h" #include "zlineedit.h" #include "zloadingwidget.h" @@ -45,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +70,9 @@ public: QString getBundleId() const { return m_bundleId; } QString getAppName() const { return m_appName; } QString getVersion() const { return m_version; } + + void setIcon(const QPixmap &icon); + void updateStyles(); signals: @@ -91,7 +96,7 @@ private: QString m_version; bool m_selected = false; - QLabel *m_iconLabel; + IDLoadingIconLabel *m_iconLabel; QLabel *m_nameLabel; QLabel *m_versionLabel; QNetworkAccessManager *m_networkManager = new QNetworkAccessManager(this); @@ -131,6 +136,9 @@ private: void cleanupHouseArrestClients(); void disableTabs(bool disable); + void enqueueIconLoad(const QString &bundleId); + void startNextIconLoad(); + const iDescriptorDevice *m_device; QHBoxLayout *m_mainLayout; QStackedWidget *m_stackedWidget; @@ -147,8 +155,8 @@ private: QScrollArea *m_containerScrollArea; QWidget *m_containerWidget; QVBoxLayout *m_containerLayout; - QFutureWatcher *m_watcher; - QFutureWatcher *m_containerWatcher; + QFutureWatcher *m_watcher = nullptr; + QFutureWatcher *m_containerWatcher = nullptr; QSplitter *m_splitter; ZLoadingWidget *m_zloadingWidget; @@ -156,7 +164,10 @@ private: // App data storage QList m_appTabs; AppTabWidget *m_selectedTab = nullptr; - SpringBoardServicesClientHandle *m_springboardClient = nullptr; + + QQueue m_iconLoadQueue; + bool m_iconLoading = false; + bool m_loadingContainer = false; bool m_loaded = false; }; diff --git a/src/livescreenwidget.cpp b/src/livescreenwidget.cpp index 631fa1c..6ec322f 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -123,8 +123,6 @@ void LiveScreenWidget::startInitialization() connect(helper, &DevDiskImageHelper::mountingCompleted, this, [this, helper](bool success) { - helper->deleteLater(); - if (success) { // for some reason it does not work immediately, so delay a // bit diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6c3863a..c493975 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -79,22 +79,37 @@ void handleCallback(const IdeviceEvent *e) qDebug() << "Device event: " << (e->kind == 1 ? "Connected" : "Disconnected") << ", UDID: " << udid; - free(e->udid); - bool isConnected = (e->kind == 1); + // free(e->udid); + AddType addType; - if (isConnected) { - QMetaObject::invokeMethod( - AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, - Q_ARG(iDescriptor::Uniq, iDescriptor::Uniq(udid)), - Q_ARG(iDescriptor::IdeviceConnectionType, - static_cast( - iDescriptor::CONNECTION_USB)), - Q_ARG(AddType, AddType::Regular)); - } else { + switch (e->kind) { + case 1: { // fully connected + addType = AddType::Regular; + break; + } + case 2: { // disconnected QMetaObject::invokeMethod( AppContext::sharedInstance(), "removeDevice", Qt::QueuedConnection, Q_ARG(iDescriptor::Uniq, iDescriptor::Uniq(udid))); + return; } + case 3: { // pairing pending + addType = AddType::Pairing; + break; + } + case 4: { // pairing failed + addType = AddType::FailedToPair; + break; + } + } + + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, + Q_ARG(iDescriptor::Uniq, iDescriptor::Uniq(udid)), + Q_ARG(iDescriptor::IdeviceConnectionType, + static_cast( + iDescriptor::CONNECTION_USB)), + Q_ARG(AddType, addType)); } MainWindow *MainWindow::sharedInstance() @@ -188,10 +203,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) if (m_mainStackedWidget->currentIndex() != 0) { welcomeMenuSwitch->setToolTip( "Switch to Connected Devices"); - return m_mainStackedWidget->setCurrentIndex(0); + return showWelcomeTab(); } welcomeMenuSwitch->setToolTip("Switch to Welcome Menu"); - m_mainStackedWidget->setCurrentIndex(1); + showConnectedDevicesTab(); }); connect(m_ZTabWidget, &ZTabWidget::currentChanged, this, @@ -213,11 +228,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) statusLayout->addWidget(appVersionLabel); statusLayout->addWidget(githubButton); statusLayout->addWidget(settingsButton); - // #ifdef WIN32 - // statusLayout->setStyleSheet("QStatusBar { border-top: 1px solid - // #dcdcdc; - // }"); - // #endif mainLayout->addWidget(statusbar); @@ -496,6 +506,13 @@ void MainWindow::raiseDeviceTab() activateWindow(); } +void MainWindow::showConnectedDevicesTab() +{ + m_mainStackedWidget->setCurrentIndex(1); +} + +void MainWindow::showWelcomeTab() { m_mainStackedWidget->setCurrentIndex(0); } + MainWindow::~MainWindow() { #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT diff --git a/src/mainwindow.h b/src/mainwindow.h index 50b4a94..2f20beb 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -40,6 +40,8 @@ public: ~MainWindow(); ZUpdater *m_updater = nullptr; void raiseDeviceTab(); + void showConnectedDevicesTab(); + void showWelcomeTab(); public slots: void updateNoDevicesConnected(); diff --git a/src/networkdevicestoconnectwidget.cpp b/src/networkdevicestoconnectwidget.cpp index 7d57550..39e61eb 100644 --- a/src/networkdevicestoconnectwidget.cpp +++ b/src/networkdevicestoconnectwidget.cpp @@ -134,6 +134,17 @@ void NetworkDeviceCard::connected() }); } +void NetworkDeviceCard::alreadyExists() +{ + m_connectButton->setText("Already connected"); + m_connectButton->setEnabled(false); + + QTimer::singleShot(3000, this, [this]() { + m_connectButton->setText("Connect"); + m_connectButton->setEnabled(true); + }); +} + void NetworkDeviceCard::initStarted() { m_connectButton->setText("Connecting..."); @@ -244,7 +255,6 @@ void NetworkDevicesToConnectWidget::clearDeviceCards() } m_deviceCards.clear(); } - void NetworkDevicesToConnectWidget::updateDeviceList() { clearDeviceCards(); @@ -344,8 +354,8 @@ void NetworkDevicesToConnectWidget::onDeviceAlreadyExists( { NetworkDeviceCard *deviceCard = m_deviceCards[QString(uniq.get())]; if (deviceCard) { - qDebug() << "Calling connected() on device card for" << uniq.get(); - deviceCard->connected(); + qDebug() << "Calling alreadyExists() on device card for" << uniq.get(); + deviceCard->alreadyExists(); return; } qDebug() << "No device card found for" << uniq.get(); diff --git a/src/networkdevicestoconnectwidget.h b/src/networkdevicestoconnectwidget.h index f410d08..89f769b 100644 --- a/src/networkdevicestoconnectwidget.h +++ b/src/networkdevicestoconnectwidget.h @@ -45,6 +45,7 @@ public: void noPairingFile(); void initStarted(); void connected(); + void alreadyExists(); }; class NetworkDevicesToConnectWidget : public QWidget diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 2e97672..47e01bb 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "aws-lc-rs" version = "1.16.2" @@ -30,6 +36,18 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytes" version = "1.11.1" @@ -90,6 +108,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "fs_extra" version = "1.3.0" @@ -203,16 +227,60 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idescriptor_rust_codebase" +version = "0.1.0" +dependencies = [ + "futures", + "idevice", + "tokio", + "uuid", +] + [[package]] name = "idevice" version = "0.1.53" @@ -236,7 +304,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -255,12 +325,34 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + [[package]] name = "memchr" version = "2.8.0" @@ -324,6 +416,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -357,6 +459,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "ring" version = "0.17.14" @@ -371,15 +479,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rust_codebase" -version = "0.1.0" -dependencies = [ - "futures", - "idevice", - "tokio", -] - [[package]] name = "rustls" version = "0.23.37" @@ -405,9 +504,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -415,6 +514,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -445,6 +556,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "shlex" version = "1.3.0" @@ -552,9 +676,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -608,12 +732,29 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uuid" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -629,6 +770,94 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -722,9 +951,97 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index f96d184..3c3fe1b 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -10,3 +10,4 @@ name = "idescriptor_rust_codebase" tokio = { version = "1", features = ["rt", "time", "sync", "macros"] } futures = "0.3" idevice = { path = "../../lib/idevice-rs/idevice", features = ["usbmuxd"] } +uuid = { version = "1.22.0", features = ["v4"] } diff --git a/src/rust/include/idecriptor_rust.h b/src/rust/include/idescriptor_rust.h similarity index 70% rename from src/rust/include/idecriptor_rust.h rename to src/rust/include/idescriptor_rust.h index ade0a13..216c15b 100644 --- a/src/rust/include/idecriptor_rust.h +++ b/src/rust/include/idescriptor_rust.h @@ -5,7 +5,8 @@ extern "C" { #endif typedef struct { - int kind; // 1 = connected, 2 = disconnected + int kind; // 1 = connected, 2 = disconnected, 3 = pairing pending, 4 = + // pairing failed char *udid; } IdeviceEvent; diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index ca3521e..5f7dc66 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -1,18 +1,37 @@ use futures::StreamExt; -use idevice::usbmuxd::{Connection, UsbmuxdConnection, UsbmuxdListenEvent}; +use idevice::{ + IdeviceService, + lockdown::LockdownClient, + usbmuxd::{Connection, UsbmuxdAddr, UsbmuxdConnection, UsbmuxdListenEvent}, +}; use std::collections::HashMap; +use std::ffi::CString; use std::os::raw::c_char; -use std::{ffi::CString, thread}; +use std::thread; use tokio::runtime::Builder; #[repr(C)] pub struct IdeviceEvent { - pub kind: i32, // 1 = connected, 2 = disconnected + pub kind: i32, pub udid: *mut c_char, } pub type IdeviceEventCallback = extern "C" fn(event: *const IdeviceEvent); +fn with_event(kind: i32, udid: &str, cb: IdeviceEventCallback) { + let c_udid = CString::new(udid).unwrap_or_default(); + let mut ev = IdeviceEvent { + kind, + udid: c_udid.into_raw(), + }; + + cb(&ev); + + unsafe { + let _ = CString::from_raw(ev.udid); + } +} + fn make_event(kind: i32, udid: &str) -> IdeviceEvent { let c_udid = CString::new(udid).unwrap_or_default(); IdeviceEvent { @@ -42,18 +61,119 @@ pub extern "C" fn idevice_event_subscribe(cb: IdeviceEventCallback) { let udid = d.udid.clone(); let device_id = d.device_id; - device_map.insert(device_id, udid.clone()); - let ev = make_event(1, &udid); - cb(&ev); + let cb_clone = cb; + tokio::spawn(async move { + let already_paired = { + let mut u2 = + match UsbmuxdConnection::default().await { + Ok(u) => u, + Err(_) => return, + }; + + match u2.get_pair_record(&udid).await { + Ok(_) => true, + Err(_) => false, + } + }; + + if already_paired { + with_event(1, &udid, cb_clone); + return; + } + + with_event(3, &udid, cb_clone); + + let ev_fail_kind = 4; + with_event(ev_fail_kind, &udid, cb_clone); + + with_event(1, &udid, cb_clone); + + let mut uc2 = match UsbmuxdConnection::default().await { + Ok(u) => u, + Err(_) => { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + }; + + let dev = match uc2.get_device(&udid).await { + Ok(d) => d, + Err(_) => { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + }; + let provider = dev + .to_provider(UsbmuxdAddr::default(), "iDescriptor"); + + let mut lc = + match LockdownClient::connect(&provider).await { + Ok(l) => l, + Err(_) => { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + }; + + let mut buid = match uc2.get_buid().await { + Ok(b) => b, + Err(_) => { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + }; + + if let Some(first) = buid.chars().next() { + let mut chars: Vec = buid.chars().collect(); + // https://github.com/jkcoxson/idevice_pair/blob/63b524a5/src/main.rs + // Modify it slightly so iOS doesn't invalidate the one connected right now. + chars[0] = if first == 'F' { 'A' } else { 'F' }; + buid = chars.into_iter().collect(); + } + + let host_id = + uuid::Uuid::new_v4().to_string().to_uppercase(); + + let mut pf = match lc.pair(host_id, buid, None).await { + Ok(p) => p, + Err(_) => { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + }; + pf.udid = Some(udid.clone()); + + let bytes = match pf.serialize() { + Ok(b) => b, + Err(_) => { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + }; + + if let Err(_) = uc2.save_pair_record(&udid, bytes).await + { + let ev_fail = make_event(4, &udid); + cb_clone(&ev_fail); + return; + } + + let ev_ok = make_event(1, &udid); + cb_clone(&ev_ok); + }); } Ok(UsbmuxdListenEvent::Disconnected(device_id)) => { if let Some(udid) = device_map.remove(&device_id) { let ev = make_event(2, &udid); cb(&ev); - } else { - eprintln!("Unknown device disconnected: {device_id}"); } } Err(e) => {