/* * iDescriptor: A free and open-source idevice management tool. * * Copyright (C) 2025 Uncore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "appcontext.h" AppContext *AppContext::sharedInstance() { static AppContext instance; return &instance; } /* FIXME: waking up from sleep disconnects all devices and does not reconnect them until the user plugs them back in, even if they are still connected */ AppContext::AppContext(QObject *parent) : QObject{parent} { cachePairedDevices(); } void AppContext::cachePairedDevices() { #ifndef __APPLE__ m_pairingFileCache = core->get_pairing_files(); #else QMap maybeStalePairingFiles = SettingsManager::sharedInstance()->getAllIdeviceDefaultPairingFiles(); for (const QString &mac : maybeStalePairingFiles.keys()) { const QString path = maybeStalePairingFiles.value(mac); qDebug() << "Using pairing file" << path << "for MAC:" << mac << "cached from settings"; m_pairingFileCache[mac] = QVariant(path); } QMap fresh = core->get_pairing_files(); for (const QString &mac : fresh.keys()) { const QString path = fresh.value(mac).toString(); qDebug() << "Using fresh pairing file" << path << "for MAC:" << mac << "from backend"; m_pairingFileCache[mac] = QVariant(path); } #endif } /* addDevice is only called with udid from backend */ void AppContext::addDevice(iDescriptor::Uniq uniq, iDescriptor::IdeviceConnectionType conn_type, AddType addType, QString info, QString wifiMacAddress, QString ipAddress) { if (QCoreApplication::closingDown()) { qDebug() << "Ignoring addDevice during shutdown for" << uniq.get(); return; } std::shared_ptr existingDevice = nullptr; existingDevice = getDevice(uniq.get()); if (existingDevice) { uniq.isMac() ? emit deviceAlreadyExistsMAC(uniq) : 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; } qDebug() << "Device initialized: " << uniq; if (m_pendingDevices.contains(uniq)) { qDebug() << "Removing from pending devices: " << uniq; m_pendingDevices.removeAll(uniq); emit devicePairingExpired(uniq); } pugi::xml_document doc; auto res = doc.load_string(info.toUtf8().constData()); if (!res) { core->remove_device(uniq); QMessageBox::warning(nullptr, "Failed to add device", "Failed to parse device info XML for device " + uniq.get() + ". The device may not function " "correctly. Please report this issue."); qWarning() << "Failed to parse device info XML for" << uniq << ":" << res.description(); return; } DeviceInfo deviceInfo; fullDeviceInfo(doc, deviceInfo); const iDescriptorDevice device = { .udid = uniq.get(), .conn_type = conn_type, .deviceInfo = deviceInfo, .ios_version = deviceInfo.parsedDeviceVersion.major, .service_manager = new CXX::ServiceManager( uniq.get(), deviceInfo.parsedDeviceVersion.major), .afc_backend = new CXX::AfcBackend(uniq.get()), .afc2_backend = new CXX::Afc2Backend(uniq.get())}; m_devices[device.udid] = std::make_shared(device); if (addType == AddType::Wireless || addType == AddType::UpgradeToWireless || addType == AddType::Regular) { qDebug() << "Wireless device added: " << uniq; emit deviceAdded(m_devices[device.udid]); emit deviceChange(); return; } emit devicePaired(m_devices[device.udid]); emit deviceChange(); m_pendingDevices.removeAll(uniq); } int AppContext::getConnectedDeviceCount() const { #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT return m_devices.size() + m_recoveryDevices.size(); #else return m_devices.size(); #endif } void AppContext::removeDevice(iDescriptor::Uniq uniq, bool ask_backend) { qDebug() << "AppContext::removeDevice device with" << (uniq.isMac() ? "MAC" : "UDID") << uniq.get(); QString q_udid = uniq.get(); if (m_pendingDevices.contains(q_udid)) { m_pendingDevices.removeAll(q_udid); emit devicePairingExpired(q_udid); emit deviceChange(); return; } else { qDebug() << "Device with UUID " + q_udid + " not found in pending devices."; } if (!m_devices.contains(q_udid)) { qDebug() << "Device with UUID " + q_udid + " not found in normal devices."; return; } auto device = m_devices[q_udid]; m_devices.remove(q_udid); emit deviceRemoved(q_udid, device->deviceInfo.wifiMacAddress, device->deviceInfo.ipAddress, device->deviceInfo.isWireless); emit deviceChange(); if (ask_backend) { core->remove_device(q_udid); } } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void AppContext::removeRecoveryDevice(uint64_t ecid) { if (!m_recoveryDevices.contains(ecid)) { qDebug() << "Device with ECID " + QString::number(ecid) + " not found. Please report this issue."; return; } qDebug() << "Removing recovery device with ECID:" << ecid; iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices.value(ecid); m_recoveryDevices.remove(ecid); emit recoveryDeviceRemoved(ecid); // TODO: do we need this ? // emit deviceChange(); std::lock_guard lock(deviceInfo->mutex); delete deviceInfo; } #endif std::shared_ptr AppContext::getDevice(const QString &uniq) { auto it = m_devices.find(uniq); if (it != m_devices.end()) { return it.value(); } return nullptr; } QList> AppContext::getAllDevices() { return m_devices.values(); } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT QList AppContext::getAllRecoveryDevices() { return m_recoveryDevices.values(); } #endif // Returns whether there are any devices connected (regular or recovery) bool AppContext::noDevicesConnected() const { #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT return (m_devices.isEmpty() && m_recoveryDevices.isEmpty() && m_pendingDevices.isEmpty()); #else return (m_devices.isEmpty() && m_pendingDevices.isEmpty()); #endif } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void AppContext::addRecoveryDevice(uint64_t ecid) { auto res = std::make_shared(); QFuture future = QtConcurrent::run( [this, ecid, res]() { init_idescriptor_recovery_device(ecid, *res); }); QFutureWatcher *watcher = new QFutureWatcher(); watcher->setFuture(future); connect(watcher, &QFutureWatcher::finished, this, [this, ecid, res, watcher]() { watcher->deleteLater(); if (!res->success) { qDebug() << "Failed to initialize recovery device with ECID: " << QString::number(ecid); qDebug() << "Error code: " << res->error; return; } iDescriptorRecoveryDevice *recoveryDevice = new iDescriptorRecoveryDevice(); recoveryDevice->ecid = res->deviceInfo.ecid; recoveryDevice->mode = res->mode; recoveryDevice->cpid = res->deviceInfo.cpid; recoveryDevice->bdid = res->deviceInfo.bdid; recoveryDevice->displayName = res->displayName; m_recoveryDevices[res->deviceInfo.ecid] = recoveryDevice; emit recoveryDeviceAdded(recoveryDevice); emit deviceChange(); }); } #endif AppContext::~AppContext() { m_devices.clear(); #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT for (auto recoveryDevice : m_recoveryDevices) { emit recoveryDeviceRemoved(recoveryDevice->ecid); delete recoveryDevice; } #endif } 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 { return m_currentSelection; } const iDescriptorDevice * AppContext::getDeviceByMacAddress(const QString &macAddress) const { for (const auto &device : m_devices) { if (device->deviceInfo.wifiMacAddress == macAddress.toStdString()) { return device.get(); } } return nullptr; } void AppContext::cachePairingFile(const QString &uniq, const QString &pairingFilePath) { m_pairingFileCache.insert(uniq, pairingFilePath); } const QString AppContext::getCachedPairingFile(const QString &uniq) const { QString pairingFile; if (m_pairingFileCache.contains(uniq)) { pairingFile = m_pairingFileCache.value(uniq).toString(); } return pairingFile; } void AppContext::heartbeatFailed(const QString &macAddress, int tries) { emit deviceHeartbeatFailed(macAddress, tries); } void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device) { qDebug() << "Trying to connect to network device with MAC:" << device.macAddress << "IP:" << device.address; // force refresh macAddress-pairing file mapping cachePairedDevices(); const iDescriptorDevice *existingDevice = nullptr; existingDevice = getDeviceByMacAddress(device.macAddress); if (existingDevice) { emit deviceAlreadyExistsMAC( iDescriptor::Uniq(existingDevice->deviceInfo.wifiMacAddress, true)); // TODO: add a setting for this setCurrentDeviceSelection(DeviceSelection(existingDevice->udid), true); return; } cachePairedDevices(); QString pairing_file = getCachedPairingFile(device.macAddress); if (pairing_file.isEmpty()) { qDebug() << "No pairing file cached for device with MAC:" << device.macAddress << "Emitting noPairingFileForWirelessDevice event"; emit noPairingFileForWirelessDevice(device.macAddress); return; } core->init_wireless_device(device.address, pairing_file, device.macAddress); emit initStarted(device.macAddress); } void AppContext::handlePairing(iDescriptor::Uniq uniq, bool timeout) { 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(); } }); } }