/* * 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 "mainwindow.h" #include "./ui_mainwindow.h" #include "detailwindow.h" #include "ifusediskunmountbutton.h" #include "ifusemanager.h" #include "settingswidget.h" #include "appswidget.h" #include "devicemanagerwidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "jailbrokenwidget.h" #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT #include "libirecovery.h" #endif #include "toolboxwidget.h" #include "welcomewidget.h" #include #include #include #include #include #include #include "appcontext.h" #include "settingsmanager.h" #include #include #include #include #include void handleCallback(const idevice_event_t *event, void *userData) { printf("Device event received: "); switch (event->event) { case IDEVICE_DEVICE_ADD: { /* this should never happen iDescriptor does not support network devices but for some reason even though we are only listening for USB devices, we still get network devices on macOS*/ if (event->conn_type == CONNECTION_NETWORK) { return; } qDebug() << "Device added: " << QString::fromUtf8(event->udid); QMetaObject::invokeMethod( AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, Q_ARG(QString, QString::fromUtf8(event->udid)), Q_ARG(idevice_connection_type, event->conn_type), Q_ARG(AddType, AddType::Regular)); break; } case IDEVICE_DEVICE_REMOVE: { QMetaObject::invokeMethod(AppContext::sharedInstance(), "removeDevice", Qt::QueuedConnection, Q_ARG(QString, QString(event->udid))); break; } case IDEVICE_DEVICE_PAIRED: { if (event->conn_type == CONNECTION_NETWORK) { qDebug() << "Network devices are not supported but a network device was " "received in event listener. Please report this issue."; return; } qDebug() << "Device paired: " << QString::fromUtf8(event->udid); QMetaObject::invokeMethod( AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, Q_ARG(QString, QString::fromUtf8(event->udid)), Q_ARG(idevice_connection_type, event->conn_type), Q_ARG(AddType, AddType::Pairing)); break; } default: qDebug() << "Unhandled event: " << event->event; } } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void handleCallbackRecovery(const irecv_device_event_t *event, void *userData) { switch (event->type) { case IRECV_DEVICE_ADD: qDebug() << "Recovery device added: "; QMetaObject::invokeMethod(AppContext::sharedInstance(), "addRecoveryDevice", Qt::QueuedConnection, Q_ARG(uint64_t, event->device_info->ecid)); break; case IRECV_DEVICE_REMOVE: qDebug() << "Recovery device removed: "; QMetaObject::invokeMethod(AppContext::sharedInstance(), "removeRecoveryDevice", Qt::QueuedConnection, Q_ARG(uint64_t, event->device_info->ecid)); break; default: printf("Unhandled recovery event: %d\n", event->type); } } irecv_device_event_context_t context; #endif MainWindow *MainWindow::sharedInstance() { static MainWindow instance; return &instance; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); const QSize minSize(900, 600); setMinimumSize(minSize); resize(minSize); m_ZTabWidget = new ZTabWidget(this); m_ZTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false); setContentsMargins(0, 0, 0, 0); #ifdef __APPLE__ setupMacOSWindow(this); setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false); #endif setCentralWidget(m_ZTabWidget); m_mainStackedWidget = new QStackedWidget(); WelcomeWidget *welcomePage = new WelcomeWidget(this); m_deviceManager = new DeviceManagerWidget(this); m_mainStackedWidget->addWidget(welcomePage); m_mainStackedWidget->addWidget(m_deviceManager); connect(m_deviceManager, &DeviceManagerWidget::updateNoDevicesConnected, this, &MainWindow::updateNoDevicesConnected); m_ZTabWidget->addTab(m_mainStackedWidget, "iDevice"); auto *appsWidgetTab = m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); auto *jailbrokenWidget = new JailbrokenWidget(this); m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); m_ZTabWidget->finalizeStyles(); connect( appsWidgetTab, &ZTab::clicked, this, [this](int index) { AppsWidget::sharedInstance()->init(); }, Qt::SingleShotConnection); // settings button ZIconWidget *settingsButton = new ZIconWidget( QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings"); settingsButton->setCursor(Qt::PointingHandCursor); settingsButton->setFixedSize(24, 24); connect(settingsButton, &ZIconWidget::clicked, this, [this]() { SettingsManager::sharedInstance()->showSettingsDialog(); }); ZIconWidget *githubButton = new ZIconWidget( QIcon(":/resources/icons/MdiGithub.png"), "iDescriptor on GitHub"); githubButton->setCursor(Qt::PointingHandCursor); githubButton->setFixedSize(24, 24); connect(githubButton, &ZIconWidget::clicked, this, []() { QDesktopServices::openUrl(QUrl(REPO_URL)); }); m_connectedDeviceCountLabel = new QLabel("iDescriptor: no devices"); m_connectedDeviceCountLabel->setContentsMargins(5, 0, 5, 0); m_connectedDeviceCountLabel->setStyleSheet( "QLabel:hover { background-color : #13131319; }"); ui->statusbar->addWidget(m_connectedDeviceCountLabel); ui->statusbar->setContentsMargins(0, 0, 0, 0); QLabel *appVersionLabel = new QLabel(QString("v%1").arg(APP_VERSION)); appVersionLabel->setContentsMargins(5, 0, 5, 0); appVersionLabel->setStyleSheet( "QLabel:hover { background-color : #13131319; }"); ui->statusbar->addPermanentWidget(appVersionLabel); ui->statusbar->addPermanentWidget(githubButton); ui->statusbar->addPermanentWidget(settingsButton); #ifdef __linux__ QList mounted_iFusePaths = iFuseManager::getMountPoints(); for (const QString &path : mounted_iFusePaths) { auto *p = new iFuseDiskUnmountButton(path); ui->statusbar->addPermanentWidget(p); connect(p, &iFuseDiskUnmountButton::clicked, this, [this, p, path]() { bool ok = iFuseManager::linuxUnmount(path); if (!ok) { QMessageBox::warning(nullptr, "Unmount Failed", "Failed to unmount iFuse at " + path + ". Please try again."); return; } ui->statusbar->removeWidget(p); p->deleteLater(); }); } #endif #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT irecv_error_t res_recovery = irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr); if (res_recovery != IRECV_E_SUCCESS) { qDebug() << "ERROR: Unable to subscribe to recovery device events. " "Error code:" << res_recovery; } qDebug() << "Subscribed to recovery device events successfully."; #endif idevice_error_t res = idevice_event_subscribe(handleCallback, nullptr); if (res != IDEVICE_E_SUCCESS) { qDebug() << "ERROR: Unable to subscribe to device events. Error code:" << res; } qDebug() << "Subscribed to device events successfully."; createMenus(); UpdateProcedure updateProcedure; bool packageManagerManaged = false; bool isPortable = false; bool skipPrerelease = true; #ifdef WIN32 // dynamic portable detection read .portable file in app dir on Windows QString appDir = QApplication::applicationDirPath(); QFile portableFile(appDir + "/.portable"); if (portableFile.exists()) { isPortable = true; } #endif /* struct UpdateProcedure { bool openFile; bool openFileDir; bool quitApp; QString boxInformativeText; QString boxText; }; */ switch (ZUpdater::detectPlatform()) { // todo: adjust for portable case Platform::Windows: updateProcedure = UpdateProcedure{ !isPortable, isPortable, !isPortable, isPortable ? "New portable version downloaded, app location will " "be shown after this message" : "The application will now quit to install the update.", isPortable ? "New portable version downloaded" : "Do you want to install the downloaded update now?", }; break; // todo: adjust for pkg managers case Platform::MacOS: updateProcedure = UpdateProcedure{ false, false, true, "The application will now quit to install the update.", "Do you want to install the downloaded update now?", }; break; case Platform::Linux: // currently only on linux (arch aur) is enabled #ifdef PACKAGE_MANAGER_MANAGED packageManagerManaged = true; #endif updateProcedure = UpdateProcedure{ false, true, false, "There is new update available", "Would you like to download it now?", }; break; default: updateProcedure = UpdateProcedure{ false, false, false, "", "", }; } // FIXME: fix repo name m_updater = new ZUpdater("uncor3/libtest", APP_VERSION, "iDescriptor", updateProcedure, isPortable, packageManagerManaged, skipPrerelease, this); #if defined(PACKAGE_MANAGER_MANAGED) && defined(__linux__) m_updater->setPackageManagerManagedMessage( QString( "You seem to have installed iDescriptor using a package manager. " "Please use %1 to update it.") .arg(PACKAGE_MANAGER_HINT)); #endif SettingsManager::sharedInstance()->doIfEnabled( SettingsManager::Setting::AutoCheckUpdates, [this]() { qDebug() << "Checking for updates..."; m_updater->checkForUpdates(); }); } void MainWindow::createMenus() { #ifdef Q_OS_MAC QMenu *actionsMenu = menuBar()->addMenu("&Actions"); QAction *aboutAct = new QAction("&About iDescriptor", this); connect(aboutAct, &QAction::triggered, this, [this]() { QMessageBox::about(this, "iDescriptor", "A free and open-source idevice management tool."); }); actionsMenu->addAction(aboutAct); #endif } void MainWindow::updateNoDevicesConnected() { qDebug() << "Is there no devices connected? " << AppContext::sharedInstance()->noDevicesConnected(); if (AppContext::sharedInstance()->noDevicesConnected()) { m_connectedDeviceCountLabel->setText("iDescriptor: no devices"); return m_mainStackedWidget->setCurrentIndex(0); // Show Welcome page } int deviceCount = AppContext::sharedInstance()->getConnectedDeviceCount(); m_connectedDeviceCountLabel->setText( "iDescriptor: " + QString::number(deviceCount) + (deviceCount == 1 ? " device" : " devices") + " connected"); m_mainStackedWidget->setCurrentIndex(1); // Show device list page } MainWindow::~MainWindow() { idevice_event_unsubscribe(); #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT irecv_device_event_unsubscribe(context); #endif delete ui; delete m_updater; sleep(2); // Give some time for cleanup to finish }