mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-22 03:45:51 +08:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -46,7 +46,7 @@ private:
|
||||
QLabel *m_chargingStatusLabel;
|
||||
QLabel *m_chargingWattsWithCableTypeLabel;
|
||||
BatteryWidget *m_batteryWidget;
|
||||
ZIconWidget *m_lightningIconLabel;
|
||||
ZIconLabel *m_lightningIconLabel;
|
||||
|
||||
DeviceImageWidget *m_deviceImageWidget;
|
||||
};
|
||||
|
||||
@@ -99,3 +99,8 @@ void DeviceMenuWidget::switchToTab(const QString &tabName)
|
||||
qDebug() << "Tab not found:" << tabName;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceMenuWidget::~DeviceMenuWidget()
|
||||
{
|
||||
qDebug() << "DeviceMenuWidget destructor called";
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -37,7 +37,6 @@ class DiskUsageWidget : public QWidget
|
||||
public:
|
||||
explicit DiskUsageWidget(iDescriptorDevice *device,
|
||||
QWidget *parent = nullptr);
|
||||
QSize sizeHint() const override;
|
||||
|
||||
private:
|
||||
void fetchData();
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
explicit GalleryWidget(iDescriptorDevice *device,
|
||||
QWidget *parent = nullptr);
|
||||
void load();
|
||||
~GalleryWidget();
|
||||
|
||||
private slots:
|
||||
void onSortOrderChanged();
|
||||
|
||||
+64
-2
@@ -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
@@ -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
@@ -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
|
||||
@@ -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() {}
|
||||
@@ -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
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user