Files
iDescriptor/src/appcontext.cpp
T

301 lines
10 KiB
C++

/*
* iDescriptor: A free and open-source idevice management tool.
*
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "appcontext.h"
#include "iDescriptor.h"
#include "mainwindow.h"
#include "settingsmanager.h"
#include <QDebug>
#include <QMessageBox>
#include <QTimer>
#include <QUuid>
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} {}
void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
AddType addType)
{
try {
iDescriptorInitDeviceResult initResult =
init_idescriptor_device(udid.toStdString().c_str());
qDebug() << "init_idescriptor_device success ?: " << initResult.success;
qDebug() << "init_idescriptor_device error code: " << initResult.error;
if (!initResult.success) {
qDebug() << "Failed to initialize device with UDID: " << udid;
if (initResult.error == LOCKDOWN_E_PASSWORD_PROTECTED) {
if (addType == AddType::Regular) {
m_pendingDevices.append(udid);
emit devicePasswordProtected(udid);
emit deviceChange();
// After 30 seconds, if the device is still pending,
// consider the pairing expired
QTimer::singleShot(30000, this, [this, udid]() {
if (m_pendingDevices.contains(udid)) {
qDebug()
<< "Pairing expired for device UDID: " << udid;
m_pendingDevices.removeAll(udid);
emit devicePairingExpired(udid);
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();
// After 30 seconds, if the device is still pending,
// consider the pairing expired
QTimer::singleShot(30000, 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;
}
qDebug() << "Device initialized: " << udid;
iDescriptorDevice *device = new iDescriptorDevice{
.udid = udid.toStdString(),
.conn_type = conn_type,
.device = initResult.device,
.deviceInfo = initResult.deviceInfo,
.afcClient = initResult.afcClient,
.afc2Client = initResult.afc2Client,
.mutex = new std::recursive_mutex(),
};
m_devices[device->udid] = device;
if (addType == AddType::Regular) {
// Apply settings-based behaviors
SettingsManager::sharedInstance()->doIfEnabled(
SettingsManager::Setting::AutoRaiseWindow, []() {
if (MainWindow *mainWindow = MainWindow::sharedInstance()) {
mainWindow->raise();
mainWindow->activateWindow();
}
});
emit deviceAdded(device);
emit deviceChange();
return;
}
emit devicePaired(device);
emit deviceChange();
m_pendingDevices.removeAll(udid);
} catch (const std::exception &e) {
qDebug() << "Exception in onDeviceAdded: " << e.what();
}
}
int AppContext::getConnectedDeviceCount() const
{
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
return m_devices.size() + m_recoveryDevices.size();
#else
return m_devices.size();
#endif
}
/*
FIXME:
on macOS, sometimes you get wireless disconnects even though we are not
listening for wireless devices it does not have any to do with us, but it
still happens so be aware of that
*/
void AppContext::removeDevice(QString _udid)
{
const std::string uuid = _udid.toStdString();
qDebug() << "AppContext::removeDevice device with UUID:"
<< QString::fromStdString(uuid);
if (m_pendingDevices.contains(_udid)) {
m_pendingDevices.removeAll(_udid);
emit devicePairingExpired(_udid);
emit deviceChange();
return;
} else {
qDebug() << "Device with UUID " + _udid +
" not found in pending devices.";
}
if (!m_devices.contains(uuid)) {
qDebug() << "Device with UUID " + _udid +
" not found in normal devices.";
return;
}
iDescriptorDevice *device = m_devices[uuid];
m_devices.remove(uuid);
emit deviceRemoved(uuid);
emit deviceChange();
std::lock_guard<std::recursive_mutex> lock(*device->mutex);
if (device->afcClient)
afc_client_free(device->afcClient);
idevice_free(device->device);
delete device->mutex;
delete device;
}
#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;
// Fix use-after-free: get pointer before removing from map
iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices.value(ecid);
m_recoveryDevices.remove(ecid);
emit recoveryDeviceRemoved(ecid);
emit deviceChange();
std::lock_guard<std::recursive_mutex> lock(*deviceInfo->mutex);
delete deviceInfo->mutex;
delete deviceInfo;
}
#endif
iDescriptorDevice *AppContext::getDevice(const std::string &uuid)
{
return m_devices.value(uuid, nullptr);
}
QList<iDescriptorDevice *> AppContext::getAllDevices()
{
return m_devices.values();
}
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
QList<iDescriptorRecoveryDevice *> 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)
{
iDescriptorInitDeviceResultRecovery res =
init_idescriptor_recovery_device(ecid);
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;
recoveryDevice->mutex = new std::recursive_mutex();
m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice;
emit recoveryDeviceAdded(recoveryDevice);
emit deviceChange();
}
#endif
AppContext::~AppContext()
{
for (auto device : m_devices) {
emit deviceRemoved(device->udid);
if (device->afcClient)
afc_client_free(device->afcClient);
if (device->afc2Client)
afc_client_free(device->afc2Client);
idevice_free(device->device);
delete device->mutex;
delete device;
}
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
for (auto recoveryDevice : m_recoveryDevices) {
emit recoveryDeviceRemoved(recoveryDevice->ecid);
delete recoveryDevice->mutex;
delete recoveryDevice;
}
#endif
}
void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection)
{
qDebug() << "New selection -"
<< " Type:" << selection.type
<< " UUID:" << QString::fromStdString(selection.uuid)
<< " ECID:" << selection.ecid << " Section:" << selection.section;
if (m_currentSelection.uuid == selection.uuid &&
m_currentSelection.ecid == selection.ecid &&
m_currentSelection.section == selection.section) {
qDebug() << "setCurrentDeviceSelection: No change in selection";
return; // No change
}
m_currentSelection = selection;
emit currentDeviceSelectionChanged(m_currentSelection);
}
const DeviceSelection &AppContext::getCurrentDeviceSelection() const
{
return m_currentSelection;
}