diff --git a/resources.qrc b/resources.qrc index 2777c10..21645c9 100644 --- a/resources.qrc +++ b/resources.qrc @@ -19,6 +19,18 @@ resources/icons/MaterialSymbolsLightHome.png resources/icons/MdiGithub.png resources/icons/app-icon/icon.ico + resources/icons/MaterialSymbolsLightAirplayOutline.png + resources/icons/MaterialSymbolsLightCableRounded.png + resources/icons/MaterialSymbolsLocationOnOutline.png + resources/icons/MdiDisk.png + resources/icons/PepiconsPrintCellphoneEye.png + resources/icons/StreamlineProgrammingBrowserSearchSearchWindowGlassAppCodeProgrammingQueryFindMagnifyingApps.png + resources/icons/fuse.png + resources/icons/TablerDatabaseExport.png + resources/icons/StreamlineUltimateMultipleUsersNetwork.png + resources/icons/MaterialSymbolsAndroidWifi3BarPlus.png + resources/icons/IconParkTwotoneMoreTwo.png + resources/icons/BxBxsTerminal.png qml/MapView.qml resources/iphone.png resources/ios-wallpapers/iphone-ios4.png diff --git a/resources/icons/BxBxsTerminal.png b/resources/icons/BxBxsTerminal.png new file mode 100644 index 0000000..f76e210 Binary files /dev/null and b/resources/icons/BxBxsTerminal.png differ diff --git a/resources/icons/IconParkTwotoneMoreTwo.png b/resources/icons/IconParkTwotoneMoreTwo.png new file mode 100644 index 0000000..c2f5dcd Binary files /dev/null and b/resources/icons/IconParkTwotoneMoreTwo.png differ diff --git a/resources/icons/MaterialSymbolsAndroidWifi3BarPlus.png b/resources/icons/MaterialSymbolsAndroidWifi3BarPlus.png new file mode 100644 index 0000000..e1c39b0 Binary files /dev/null and b/resources/icons/MaterialSymbolsAndroidWifi3BarPlus.png differ diff --git a/resources/icons/MaterialSymbolsLightAirplayOutline.png b/resources/icons/MaterialSymbolsLightAirplayOutline.png new file mode 100644 index 0000000..99e9907 Binary files /dev/null and b/resources/icons/MaterialSymbolsLightAirplayOutline.png differ diff --git a/resources/icons/MaterialSymbolsLightCableRounded.png b/resources/icons/MaterialSymbolsLightCableRounded.png new file mode 100644 index 0000000..ca9bc5e Binary files /dev/null and b/resources/icons/MaterialSymbolsLightCableRounded.png differ diff --git a/resources/icons/MaterialSymbolsLocationOnOutline.png b/resources/icons/MaterialSymbolsLocationOnOutline.png new file mode 100644 index 0000000..88ea17e Binary files /dev/null and b/resources/icons/MaterialSymbolsLocationOnOutline.png differ diff --git a/resources/icons/MdiDisk.png b/resources/icons/MdiDisk.png new file mode 100644 index 0000000..3729f91 Binary files /dev/null and b/resources/icons/MdiDisk.png differ diff --git a/resources/icons/PepiconsPrintCellphoneEye.png b/resources/icons/PepiconsPrintCellphoneEye.png new file mode 100644 index 0000000..e537949 Binary files /dev/null and b/resources/icons/PepiconsPrintCellphoneEye.png differ diff --git a/resources/icons/StreamlineProgrammingBrowserSearchSearchWindowGlassAppCodeProgrammingQueryFindMagnifyingApps.png b/resources/icons/StreamlineProgrammingBrowserSearchSearchWindowGlassAppCodeProgrammingQueryFindMagnifyingApps.png new file mode 100644 index 0000000..8ccfcd4 Binary files /dev/null and b/resources/icons/StreamlineProgrammingBrowserSearchSearchWindowGlassAppCodeProgrammingQueryFindMagnifyingApps.png differ diff --git a/resources/icons/StreamlineUltimateMultipleUsersNetwork.png b/resources/icons/StreamlineUltimateMultipleUsersNetwork.png new file mode 100644 index 0000000..418c0d0 Binary files /dev/null and b/resources/icons/StreamlineUltimateMultipleUsersNetwork.png differ diff --git a/resources/icons/TablerDatabaseExport.png b/resources/icons/TablerDatabaseExport.png new file mode 100644 index 0000000..97e76c3 Binary files /dev/null and b/resources/icons/TablerDatabaseExport.png differ diff --git a/resources/icons/fuse.png b/resources/icons/fuse.png new file mode 100644 index 0000000..070981a Binary files /dev/null and b/resources/icons/fuse.png differ diff --git a/src/core/helpers/read_afc_file_to_byte_array.cpp b/src/core/helpers/read_afc_file_to_byte_array.cpp index a2c4daa..88c3152 100644 --- a/src/core/helpers/read_afc_file_to_byte_array.cpp +++ b/src/core/helpers/read_afc_file_to_byte_array.cpp @@ -28,11 +28,11 @@ QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path) afc_file_open(afcClient, path, AFC_FOPEN_RDONLY, &fd_handle); if (fd_err != AFC_E_SUCCESS) { - qDebug() << "Could not open file" << path; + qDebug() << "Could not open file" << path << "Error:" << fd_err; return QByteArray(); } - // TODO: is this necessary + // TODO:Maybe use afc_get_file_info_plist instead? char **info = NULL; afc_get_file_info(afcClient, path, &info); uint64_t fileSize = 0; @@ -52,14 +52,18 @@ QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path) } QByteArray buffer; - buffer.resize(fileSize); - uint32_t bytesRead = 0; - afc_file_read(afcClient, fd_handle, buffer.data(), buffer.size(), - &bytesRead); - if (bytesRead != fileSize) { - qDebug() << "AFC Error: Read mismatch for file" << path; + uint32_t bytesRead = 0; + afc_error_t read_err = afc_file_read(afcClient, fd_handle, buffer.data(), + buffer.size(), &bytesRead); + + afc_file_close(afcClient, fd_handle); + + if (read_err != AFC_E_SUCCESS || bytesRead != fileSize) { + qDebug() << "AFC Error: Read mismatch for file" << path + << "Error:" << read_err << "Read:" << bytesRead + << "Expected:" << fileSize; return QByteArray(); // Read failed } diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index c56bc06..721dcb4 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -123,7 +123,6 @@ void DevDiskImagesWidget::setupUi() m_imageListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_imageListWidget->setStyleSheet( "QListWidget { background: transparent; border: none; }"); - m_imageListWidget->viewport()->setStyleSheet("background: transparent;"); m_stackedWidget->addWidget(m_imageListWidget); diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index c73827f..f7f597a 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -153,7 +153,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) chargingLayout->setSpacing(5); // Create icon label - m_lightningIconLabel = new ZIconWidget( + m_lightningIconLabel = new ZIconLabel( QIcon(":/resources/icons/MdiLightningBolt.png"), " Charging", this); m_batteryWidget = diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 2c2eeb3..4d6e0be 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -46,7 +46,7 @@ private: QLabel *m_chargingStatusLabel; QLabel *m_chargingWattsWithCableTypeLabel; BatteryWidget *m_batteryWidget; - ZIconWidget *m_lightningIconLabel; + ZIconLabel *m_lightningIconLabel; DeviceImageWidget *m_deviceImageWidget; }; diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index 99cad13..aa051b9 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -99,3 +99,8 @@ void DeviceMenuWidget::switchToTab(const QString &tabName) qDebug() << "Tab not found:" << tabName; } } + +DeviceMenuWidget::~DeviceMenuWidget() +{ + qDebug() << "DeviceMenuWidget destructor called"; +} \ No newline at end of file diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index b43b759..cf0480a 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -35,7 +35,8 @@ public: QWidget *parent = nullptr); void switchToTab(const QString &tabName); void init(); - // ~DeviceMenuWidget(); + ~DeviceMenuWidget(); + private: QStackedWidget *stackedWidget; // Pointer to the stacked widget iDescriptorDevice *device; // Pointer to the iDescriptor device diff --git a/src/diagnosewidget.cpp b/src/diagnosewidget.cpp index 27aede0..2841a2b 100644 --- a/src/diagnosewidget.cpp +++ b/src/diagnosewidget.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,7 @@ void DependencyItem::setInstalling(bool installing) void DependencyItem::onInstallClicked() { emit installRequested(m_name); } DiagnoseWidget::DiagnoseWidget(QWidget *parent) - : QWidget(parent), m_isExpanded(false) + : QFrame(parent), m_isExpanded(false) { setupUI(); @@ -144,7 +145,7 @@ DiagnoseWidget::DiagnoseWidget(QWidget *parent) #ifdef __linux__ // Add Linux-specific dependency items addDependencyItem("USB Device Permissions", - "Required for recovery device access (udev rules)"); + "Required for recovery devices (udev rules)"); #endif // Auto-check on startup @@ -153,7 +154,15 @@ DiagnoseWidget::DiagnoseWidget(QWidget *parent) void DiagnoseWidget::setupUI() { - setAutoFillBackground(true); + setObjectName("diagnoseWidget"); + setContentsMargins(20, 10, 10, 0); + setStyleSheet("QFrame#diagnoseWidget { " + " background-color: palette(window); " // Set background + // from the theme + " border-top-right-radius: 10px; " + " border-top-left-radius: 10px; " + " border-top: 1px solid #ccc; " + "}"); m_mainLayout = new QVBoxLayout(this); m_mainLayout->setSpacing(10); @@ -168,7 +177,7 @@ void DiagnoseWidget::setupUI() // Check button m_checkButton = new QPushButton("Refresh Check(s)"); - m_checkButton->setMaximumWidth(150); + m_checkButton->setMaximumWidth(m_checkButton->sizeHint().width()); connect(m_checkButton, &QPushButton::clicked, this, [this]() { checkDependencies(false); }); @@ -488,8 +497,8 @@ void DiagnoseWidget::onInstallRequested(const QString &name) QMessageBox::information( this, "Installation Complete", "USB device permissions have been configured.\n\n" - "Note: You may need to log out and log back in for " - "group membership changes to take full effect."); + "Note: You may need to log out and log back in for or " + "even restart for changes to take full effect."); checkDependencies(false); } itemToInstall->setInstalling(false); diff --git a/src/diagnosewidget.h b/src/diagnosewidget.h index 006987d..f6d346f 100644 --- a/src/diagnosewidget.h +++ b/src/diagnosewidget.h @@ -20,6 +20,7 @@ #ifndef DIAGNOSE_WIDGET_H #define DIAGNOSE_WIDGET_H +#include #include #include #include @@ -27,7 +28,6 @@ #include #include #include -#include #include "qprocessindicator.h" @@ -57,7 +57,7 @@ private: QProcessIndicator *m_processIndicator; }; -class DiagnoseWidget : public QWidget +class DiagnoseWidget : public QFrame { Q_OBJECT diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 0f70cb1..619ef67 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -88,11 +88,9 @@ void DiskUsageWidget::setupUI() // Disk usage bar container m_diskBarContainer = new QWidget(this); - m_diskBarContainer->setMinimumHeight(20); - m_diskBarContainer->setMaximumHeight(20); - m_diskBarContainer->setObjectName("diskBarContainer"); - m_diskBarContainer->setStyleSheet( - "QWidget#diskBarContainer { margin: 0; padding: 0; border: none; }"); + m_diskBarContainer->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Fixed); + m_diskBarContainer->setFixedHeight(20); m_diskBarLayout = new QHBoxLayout(m_diskBarContainer); m_diskBarLayout->setContentsMargins(0, 0, 0, 0); m_diskBarLayout->setSpacing(0); @@ -149,6 +147,13 @@ void DiskUsageWidget::setupUI() "QWidget#freeBar { background-color: #474747; border: 1px solid " "#4f4f4f; padding: 0; margin: 0; border-top-right-radius: 3px; " "border-bottom-right-radius: 3px; }"); + + // remove padding margin from layout + m_systemBar->setContentsMargins(0, 0, 0, 0); + m_appsBar->setContentsMargins(0, 0, 0, 0); + m_mediaBar->setContentsMargins(0, 0, 0, 0); + m_othersBar->setContentsMargins(0, 0, 0, 0); + m_freeBar->setContentsMargins(0, 0, 0, 0); #endif m_diskBarLayout->addWidget(m_systemBar); @@ -159,21 +164,22 @@ void DiskUsageWidget::setupUI() m_dataLayout->addWidget(m_diskBarContainer); - // Legend layout - m_legendLayout = new QHBoxLayout(); + QWidget *m_legendWidget = new QWidget(); + m_legendWidget->setContentsMargins(0, 0, 0, 0); + m_legendWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + m_legendLayout = new QHBoxLayout(m_legendWidget); m_legendLayout->setSpacing(0); m_legendLayout->setContentsMargins(0, 0, 0, 0); - // Legend labels - m_systemLabel = new QLabel("System", m_dataPage); - m_appsLabel = new QLabel("Apps", m_dataPage); - m_mediaLabel = new QLabel("Media", m_dataPage); - m_othersLabel = new QLabel("Others", m_dataPage); - m_freeLabel = new QLabel("Free", m_dataPage); + m_systemLabel = new QLabel("System", m_legendWidget); + m_appsLabel = new QLabel("Apps", m_legendWidget); + m_mediaLabel = new QLabel("Media", m_legendWidget); + m_othersLabel = new QLabel("Others", m_legendWidget); + m_freeLabel = new QLabel("Free", m_legendWidget); - // Style legend labels with colored backgrounds QString labelStyle = - "padding: 2px 6px; margin: 0px; border-radius: 3px; font-size: 10px;"; + "margin: 0px; padding: 0px 4px 0px 0px; font-size: 10px;"; m_systemLabel->setStyleSheet(labelStyle); m_appsLabel->setStyleSheet(labelStyle); m_mediaLabel->setStyleSheet(labelStyle); @@ -187,8 +193,8 @@ void DiskUsageWidget::setupUI() m_legendLayout->addWidget(m_freeLabel); m_legendLayout->addStretch(); - m_dataLayout->addLayout(m_legendLayout); - // m_dataLayout->addStretch(); + // Add the legend widget (not the layout) to the data layout + m_dataLayout->addWidget(m_legendWidget); m_stackedWidget->addWidget(m_dataPage); @@ -196,8 +202,6 @@ void DiskUsageWidget::setupUI() m_stackedWidget->setCurrentWidget(m_loadingErrorPage); } -QSize DiskUsageWidget::sizeHint() const { return QSize(400, 80); } - void DiskUsageWidget::updateUI() { if (m_state == Loading) { diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h index 461fe36..40601a6 100644 --- a/src/diskusagewidget.h +++ b/src/diskusagewidget.h @@ -37,7 +37,6 @@ class DiskUsageWidget : public QWidget public: explicit DiskUsageWidget(iDescriptorDevice *device, QWidget *parent = nullptr); - QSize sizeHint() const override; private: void fetchData(); diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 76e5843..9db47e3 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -621,4 +621,9 @@ void GalleryWidget::onPhotoContextMenu(const QPoint &pos) &GalleryWidget::onExportSelected); contextMenu.exec(m_listView->viewport()->mapToGlobal(pos)); +} + +GalleryWidget::~GalleryWidget() +{ + qDebug() << "GalleryWidget destructor called"; } \ No newline at end of file diff --git a/src/gallerywidget.h b/src/gallerywidget.h index 5a6ea16..78623f3 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -46,6 +46,7 @@ public: explicit GalleryWidget(iDescriptorDevice *device, QWidget *parent = nullptr); void load(); + ~GalleryWidget(); private slots: void onSortOrderChanged(); diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index 8f6f8aa..16964aa 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -90,8 +90,15 @@ public: ZIcon(const QString &fileName) : QIcon(fileName) {} ZIcon(const QPixmap &pixmap) : QIcon(pixmap) {} + void setThemable(bool themable) { m_themable = themable; } + QPixmap getThemedPixmap(const QSize &size, const QPalette &palette) const { + // If not themable, return the original pixmap without color filling. + if (!m_themable) { + return QIcon::pixmap(size); + } + QPixmap pixmap = QIcon::pixmap(size); if (pixmap.isNull()) { return pixmap; @@ -123,6 +130,9 @@ public: QIcon::paint(painter, rect); } } + +private: + bool m_themable = true; }; class ZIconWidget : public QAbstractButton @@ -174,9 +184,61 @@ private: ZIcon m_icon; }; +// Add this new class for display-only icons +class ZIconLabel : public QLabel +{ + Q_OBJECT +public: + ZIconLabel(const QIcon &icon, const QString &tooltip, + QWidget *parent = nullptr) + : QLabel(parent), m_icon(icon), m_iconSize(24, 24) + { + setToolTip(tooltip); + // setFixedSize(32, 32); + connect(qApp, &QApplication::paletteChanged, this, + [this]() { update(); }); + } + + void setIcon(const QIcon &icon) + { + m_icon = ZIcon(icon); + update(); + } + + void setIconThemable(bool themable) + { + m_icon.setThemable(themable); + update(); + } + + void setIconSize(const QSize &size) + { + m_iconSize = size; + update(); + } + +protected: + void paintEvent(QPaintEvent *event) override + { + Q_UNUSED(event) + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRect iconRect = rect(); + iconRect.setSize(m_iconSize); + iconRect.moveCenter(rect().center()); + + m_icon.paint(&painter, iconRect, palette()); + } + +private: + ZIcon m_icon; + QSize m_iconSize; +}; + enum class iDescriptorTool { Airplayer, - RealtimeScreen, + LiveScreen, MountDevImage, VirtualLocation, Restart, @@ -184,7 +246,7 @@ enum class iDescriptorTool { RecoveryMode, QueryMobileGestalt, DeveloperDiskImages, - WirelessPhotoImport, + WirelessGalleryImport, CableInfoWidget, /* TODO: to be implemented diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index 947c674..de1906e 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -19,8 +19,8 @@ #include "jailbrokenwidget.h" #include "appcontext.h" +#include "opensshterminalwidget.h" #include "responsiveqlabel.h" -#include "sshterminalwidget.h" #ifdef __linux__ #include "core/services/avahi/avahi_service.h" @@ -30,6 +30,7 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include #include #include #include @@ -41,313 +42,107 @@ #include #include -// TODO: theming is broken JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} { - QHBoxLayout *mainLayout = new QHBoxLayout(this); - mainLayout->setContentsMargins(2, 2, 2, 2); - mainLayout->setSpacing(2); + QGridLayout *mainLayout = new QGridLayout(this); + mainLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->setSpacing(10); - // Create responsive image label - ResponsiveQLabel *deviceImageLabel = new ResponsiveQLabel(this); - deviceImageLabel->setPixmap(QPixmap(":/resources/iphone.png")); - deviceImageLabel->setMinimumWidth(200); - deviceImageLabel->setSizePolicy(QSizePolicy::Ignored, - QSizePolicy::Expanding); - deviceImageLabel->setStyleSheet("background: transparent; border: none;"); + // Define all the tools you want to display + QList tools; + tools.append({"Open SSH Terminal", "Connect to your device via SSH", + ":/resources/icons/TablerDatabaseExport.png"}); + tools.append({"More Tools Coming", "New features will be added soon", + ":/resources/icons/TablerDatabaseExport.png", + false}); // Disabled placeholder - mainLayout->addWidget(deviceImageLabel, 1); + const int maxColumns = 3; + for (int i = 0; i < tools.size(); ++i) { + const auto &toolInfo = tools[i]; + ClickableWidget *toolWidget = createJailbreakTool(toolInfo); - // Connect to AppContext for device events - connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this, - &JailbrokenWidget::onWiredDeviceAdded); - connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, - &JailbrokenWidget::onWiredDeviceRemoved); + int row = i / maxColumns; + int col = i % maxColumns; + mainLayout->addWidget(toolWidget, row, col); + } -#ifdef __linux__ - m_wirelessProvider = new AvahiService(this); - connect(m_wirelessProvider, &AvahiService::deviceAdded, this, - &JailbrokenWidget::onWirelessDeviceAdded); - connect(m_wirelessProvider, &AvahiService::deviceRemoved, this, - &JailbrokenWidget::onWirelessDeviceRemoved); -#else - m_wirelessProvider = new DnssdService(this); - connect(m_wirelessProvider, &DnssdService::deviceAdded, this, - &JailbrokenWidget::onWirelessDeviceAdded); - connect(m_wirelessProvider, &DnssdService::deviceRemoved, this, - &JailbrokenWidget::onWirelessDeviceRemoved); -#endif - - // Right side: Device selection and Terminal - QWidget *rightContainer = new QWidget(); - rightContainer->setSizePolicy(QSizePolicy::Expanding, - QSizePolicy::Expanding); - rightContainer->setMinimumWidth(400); - QVBoxLayout *rightLayout = new QVBoxLayout(rightContainer); - rightLayout->setContentsMargins(15, 15, 15, 15); - rightLayout->setSpacing(10); - - setupDeviceSelectionUI(rightLayout); - - mainLayout->addWidget(rightContainer, 3); - - // Start scanning for wireless devices - m_wirelessProvider->startBrowsing(); - - // Populate initial devices - updateDeviceList(); + // Add a stretch to the last row and column to push everything to the + // top-left + mainLayout->setRowStretch(mainLayout->rowCount(), 1); + mainLayout->setColumnStretch(mainLayout->columnCount(), 1); } -void JailbrokenWidget::setupDeviceSelectionUI(QVBoxLayout *layout) +ClickableWidget * +JailbrokenWidget::createJailbreakTool(const JailbreakToolInfo &info) { - // Create scroll area for device selection - QScrollArea *scrollArea = new QScrollArea(); - scrollArea->setWidgetResizable(true); - scrollArea->setMinimumHeight(200); - scrollArea->setMaximumHeight(300); - scrollArea->setObjectName("devicescrollArea"); + ClickableWidget *b = new ClickableWidget(); + b->setCursor(Qt::PointingHandCursor); + b->setEnabled(info.enabled); - scrollArea->setStyleSheet("QWidget#devicescrollArea {border: none;}"); - QWidget *scrollContent = new QWidget(); - m_deviceLayout = new QVBoxLayout(scrollContent); - m_deviceLayout->setContentsMargins(5, 5, 5, 5); - m_deviceLayout->setSpacing(10); + // Use a theme-aware stylesheet for the background and hover effect + b->setStyleSheet("ClickableWidget {" + " border-radius: 8px;" + " padding: 10px;" + "}"); - // Button group for device selection - m_deviceButtonGroup = new QButtonGroup(this); - connect(m_deviceButtonGroup, - QOverload::of(&QButtonGroup::buttonClicked), - this, &JailbrokenWidget::onDeviceSelected); + QVBoxLayout *layout = new QVBoxLayout(b); - // Wired devices group - m_wiredDevicesGroup = new QGroupBox("Connected Devices"); - m_wiredDevicesLayout = new QVBoxLayout(m_wiredDevicesGroup); - m_deviceLayout->addWidget(m_wiredDevicesGroup); + // Icon (using the theme-aware ZIcon pattern) + // ZIconLabel *iconLabel = new ZIconLabel(); + ZIconLabel *iconLabel = new ZIconLabel(QIcon(), nullptr, this); - // Wireless devices group - m_wirelessDevicesGroup = new QGroupBox("Network Devices"); - m_wirelessDevicesLayout = new QVBoxLayout(m_wirelessDevicesGroup); - m_deviceLayout->addWidget(m_wirelessDevicesGroup); + // iconLabel->setAlignment(Qt::AlignCenter); + // ZIcon toolIcon(QIcon(info.iconPath)); - scrollArea->setWidget(scrollContent); - layout->addWidget(scrollArea); + // auto updateIcon = [b, iconLabel, toolIcon]() { + // iconLabel->setPixmap( + // toolIcon.getThemedPixmap(QSize(45, 45), b->palette())); + // }; + // updateIcon(); + // connect(qApp, &QApplication::paletteChanged, b, updateIcon); - // Info and connect button - m_infoLabel = new QLabel("Select a device to connect"); - layout->addWidget(m_infoLabel); + // Title + QLabel *titleLabel = new QLabel(info.title); + titleLabel->setAlignment(Qt::AlignCenter); + QFont titleFont = titleLabel->font(); + titleFont.setBold(true); + titleLabel->setFont(titleFont); - m_connectButton = new QPushButton("Open SSH Terminal"); - m_connectButton->setEnabled(false); - connect(m_connectButton, &QPushButton::clicked, this, - &JailbrokenWidget::onOpenSSHTerminal); - layout->addWidget(m_connectButton); + // Description (using a theme-aware palette color) + QLabel *descLabel = new QLabel(info.description); + descLabel->setWordWrap(true); + descLabel->setAlignment(Qt::AlignCenter); + descLabel->setStyleSheet("font-size: 12px;"); + + layout->addWidget(iconLabel, 0, Qt::AlignCenter); + layout->addWidget(titleLabel); + layout->addWidget(descLabel); + + // TODO: Connect the clicked signal to a slot + if (info.title == "Open SSH Terminal") { + iconLabel->setIcon(QIcon(":/resources/icons/BxBxsTerminal.png")); + + connect(b, &ClickableWidget::clicked, this, [this]() { + if (m_sshTerminalWidget) { + m_sshTerminalWidget->raise(); + m_sshTerminalWidget->activateWindow(); + return; + } + m_sshTerminalWidget = new OpenSSHTerminalWidget(); + m_sshTerminalWidget->setAttribute(Qt::WA_DeleteOnClose); + m_sshTerminalWidget->show(); + m_sshTerminalWidget->raise(); + m_sshTerminalWidget->activateWindow(); + connect(m_sshTerminalWidget, &QObject::destroyed, this, + [this]() { m_sshTerminalWidget = nullptr; }); + }); + } else if (info.title == "More Tools Coming") { + iconLabel->setIcon( + QIcon(":/resources/icons/IconParkTwotoneMoreTwo.png")); + } + iconLabel->setFixedSize(60, 60); + iconLabel->setIconSize(QSize(45, 45)); + return b; } -void JailbrokenWidget::updateDeviceList() -{ - // Clear existing devices - clearDeviceButtons(); - - // Add wired devices - QList wiredDevices = - AppContext::sharedInstance()->getAllDevices(); - for (iDescriptorDevice *device : wiredDevices) { - addWiredDevice(device); - } - - // Add wireless devices - QList wirelessDevices = - m_wirelessProvider->getNetworkDevices(); - for (const NetworkDevice &device : wirelessDevices) { - addWirelessDevice(device); - } -} - -void JailbrokenWidget::clearDeviceButtons() -{ - // Remove all buttons from button group and layouts - for (QAbstractButton *button : m_deviceButtonGroup->buttons()) { - m_deviceButtonGroup->removeButton(button); - button->deleteLater(); - } - - // Clear layouts - QLayoutItem *item; - while ((item = m_wiredDevicesLayout->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } - while ((item = m_wirelessDevicesLayout->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } -} - -void JailbrokenWidget::addWiredDevice(iDescriptorDevice *device) -{ - QString deviceName = QString::fromStdString(device->deviceInfo.deviceName); - QString udid = QString::fromStdString(device->udid); - QString displayText = QString("%1\n%2").arg(deviceName, udid); - - QRadioButton *radioButton = new QRadioButton(displayText); - radioButton->setProperty("deviceType", "wired"); - radioButton->setProperty("devicePointer", - QVariant::fromValue(static_cast(device))); - radioButton->setProperty("udid", udid); - - m_deviceButtonGroup->addButton(radioButton); - m_wiredDevicesLayout->addWidget(radioButton); -} - -void JailbrokenWidget::addWirelessDevice(const NetworkDevice &device) -{ - QString displayText = QString("%1\n%2").arg(device.name, device.address); - - QRadioButton *radioButton = new QRadioButton(displayText); - radioButton->setProperty("deviceType", "wireless"); - radioButton->setProperty("deviceAddress", device.address); - radioButton->setProperty("deviceName", device.name); - radioButton->setProperty("devicePort", device.port); - - m_deviceButtonGroup->addButton(radioButton); - m_wirelessDevicesLayout->addWidget(radioButton); -} - -void JailbrokenWidget::onWiredDeviceAdded(iDescriptorDevice *device) -{ - addWiredDevice(device); -} - -void JailbrokenWidget::onWiredDeviceRemoved(const std::string &udid) -{ - QString qudid = QString::fromStdString(udid); - - // Find and remove the corresponding radio button - for (QAbstractButton *button : m_deviceButtonGroup->buttons()) { - if (button->property("deviceType").toString() == "wired" && - button->property("udid").toString() == qudid) { - m_deviceButtonGroup->removeButton(button); - button->deleteLater(); - break; - } - } - - // Reset selection if this device was selected - if (m_selectedDeviceType == DeviceType::Wired && m_selectedWiredDevice && - m_selectedWiredDevice->udid == udid) { - resetSelection(); - } -} - -void JailbrokenWidget::onWirelessDeviceAdded(const NetworkDevice &device) -{ - addWirelessDevice(device); -} - -void JailbrokenWidget::onWirelessDeviceRemoved(const QString &deviceName) -{ - // Find and remove the corresponding radio button - for (QAbstractButton *button : m_deviceButtonGroup->buttons()) { - if (button->property("deviceType").toString() == "wireless" && - button->property("deviceName").toString() == deviceName) { - m_deviceButtonGroup->removeButton(button); - button->deleteLater(); - break; - } - } - - // Reset selection if this device was selected - if (m_selectedDeviceType == DeviceType::Wireless && - m_selectedNetworkDevice.name == deviceName) { - resetSelection(); - } -} - -void JailbrokenWidget::onDeviceSelected(QAbstractButton *button) -{ - QString deviceType = button->property("deviceType").toString(); - - if (deviceType == "wired") { - m_selectedDeviceType = DeviceType::Wired; - m_selectedWiredDevice = static_cast( - button->property("devicePointer").value()); - - if (m_selectedWiredDevice->deviceInfo.jailbroken) { - m_infoLabel->setText("Jailbroken device selected"); - } else { - m_infoLabel->setText( - "Device selected (detected as non-jailbroken)"); - } - } else if (deviceType == "wireless") { - m_selectedDeviceType = DeviceType::Wireless; - m_selectedNetworkDevice.name = - button->property("deviceName").toString(); - m_selectedNetworkDevice.address = - button->property("deviceAddress").toString(); - m_selectedNetworkDevice.port = button->property("devicePort").toUInt(); - - m_infoLabel->setText( - "Network device selected (jailbreak status unknown)"); - } - - m_connectButton->setEnabled(true); - m_connectButton->setText("Open SSH Terminal"); -} - -void JailbrokenWidget::resetSelection() -{ - m_selectedDeviceType = DeviceType::None; - m_selectedWiredDevice = nullptr; - m_selectedNetworkDevice = NetworkDevice{}; - m_connectButton->setEnabled(false); - m_infoLabel->setText("Select a device to connect"); - - // Uncheck all radio buttons - if (m_deviceButtonGroup->checkedButton()) { - m_deviceButtonGroup->setExclusive(false); - m_deviceButtonGroup->checkedButton()->setChecked(false); - m_deviceButtonGroup->setExclusive(true); - } -} - -void JailbrokenWidget::onOpenSSHTerminal() -{ - if (m_selectedDeviceType == DeviceType::None) { - m_infoLabel->setText("Please select a device first"); - return; - } - - // Prepare connection info - ConnectionInfo connectionInfo; - - if (m_selectedDeviceType == DeviceType::Wired) { - if (!m_selectedWiredDevice) { - m_infoLabel->setText("No wired device selected"); - return; - } - - connectionInfo.type = ConnectionType::Wired; - connectionInfo.deviceName = QString::fromStdString( - m_selectedWiredDevice->deviceInfo.deviceName); - connectionInfo.deviceUdid = - QString::fromStdString(m_selectedWiredDevice->udid); - connectionInfo.hostAddress = "127.0.0.1"; - connectionInfo.port = 22; - - } else if (m_selectedDeviceType == DeviceType::Wireless) { - connectionInfo.type = ConnectionType::Wireless; - connectionInfo.deviceName = m_selectedNetworkDevice.name; - connectionInfo.deviceUdid = ""; - connectionInfo.hostAddress = m_selectedNetworkDevice.address; - connectionInfo.port = m_selectedNetworkDevice.port; - } - - // Create and show SSH terminal widget in a new window - SSHTerminalWidget *sshTerminal = new SSHTerminalWidget(connectionInfo); - sshTerminal->setAttribute(Qt::WA_DeleteOnClose); - sshTerminal->show(); - sshTerminal->raise(); - sshTerminal->activateWindow(); -} - -JailbrokenWidget::~JailbrokenWidget() {} +JailbrokenWidget::~JailbrokenWidget() {} \ No newline at end of file diff --git a/src/jailbrokenwidget.h b/src/jailbrokenwidget.h index bc8fa51..1c37b17 100644 --- a/src/jailbrokenwidget.h +++ b/src/jailbrokenwidget.h @@ -27,7 +27,7 @@ #endif #include "iDescriptor.h" -#include "sshterminalwidget.h" +#include "opensshterminalwidget.h" #include #include #include @@ -36,7 +36,15 @@ #include #include -enum class DeviceType { None, Wired, Wireless }; +class ClickableWidget; + +// Define the struct here so it's available to the class declaration +struct JailbreakToolInfo { + QString title; + QString description; + QString iconPath; + bool enabled = true; +}; class JailbrokenWidget : public QWidget { @@ -47,53 +55,10 @@ public: ~JailbrokenWidget(); private slots: - void onOpenSSHTerminal(); - void onWiredDeviceAdded(iDescriptorDevice *device); - void onWiredDeviceRemoved(const std::string &udid); - void onWirelessDeviceAdded(const NetworkDevice &device); - void onWirelessDeviceRemoved(const QString &deviceName); - void onDeviceSelected(QAbstractButton *button); - private: - void setupDeviceSelectionUI(QVBoxLayout *layout); - void updateDeviceList(); - void clearDeviceButtons(); - void addWiredDevice(iDescriptorDevice *device); - void addWirelessDevice(const NetworkDevice &device); - void resetSelection(); - - QLabel *m_infoLabel; - QPushButton *m_connectButton; - - // Device selection UI - QVBoxLayout *m_deviceLayout; - QGroupBox *m_wiredDevicesGroup; - QGroupBox *m_wirelessDevicesGroup; - QVBoxLayout *m_wiredDevicesLayout; - QVBoxLayout *m_wirelessDevicesLayout; - QButtonGroup *m_deviceButtonGroup; - -#ifdef __linux__ - AvahiService *m_wirelessProvider = nullptr; -#else - DnssdService *m_wirelessProvider = nullptr; -#endif - - DeviceType m_selectedDeviceType = DeviceType::None; - iDescriptorDevice *m_selectedWiredDevice = nullptr; - NetworkDevice m_selectedNetworkDevice; - - // Legacy device pointer (kept for compatibility) - iDescriptorDevice *m_device = nullptr; - - // SSH components - ssh_session m_sshSession; - ssh_channel m_sshChannel; - QTimer *m_sshTimer; - QProcess *iproxyProcess = nullptr; - - bool m_sshConnected = false; - bool m_isInitialized = false; + // Helper function to create a tool widget + ClickableWidget *createJailbreakTool(const JailbreakToolInfo &info); + OpenSSHTerminalWidget *m_sshTerminalWidget = nullptr; }; #endif // JAILBROKENWIDGET_H \ No newline at end of file diff --git a/src/realtimescreenwidget.cpp b/src/livescreenwidget.cpp similarity index 84% rename from src/realtimescreenwidget.cpp rename to src/livescreenwidget.cpp index d3deace..e9bcc0b 100644 --- a/src/realtimescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -#include "realtimescreenwidget.h" +#include "livescreenwidget.h" #include "appcontext.h" #include "devdiskimagehelper.h" #include "devdiskmanager.h" @@ -31,13 +31,12 @@ #include #include #include -// todo: rename to livescreenwidget -RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device, - QWidget *parent) + +LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) : QWidget{parent}, m_device(device), m_timer(nullptr), - m_shotrClient(nullptr), m_fps(50) + m_shotrClient(nullptr), m_fps(20) { - setWindowTitle("Real-time Screen - iDescriptor"); + setWindowTitle("Live Screen - iDescriptor"); unsigned int device_version = get_device_version(m_device->device); unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; @@ -71,20 +70,6 @@ RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device, m_statusLabel->setAlignment(Qt::AlignCenter); mainLayout->addWidget(m_statusLabel); - // FPS control - QHBoxLayout *controlLayout = new QHBoxLayout(); - QLabel *fpsLabel = new QLabel("Frame Rate:"); - QSpinBox *fpsSpinBox = new QSpinBox(); - fpsSpinBox->setRange(1, 50); - fpsSpinBox->setValue(m_fps); - fpsSpinBox->setSuffix(" FPS"); - fpsSpinBox->setMaximumWidth(100); - - controlLayout->addWidget(fpsLabel); - controlLayout->addWidget(fpsSpinBox); - controlLayout->addStretch(); - mainLayout->addLayout(controlLayout); - // Screenshot display m_imageLabel = new QLabel(); m_imageLabel->setMinimumSize(300, 600); @@ -96,15 +81,7 @@ RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device, m_timer = new QTimer(this); m_timer->setInterval(1000 / m_fps); connect(m_timer, &QTimer::timeout, this, - &RealtimeScreenWidget::updateScreenshot); - - connect(fpsSpinBox, QOverload::of(&QSpinBox::valueChanged), this, - [this](int value) { - m_fps = value; - if (m_timer && m_timer->isActive()) { - m_timer->setInterval(1000 / m_fps); - } - }); + &LiveScreenWidget::updateScreenshot); const bool initializeScreenshotServiceSuccess = initializeScreenshotService(false); @@ -135,7 +112,7 @@ RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device, helper->start(); } -RealtimeScreenWidget::~RealtimeScreenWidget() +LiveScreenWidget::~LiveScreenWidget() { if (m_timer) { m_timer->stop(); @@ -147,7 +124,7 @@ RealtimeScreenWidget::~RealtimeScreenWidget() } } -bool RealtimeScreenWidget::initializeScreenshotService(bool notify) +bool LiveScreenWidget::initializeScreenshotService(bool notify) { lockdownd_client_t lockdownClient = nullptr; lockdownd_service_descriptor_t service = nullptr; @@ -206,8 +183,7 @@ bool RealtimeScreenWidget::initializeScreenshotService(bool notify) } // Successfully initialized, start capturing - m_statusLabel->setText("Capturing at " + QString::number(m_fps) + - " FPS"); + m_statusLabel->setText("Capturing"); startCapturing(); return true; } catch (const std::exception &e) { @@ -226,7 +202,7 @@ bool RealtimeScreenWidget::initializeScreenshotService(bool notify) } } -void RealtimeScreenWidget::startCapturing() +void LiveScreenWidget::startCapturing() { if (!m_shotrClient) { qWarning() @@ -236,11 +212,11 @@ void RealtimeScreenWidget::startCapturing() if (m_timer) { m_timer->start(); - qDebug() << "Started capturing at" << m_fps << "FPS"; + qDebug() << "Started capturing"; } } -void RealtimeScreenWidget::updateScreenshot() +void LiveScreenWidget::updateScreenshot() { if (!m_shotrClient) { qWarning() << "Screenshot client not initialized"; diff --git a/src/realtimescreenwidget.h b/src/livescreenwidget.h similarity index 82% rename from src/realtimescreenwidget.h rename to src/livescreenwidget.h index 8d6f36a..19ebf4f 100644 --- a/src/realtimescreenwidget.h +++ b/src/livescreenwidget.h @@ -17,8 +17,8 @@ * along with this program. If not, see . */ -#ifndef REALTIMESCREEN_H -#define REALTIMESCREEN_H +#ifndef LIVESCREEN_H +#define LIVESCREEN_H #include "iDescriptor.h" #include @@ -27,13 +27,13 @@ #include #include -class RealtimeScreenWidget : public QWidget +class LiveScreenWidget : public QWidget { Q_OBJECT public: - explicit RealtimeScreenWidget(iDescriptorDevice *device, - QWidget *parent = nullptr); - ~RealtimeScreenWidget(); + explicit LiveScreenWidget(iDescriptorDevice *device, + QWidget *parent = nullptr); + ~LiveScreenWidget(); private: bool initializeScreenshotService(bool notify); @@ -50,4 +50,4 @@ private: signals: }; -#endif // REALTIMESCREEN_H +#endif // LIVESCREEN_H diff --git a/src/opensshterminalwidget.cpp b/src/opensshterminalwidget.cpp new file mode 100644 index 0000000..39dbeb6 --- /dev/null +++ b/src/opensshterminalwidget.cpp @@ -0,0 +1,354 @@ +/* + * 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 "opensshterminalwidget.h" +#include "appcontext.h" +#include "responsiveqlabel.h" +#include "sshterminalwidget.h" + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include "iDescriptor-ui.h" +#include "iDescriptor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: theming is broken +OpenSSHTerminalWidget::OpenSSHTerminalWidget(QWidget *parent) : QWidget{parent} +{ + QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(2, 2, 2, 2); + mainLayout->setSpacing(2); + + // Create responsive image label + ResponsiveQLabel *deviceImageLabel = new ResponsiveQLabel(this); + deviceImageLabel->setPixmap(QPixmap(":/resources/iphone.png")); + deviceImageLabel->setMinimumWidth(200); + deviceImageLabel->setSizePolicy(QSizePolicy::Ignored, + QSizePolicy::Expanding); + deviceImageLabel->setStyleSheet("background: transparent; border: none;"); + + mainLayout->addWidget(deviceImageLabel, 1); + + // Connect to AppContext for device events + connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this, + &OpenSSHTerminalWidget::onWiredDeviceAdded); + connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, + &OpenSSHTerminalWidget::onWiredDeviceRemoved); + +#ifdef __linux__ + m_wirelessProvider = new AvahiService(this); + connect(m_wirelessProvider, &AvahiService::deviceAdded, this, + &OpenSSHTerminalWidget::onWirelessDeviceAdded); + connect(m_wirelessProvider, &AvahiService::deviceRemoved, this, + &OpenSSHTerminalWidget::onWirelessDeviceRemoved); +#else + m_wirelessProvider = new DnssdService(this); + connect(m_wirelessProvider, &DnssdService::deviceAdded, this, + &OpenSSHTerminalWidget::onWirelessDeviceAdded); + connect(m_wirelessProvider, &DnssdService::deviceRemoved, this, + &OpenSSHTerminalWidget::onWirelessDeviceRemoved); +#endif + + // Right side: Device selection and Terminal + QWidget *rightContainer = new QWidget(); + rightContainer->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding); + rightContainer->setMinimumWidth(400); + QVBoxLayout *rightLayout = new QVBoxLayout(rightContainer); + rightLayout->setContentsMargins(15, 15, 15, 15); + rightLayout->setSpacing(10); + + setupDeviceSelectionUI(rightLayout); + + mainLayout->addWidget(rightContainer, 3); + + // Start scanning for wireless devices + m_wirelessProvider->startBrowsing(); + + // Populate initial devices + updateDeviceList(); +} + +void OpenSSHTerminalWidget::setupDeviceSelectionUI(QVBoxLayout *layout) +{ + // Create scroll area for device selection + QScrollArea *scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + scrollArea->setMinimumHeight(200); + scrollArea->setMaximumHeight(300); + scrollArea->setObjectName("devicescrollArea"); + + scrollArea->setStyleSheet("QWidget#devicescrollArea {border: none;}"); + QWidget *scrollContent = new QWidget(); + m_deviceLayout = new QVBoxLayout(scrollContent); + m_deviceLayout->setContentsMargins(5, 5, 5, 5); + m_deviceLayout->setSpacing(10); + + // Button group for device selection + m_deviceButtonGroup = new QButtonGroup(this); + connect(m_deviceButtonGroup, + QOverload::of(&QButtonGroup::buttonClicked), + this, &OpenSSHTerminalWidget::onDeviceSelected); + + // Wired devices group + m_wiredDevicesGroup = new QGroupBox("Connected Devices"); + m_wiredDevicesLayout = new QVBoxLayout(m_wiredDevicesGroup); + m_deviceLayout->addWidget(m_wiredDevicesGroup); + + // Wireless devices group + m_wirelessDevicesGroup = new QGroupBox("Network Devices"); + m_wirelessDevicesLayout = new QVBoxLayout(m_wirelessDevicesGroup); + m_deviceLayout->addWidget(m_wirelessDevicesGroup); + + scrollArea->setWidget(scrollContent); + layout->addWidget(scrollArea); + + // Info and connect button + m_infoLabel = new QLabel("Select a device to connect"); + layout->addWidget(m_infoLabel); + + m_connectButton = new QPushButton("Open SSH Terminal"); + m_connectButton->setMaximumWidth(m_connectButton->sizeHint().width()); + m_connectButton->setEnabled(false); + connect(m_connectButton, &QPushButton::clicked, this, + &OpenSSHTerminalWidget::onOpenSSHTerminal); + layout->addWidget(m_connectButton, 0, Qt::AlignCenter); +} + +void OpenSSHTerminalWidget::updateDeviceList() +{ + // Clear existing devices + clearDeviceButtons(); + + // Add wired devices + QList wiredDevices = + AppContext::sharedInstance()->getAllDevices(); + for (iDescriptorDevice *device : wiredDevices) { + addWiredDevice(device); + } + + // Add wireless devices + QList wirelessDevices = + m_wirelessProvider->getNetworkDevices(); + for (const NetworkDevice &device : wirelessDevices) { + addWirelessDevice(device); + } +} + +void OpenSSHTerminalWidget::clearDeviceButtons() +{ + // Remove all buttons from button group and layouts + for (QAbstractButton *button : m_deviceButtonGroup->buttons()) { + m_deviceButtonGroup->removeButton(button); + button->deleteLater(); + } + + // Clear layouts + QLayoutItem *item; + while ((item = m_wiredDevicesLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + while ((item = m_wirelessDevicesLayout->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } +} + +void OpenSSHTerminalWidget::addWiredDevice(iDescriptorDevice *device) +{ + QString deviceName = QString::fromStdString(device->deviceInfo.deviceName); + QString udid = QString::fromStdString(device->udid); + QString displayText = QString("%1\n%2").arg(deviceName, udid); + + QRadioButton *radioButton = new QRadioButton(displayText); + radioButton->setProperty("deviceType", "wired"); + radioButton->setProperty("devicePointer", + QVariant::fromValue(static_cast(device))); + radioButton->setProperty("udid", udid); + + m_deviceButtonGroup->addButton(radioButton); + m_wiredDevicesLayout->addWidget(radioButton); +} + +void OpenSSHTerminalWidget::addWirelessDevice(const NetworkDevice &device) +{ + QString displayText = QString("%1\n%2").arg(device.name, device.address); + + QRadioButton *radioButton = new QRadioButton(displayText); + radioButton->setProperty("deviceType", "wireless"); + radioButton->setProperty("deviceAddress", device.address); + radioButton->setProperty("deviceName", device.name); + radioButton->setProperty("devicePort", device.port); + + m_deviceButtonGroup->addButton(radioButton); + m_wirelessDevicesLayout->addWidget(radioButton); +} + +void OpenSSHTerminalWidget::onWiredDeviceAdded(iDescriptorDevice *device) +{ + addWiredDevice(device); +} + +void OpenSSHTerminalWidget::onWiredDeviceRemoved(const std::string &udid) +{ + QString qudid = QString::fromStdString(udid); + + // Find and remove the corresponding radio button + for (QAbstractButton *button : m_deviceButtonGroup->buttons()) { + if (button->property("deviceType").toString() == "wired" && + button->property("udid").toString() == qudid) { + m_deviceButtonGroup->removeButton(button); + button->deleteLater(); + break; + } + } + + // Reset selection if this device was selected + if (m_selectedDeviceType == DeviceType::Wired && m_selectedWiredDevice && + m_selectedWiredDevice->udid == udid) { + resetSelection(); + } +} + +void OpenSSHTerminalWidget::onWirelessDeviceAdded(const NetworkDevice &device) +{ + addWirelessDevice(device); +} + +void OpenSSHTerminalWidget::onWirelessDeviceRemoved(const QString &deviceName) +{ + // Find and remove the corresponding radio button + for (QAbstractButton *button : m_deviceButtonGroup->buttons()) { + if (button->property("deviceType").toString() == "wireless" && + button->property("deviceName").toString() == deviceName) { + m_deviceButtonGroup->removeButton(button); + button->deleteLater(); + break; + } + } + + // Reset selection if this device was selected + if (m_selectedDeviceType == DeviceType::Wireless && + m_selectedNetworkDevice.name == deviceName) { + resetSelection(); + } +} + +void OpenSSHTerminalWidget::onDeviceSelected(QAbstractButton *button) +{ + QString deviceType = button->property("deviceType").toString(); + + if (deviceType == "wired") { + m_selectedDeviceType = DeviceType::Wired; + m_selectedWiredDevice = static_cast( + button->property("devicePointer").value()); + + if (m_selectedWiredDevice->deviceInfo.jailbroken) { + m_infoLabel->setText("Jailbroken device selected"); + } else { + m_infoLabel->setText( + "Device selected (detected as non-jailbroken)"); + } + } else if (deviceType == "wireless") { + m_selectedDeviceType = DeviceType::Wireless; + m_selectedNetworkDevice.name = + button->property("deviceName").toString(); + m_selectedNetworkDevice.address = + button->property("deviceAddress").toString(); + m_selectedNetworkDevice.port = button->property("devicePort").toUInt(); + + m_infoLabel->setText( + "Network device selected (jailbreak status unknown)"); + } + + m_connectButton->setEnabled(true); + m_connectButton->setText("Open SSH Terminal"); +} + +void OpenSSHTerminalWidget::resetSelection() +{ + m_selectedDeviceType = DeviceType::None; + m_selectedWiredDevice = nullptr; + m_selectedNetworkDevice = NetworkDevice{}; + m_connectButton->setEnabled(false); + m_infoLabel->setText("Select a device to connect"); + + // Uncheck all radio buttons + if (m_deviceButtonGroup->checkedButton()) { + m_deviceButtonGroup->setExclusive(false); + m_deviceButtonGroup->checkedButton()->setChecked(false); + m_deviceButtonGroup->setExclusive(true); + } +} + +void OpenSSHTerminalWidget::onOpenSSHTerminal() +{ + if (m_selectedDeviceType == DeviceType::None) { + m_infoLabel->setText("Please select a device first"); + return; + } + + // Prepare connection info + ConnectionInfo connectionInfo; + + if (m_selectedDeviceType == DeviceType::Wired) { + if (!m_selectedWiredDevice) { + m_infoLabel->setText("No wired device selected"); + return; + } + + connectionInfo.type = ConnectionType::Wired; + connectionInfo.deviceName = QString::fromStdString( + m_selectedWiredDevice->deviceInfo.deviceName); + connectionInfo.deviceUdid = + QString::fromStdString(m_selectedWiredDevice->udid); + connectionInfo.hostAddress = "127.0.0.1"; + connectionInfo.port = 22; + + } else if (m_selectedDeviceType == DeviceType::Wireless) { + connectionInfo.type = ConnectionType::Wireless; + connectionInfo.deviceName = m_selectedNetworkDevice.name; + connectionInfo.deviceUdid = ""; + connectionInfo.hostAddress = m_selectedNetworkDevice.address; + connectionInfo.port = m_selectedNetworkDevice.port; + } + + // Create and show SSH terminal widget in a new window + SSHTerminalWidget *sshTerminal = new SSHTerminalWidget(connectionInfo); + sshTerminal->setAttribute(Qt::WA_DeleteOnClose); + sshTerminal->show(); + sshTerminal->raise(); + sshTerminal->activateWindow(); +} + +OpenSSHTerminalWidget::~OpenSSHTerminalWidget() {} diff --git a/src/opensshterminalwidget.h b/src/opensshterminalwidget.h new file mode 100644 index 0000000..7a04b21 --- /dev/null +++ b/src/opensshterminalwidget.h @@ -0,0 +1,99 @@ +/* + * 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 . + */ + +#ifndef OPENSSHTERMINALWIDGET_H +#define OPENSSHTERMINALWIDGET_H + +#include +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include "iDescriptor.h" +#include "sshterminalwidget.h" +#include +#include +#include +#include +#include +#include +#include + +enum class DeviceType { None, Wired, Wireless }; + +class OpenSSHTerminalWidget : public QWidget +{ + Q_OBJECT +public: + explicit OpenSSHTerminalWidget(QWidget *parent = nullptr); + ~OpenSSHTerminalWidget(); +private slots: + void onOpenSSHTerminal(); + void onWiredDeviceAdded(iDescriptorDevice *device); + void onWiredDeviceRemoved(const std::string &udid); + void onWirelessDeviceAdded(const NetworkDevice &device); + void onWirelessDeviceRemoved(const QString &deviceName); + void onDeviceSelected(QAbstractButton *button); + +private: + void setupDeviceSelectionUI(QVBoxLayout *layout); + void updateDeviceList(); + void clearDeviceButtons(); + void addWiredDevice(iDescriptorDevice *device); + void addWirelessDevice(const NetworkDevice &device); + void resetSelection(); + + QLabel *m_infoLabel; + QPushButton *m_connectButton; + + // Device selection UI + QVBoxLayout *m_deviceLayout; + QGroupBox *m_wiredDevicesGroup; + QGroupBox *m_wirelessDevicesGroup; + QVBoxLayout *m_wiredDevicesLayout; + QVBoxLayout *m_wirelessDevicesLayout; + QButtonGroup *m_deviceButtonGroup; + +#ifdef __linux__ + AvahiService *m_wirelessProvider = nullptr; +#else + DnssdService *m_wirelessProvider = nullptr; +#endif + + DeviceType m_selectedDeviceType = DeviceType::None; + iDescriptorDevice *m_selectedWiredDevice = nullptr; + NetworkDevice m_selectedNetworkDevice; + + // Legacy device pointer (kept for compatibility) + iDescriptorDevice *m_device = nullptr; + + // SSH components + ssh_session m_sshSession; + ssh_channel m_sshChannel; + QTimer *m_sshTimer; + QProcess *iproxyProcess = nullptr; + + bool m_sshConnected = false; + bool m_isInitialized = false; +signals: +}; + +#endif // OPENSSHTERMINALWIDGET_H diff --git a/src/photoimportdialog.cpp b/src/photoimportdialog.cpp index 8a7e8d9..89da104 100644 --- a/src/photoimportdialog.cpp +++ b/src/photoimportdialog.cpp @@ -135,13 +135,14 @@ void PhotoImportDialog::onServerStarted() QString localIP = getLocalIP(); int port = m_httpServer->getPort(); QString jsonFileName = m_httpServer->getJsonFileName(); - - generateQRCode( - QString("http://192.168.1.149:5173/?local=%1&port=%2&file=%3") - // QString("https://uncor3.github.io/test-2?local=%1&port=%2&file=%3") + QString url = + QString("https://idescriptor.github.io/import?local=%1&port=%2&file=%3") .arg(localIP) .arg(port) - .arg(jsonFileName)); + .arg(jsonFileName); + qDebug() << "Server url" << url; + + generateQRCode(url); instructionLabel->setText( QString("Server started at %1:%2\n\n1. Scan the QR code to open the " diff --git a/src/photomodel.cpp b/src/photomodel.cpp index 00ea1ef..608c7c7 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -44,8 +44,8 @@ PhotoModel::PhotoModel(iDescriptorDevice *device, QObject *parent) "/photo_thumbs"; QDir().mkpath(m_cacheDir); - // Configure memory cache (50MB limit - much more reasonable) - m_thumbnailCache.setMaxCost(50 * 1024 * 1024); + // Configure memory cache (150MB limit) + m_thumbnailCache.setMaxCost(150 * 1024 * 1024); connect(this, &PhotoModel::thumbnailNeedsToBeLoaded, this, &PhotoModel::requestThumbnail, Qt::QueuedConnection); @@ -55,6 +55,7 @@ PhotoModel::PhotoModel(iDescriptorDevice *device, QObject *parent) PhotoModel::~PhotoModel() { + qDebug() << "PhotoModel destructor called"; // Clean up any active watchers for (auto *watcher : m_activeLoaders.values()) { if (watcher) { @@ -64,6 +65,9 @@ PhotoModel::~PhotoModel() } } m_activeLoaders.clear(); + m_loadingPaths.clear(); + m_thumbnailCache.clear(); + QDir(m_cacheDir).removeRecursively(); } QPixmap PhotoModel::generateVideoThumbnail(iDescriptorDevice *device, @@ -262,12 +266,16 @@ void PhotoModel::requestThumbnail(int index) // Remove from loading sets m_loadingPaths.remove(filePath); m_activeLoaders.remove(cacheKey); - + // scale down and store in cache if (!thumbnail.isNull()) { // Cache the thumbnail (both memory and disk) int cost = thumbnail.width() * thumbnail.height() * 4; - m_thumbnailCache.insert(cacheKey, new QPixmap(thumbnail), - cost); + m_thumbnailCache.insert( + cacheKey, + new QPixmap(thumbnail.scaled(m_thumbnailSize, + Qt::KeepAspectRatio, + Qt::SmoothTransformation)), + cost); // Find the model index and emit dataChanged for (int i = 0; i < m_photos.size(); ++i) { @@ -714,6 +722,20 @@ PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const void PhotoModel::setAlbumPath(const QString &albumPath) { if (m_albumPath != albumPath) { + // Clear cache when switching albums to prevent memory buildup + clearCache(); + + // Cancel any pending thumbnail requests + for (auto *watcher : m_activeLoaders.values()) { + if (watcher) { + watcher->cancel(); + watcher->waitForFinished(); + watcher->deleteLater(); + } + } + m_activeLoaders.clear(); + m_loadingPaths.clear(); + m_albumPath = albumPath; populatePhotoPaths(); } diff --git a/src/sponsorwidget.cpp b/src/sponsorwidget.cpp index 4cd1fdc..1754afd 100644 --- a/src/sponsorwidget.cpp +++ b/src/sponsorwidget.cpp @@ -26,6 +26,7 @@ SponsorWidget::SponsorWidget(QWidget *parent) : QWidget(parent) { setLayout(new QVBoxLayout(this)); QLabel *sponsorTitle = new QLabel("Would you like to sponsor us?"); + sponsorTitle->setStyleSheet("font-weight: bold; font-size: 16pt;"); sponsorTitle->setAlignment(Qt::AlignCenter); QLabel *sponsorDesc = @@ -33,6 +34,7 @@ SponsorWidget::SponsorWidget(QWidget *parent) : QWidget(parent) "And in order to keep it that way, we rely on donations. " "Consider becoming a sponsor to support " "and promote your app/brand here"); + sponsorDesc->setStyleSheet("font-size: 10pt;"); sponsorDesc->setWordWrap(true); layout()->addWidget(sponsorTitle); layout()->addWidget(sponsorDesc); diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index d78d07b..a1d2f91 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -28,10 +28,10 @@ #ifndef __APPLE__ #include "ifusewidget.h" #endif +#include "livescreenwidget.h" #include "querymobilegestaltwidget.h" -#include "realtimescreenwidget.h" #include "virtuallocationwidget.h" -#include "wirelessphotoimportwidget.h" +#include "wirelessgalleryimportwidget.h" #include #include #include @@ -138,17 +138,15 @@ void ToolboxWidget::setupUI() {iDescriptorTool::Airplayer, "Cast your device screen ", false, ""}); mainToolWidgets.append({iDescriptorTool::VirtualLocation, "Simulate GPS location on your device", true, ""}); - mainToolWidgets.append( - {iDescriptorTool::RealtimeScreen, - "View device screen in real-time (wired connection required)", true, - ""}); + mainToolWidgets.append({iDescriptorTool::LiveScreen, + "View device screen in real-time", true, ""}); mainToolWidgets.append({iDescriptorTool::QueryMobileGestalt, "Query device hardware information", true, ""}); mainToolWidgets.append({iDescriptorTool::DeveloperDiskImages, "Manage developer disk images", false, ""}); mainToolWidgets.append( - {iDescriptorTool::WirelessPhotoImport, - "Import photos wirelessly to your iDevice (requires Shortcut app)", + {iDescriptorTool::WirelessGalleryImport, + "Import photos wirelessly to your iDevice (requires Shortcuts app)", false, ""}); #ifndef __APPLE__ mainToolWidgets.append({iDescriptorTool::iFuse, @@ -222,59 +220,75 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, QVBoxLayout *layout = new QVBoxLayout(b); - // Icon - QLabel *iconLabel = new QLabel(); - QIcon icon = - // TODO:icons - this->style()->standardIcon( - static_cast(QStyle::SP_DialogOkButton)); - iconLabel->setPixmap(icon.pixmap(32, 32)); - iconLabel->setAlignment(Qt::AlignCenter); + ZIconLabel *icon = new ZIconLabel(QIcon(), nullptr, this); QString title; switch (tool) { case iDescriptorTool::Airplayer: title = "Airplayer"; + icon->setIcon( + QIcon(":/resources/icons/MaterialSymbolsLightAirplayOutline.png")); break; - case iDescriptorTool::RealtimeScreen: - title = "Realtime Screen"; + case iDescriptorTool::LiveScreen: + title = "Live Screen"; + icon->setIcon(QIcon(":/resources/icons/PepiconsPrintCellphoneEye.png")); break; case iDescriptorTool::MountDevImage: title = "Mount Dev Image"; + icon->setIcon(QIcon(":/resources/icons/MdiDisk.png")); break; case iDescriptorTool::VirtualLocation: title = "Virtual Location"; + icon->setIcon( + QIcon(":/resources/icons/MaterialSymbolsLocationOnOutline.png")); break; case iDescriptorTool::Restart: title = "Restart"; + icon->setIcon(QIcon(":/resources/icons/IcTwotoneRestartAlt.png")); break; case iDescriptorTool::Shutdown: title = "Shutdown"; + icon->setIcon(QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png")); break; case iDescriptorTool::RecoveryMode: title = "Recovery Mode"; + icon->setIcon(QIcon(":/resources/icons/HugeiconsWrench01.png")); break; case iDescriptorTool::QueryMobileGestalt: title = "Query Mobile Gestalt"; + icon->setIcon( + QIcon(":/resources/icons/" + "StreamlineProgrammingBrowserSearchSearchWindowGlassAppCod" + "eProgrammingQueryFindMagnifyingApps.png")); break; case iDescriptorTool::DeveloperDiskImages: title = "Dev Disk Images"; + icon->setIcon(QIcon(":/resources/icons/TablerDatabaseExport.png")); break; - case iDescriptorTool::WirelessPhotoImport: - title = "Wireless Photo Import"; + case iDescriptorTool::WirelessGalleryImport: + title = "Wireless Gallery Import"; + icon->setIcon( + QIcon(":/resources/icons/MaterialSymbolsAndroidWifi3BarPlus.png")); break; case iDescriptorTool::iFuse: title = "iFuse Mount"; + icon->setIcon(QIcon(":/resources/icons/fuse.png")); + icon->setIconThemable(false); break; case iDescriptorTool::CableInfoWidget: title = "Cable Info"; + icon->setIcon( + QIcon(":/resources/icons/MaterialSymbolsLightCableRounded.png")); break; case iDescriptorTool::NetworkDevices: title = "Network Devices"; + icon->setIcon(QIcon( + ":/resources/icons/StreamlineUltimateMultipleUsersNetwork.png")); break; default: title = "Unknown Tool"; break; } + // Title QLabel *titleLabel = new QLabel(title); titleLabel->setAlignment(Qt::AlignCenter); @@ -284,8 +298,10 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, descLabel->setWordWrap(true); descLabel->setAlignment(Qt::AlignCenter); descLabel->setStyleSheet("color: #666; font-size: 12px;"); + icon->setFixedSize(60, 60); + icon->setIconSize(QSize(45, 45)); - layout->addWidget(iconLabel); + layout->addWidget(icon, 0, Qt::AlignCenter); layout->addWidget(titleLabel); layout->addWidget(descLabel); @@ -321,8 +337,6 @@ void ToolboxWidget::updateDeviceList() } } - // After rebuilding the list, explicitly sync the UI to match the - // state from AppContext. This avoids creating a feedback loop. onCurrentDeviceChanged( AppContext::sharedInstance()->getCurrentDeviceSelection()); @@ -410,11 +424,10 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) } } break; - case iDescriptorTool::RealtimeScreen: { - RealtimeScreenWidget *realtimeScreen = - new RealtimeScreenWidget(m_currentDevice); - realtimeScreen->setAttribute(Qt::WA_DeleteOnClose); - realtimeScreen->show(); + case iDescriptorTool::LiveScreen: { + LiveScreenWidget *liveScreen = new LiveScreenWidget(m_currentDevice); + liveScreen->setAttribute(Qt::WA_DeleteOnClose); + liveScreen->show(); } break; case iDescriptorTool::RecoveryMode: { // Handle entering recovery mode @@ -498,18 +511,18 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) m_devDiskImagesWidget->activateWindow(); } } break; - case iDescriptorTool::WirelessPhotoImport: { - if (!m_wirelessPhotoImportWidget) { - m_wirelessPhotoImportWidget = new WirelessPhotoImportWidget(); - connect(m_wirelessPhotoImportWidget, &QObject::destroyed, this, - [this]() { m_wirelessPhotoImportWidget = nullptr; }); - m_wirelessPhotoImportWidget->setAttribute(Qt::WA_DeleteOnClose); - m_wirelessPhotoImportWidget->setWindowFlag(Qt::Window); - // m_wirelessPhotoImportWidget->resize(800, 600); - m_wirelessPhotoImportWidget->show(); + case iDescriptorTool::WirelessGalleryImport: { + if (!m_wirelessGalleryImportWidget) { + m_wirelessGalleryImportWidget = new WirelessGalleryImportWidget(); + connect(m_wirelessGalleryImportWidget, &QObject::destroyed, this, + [this]() { m_wirelessGalleryImportWidget = nullptr; }); + m_wirelessGalleryImportWidget->setAttribute(Qt::WA_DeleteOnClose); + m_wirelessGalleryImportWidget->setWindowFlag(Qt::Window); + // m_wirelessGalleryImportWidget->resize(800, 600); + m_wirelessGalleryImportWidget->show(); } else { - m_wirelessPhotoImportWidget->raise(); - m_wirelessPhotoImportWidget->activateWindow(); + m_wirelessGalleryImportWidget->raise(); + m_wirelessGalleryImportWidget->activateWindow(); } } break; #ifndef __APPLE__ diff --git a/src/toolboxwidget.h b/src/toolboxwidget.h index cd5cc83..1aa65b6 100644 --- a/src/toolboxwidget.h +++ b/src/toolboxwidget.h @@ -26,7 +26,7 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "networkdeviceswidget.h" -#include "wirelessphotoimportwidget.h" +#include "wirelessgalleryimportwidget.h" #include #include #include @@ -75,7 +75,7 @@ private: #ifndef __APPLE__ iFuseWidget *m_ifuseWidget = nullptr; #endif - WirelessPhotoImportWidget *m_wirelessPhotoImportWidget = nullptr; + WirelessGalleryImportWidget *m_wirelessGalleryImportWidget = nullptr; signals: }; diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 338ffb0..ef22c9b 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -35,7 +35,7 @@ void WelcomeWidget::setupUI() { // Main layout with proper spacing and margins m_mainLayout = new QVBoxLayout(this); - m_mainLayout->setContentsMargins(0, 0, 0, 0); + m_mainLayout->setContentsMargins(0, 10, 0, 0); m_mainLayout->setSpacing(0); // Add top stretch diff --git a/src/wirelessgalleryimportwidget.cpp b/src/wirelessgalleryimportwidget.cpp index cc8dcbd..294e06c 100644 --- a/src/wirelessgalleryimportwidget.cpp +++ b/src/wirelessgalleryimportwidget.cpp @@ -87,7 +87,7 @@ void WirelessGalleryImportWidget::setupUI() leftLayout->addWidget(m_scrollArea, 1); // Import button - m_importButton = new QPushButton("Import Photos to iOS"); + m_importButton = new QPushButton("Import to Gallery"); m_importButton->setEnabled(false); connect(m_importButton, &QPushButton::clicked, this, &WirelessGalleryImportWidget::onImportPhotos);