fix bugs and styles

- Refactored jailbroken tab
- Added new icons
- Fixed files not closed in  read_afc_file_to_byte_array
- Improved memory cache for thumbnails in `PhotoModel`, increasing limit to 150MB.
- Updated UI elements in `SponsorWidget` and `ToolboxWidget` for better aesthetics and usability.
This commit is contained in:
uncor3
2025-11-07 14:31:28 +00:00
parent e3a5d576f9
commit d0fb0870d4
39 changed files with 807 additions and 479 deletions
+12
View File
@@ -19,6 +19,18 @@
<file>resources/icons/MaterialSymbolsLightHome.png</file>
<file>resources/icons/MdiGithub.png</file>
<file>resources/icons/app-icon/icon.ico</file>
<file>resources/icons/MaterialSymbolsLightAirplayOutline.png</file>
<file>resources/icons/MaterialSymbolsLightCableRounded.png</file>
<file>resources/icons/MaterialSymbolsLocationOnOutline.png</file>
<file>resources/icons/MdiDisk.png</file>
<file>resources/icons/PepiconsPrintCellphoneEye.png</file>
<file>resources/icons/StreamlineProgrammingBrowserSearchSearchWindowGlassAppCodeProgrammingQueryFindMagnifyingApps.png</file>
<file>resources/icons/fuse.png</file>
<file>resources/icons/TablerDatabaseExport.png</file>
<file>resources/icons/StreamlineUltimateMultipleUsersNetwork.png</file>
<file>resources/icons/MaterialSymbolsAndroidWifi3BarPlus.png</file>
<file>resources/icons/IconParkTwotoneMoreTwo.png</file>
<file>resources/icons/BxBxsTerminal.png</file>
<file>qml/MapView.qml</file>
<file>resources/iphone.png</file>
<file>resources/ios-wallpapers/iphone-ios4.png</file>
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -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
}
-1
View File
@@ -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);
+1 -1
View File
@@ -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 =
+1 -1
View File
@@ -46,7 +46,7 @@ private:
QLabel *m_chargingStatusLabel;
QLabel *m_chargingWattsWithCableTypeLabel;
BatteryWidget *m_batteryWidget;
ZIconWidget *m_lightningIconLabel;
ZIconLabel *m_lightningIconLabel;
DeviceImageWidget *m_deviceImageWidget;
};
+5
View File
@@ -99,3 +99,8 @@ void DeviceMenuWidget::switchToTab(const QString &tabName)
qDebug() << "Tab not found:" << tabName;
}
}
DeviceMenuWidget::~DeviceMenuWidget()
{
qDebug() << "DeviceMenuWidget destructor called";
}
+2 -1
View File
@@ -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
+15 -6
View File
@@ -26,6 +26,7 @@
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFrame>
#include <QMessageBox>
#include <QProcess>
#include <QRegularExpression>
@@ -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);
+2 -2
View File
@@ -20,6 +20,7 @@
#ifndef DIAGNOSE_WIDGET_H
#define DIAGNOSE_WIDGET_H
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
@@ -27,7 +28,6 @@
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
#include "qprocessindicator.h"
@@ -57,7 +57,7 @@ private:
QProcessIndicator *m_processIndicator;
};
class DiagnoseWidget : public QWidget
class DiagnoseWidget : public QFrame
{
Q_OBJECT
+23 -19
View File
@@ -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) {
-1
View File
@@ -37,7 +37,6 @@ class DiskUsageWidget : public QWidget
public:
explicit DiskUsageWidget(iDescriptorDevice *device,
QWidget *parent = nullptr);
QSize sizeHint() const override;
private:
void fetchData();
+5
View File
@@ -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";
}
+1
View File
@@ -46,6 +46,7 @@ public:
explicit GalleryWidget(iDescriptorDevice *device,
QWidget *parent = nullptr);
void load();
~GalleryWidget();
private slots:
void onSortOrderChanged();
+64 -2
View File
@@ -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
+88 -293
View File
@@ -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 <QApplication>
#include <QButtonGroup>
#include <QDebug>
#include <QGroupBox>
@@ -41,313 +42,107 @@
#include <QVBoxLayout>
#include <QWidget>
// 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<JailbreakToolInfo> 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<QAbstractButton *>::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<iDescriptorDevice *> wiredDevices =
AppContext::sharedInstance()->getAllDevices();
for (iDescriptorDevice *device : wiredDevices) {
addWiredDevice(device);
}
// Add wireless devices
QList<NetworkDevice> 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<void *>(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<iDescriptorDevice *>(
button->property("devicePointer").value<void *>());
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() {}
+13 -48
View File
@@ -27,7 +27,7 @@
#endif
#include "iDescriptor.h"
#include "sshterminalwidget.h"
#include "opensshterminalwidget.h"
#include <QAbstractButton>
#include <QButtonGroup>
#include <QGroupBox>
@@ -36,7 +36,15 @@
#include <QVBoxLayout>
#include <QWidget>
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
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "realtimescreenwidget.h"
#include "livescreenwidget.h"
#include "appcontext.h"
#include "devdiskimagehelper.h"
#include "devdiskmanager.h"
@@ -31,13 +31,12 @@
#include <QVBoxLayout>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/screenshotr.h>
// 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<int>::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";
@@ -17,8 +17,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef REALTIMESCREEN_H
#define REALTIMESCREEN_H
#ifndef LIVESCREEN_H
#define LIVESCREEN_H
#include "iDescriptor.h"
#include <QLabel>
@@ -27,13 +27,13 @@
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/screenshotr.h>
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
+354
View File
@@ -0,0 +1,354 @@
/*
* iDescriptor: A free and open-source idevice management tool.
*
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "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 <QButtonGroup>
#include <QDebug>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
// 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<QAbstractButton *>::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<iDescriptorDevice *> wiredDevices =
AppContext::sharedInstance()->getAllDevices();
for (iDescriptorDevice *device : wiredDevices) {
addWiredDevice(device);
}
// Add wireless devices
QList<NetworkDevice> 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<void *>(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<iDescriptorDevice *>(
button->property("devicePointer").value<void *>());
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() {}
+99
View File
@@ -0,0 +1,99 @@
/*
* iDescriptor: A free and open-source idevice management tool.
*
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef OPENSSHTERMINALWIDGET_H
#define OPENSSHTERMINALWIDGET_H
#include <QWidget>
#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 <QAbstractButton>
#include <QButtonGroup>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
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
+6 -5
View File
@@ -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 "
+27 -5
View File
@@ -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();
}
+2
View File
@@ -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);
+52 -39
View File
@@ -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 <QApplication>
#include <QDebug>
#include <QMessageBox>
@@ -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::StandardPixmap>(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__
+2 -2
View File
@@ -26,7 +26,7 @@
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "networkdeviceswidget.h"
#include "wirelessphotoimportwidget.h"
#include "wirelessgalleryimportwidget.h"
#include <QComboBox>
#include <QGridLayout>
#include <QHBoxLayout>
@@ -75,7 +75,7 @@ private:
#ifndef __APPLE__
iFuseWidget *m_ifuseWidget = nullptr;
#endif
WirelessPhotoImportWidget *m_wirelessPhotoImportWidget = nullptr;
WirelessGalleryImportWidget *m_wirelessGalleryImportWidget = nullptr;
signals:
};
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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);