diff --git a/CMakeLists.txt b/CMakeLists.txt index b6d33cb..0eaa5d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,11 +203,31 @@ find_package(Qt6 REQUIRED COMPONENTS Multimedia) find_package(Qt6 REQUIRED COMPONENTS MultimediaWidgets) -qt_add_executable(iDescriptor +if (WIN32) +# todo + set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/resources/todo.rc") + qt_add_executable(iDescriptor + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ${app_icon_resource_windows} +) +elseif (APPLE) + set(MACOSX_BUNDLE_ICON_FILE icon.icns) + set(app_icon_macos "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons/app-icon/icon.icns") + set_source_files_properties(${app_icon_macos} PROPERTIES + MACOSX_PACKAGE_LOCATION "Resources") + + qt_add_executable(iDescriptor + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ${app_icon_macos} +) +else() + qt_add_executable(iDescriptor MANUAL_FINALIZATION ${PROJECT_SOURCES} ) - +endif() target_link_libraries(iDescriptor PRIVATE Qt${QT_VERSION_MAJOR}::Widgets diff --git a/resources.qrc b/resources.qrc index 7c562b0..a418045 100644 --- a/resources.qrc +++ b/resources.qrc @@ -7,6 +7,14 @@ resources/icons/IcOutlinePowerSettingsNew.png resources/icons/HugeiconsWrench01.png resources/icons/IcTwotoneRestartAlt.png + resources/icons/MaterialSymbolsArrowUpwardAltRounded.png + resources/icons/LetsIconsImport.png + resources/icons/MaterialSymbolsArrowLeftAlt.png + resources/icons/MaterialSymbolsArrowRightAlt.png + resources/icons/PhExport.png + resources/icons/MaterialSymbolsLightKeyboardReturn.png + resources/icons/MaterialSymbolsFavorite.png + resources/icons/MdiLightMagnify.png qml/MapView.qml diff --git a/resources/icons/LetsIconsImport.png b/resources/icons/LetsIconsImport.png new file mode 100644 index 0000000..42c77ec Binary files /dev/null and b/resources/icons/LetsIconsImport.png differ diff --git a/resources/icons/MaterialSymbolsArrowLeftAlt.png b/resources/icons/MaterialSymbolsArrowLeftAlt.png new file mode 100644 index 0000000..d7a4333 Binary files /dev/null and b/resources/icons/MaterialSymbolsArrowLeftAlt.png differ diff --git a/resources/icons/MaterialSymbolsArrowRightAlt.png b/resources/icons/MaterialSymbolsArrowRightAlt.png new file mode 100644 index 0000000..ccb9868 Binary files /dev/null and b/resources/icons/MaterialSymbolsArrowRightAlt.png differ diff --git a/resources/icons/MaterialSymbolsArrowUpwardAltRounded.png b/resources/icons/MaterialSymbolsArrowUpwardAltRounded.png new file mode 100644 index 0000000..40ba23f Binary files /dev/null and b/resources/icons/MaterialSymbolsArrowUpwardAltRounded.png differ diff --git a/resources/icons/MaterialSymbolsFavorite.png b/resources/icons/MaterialSymbolsFavorite.png new file mode 100644 index 0000000..33e0fa3 Binary files /dev/null and b/resources/icons/MaterialSymbolsFavorite.png differ diff --git a/resources/icons/MaterialSymbolsLightKeyboardReturn.png b/resources/icons/MaterialSymbolsLightKeyboardReturn.png new file mode 100644 index 0000000..df5a40a Binary files /dev/null and b/resources/icons/MaterialSymbolsLightKeyboardReturn.png differ diff --git a/resources/icons/MdiLightMagnify.png b/resources/icons/MdiLightMagnify.png new file mode 100644 index 0000000..367f43a Binary files /dev/null and b/resources/icons/MdiLightMagnify.png differ diff --git a/resources/icons/PhExport.png b/resources/icons/PhExport.png new file mode 100644 index 0000000..8358f61 Binary files /dev/null and b/resources/icons/PhExport.png differ diff --git a/resources/icons/app-icon/icon.icns b/resources/icons/app-icon/icon.icns new file mode 100644 index 0000000..eed2f3d Binary files /dev/null and b/resources/icons/app-icon/icon.icns differ diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 2a7123f..de9f08b 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -218,7 +218,8 @@ void AfcExplorerWidget::onFileListContextMenu(const QPoint &pos) bool isDir = item->data(Qt::UserRole).toBool(); if (isDir) - return; // Only export files + return; // TODO: Implement directory export later - Only export files + // for now QMenu menu; QAction *exportAction = menu.addAction("Export"); @@ -253,7 +254,7 @@ void AfcExplorerWidget::onExportClicked() if (selectedItems.isEmpty()) return; - // Only files (not directories) + // Only files (not directories) - TODO: Implement directory export later QList filesToExport; for (QListWidgetItem *item : selectedItems) { if (!item->data(Qt::UserRole).toBool()) @@ -320,7 +321,11 @@ void AfcExplorerWidget::exportSelectedFile(QListWidgetItem *item, } } -// TODO : abstract to services +/* + FIXME : abstract to services + even though we are using safe wrappers, + we better move this to services +*/ int AfcExplorerWidget::export_file_to_path(afc_client_t afc, const char *device_path, const char *local_path) @@ -348,32 +353,23 @@ int AfcExplorerWidget::export_file_to_path(afc_client_t afc, } fclose(out); - afc_file_close(afc, handle); + ServiceManager::safeAfcFileClose(m_device, handle); return 0; } +// should be disabled if there is an error loading afc void AfcExplorerWidget::onImportClicked() { - // TODO: check devices - - // Select one or more files to import QStringList fileNames = QFileDialog::getOpenFileNames(this, "Import Files"); if (fileNames.isEmpty()) return; - // Use current breadcrumb directory as target QString currPath = "/"; if (!m_history.isEmpty()) currPath = m_history.top(); if (!currPath.endsWith("/")) currPath += "/"; - // if (!device || !client || !serviceDesc) - // { - // qDebug() << "Failed to connect to device or lockdown service"; - // return; - // } - // Import each file for (const QString &localPath : fileNames) { QFileInfo fi(localPath); @@ -437,16 +433,13 @@ void AfcExplorerWidget::setupFileExplorer() m_explorer->setStyleSheet("border : none;"); // Export/Import buttons layout - QHBoxLayout *exportLayout = new QHBoxLayout(); - m_exportBtn = new QPushButton("Export"); - m_importBtn = new QPushButton("Import"); - m_addToFavoritesBtn = new QPushButton("Add to Favorites"); - exportLayout->addWidget(m_exportBtn); - exportLayout->addWidget(m_importBtn); - exportLayout->addWidget(m_addToFavoritesBtn); - exportLayout->setContentsMargins(0, 0, 0, 0); - exportLayout->addStretch(); - explorerLayout->addLayout(exportLayout); + m_exportBtn = + new ZIconWidget(QIcon(":/resources/icons/PhExport.png"), "Export"); + m_importBtn = new ZIconWidget( + QIcon(":/resources/icons/LetsIconsImport.png"), "Import"); + m_addToFavoritesBtn = + new ZIconWidget(QIcon(":/resources/icons/MaterialSymbolsFavorite.png"), + "Add to Favorites"); // Navigation layout (Address Bar with embedded icons) m_navWidget = new QWidget(); @@ -474,17 +467,19 @@ void AfcExplorerWidget::setupFileExplorer() // red;"); leftNavLayout->setContentsMargins(0, 0, 0, 0); leftNavLayout->setSpacing(1); - - m_backButton = new ClickableIconWidget( - QIcon::fromTheme("go-previous", QIcon("←")), "Go Back"); + // rename to ziconwidget + m_backButton = new ZIconWidget( + QIcon(":/resources/icons/MaterialSymbolsArrowLeftAlt.png"), "Go Back"); m_backButton->setEnabled(false); - m_forwardButton = new ClickableIconWidget( - QIcon::fromTheme("go-next", QIcon("→")), "Go Forward"); + m_forwardButton = new ZIconWidget( + QIcon(":/resources/icons/MaterialSymbolsArrowRightAlt.png"), + "Go Forward"); m_forwardButton->setEnabled(false); - m_enterButton = new ClickableIconWidget( - QIcon::fromTheme("go-jump", QIcon("⏎")), "Navigate to path"); + m_enterButton = new ZIconWidget( + QIcon(":/resources/icons/MaterialSymbolsLightKeyboardReturn.png"), + "Navigate to path"); m_addressBar = new QLineEdit(); m_addressBar->setPlaceholderText("Enter path..."); @@ -495,6 +490,9 @@ void AfcExplorerWidget::setupFileExplorer() leftNavLayout->addWidget(m_forwardButton); navLayout->addWidget(explorerLeftSideNavButtons); navLayout->addWidget(m_addressBar); + navLayout->addWidget(m_importBtn); + navLayout->addWidget(m_exportBtn); + navLayout->addWidget(m_addToFavoritesBtn); navLayout->addWidget(m_enterButton); // Add the container layout (which centers navWidget) to the main layout @@ -515,24 +513,28 @@ void AfcExplorerWidget::setupFileExplorer() explorerLayout->addWidget(m_fileList); // Connect buttons and actions - connect(m_backButton, &ClickableIconWidget::clicked, this, + connect(m_backButton, &ZIconWidget::clicked, this, &AfcExplorerWidget::goBack); - connect(m_forwardButton, &ClickableIconWidget::clicked, this, + connect(m_forwardButton, &ZIconWidget::clicked, this, &AfcExplorerWidget::goForward); - connect(m_enterButton, &ClickableIconWidget::clicked, this, + connect(m_enterButton, &ZIconWidget::clicked, this, &AfcExplorerWidget::onAddressBarReturnPressed); connect(m_addressBar, &QLineEdit::returnPressed, this, &AfcExplorerWidget::onAddressBarReturnPressed); connect(m_fileList, &QListWidget::itemDoubleClicked, this, &AfcExplorerWidget::onItemDoubleClicked); - connect(m_exportBtn, &QPushButton::clicked, this, + connect(m_exportBtn, &ZIconWidget::clicked, this, &AfcExplorerWidget::onExportClicked); - connect(m_importBtn, &QPushButton::clicked, this, + connect(m_importBtn, &ZIconWidget::clicked, this, &AfcExplorerWidget::onImportClicked); - connect(m_addToFavoritesBtn, &QPushButton::clicked, this, + connect(m_addToFavoritesBtn, &ZIconWidget::clicked, this, &AfcExplorerWidget::onAddToFavoritesClicked); + connect(m_fileList->selectionModel(), + &QItemSelectionModel::selectionChanged, this, + &AfcExplorerWidget::updateButtonStates); updateNavigationButtons(); + updateButtonStates(); // Initialize button states updateNavStyles(); } @@ -562,8 +564,10 @@ void AfcExplorerWidget::saveFavoritePlace(const QString &path, void AfcExplorerWidget::updateNavStyles() { - QColor bgColor = isDarkMode() ? qApp->palette().color(QPalette::Light) - : qApp->palette().color(QPalette::Dark); + bool isDark = isDarkMode(); + QColor lightColor = qApp->palette().color(QPalette::Light); + QColor darkColor = qApp->palette().color(QPalette::Dark); + QColor bgColor = isDark ? lightColor : darkColor; QColor borderColor = qApp->palette().color(QPalette::Mid); QColor accentColor = qApp->palette().color(QPalette::Highlight); @@ -585,9 +589,27 @@ void AfcExplorerWidget::updateNavStyles() // Update address bar styles to complement the nav widget QString addressBarStyles = QString("QLineEdit { background-color: %1; border-radius: 10px; " - "border: 1px solid %2; }") - .arg(bgColor.name()) - .arg(borderColor.lighter().name()); + "border: 1px solid %2; padding: 2px 4px; color: %3; }" + "QLineEdit:focus {border: 3px solid %4; }") + .arg(isDark ? QColor(Qt::white).name() : QColor(Qt::black).name()) + .arg(borderColor.lighter().name()) + .arg(isDark ? QColor(Qt::black).name() : QColor(Qt::white).name()) + .arg(COLOR_ACCENT_BLUE.name()); m_addressBar->setStyleSheet(addressBarStyles); +} + +void AfcExplorerWidget::updateButtonStates() +{ + QList selectedItems = m_fileList->selectedItems(); + + // Export is only enabled if non-directory items are selected + bool hasExportableFiles = false; + for (QListWidgetItem *item : selectedItems) { + if (!item->data(Qt::UserRole).toBool()) { // Not a directory + hasExportableFiles = true; + break; + } + } + m_exportBtn->setEnabled(hasExportableFiles); } \ No newline at end of file diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h index 0446586..ae59c60 100644 --- a/src/afcexplorerwidget.h +++ b/src/afcexplorerwidget.h @@ -43,17 +43,17 @@ private slots: private: QWidget *m_explorer; QWidget *m_navWidget; - QPushButton *m_exportBtn; - QPushButton *m_importBtn; - QPushButton *m_addToFavoritesBtn; + ZIconWidget *m_exportBtn; + ZIconWidget *m_importBtn; + ZIconWidget *m_addToFavoritesBtn; QListWidget *m_fileList; QStack m_history; QStack m_forwardHistory; int m_currentHistoryIndex; QLineEdit *m_addressBar; - ClickableIconWidget *m_backButton; - ClickableIconWidget *m_forwardButton; - ClickableIconWidget *m_enterButton; + ZIconWidget *m_backButton; + ZIconWidget *m_forwardButton; + ZIconWidget *m_enterButton; iDescriptorDevice *m_device; // Current AFC mode @@ -73,6 +73,7 @@ private: int import_file_to_device(afc_client_t afc, const char *device_path, const char *local_path); void updateNavStyles(); + void updateButtonStates(); }; #endif // AFCEXPLORER_H diff --git a/src/appinstalldialog.cpp b/src/appinstalldialog.cpp index 12d0678..be15062 100644 --- a/src/appinstalldialog.cpp +++ b/src/appinstalldialog.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -170,8 +171,23 @@ void AppInstallDialog::onInstallClicked() m_actionButton->deleteLater(); m_actionButton = nullptr; - m_tempDir = QDir::tempPath(); - startDownloadProcess(m_bundleId, m_tempDir, buttonIndex, false); + if (m_tempDir) { + delete m_tempDir; + m_tempDir = nullptr; + } + // Create a new temporary directory for each installation + m_tempDir = new QTemporaryDir(); + if (!m_tempDir->isValid()) { + m_statusLabel->setText("Failed to create temporary directory"); + m_statusLabel->setStyleSheet( + "font-size: 14px; color: #FF3B30; padding: 5px;"); + QMessageBox::critical( + this, "Error", + "Could not create temporary directory for download."); + return; + } + + startDownloadProcess(m_bundleId, m_tempDir->path(), buttonIndex, false); connect(this, &AppDownloadBaseDialog::downloadFinished, this, [this, selectedDevice](bool success) { if (success) { @@ -182,7 +198,7 @@ void AppInstallDialog::onInstallClicked() it. */ // Find the actual downloaded IPA file - QDir outDir = m_tempDir; + QDir outDir(m_tempDir->path()); QStringList filters; filters << m_bundleId + "*.ipa"; QStringList matches = @@ -226,3 +242,11 @@ void AppInstallDialog::reject() AppDownloadBaseDialog::reject(); } + +AppInstallDialog::~AppInstallDialog() +{ + if (m_tempDir) { + delete m_tempDir; + m_tempDir = nullptr; + } +} \ No newline at end of file diff --git a/src/appinstalldialog.h b/src/appinstalldialog.h index 1a522d3..982dfaa 100644 --- a/src/appinstalldialog.h +++ b/src/appinstalldialog.h @@ -6,6 +6,7 @@ #include #include #include +#include class AppInstallDialog : public AppDownloadBaseDialog { @@ -15,6 +16,7 @@ public: const QString &description, const QString &bundleId, QWidget *parent = nullptr); + ~AppInstallDialog(); protected: void reject() override; @@ -27,7 +29,7 @@ private: QString m_bundleId; QLabel *m_statusLabel; QFutureWatcher *m_installWatcher; - QString m_tempDir; + QTemporaryDir *m_tempDir; void updateDeviceList(); void performInstallation(const QString &ipaPath, const QString &deviceUdid); }; diff --git a/src/appswidget.cpp b/src/appswidget.cpp index 9516599..00da097 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -61,13 +61,7 @@ void AppsWidget::setupUI() m_loginButton = new QPushButton(); m_searchEdit = new ZLineEdit(); - m_searchEdit->setMaximumWidth(400); - m_searchEdit->setStyleSheet("QLineEdit { " - " padding: 8px; " - " border: 1px solid #ccc; " - " border-radius: 4px; " - " font-size: 14px; " - "}"); + m_searchEdit->setMaximumWidth(350); // --- Status and Login Button --- m_manager = AppStoreManager::sharedInstance(); @@ -86,10 +80,10 @@ void AppsWidget::setupUI() m_statusLabel->setStyleSheet("font-size: 14px; color: #666;"); mainLayout->addWidget(headerWidget); - - QAction *searchAction = m_searchEdit->addAction( - this->style()->standardIcon(QStyle::SP_FileDialogContentsView), - QLineEdit::TrailingPosition); + // todo: implement theme aware icon + QAction *searchAction = + m_searchEdit->addAction(QIcon(":/resources/icons/MdiLightMagnify.png"), + QLineEdit::TrailingPosition); searchAction->setToolTip("Search"); connect(searchAction, &QAction::triggered, this, &AppsWidget::performSearch); diff --git a/src/core/services/avahi/avahi_service.cpp b/src/core/services/avahi/avahi_service.cpp index 54b46ba..966db94 100644 --- a/src/core/services/avahi/avahi_service.cpp +++ b/src/core/services/avahi/avahi_service.cpp @@ -128,7 +128,6 @@ void AvahiService::browseCallback(AvahiServiceBrowser *browser, switch (event) { case AVAHI_BROWSER_NEW: - qDebug() << "Found Apple device:" << name; if (!avahi_service_resolver_new(service->m_client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0, resolveCallback, diff --git a/src/core/services/dnssd/dnssd_service.cpp b/src/core/services/dnssd/dnssd_service.cpp index 13e1436..97c049e 100644 --- a/src/core/services/dnssd/dnssd_service.cpp +++ b/src/core/services/dnssd/dnssd_service.cpp @@ -100,8 +100,6 @@ void DNSSD_API DnssdService::browseCallback( DnssdService *service = static_cast(context); if (flags & kDNSServiceFlagsAdd) { - qDebug() << "Found Apple device:" << serviceName; - // Start resolving the service DNSServiceRef resolveRef; DNSServiceErrorType err = diff --git a/src/deviceimagewidget.cpp b/src/deviceimagewidget.cpp index 3de8fc4..526aeb1 100644 --- a/src/deviceimagewidget.cpp +++ b/src/deviceimagewidget.cpp @@ -157,7 +157,7 @@ int DeviceImageWidget::getIosVersionFromDevice() const /* this method is only here to calculate the screen area so that wallpaper perfectly fits to the screen size - it's costy so if you want to add a new mock run + it's costy so if you want to add a new mockup run through this method qDebug the result and add it to createCompositeImage example : screenRect = QRect(152, 79, 195, 296); */ @@ -204,12 +204,7 @@ QRect DeviceImageWidget::findScreenArea(const QPixmap &mockup) const bottom++; } - // Add a small margin to avoid drawing over the bezel anti-aliasing - int margin = 2; - - return QRect(left + 1 + margin, top + 1 + margin, - right - left - 2 - (margin * 2), - bottom - top - 2 - (margin * 2)); + return QRect(left + 1, top + 1, right - left - 2, bottom - top - 2); } QPixmap DeviceImageWidget::createCompositeImage() const @@ -239,19 +234,19 @@ QPixmap DeviceImageWidget::createCompositeImage() const QString::fromStdString(m_device->deviceInfo.productType)); if (mockupName == "3") { - screenRect = QRect(152, 79, 195, 296); + screenRect = QRect(145, 72, 209, 310); } else if (mockupName == "4") { - screenRect = QRect(421, 188, 366, 534); + screenRect = QRect(414, 181, 380, 548); } else if (mockupName == "5") { - screenRect = QRect(34, 113, 290, 523); + screenRect = QRect(27, 106, 304, 537); } else if (mockupName == "6") { - screenRect = QRect(75, 355, 1265, 2256); + screenRect = QRect(68, 348, 1279, 2270); } else if (mockupName == "x") { - screenRect = QRect(252, 436, 2375, 4989); + screenRect = QRect(245, 429, 2389, 5003); } else if (mockupName == "15") { - screenRect = QRect(22, 56, 323, 674); + screenRect = QRect(15, 49, 337, 688); } else if (mockupName == "16") { - screenRect = QRect(24, 61, 319, 668); + screenRect = QRect(17, 54, 333, 682); } else { // Fallback for unknown devices screenRect = QRect(mockup.width() * 0.12, mockup.height() * 0.08, diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 10ecc47..33644dc 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -54,23 +54,23 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) actionsLayout->setContentsMargins(1, 1, 1, 1); actionsLayout->setSpacing(10); - ClickableIconWidget *shutdownBtn = new ClickableIconWidget( + ZIconWidget *shutdownBtn = new ZIconWidget( QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown", this); shutdownBtn->setIconSize(QSize(20, 20)); - connect(shutdownBtn, &ClickableIconWidget::clicked, this, + connect(shutdownBtn, &ZIconWidget::clicked, this, [device]() { ToolboxWidget::shutdownDevice(device); }); - ClickableIconWidget *restartBtn = new ClickableIconWidget( + ZIconWidget *restartBtn = new ZIconWidget( QIcon(":/resources/icons/IcTwotoneRestartAlt.png"), "Restart", this); restartBtn->setIconSize(QSize(20, 20)); - connect(restartBtn, &ClickableIconWidget::clicked, this, + connect(restartBtn, &ZIconWidget::clicked, this, [device]() { ToolboxWidget::restartDevice(device); }); - ClickableIconWidget *recoveryBtn = new ClickableIconWidget( + ZIconWidget *recoveryBtn = new ZIconWidget( QIcon(":/resources/icons/HugeiconsWrench01.png"), "Recovery", this); recoveryBtn->setIconSize(QSize(20, 20)); - connect(recoveryBtn, &ClickableIconWidget::clicked, this, + connect(recoveryBtn, &ZIconWidget::clicked, this, [device]() { ToolboxWidget::_enterRecoveryMode(device); }); actionsLayout->addWidget(shutdownBtn); diff --git a/src/fileexportdialog.cpp b/src/fileexportdialog.cpp index 69d428b..287df4c 100644 --- a/src/fileexportdialog.cpp +++ b/src/fileexportdialog.cpp @@ -1,5 +1,6 @@ #include "fileexportdialog.h" #include +#include #include #include #include @@ -8,9 +9,10 @@ #include // TODO: needs progress bar improvements -FileExportDialog::FileExportDialog(QWidget *parent) +FileExportDialog::FileExportDialog(const QString &exportDir, QWidget *parent) : QDialog(parent), m_progressBar(nullptr), m_statusLabel(nullptr), - m_fileLabel(nullptr), m_cancelButton(nullptr), m_layout(nullptr) + m_fileLabel(nullptr), m_cancelButton(nullptr), m_layout(nullptr), + m_exportDir(exportDir) { setupUI(); } @@ -136,9 +138,17 @@ void FileExportDialog::showCompletionMessage(int successful, int failed) QString message; if (failed == 0) { - message = - QString("Successfully exported all %1 files!").arg(successful); - QMessageBox::information(parentWidget(), "Export Complete", message); + // ASK USER TO OPEN FOLDER + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + parentWidget(), "Export Complete", + QString("Successfully exported all %1 files! Would you like to " + "open the output folder ?") + .arg(successful), + QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::Yes) { + QDesktopServices::openUrl(QUrl::fromLocalFile(m_exportDir)); + } } else { message = QString("Export completed with %1 successful and %2 failed files.") diff --git a/src/fileexportdialog.h b/src/fileexportdialog.h index ed8e924..ded27fd 100644 --- a/src/fileexportdialog.h +++ b/src/fileexportdialog.h @@ -15,7 +15,8 @@ class FileExportDialog : public QDialog Q_OBJECT public: - explicit FileExportDialog(QWidget *parent = nullptr); + explicit FileExportDialog(const QString &exportDir, + QWidget *parent = nullptr); ~FileExportDialog() override; public slots: @@ -38,6 +39,7 @@ private: QLabel *m_fileLabel; QPushButton *m_cancelButton; QVBoxLayout *m_layout; + QString m_exportDir; }; #endif // FILEEXPORTDIALOG_H \ No newline at end of file diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index b43634a..ca75661 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -4,6 +4,7 @@ #include "mediapreviewdialog.h" #include "photoexportmanager.h" #include "photomodel.h" +#include "servicemanager.h" #include #include #include @@ -19,7 +20,6 @@ #include #include #include -#include "servicemanager.h" void GalleryWidget::load() { @@ -209,7 +209,7 @@ void GalleryWidget::onExportSelected() // Create export dialog and connect signals // todo:cleanup - auto *exportDialog = new FileExportDialog(this); + auto *exportDialog = new FileExportDialog(exportDir, this); // Connect PhotoExportManager signals to FileExportDialog connect(m_exportManager, &PhotoExportManager::exportStarted, exportDialog, @@ -267,7 +267,7 @@ void GalleryWidget::onExportAll() // Create export dialog and connect signals // todo:cleanup - auto *exportDialog = new FileExportDialog(this); + auto *exportDialog = new FileExportDialog(exportDir, this); // Connect PhotoExportManager signals to FileExportDialog connect(m_exportManager, &PhotoExportManager::exportStarted, exportDialog, @@ -399,8 +399,7 @@ void GalleryWidget::loadAlbumList() { // Get DCIM directory contents qDebug() << "Loading album list from /DCIM"; - AFCFileTree dcimTree = - ServiceManager::safeGetFileTree(m_device, "/DCIM"); + AFCFileTree dcimTree = ServiceManager::safeGetFileTree(m_device, "/DCIM"); if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index f8ccf13..923609c 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -61,12 +61,12 @@ protected: } }; -class ClickableIconWidget : public QWidget +class ZIconWidget : public QWidget { Q_OBJECT public: - ClickableIconWidget(const QIcon &icon, const QString &tooltip, - QWidget *parent = nullptr) + ZIconWidget(const QIcon &icon, const QString &tooltip, + QWidget *parent = nullptr) : QWidget(parent), m_icon(icon), m_iconSize(24, 24), m_pressed(false) { setToolTip(tooltip); @@ -190,6 +190,7 @@ enum class iDescriptorTool { TouchIdTest, FaceIdTest, UnmountDevImage, + NetworkDevices, Unknown, iFuse }; diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 8b5041f..567db6d 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -24,7 +24,7 @@ AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, const QString &version, QWidget *parent) : QGroupBox(parent), m_appName(appName), m_bundleId(bundleId), - m_version(version), m_selected(false), m_hovered(false) + m_version(version), m_selected(false) { setFixedHeight(60); setMinimumWidth(100); @@ -122,41 +122,22 @@ void AppTabWidget::mousePressEvent(QMouseEvent *event) emit clicked(); } -void AppTabWidget::enterEvent(QEnterEvent *event) -{ - Q_UNUSED(event) - m_hovered = true; - updateStyles(); -} - -void AppTabWidget::leaveEvent(QEvent *event) -{ - Q_UNUSED(event) - m_hovered = false; - updateStyles(); -} - void AppTabWidget::updateStyles() { - // QStyleHints::colorScheme() - QString borderStyle; - // QColor bgColor = qApp->palette().color(QPalette::Window); + QString style; QColor bgColor = isDarkMode() ? qApp->palette().color(QPalette::Light) : qApp->palette().color(QPalette::Dark); - qDebug() << styleSheet(); if (m_selected) { - borderStyle = "QGroupBox { background-color: " + - qApp->palette().color(QPalette::Highlight).name() + - "; border-radius: " - "10px; border : 1px solid " + - bgColor.lighter().name() + "; }"; + style = "QGroupBox { background-color: " + COLOR_ACCENT_BLUE.name() + + "; border-radius: " + "10px; border : 1px solid " + + bgColor.lighter().name() + "; }"; } else { - borderStyle = "QGroupBox { background-color: " + bgColor.name() + - "; border-radius: 10px; border: 1px solid " + - bgColor.lighter().name() + "; }"; + style = "QGroupBox { background-color: " + bgColor.name() + + "; border-radius: 10px; border: 1px solid " + + bgColor.lighter().name() + "; }"; } - // update(); - setStyleSheet(borderStyle); + setStyleSheet(style); } InstalledAppsWidget::InstalledAppsWidget(iDescriptorDevice *device, @@ -547,7 +528,6 @@ void InstalledAppsWidget::createAppTab(const QString &appName, new AppTabWidget(appName, bundleId, version, this); connect(tabWidget, &AppTabWidget::clicked, this, &InstalledAppsWidget::onAppTabClicked); - m_appTabs.append(tabWidget); // Remove the stretch before adding the new tab m_tabLayout->removeItem(m_tabLayout->itemAt(m_tabLayout->count() - 1)); diff --git a/src/installedappswidget.h b/src/installedappswidget.h index a01d302..8799cee 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -45,8 +45,6 @@ signals: protected: void mousePressEvent(QMouseEvent *event) override; - void enterEvent(QEnterEvent *event) override; - void leaveEvent(QEvent *event) override; private: void fetchAppIcon(); @@ -56,7 +54,6 @@ private: QString m_bundleId; QString m_version; bool m_selected = false; - bool m_hovered = false; QLabel *m_iconLabel; QLabel *m_nameLabel; diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index 29a5fa1..ccd7b74 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -22,6 +22,7 @@ #include #include +// TODO: theming is broken JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} { QHBoxLayout *mainLayout = new QHBoxLayout(this); diff --git a/src/networkdeviceswidget.cpp b/src/networkdeviceswidget.cpp new file mode 100644 index 0000000..2154d34 --- /dev/null +++ b/src/networkdeviceswidget.cpp @@ -0,0 +1,230 @@ +#include "networkdeviceswidget.h" + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include +#include +#include +#include +#include + +NetworkDevicesWidget::NetworkDevicesWidget(QWidget *parent) : QWidget(parent) +{ + setWindowTitle("Network Devices - iDescriptor"); + setupUI(); + +#ifdef __linux__ + m_networkProvider = new AvahiService(this); + connect(m_networkProvider, &AvahiService::deviceAdded, this, + &NetworkDevicesWidget::onWirelessDeviceAdded); + connect(m_networkProvider, &AvahiService::deviceRemoved, this, + &NetworkDevicesWidget::onWirelessDeviceRemoved); +#else + m_networkProvider = new DnssdService(this); + connect(m_networkProvider, &DnssdService::deviceAdded, this, + &NetworkDevicesWidget::onWirelessDeviceAdded); + connect(m_networkProvider, &DnssdService::deviceRemoved, this, + &NetworkDevicesWidget::onWirelessDeviceRemoved); +#endif + + // Start scanning for network devices + m_networkProvider->startBrowsing(); + + // Initial device list update + updateDeviceList(); +} + +NetworkDevicesWidget::~NetworkDevicesWidget() +{ + if (m_networkProvider) { + m_networkProvider->stopBrowsing(); + } +} + +void NetworkDevicesWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->setSpacing(10); + + // Status label + m_statusLabel = new QLabel("Scanning for network devices..."); + QFont statusFont = m_statusLabel->font(); + statusFont.setPointSize(12); + statusFont.setWeight(QFont::Medium); + m_statusLabel->setFont(statusFont); + m_statusLabel->setAlignment(Qt::AlignCenter); + mainLayout->addWidget(m_statusLabel); + + // Device group + m_deviceGroup = new QGroupBox("Network Devices"); + QFont groupFont = m_deviceGroup->font(); + groupFont.setPointSize(14); + groupFont.setWeight(QFont::Bold); + m_deviceGroup->setFont(groupFont); + + QVBoxLayout *groupLayout = new QVBoxLayout(m_deviceGroup); + groupLayout->setContentsMargins(5, 15, 5, 5); + groupLayout->setSpacing(0); + + // Scroll area + m_scrollArea = new QScrollArea(); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setMinimumHeight(200); + m_scrollArea->setMaximumHeight(400); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + /* FIXME: We need a better approach to theme awareness */ + connect(qApp, &QApplication::paletteChanged, this, [this]() { + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + }); + + // Scroll content + m_scrollContent = new QWidget(); + m_deviceLayout = new QVBoxLayout(m_scrollContent); + m_deviceLayout->setContentsMargins(5, 5, 5, 5); + m_deviceLayout->setSpacing(8); + m_deviceLayout->addStretch(); + + m_scrollArea->setWidget(m_scrollContent); + groupLayout->addWidget(m_scrollArea); + + mainLayout->addWidget(m_deviceGroup); + mainLayout->addStretch(); +} + +void NetworkDevicesWidget::createDeviceCard(const NetworkDevice &device) +{ + // Main card frame + QWidget *card = new QWidget(); + + QVBoxLayout *cardLayout = new QVBoxLayout(card); + cardLayout->setContentsMargins(12, 10, 12, 10); + cardLayout->setSpacing(4); + + // Device name (primary) + QLabel *nameLabel = new QLabel(device.name); + nameLabel->setWordWrap(true); + QFont nameFont = nameLabel->font(); + nameFont.setPointSize(13); + nameFont.setWeight(QFont::Medium); + nameLabel->setFont(nameFont); + QPalette namePalette = nameLabel->palette(); + namePalette.setColor(QPalette::WindowText, + palette().color(QPalette::WindowText)); + nameLabel->setPalette(namePalette); + + // Device info container + QWidget *infoContainer = new QWidget(); + QHBoxLayout *infoLayout = new QHBoxLayout(infoContainer); + infoLayout->setContentsMargins(0, 0, 0, 0); + infoLayout->setSpacing(12); + + // Address info + QLabel *addressLabel = new QLabel(QString("IP: %1").arg(device.address)); + QFont addressFont = addressLabel->font(); + addressFont.setPointSize(11); + addressLabel->setFont(addressFont); + QPalette addressPalette = addressLabel->palette(); + QColor secondaryColor = palette().color(QPalette::WindowText); + secondaryColor.setAlpha(180); + addressPalette.setColor(QPalette::WindowText, secondaryColor); + addressLabel->setPalette(addressPalette); + + // Port info + QLabel *portLabel = new QLabel(QString("Port: %1").arg(device.port)); + portLabel->setFont(addressFont); + portLabel->setPalette(addressPalette); + + infoLayout->addWidget(addressLabel); + infoLayout->addWidget(portLabel); + infoLayout->addStretch(); + + // Status indicator + QLabel *statusIndicator = new QLabel("●"); + QFont statusFont = statusIndicator->font(); + statusFont.setPointSize(12); + statusIndicator->setFont(statusFont); + QPalette statusPalette = statusIndicator->palette(); + statusPalette.setColor(QPalette::WindowText, + QColor(52, 199, 89)); // iOS green + statusIndicator->setPalette(statusPalette); + + infoLayout->addWidget(statusIndicator); + + cardLayout->addWidget(nameLabel); + cardLayout->addWidget(infoContainer); + + // Store the device info as property for later removal + card->setProperty("deviceName", device.name); + card->setProperty("deviceAddress", device.address); + + // Insert before the stretch + m_deviceLayout->insertWidget(m_deviceLayout->count() - 1, card); + m_deviceCards.append(card); +} + +void NetworkDevicesWidget::clearDeviceCards() +{ + for (QWidget *card : m_deviceCards) { + card->deleteLater(); + } + m_deviceCards.clear(); +} + +void NetworkDevicesWidget::updateDeviceList() +{ + clearDeviceCards(); + + QList devices = m_networkProvider->getNetworkDevices(); + + if (devices.isEmpty()) { + m_statusLabel->setText("No network devices found"); + } else { + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(devices.count())); + + for (const NetworkDevice &device : devices) { + createDeviceCard(device); + } + } +} + +void NetworkDevicesWidget::onWirelessDeviceAdded(const NetworkDevice &device) +{ + createDeviceCard(device); + + // Update status + int deviceCount = m_deviceCards.count(); + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(deviceCount)); +} + +void NetworkDevicesWidget::onWirelessDeviceRemoved(const QString &deviceName) +{ + // Find and remove the corresponding card + for (int i = 0; i < m_deviceCards.count(); ++i) { + QWidget *card = m_deviceCards[i]; + if (card->property("deviceName").toString() == deviceName) { + m_deviceCards.removeAt(i); + card->deleteLater(); + break; + } + } + + // Update status + int deviceCount = m_deviceCards.count(); + if (deviceCount == 0) { + m_statusLabel->setText("No network devices found"); + } else { + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(deviceCount)); + } +} \ No newline at end of file diff --git a/src/networkdeviceswidget.h b/src/networkdeviceswidget.h new file mode 100644 index 0000000..bdd7898 --- /dev/null +++ b/src/networkdeviceswidget.h @@ -0,0 +1,49 @@ +#ifndef NETWORKDEVICESWIDGET_H +#define NETWORKDEVICESWIDGET_H + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include +#include +#include +#include +#include + +class NetworkDevicesWidget : public QWidget +{ + Q_OBJECT + +public: + explicit NetworkDevicesWidget(QWidget *parent = nullptr); + ~NetworkDevicesWidget(); + +private slots: + void onWirelessDeviceAdded(const NetworkDevice &device); + void onWirelessDeviceRemoved(const QString &deviceName); + +private: + void setupUI(); + void createDeviceCard(const NetworkDevice &device); + void clearDeviceCards(); + void updateDeviceList(); + + QGroupBox *m_deviceGroup = nullptr; + QScrollArea *m_scrollArea = nullptr; + QWidget *m_scrollContent = nullptr; + QVBoxLayout *m_deviceLayout = nullptr; + QLabel *m_statusLabel = nullptr; + +#ifdef __linux__ + AvahiService *m_networkProvider = nullptr; +#else + DnssdService *m_networkProvider = nullptr; +#endif + + QList m_deviceCards; +}; + +#endif // NETWORKDEVICESWIDGET_H \ No newline at end of file diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 2d1b60d..d0960fb 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -141,6 +141,9 @@ void ToolboxWidget::setupUI() "Mount your iPhone's filesystem on your PC", true, ""}); toolWidgets.append({iDescriptorTool::CableInfoWidget, "View detailed cable and connection info", true, ""}); + toolWidgets.append({iDescriptorTool::NetworkDevices, + "Discover and monitor devices on your network", false, + ""}); for (int i = 0; i < toolWidgets.size(); ++i) { const auto &tool = toolWidgets[i]; @@ -225,6 +228,9 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, case iDescriptorTool::UnmountDevImage: title = "Unmount Dev Image"; break; + case iDescriptorTool::NetworkDevices: + title = "Network Devices"; + break; default: title = "Unknown Tool"; break; @@ -431,6 +437,21 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) cableInfoWidget->resize(600, 400); cableInfoWidget->show(); } break; + case iDescriptorTool::NetworkDevices: { + // single instance lock + if (!m_networkDevicesWidget) { + m_networkDevicesWidget = new NetworkDevicesWidget(); + m_networkDevicesWidget->setAttribute(Qt::WA_DeleteOnClose); + m_networkDevicesWidget->setWindowFlag(Qt::Window); + m_networkDevicesWidget->resize(500, 600); + connect(m_networkDevicesWidget, &QObject::destroyed, this, + [this]() { m_networkDevicesWidget = nullptr; }); + m_networkDevicesWidget->show(); + } else { + m_networkDevicesWidget->raise(); + m_networkDevicesWidget->activateWindow(); + } + } break; default: qDebug() << "Clicked on unimplemented tool"; break; diff --git a/src/toolboxwidget.h b/src/toolboxwidget.h index 5bf98f5..123529c 100644 --- a/src/toolboxwidget.h +++ b/src/toolboxwidget.h @@ -4,6 +4,7 @@ #include "devdiskimageswidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "networkdeviceswidget.h" #include #include #include @@ -42,7 +43,8 @@ private: QList m_requiresDevice; iDescriptorDevice *m_currentDevice; std::string m_uuid; - DevDiskImagesWidget *m_devDiskImagesWidget; + DevDiskImagesWidget *m_devDiskImagesWidget = nullptr; + NetworkDevicesWidget *m_networkDevicesWidget = nullptr; signals: }; diff --git a/src/zlineedit.cpp b/src/zlineedit.cpp index 05ed44d..cd4aca2 100644 --- a/src/zlineedit.cpp +++ b/src/zlineedit.cpp @@ -1,4 +1,5 @@ #include "zlineedit.h" +#include "iDescriptor-ui.h" ZLineEdit::ZLineEdit(QWidget *parent) : QLineEdit(parent) { setupStyles(); } @@ -29,7 +30,7 @@ void ZLineEdit::updateStyles() "} " "QLineEdit:focus { " " border: 2px solid " + - qApp->palette().color(QPalette::Highlight).name() + + COLOR_ACCENT_BLUE.name() + "; " " outline: none; " "}");