feat: recognize recovery devices & add heic support

- Added RecoveryDeviceSidebarItem class for managing recovery devices in the sidebar.
- Unified device selection structure with DeviceSelection to handle normal, recovery, and pending devices.
- Updated DeviceSidebarWidget to support adding and removing recovery devices.
- Refactored sidebar navigation and selection handling to accommodate new device types.

refactor: Enhance Disk Usage Widget UI

- Improved styling and layout of disk usage bars for better visual representation.
- Removed unnecessary paint event override and adjusted layout settings.
- Added object names for easier styling and debugging.

fix: Update File Explorer Widget for AFC2 Support

- Integrated a stacked widget to switch between normal and AFC2 explorers.
- Simplified sidebar setup and item handling for better maintainability.
- Connected sidebar item clicks to switch between AFC explorers.

feat: Implement InfoLabel for Copying Text

- Created InfoLabel class to display text that can be copied to the clipboard.
- Added hover effects and temporary text change on copy action.

chore: Clean up Unused Code and Comments

- Removed commented-out code and unnecessary forward declarations across multiple files.
- Streamlined includes and improved code readability.

fix: Improve Recovery Device Info Widget

- Updated RecoveryDeviceInfoWidget to display device information more clearly.
- Added error handling for recovery mode exit operations with user feedback.

feat: Add Responsive QLabel for Image Display

- Introduced ResponsiveQLabel to handle responsive image scaling in the UI.
- Replaced static image display with responsive label in JailbrokenWidget for better adaptability.
This commit is contained in:
uncor3
2025-10-08 05:00:02 +00:00
parent ef56f7af33
commit 777ea21a00
34 changed files with 921 additions and 645 deletions
+3 -1
View File
@@ -81,7 +81,7 @@ find_library(TATSU_LIBRARY
# Add QR code generation library
pkg_check_modules(QRENCODE REQUIRED IMPORTED_TARGET libqrencode)
pkg_check_modules(HEIF REQUIRED IMPORTED_TARGET libheif)
find_library(IRECOVERY_LIBRARY
NAMES irecovery-1.0
PATHS ${CUSTOM_LIB_PATH}
@@ -230,6 +230,7 @@ target_link_libraries(iDescriptor PRIVATE
${SSL_LIBRARY}
${CRYPTO_LIBRARY}
${SSH_LIBRARY}
${HEIF_LIBRARIES}
# ${FRIDA_LIBRARY}
# ${ZIP_LIBRARY}
PkgConfig::PUGIXML
@@ -237,6 +238,7 @@ target_link_libraries(iDescriptor PRIVATE
PkgConfig::PLIST
PkgConfig::QRENCODE
PkgConfig::QTERMWIDGET
PkgConfig::HEIF
airplay
ipatool-go
)
+2 -54
View File
@@ -43,9 +43,6 @@ AfcExplorerWidget::AfcExplorerWidget(afc_client_t afcClient,
loadPath("/");
setupContextMenu();
connect(SettingsManager::sharedInstance(),
&SettingsManager::favoritePlacesChanged, this,
&AfcExplorerWidget::refreshFavoritePlaces);
}
void AfcExplorerWidget::goBack()
@@ -175,7 +172,7 @@ void AfcExplorerWidget::loadPath(const QString &path)
updateBreadcrumb(path);
MediaFileTree tree =
AFCFileTree tree =
get_file_tree(m_currentAfcClient, m_device->device, path.toStdString());
if (!tree.success) {
m_fileList->addItem("Failed to load directory");
@@ -317,7 +314,6 @@ int AfcExplorerWidget::export_file_to_path(afc_client_t afc,
const char *local_path)
{
uint64_t handle = 0;
// TODO: implement safe_afc_file_open
if (afc_file_open(afc, device_path, AFC_FOPEN_RDONLY, &handle) !=
AFC_E_SUCCESS) {
qDebug() << "Failed to open file on device:" << device_path;
@@ -332,7 +328,6 @@ int AfcExplorerWidget::export_file_to_path(afc_client_t afc,
char buffer[4096];
uint32_t bytes_read = 0;
// TODO: implement safe_afc_file_read
while (afc_file_read(afc, handle, buffer, sizeof(buffer), &bytes_read) ==
AFC_E_SUCCESS &&
bytes_read > 0) {
@@ -340,7 +335,6 @@ int AfcExplorerWidget::export_file_to_path(afc_client_t afc,
}
fclose(out);
// TODO: implement safe_afc_file_close
afc_file_close(afc, handle);
return 0;
}
@@ -422,9 +416,6 @@ int AfcExplorerWidget::import_file_to_device(afc_client_t afc,
return 0;
}
// useAFC2 ,path,
typedef QPair<bool, QString> SidebarItemData;
void AfcExplorerWidget::setupFileExplorer()
{
m_explorer = new QWidget();
@@ -471,22 +462,7 @@ void AfcExplorerWidget::setupFileExplorer()
&AfcExplorerWidget::onAddToFavoritesClicked);
}
void AfcExplorerWidget::onSidebarItemClicked(QTreeWidgetItem *item, int column)
{
Q_UNUSED(column)
bool useAfc2 = item->data(0, Qt::UserRole).value<SidebarItemData>().first;
QString path = item->data(0, Qt::UserRole).value<SidebarItemData>().second;
// if (itemType == "try_install_afc2") {
// onTryInstallAFC2Clicked();
// return;
// }
switchToAFC(useAfc2);
loadPath(path);
}
// todo: implement
void AfcExplorerWidget::onAddToFavoritesClicked()
{
QString currentPath = "/";
@@ -499,23 +475,6 @@ void AfcExplorerWidget::onAddToFavoritesClicked()
"Enter alias for this location:", QLineEdit::Normal, currentPath, &ok);
if (ok && !alias.isEmpty()) {
saveFavoritePlace(currentPath, alias);
refreshFavoritePlaces();
}
}
void AfcExplorerWidget::onTryInstallAFC2Clicked()
{
qDebug() << "Clicked on try to install AFC2";
}
void AfcExplorerWidget::switchToAFC(bool useAFC2)
{
if (useAFC2 && m_device->afc2Client) {
m_usingAFC2 = true;
m_currentAfcClient = m_device->afc2Client;
} else {
m_usingAFC2 = false;
m_currentAfcClient = m_device->afcClient;
}
}
@@ -526,14 +485,3 @@ void AfcExplorerWidget::saveFavoritePlace(const QString &path,
SettingsManager *settings = SettingsManager::sharedInstance();
settings->saveFavoritePlace(path, alias);
}
void AfcExplorerWidget::refreshFavoritePlaces()
{
// // Clear existing favorite items
// while (m_favoritePlacesItem->childCount() > 0) {
// delete m_favoritePlacesItem->takeChild(0);
// }
// // Reload favorite places
// loadFavoritePlaces();
}
-5
View File
@@ -34,9 +34,7 @@ private slots:
void onFileListContextMenu(const QPoint &pos);
void onExportClicked();
void onImportClicked();
void onSidebarItemClicked(QTreeWidgetItem *item, int column);
void onAddToFavoritesClicked();
void onTryInstallAFC2Clicked();
private:
QWidget *m_explorer;
@@ -50,15 +48,12 @@ private:
iDescriptorDevice *m_device;
// Current AFC mode
bool m_usingAFC2;
afc_client_t m_currentAfcClient;
void setupFileExplorer();
void loadPath(const QString &path);
void updateBreadcrumb(const QString &path);
void saveFavoritePlace(const QString &path, const QString &alias);
void refreshFavoritePlaces();
void switchToAFC(bool useAFC2);
void setupContextMenu();
void exportSelectedFile(QListWidgetItem *item);
+65 -36
View File
@@ -4,6 +4,7 @@
#include <QDBusMessage>
#include <QDebug>
#include <QMessageBox>
#include <QTimer>
#include <QUuid>
AppContext *AppContext::sharedInstance()
@@ -75,26 +76,38 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
// return onDeviceInitFailed(udid, initResult.error);
if (initResult.error == LOCKDOWN_E_PASSWORD_PROTECTED) {
if (addType == AddType::Regular) {
// FIXME: if a device never gets paired, it will stay in
// this
m_pendingDevices.append(udid);
emit devicePasswordProtected(udid);
QTimer::singleShot(30000, this, [this, udid]() {
// After 30 seconds, if the device is still pending,
// consider the pairing expired
qDebug()
<< "Pairing timer fired for device UDID: " << udid;
if (m_pendingDevices.contains(udid)) {
qDebug()
<< "Pairing expired for device UDID: " << udid;
m_pendingDevices.removeAll(udid);
emit devicePairingExpired(udid);
}
});
}
} else if (initResult.error ==
LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING) {
m_pendingDevices.append(udid);
// FIXME: if a device never gets paired, it will stay in this
// list forever
emit devicePairPending(udid);
// warn("Device with UDID " + udid +
// " is not trusted. Please trust this computer on the
// " "device and try again.",
// "Warning");
QTimer::singleShot(30000, this, [this, udid]() {
// After 30 seconds, if the device is still pending,
// consider the pairing expired
qDebug() << "Pairing timer fired for device UDID: " << udid;
if (m_pendingDevices.contains(udid)) {
qDebug() << "Pairing expired for device UDID: " << udid;
m_pendingDevices.removeAll(udid);
emit devicePairingExpired(udid);
}
});
} else {
warn("Failed to initialize device with UDID " + udid +
". Error code: " + QString::number(initResult.error),
"Warning");
qDebug() << "Unhandled error for device UDID: " << udid
<< " Error code: " << initResult.error;
}
return;
}
@@ -116,8 +129,6 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
} catch (const std::exception &e) {
qDebug() << "Exception in onDeviceAdded: " << e.what();
// QMessageBox::critical(this, "Error", "An error occurred while
// processing device information");
}
}
@@ -130,13 +141,23 @@ void AppContext::removeDevice(QString _udid)
{
const std::string uuid = _udid.toStdString();
if (!m_devices.contains(uuid)) {
qDebug() << "Device with UUID " + _udid +
" not found. Please report this issue.";
qDebug() << "AppContext::removeDevice device with UUID:"
<< QString::fromStdString(uuid);
if (m_pendingDevices.contains(_udid)) {
m_pendingDevices.removeAll(_udid);
emit devicePairingExpired(_udid);
return;
} else {
qDebug() << "Device with UUID " + _udid +
" not found in pending devices.";
}
qDebug() << "Removing device with UUID:" << QString::fromStdString(uuid);
if (!m_devices.contains(uuid)) {
qDebug() << "Device with UUID " + _udid +
" not found in normal devices.";
return;
}
iDescriptorDevice *device = m_devices[uuid];
m_devices.remove(uuid);
@@ -148,22 +169,20 @@ void AppContext::removeDevice(QString _udid)
delete device;
}
void AppContext::removeRecoveryDevice(QString ecid)
void AppContext::removeRecoveryDevice(uint64_t ecid)
{
std::string std_ecid = ecid.toStdString();
if (!m_recoveryDevices.contains(std_ecid)) {
qDebug() << "Device with ECID " + ecid +
if (!m_recoveryDevices.contains(ecid)) {
qDebug() << "Device with ECID " + QString::number(ecid) +
" not found. Please report this issue.";
return;
}
qDebug() << "Removing recovery device with ECID:"
<< QString::fromStdString(std_ecid);
RecoveryDeviceInfo *deviceInfo = m_recoveryDevices[std_ecid];
m_recoveryDevices.remove(std_ecid);
qDebug() << "Removing recovery device with ECID:" << ecid;
m_recoveryDevices.remove(ecid);
emit recoveryDeviceRemoved(ecid);
iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices[ecid];
delete deviceInfo;
}
@@ -177,7 +196,7 @@ QList<iDescriptorDevice *> AppContext::getAllDevices()
return m_devices.values();
}
QList<RecoveryDeviceInfo *> AppContext::getAllRecoveryDevices()
QList<iDescriptorRecoveryDevice *> AppContext::getAllRecoveryDevices()
{
return m_recoveryDevices.values();
}
@@ -189,17 +208,27 @@ bool AppContext::noDevicesConnected() const
m_pendingDevices.isEmpty());
}
void AppContext::addRecoveryDevice(RecoveryDeviceInfo *deviceInfo)
void AppContext::addRecoveryDevice(uint64_t ecid)
{
// Generate a unique ID for the recovery device
// std::string uuid =
// QUuid::createUuid().toString(QUuid::WithoutBraces).toStdString();
IDescriptorInitDeviceResultRecovery res =
init_idescriptor_recovery_device(ecid);
// Add the device to the map
// uint64_t to std::string
m_recoveryDevices[std::to_string(deviceInfo->ecid)] = deviceInfo;
if (!res.success) {
qDebug() << "Failed to initialize recovery device with ECID: "
<< QString::number(ecid);
qDebug() << "Error code: " << res.error;
return;
}
emit recoveryDeviceAdded(deviceInfo);
iDescriptorRecoveryDevice *recoveryDevice = new iDescriptorRecoveryDevice();
recoveryDevice->ecid = res.deviceInfo.ecid;
recoveryDevice->mode = res.mode;
recoveryDevice->cpid = res.deviceInfo.cpid;
recoveryDevice->bdid = res.deviceInfo.bdid;
recoveryDevice->displayName = res.displayName;
m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice;
emit recoveryDeviceAdded(recoveryDevice);
}
AppContext::~AppContext()
+7 -6
View File
@@ -18,30 +18,31 @@ public:
bool noDevicesConnected() const;
// Returns whether there are any devices connected (regular or recovery)
QList<RecoveryDeviceInfo *> getAllRecoveryDevices();
QList<iDescriptorRecoveryDevice *> getAllRecoveryDevices();
~AppContext();
int getConnectedDeviceCount() const;
private:
QMap<std::string, iDescriptorDevice *> m_devices;
QMap<std::string, RecoveryDeviceInfo *> m_recoveryDevices;
QMap<uint64_t, iDescriptorRecoveryDevice *> m_recoveryDevices;
QStringList m_pendingDevices;
signals:
void deviceAdded(iDescriptorDevice *device);
void deviceRemoved(const std::string &udid);
void devicePaired(iDescriptorDevice *device);
void devicePasswordProtected(const QString &udid);
void recoveryDeviceAdded(RecoveryDeviceInfo *deviceInfo);
void recoveryDeviceRemoved(const QString &udid);
void recoveryDeviceAdded(const iDescriptorRecoveryDevice *deviceInfo);
void recoveryDeviceRemoved(uint64_t ecid);
void devicePairPending(const QString &udid);
void devicePairingExpired(const QString &udid);
void systemSleepStarting();
void systemWakeup();
public slots:
void removeDevice(QString udid);
void addDevice(QString udid, idevice_connection_type connType,
AddType addType);
void addRecoveryDevice(RecoveryDeviceInfo *deviceInfo);
void removeRecoveryDevice(QString ecid);
void addRecoveryDevice(uint64_t ecid);
void removeRecoveryDevice(uint64_t ecid);
};
#endif // APPCONTEXT_H
+25 -12
View File
@@ -5,22 +5,22 @@
#include <libimobiledevice/lockdown.h>
#include <string.h>
MediaFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
const std::string &path)
AFCFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
const std::string &path)
{
MediaFileTree result;
AFCFileTree result;
result.currentPath = path;
if (afcClient == nullptr) {
qDebug() << "AFC client is not initialized in get_file_tree";
result.success = false;
return result;
}
char **dirs = NULL;
if (safe_afc_read_directory(afcClient, device, path.c_str(), &dirs) !=
AFC_E_SUCCESS) {
// afc_client_free(afcClient);
// lockdownd_service_descriptor_free(lockdownService);
result.success = false;
return result;
}
@@ -30,21 +30,35 @@ MediaFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
if (entryName == "." || entryName == "..")
continue;
// Determine if entry is a directory
char **info = NULL;
std::string fullPath = path;
if (fullPath.back() != '/')
fullPath += "/";
fullPath += entryName;
bool isDir = false;
if (afc_get_file_info(afcClient, fullPath.c_str(), &info) ==
AFC_E_SUCCESS &&
info) {
if (entryName == "var") {
qDebug() << "File info for var:" << info[0] << info[1]
<< info[2] << info[3] << info[4] << info[5];
}
for (int j = 0; info[j]; j += 2) {
if (strcmp(info[j], "st_ifmt") == 0) {
if (strcmp(info[j + 1], "S_IFDIR") == 0)
if (strcmp(info[j + 1], "S_IFDIR") == 0) {
isDir = true;
} else if (strcmp(info[j + 1], "S_IFLNK") == 0) {
/*symlink*/
char **dir_contents = NULL;
if (afc_read_directory(afcClient, fullPath.c_str(),
&dir_contents) ==
AFC_E_SUCCESS) {
isDir = true;
if (dir_contents) {
afc_dictionary_free(dir_contents);
}
}
}
break;
}
}
@@ -52,10 +66,9 @@ MediaFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
}
result.entries.push_back({entryName, isDir});
}
free(dirs);
// TODO : Freed when device is disconnected
// afc_client_free(afc);
// lockdownd_service_descriptor_free(service);
if (dirs) {
afc_dictionary_free(dirs);
}
result.success = true;
return result;
}
-12
View File
@@ -1,12 +0,0 @@
#include "../../iDescriptor.h"
#include <string>
std::string parse_product_type(const std::string &productType)
{
// Check if the product type is in the DEVICE_MAP
auto it = DEVICE_MAP.find(productType);
if (it != DEVICE_MAP.end()) {
return it->second; // Return the marketing name if found
}
return "Unknown Device"; // Return a default value if not found
}
@@ -0,0 +1,48 @@
#include "../../iDescriptor.h"
#include <QByteArray>
#include <QDebug>
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path)
{
uint64_t fd_handle = 0;
afc_error_t fd_err =
afc_file_open(afcClient, path, AFC_FOPEN_RDONLY, &fd_handle);
if (fd_err != AFC_E_SUCCESS) {
qDebug() << "Could not open file" << path;
return QByteArray();
}
// TODO: is this necessary
char **info = NULL;
afc_get_file_info(afcClient, path, &info);
uint64_t fileSize = 0;
if (info) {
for (int i = 0; info[i]; i += 2) {
if (strcmp(info[i], "st_size") == 0) {
fileSize = std::stoull(info[i + 1]);
break;
}
}
afc_dictionary_free(info);
}
if (fileSize == 0) {
afc_file_close(afcClient, fd_handle);
return QByteArray();
}
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;
return QByteArray(); // Read failed
}
return buffer;
};
+49 -13
View File
@@ -1,9 +1,11 @@
#include "../../devicedatabase.h"
#include "../../iDescriptor.h"
#include "libirecovery.h"
#include <QDebug>
#include <libimobiledevice/diagnostics_relay.h>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <string.h>
std::string safeGetXML(const char *key, pugi::xml_node dict)
{
@@ -201,7 +203,11 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc,
}
// TODO:RegionInfo: LL/A
std::string rawProductType = safeGet("ProductType");
d.productType = parse_product_type(rawProductType);
const DeviceDatabaseInfo *info =
DeviceDatabase::findByIdentifier(rawProductType);
d.productType =
info ? info->displayName ? info->marketingName : "Unknown Device"
: "Unknown Device";
d.rawProductType = rawProductType;
d.jailbroken = detect_jailbroken(afcClient);
d.is_iPhone = safeGet("DeviceClass") == "iPhone";
@@ -382,22 +388,52 @@ IDescriptorInitDeviceResult init_idescriptor_device(const char *udid)
}
IDescriptorInitDeviceResultRecovery
init_idescriptor_recovery_device(irecv_device_info *info)
init_idescriptor_recovery_device(uint64_t ecid)
{
IDescriptorInitDeviceResultRecovery result;
result.deviceInfo = *info;
uint64_t ecid = info->ecid;
// irecv_client_t client = nullptr;
// Docs say that clients are not long-lived, so instead of storing, we
// create a new one each time we need it. irecv_error_t ret =
// irecv_open_with_ecid_and_attempts(&client, ecid,
// RECOVERY_CLIENT_CONNECTION_TRIES);
irecv_client_t client = nullptr;
irecv_error_t ret = IRECV_E_UNKNOWN_ERROR;
ret = irecv_open_with_ecid_and_attempts(&client, ecid,
RECOVERY_CLIENT_CONNECTION_TRIES);
// if (ret != IRECV_E_SUCCESS)
// {
// return result;
// }
if (ret != IRECV_E_SUCCESS) {
result.error = ret;
return result;
}
ret = irecv_get_mode(client, (int *)&result.mode);
if (ret != IRECV_E_SUCCESS) {
result.error = ret;
irecv_close(client);
return result;
}
const irecv_device_info *deviceInfo = irecv_get_device_info(client);
if (!deviceInfo) {
result.error = IRECV_E_UNKNOWN_ERROR;
irecv_close(client);
return result;
}
irecv_device_t device = nullptr;
const DeviceDatabaseInfo *info = nullptr;
if (irecv_devices_get_device_by_client(client, &device) ==
IRECV_E_SUCCESS &&
device && device->hardware_model) {
qDebug() << "Recovery device hardware_model: "
<< device->hardware_model;
info =
DeviceDatabase::findByHwModel(std::string(device->hardware_model));
} else {
qDebug() << "Could not resolve hardware_model from client.";
}
result.displayName =
info ? (info->displayName ? info->displayName : info->marketingName)
: "Unknown Device";
result.deviceInfo = *deviceInfo;
result.success = true;
irecv_close(client);
return result;
}
+64
View File
@@ -0,0 +1,64 @@
#include "../../iDescriptor.h"
#include <QByteArray>
#include <QDebug>
#include <QImage>
#include <QPixmap>
#include <libheif/heif.h>
QPixmap load_heic(const QByteArray &imageData)
{
heif_context *ctx = heif_context_alloc();
if (!ctx) {
qWarning() << "Failed to allocate heif_context";
return QPixmap();
}
heif_error err = heif_context_read_from_memory(ctx, imageData.constData(),
imageData.size(), nullptr);
if (err.code != heif_error_Ok) {
qWarning() << "Failed to read HEIC from memory:" << err.message;
heif_context_free(ctx);
return QPixmap();
}
heif_image_handle *handle;
err = heif_context_get_primary_image_handle(ctx, &handle);
if (err.code != heif_error_Ok) {
qWarning() << "Failed to get primary image handle:" << err.message;
heif_context_free(ctx);
return QPixmap();
}
heif_image *img;
err = heif_decode_image(handle, &img, heif_colorspace_RGB,
heif_chroma_interleaved_RGB, nullptr);
if (err.code != heif_error_Ok) {
qWarning() << "Failed to decode HEIC image:" << err.message;
heif_image_handle_release(handle);
heif_context_free(ctx);
return QPixmap();
}
int width = heif_image_get_width(img, heif_channel_interleaved);
int height = heif_image_get_height(img, heif_channel_interleaved);
size_t stride;
const uint8_t *data =
heif_image_get_plane_readonly2(img, heif_channel_interleaved, &stride);
if (!data) {
qWarning() << "Failed to get image plane data";
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
return QPixmap();
}
QImage qimg(data, width, height, stride, QImage::Format_RGB888);
QPixmap result = QPixmap::fromImage(qimg);
heif_image_release(img);
heif_image_handle_release(handle);
heif_context_free(ctx);
return result;
}
+16 -31
View File
@@ -4,12 +4,10 @@
#include "fileexplorerwidget.h"
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "infolabel.h"
#include <QApplication>
#include <QDebug>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
@@ -32,30 +30,22 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 10, 0);
mainLayout->setSpacing(1);
m_graphicsScene = new QGraphicsScene(this); // no parent
QGraphicsPixmapItem *pixmapItem =
new QGraphicsPixmapItem(QPixmap(":/resources/iphone.png"));
m_graphicsScene->addItem(pixmapItem);
m_graphicsView = new ResponsiveGraphicsView(m_graphicsScene, this);
m_graphicsView->setRenderHint(QPainter::Antialiasing);
m_graphicsView->setMinimumWidth(200);
m_graphicsView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
m_graphicsView->setStyleSheet("background: transparent; border: none;");
// Create responsive image label
m_deviceImageLabel = new ResponsiveQLabel(this);
m_deviceImageLabel->setPixmap(QPixmap(":/resources/iphone.png"));
m_deviceImageLabel->setMinimumWidth(200);
m_deviceImageLabel->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Expanding);
m_deviceImageLabel->setStyleSheet("background: transparent; border: none;");
mainLayout->addWidget(m_graphicsView, 1); // Stretch factor 1
mainLayout->addWidget(m_deviceImageLabel, 1); // Stretch factor 1
// Right side: Info Table
QWidget *infoContainer = new QWidget();
// infoContainer->setObjectName("infoContainer");
// infoContainer->setStyleSheet("QWidget#infoContainer { "
// " border: 1px solid #ccc; "
// " border-radius: 6px; "
// "}");
infoContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
infoContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
QVBoxLayout *infoLayout = new QVBoxLayout(infoContainer);
// infoLayout->setSpacing(10);
// Header
QGroupBox *headerWidget = new QGroupBox();
@@ -128,6 +118,8 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
infoLayout->addStretch();
QGroupBox *gridContainer = new QGroupBox("Device Information");
gridContainer->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
QGridLayout *gridLayout = new QGridLayout(); // Set layout on gridWidget
gridLayout->setSpacing(8);
gridLayout->setColumnStretch(1, 1); // Allow value column to stretch
@@ -138,7 +130,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
QList<QPair<QString, QWidget *>> infoItems;
auto createValueLabel = [](const QString &text) {
return new QLabel(text);
return new InfoLabel(text);
};
infoItems.append({"iOS Version:", createValueLabel(QString::fromStdString(
@@ -259,23 +251,16 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
rightSideLayout->setSpacing(10);
rightSideLayout->addWidget(infoContainer);
rightSideLayout->addWidget(new DiskUsageWidget(device, this));
rightSideLayout->setAlignment(Qt::AlignCenter);
// TODO: layout shift cause ?
// rightSideLayout->setAlignment(Qt::AlignCenter);
mainLayout->addLayout(rightSideLayout, 2); // Stretch factor 2
m_updateTimer = new QTimer(this);
connect(m_updateTimer, &QTimer::timeout, this,
&DeviceInfoWidget::updateBatteryInfo);
m_updateTimer->start(30000); // Update every 30 seconds
}
DeviceInfoWidget::~DeviceInfoWidget()
{
if (m_graphicsView) {
m_graphicsView->setScene(
nullptr); // prevents QGraphicsScene from calling into view during
// its destructor only needed on macos ?
}
}
DeviceInfoWidget::~DeviceInfoWidget() {}
void DeviceInfoWidget::onBatteryMoreClicked()
{
+2 -4
View File
@@ -2,8 +2,7 @@
#define DEVICEINFOWIDGET_H
#include "batterywidget.h"
#include "iDescriptor.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include "responsiveqlabel.h"
#include <QLabel>
#include <QTimer>
#include <QWidget>
@@ -29,8 +28,7 @@ private:
BatteryWidget *m_batteryWidget;
QLabel *m_lightningIconLabel;
QGraphicsView *m_graphicsView = nullptr;
QGraphicsScene *m_graphicsScene = nullptr;
ResponsiveQLabel *m_deviceImageLabel = nullptr;
};
#endif // DEVICEINFOWIDGET_H
+125 -105
View File
@@ -42,54 +42,22 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
});
connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceAdded,
this, [this](QObject *recoveryDeviceInfoObj) {
if (!recoveryDeviceInfoObj)
return;
try {
RecoveryDeviceInfo *device =
qobject_cast<RecoveryDeviceInfo *>(
recoveryDeviceInfoObj);
if (!device) {
qDebug() << "Invalid recovery device info object";
return;
}
// IDescriptorInitDeviceResultRecovery initResult=
// init_idescriptor_recovery_device(deviceInfo);
// IDescriptorInitDeviceResult initResult =
// init_idescriptor_device(udid.toStdString().c_str());
qDebug() << "Recovery device initialized: " << device->ecid;
// std::string added_ecid =
// AppContext::sharedInstance()->addRecoveryDevice(device);
// Create device info widget
RecoveryDeviceInfoWidget *recoveryDeviceInfoWidget =
new RecoveryDeviceInfoWidget(device);
m_stackedWidget->addWidget(recoveryDeviceInfoWidget);
} catch (...) {
qDebug() << "Error initializing recovery device";
}
this, [this](const iDescriptorRecoveryDevice *recoveryDeviceInfo) {
addRecoveryDevice(recoveryDeviceInfo);
emit updateNoDevicesConnected();
});
// connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved,
// this, [this](const QString &ecid) {
// qDebug() << "Removing:" << ecid;
// std::string ecidStr = ecid.toStdString();
// DeviceMenuWidget *deviceWidget =
// qobject_cast<DeviceMenuWidget *>(
// m_device_menu_widgets[ecidStr]);
connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved,
this, [this](uint64_t ecid) {
removeRecoveryDevice(ecid);
emit updateNoDevicesConnected();
});
// if (deviceWidget) {
// // TODO: Implement proper removal by device index
// m_device_menu_widgets.erase(ecidStr);
// delete deviceWidget;
// }
// emit updateNoDevicesConnected();
// });
connect(AppContext::sharedInstance(), &AppContext::devicePairingExpired,
this, [this](const QString &udid) {
removePendingDevice(udid);
emit updateNoDevicesConnected();
});
}
void DeviceManagerWidget::setupUI()
@@ -109,10 +77,8 @@ void DeviceManagerWidget::setupUI()
m_mainLayout->addWidget(m_stackedWidget);
// Connect signals
connect(m_sidebar, &DeviceSidebarWidget::sidebarDeviceChanged, this,
&DeviceManagerWidget::onSidebarDeviceChanged);
connect(m_sidebar, &DeviceSidebarWidget::sidebarNavigationChanged, this,
&DeviceManagerWidget::onSidebarNavigationChanged);
connect(m_sidebar, &DeviceSidebarWidget::deviceSelectionChanged, this,
&DeviceManagerWidget::onDeviceSelectionChanged);
}
void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
@@ -130,8 +96,8 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
QString tabTitle = QString::fromStdString(device->deviceInfo.productType);
m_stackedWidget->addWidget(deviceWidget);
m_deviceWidgets[device->udid] = std::pair{
deviceWidget, m_sidebar->addToSidebar(tabTitle, device->udid)};
m_deviceWidgets[device->udid] =
std::pair{deviceWidget, m_sidebar->addDevice(tabTitle, device->udid)};
// todo
// If this is the first device, make it current
@@ -140,24 +106,48 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
// }
}
void DeviceManagerWidget::addRecoveryDevice(RecoveryDeviceInfo *device)
void DeviceManagerWidget::addRecoveryDevice(
const iDescriptorRecoveryDevice *device)
{
// if (m_deviceWidgets.contains(device->ecid)) {
// qWarning() << "Recovery device already exists:"
// << QString::fromStdString(device->ecid);
// return;
// }
// qDebug() << "Connect ::recoveryDeviceAdded Adding:"
// << QString::fromStdString(device->ecid);
try {
// Create device info widget
RecoveryDeviceInfoWidget *recoveryDeviceInfoWidget =
new RecoveryDeviceInfoWidget(device);
m_recoveryDeviceWidgets.insert(
device->ecid,
std::pair{recoveryDeviceInfoWidget,
m_sidebar->addRecoveryDevice(device->ecid)});
m_stackedWidget->addWidget(recoveryDeviceInfoWidget);
} catch (...) {
qDebug() << "Error initializing recovery device";
}
}
void DeviceManagerWidget::removeRecoveryDevice(uint64_t ecid)
{
qDebug() << "Removing recovery device with ECID:" << ecid;
if (!m_recoveryDeviceWidgets.contains(ecid)) {
qDebug() << "Recovery device with ECID" + QString::number(ecid) +
" not found. Please report this issue.";
return;
}
RecoveryDeviceInfoWidget *deviceWidget =
new RecoveryDeviceInfoWidget(device, this);
m_recoveryDeviceWidgets[ecid].first;
RecoveryDeviceSidebarItem *sidebarItem =
m_recoveryDeviceWidgets[ecid].second;
// QString tabTitle = QString::fromStdString(device->product);
if (deviceWidget != nullptr && sidebarItem != nullptr) {
qDebug() << "Recovery device exists removing:" << QString::number(ecid);
m_stackedWidget->addWidget(deviceWidget);
// m_deviceWidgets[device->ecid] = std::pair{
// deviceWidget, m_sidebar->addToSidebar(tabTitle, device->ecid)};
m_recoveryDeviceWidgets.remove(ecid);
m_stackedWidget->removeWidget(deviceWidget);
m_sidebar->removeRecoveryDevice(ecid);
deviceWidget->deleteLater();
emit updateNoDevicesConnected();
}
}
void DeviceManagerWidget::addPendingDevice(const QString &udid, bool locked)
@@ -180,12 +170,28 @@ void DeviceManagerWidget::addPendingDevice(const QString &udid, bool locked)
DevicePendingWidget *pendingWidget = new DevicePendingWidget(locked, this);
m_stackedWidget->addWidget(pendingWidget);
m_pendingDeviceWidgets[udid.toStdString()] =
std::pair{pendingWidget, m_sidebar->addPendingToSidebar(udid)};
std::pair{pendingWidget, m_sidebar->addPendingDevice(udid)};
}
// If this is the first device, make it current
// if (m_currentDeviceIndex == -1) {
// setCurrentDevice(deviceIndex);
// }
void DeviceManagerWidget::removePendingDevice(const QString &udid)
{
qDebug() << "Removing pending device:" << udid;
if (!m_pendingDeviceWidgets.contains(udid.toStdString())) {
qDebug() << "Pending device not found:" << udid;
return;
}
std::string udidStr = udid.toStdString();
DevicePendingWidget *deviceWidget = m_pendingDeviceWidgets[udidStr].first;
DevicePendingSidebarItem *sidebarItem =
m_pendingDeviceWidgets[udidStr].second;
if (deviceWidget != nullptr && sidebarItem != nullptr) {
qDebug() << "Pending device exists removing:" << udid;
m_pendingDeviceWidgets.remove(udidStr);
m_stackedWidget->removeWidget(deviceWidget);
m_sidebar->removePendingDevice(udidStr);
deviceWidget->deleteLater();
}
}
void DeviceManagerWidget::addPairedDevice(iDescriptorDevice *device)
@@ -201,7 +207,7 @@ void DeviceManagerWidget::addPairedDevice(iDescriptorDevice *device)
if (pair.second) {
qDebug() << "Removing pending device from sidebar:"
<< QString::fromStdString(device->udid);
m_sidebar->removePendingFromSidebar(pair.second);
m_sidebar->removePendingDevice(device->udid);
}
// Clean up widget if it exists
@@ -230,13 +236,12 @@ void DeviceManagerWidget::removeDevice(const std::string &uuid)
// TODO: cleanups
m_deviceWidgets.remove(uuid);
m_stackedWidget->removeWidget(deviceWidget);
m_sidebar->removeFromSidebar(sidebarItem);
m_sidebar->removeDevice(uuid);
deviceWidget->deleteLater();
// delete d.second;
// TODO:
if (m_deviceWidgets.count() > 0) {
setCurrentDevice(m_deviceWidgets.firstKey());
m_sidebar->updateSidebar(m_deviceWidgets.firstKey());
}
}
}
@@ -252,16 +257,15 @@ void DeviceManagerWidget::setCurrentDevice(const std::string &uuid)
return;
}
// m_currentDeviceIndex = deviceIndex;
m_currentDeviceUuid = uuid;
// // Update sidebar selection
// m_sidebar->setCurrentDevice(deviceIndex);
// // Update stacked widget
// Update stacked widget
QWidget *widget = m_deviceWidgets[uuid].first;
m_stackedWidget->setCurrentWidget(widget);
// Update sidebar selection
m_sidebar->setCurrentSelection(DeviceSelection(uuid));
emit deviceChanged(uuid);
}
@@ -270,35 +274,51 @@ std::string DeviceManagerWidget::getCurrentDevice() const
return m_currentDeviceUuid;
}
void DeviceManagerWidget::setDeviceNavigation(int deviceIndex,
const QString &section)
void DeviceManagerWidget::onDeviceSelectionChanged(
const DeviceSelection &selection)
{
m_sidebar->setDeviceNavigationSection(deviceIndex, section);
// emit deviceNavigationChanged(deviceIndex, section);
}
// Update sidebar selection
m_sidebar->setCurrentSelection(selection);
void DeviceManagerWidget::onSidebarDeviceChanged(std::string deviceUuid)
{
setCurrentDevice(deviceUuid);
}
switch (selection.type) {
case DeviceSelection::Normal:
if (m_deviceWidgets.contains(selection.uuid)) {
if (m_currentDeviceUuid != selection.uuid) {
setCurrentDevice(selection.uuid);
}
void DeviceManagerWidget::onSidebarNavigationChanged(std::string deviceUuid,
const QString &section)
{
if (deviceUuid != m_currentDeviceUuid) {
setCurrentDevice(deviceUuid);
// Handle navigation section
QWidget *tabWidget = m_deviceWidgets[selection.uuid].first;
DeviceMenuWidget *deviceMenuWidget =
qobject_cast<DeviceMenuWidget *>(tabWidget);
qDebug() << "Switching to tab:" << selection.section
<< deviceMenuWidget;
if (deviceMenuWidget && !selection.section.isEmpty()) {
deviceMenuWidget->switchToTab(selection.section);
}
}
break;
case DeviceSelection::Recovery:
if (m_recoveryDeviceWidgets.contains(selection.ecid)) {
QWidget *tabWidget = m_recoveryDeviceWidgets[selection.ecid].first;
if (tabWidget) {
m_stackedWidget->setCurrentWidget(tabWidget);
// Clear current device since we're viewing recovery device
m_currentDeviceUuid = "";
}
}
break;
case DeviceSelection::Pending:
if (m_pendingDeviceWidgets.contains(selection.uuid)) {
QWidget *tabWidget = m_pendingDeviceWidgets[selection.uuid].first;
if (tabWidget) {
m_stackedWidget->setCurrentWidget(tabWidget);
// Clear current device since we're viewing pending device
m_currentDeviceUuid = "";
}
}
break;
}
QWidget *tabWidget = m_deviceWidgets[deviceUuid].first;
DeviceMenuWidget *deviceMenuWidget =
qobject_cast<DeviceMenuWidget *>(tabWidget);
if (deviceMenuWidget) {
// Call a method to change the internal tab
deviceMenuWidget->switchToTab(section);
}
// if (deviceIndex != m_currentDeviceIndex) {
// setCurrentDevice(deviceIndex);
// }
// emit sidebarNavigationChanged(deviceUuid, section);
}
}
+11 -9
View File
@@ -5,6 +5,7 @@
#include "devicependingwidget.h"
#include "devicesidebarwidget.h"
#include "iDescriptor.h"
#include "recoverydeviceinfowidget.h"
#include <QHBoxLayout>
#include <QMap>
#include <QStackedWidget>
@@ -20,28 +21,25 @@ public:
void setCurrentDevice(const std::string &uuid);
std::string getCurrentDevice() const;
// Navigation methods
void setDeviceNavigation(int deviceIndex, const QString &section);
signals:
void deviceChanged(std::string deviceUuid);
void sidebarNavigationChanged(std::string deviceUuid,
const QString &section);
void updateNoDevicesConnected();
private slots:
void onSidebarDeviceChanged(std::string deviceUuid);
void onSidebarNavigationChanged(std::string deviceUuid,
const QString &section);
void onDeviceSelectionChanged(const DeviceSelection &selection);
private:
void setupUI();
void addDevice(iDescriptorDevice *device);
void removeDevice(const std::string &uuid);
void addRecoveryDevice(RecoveryDeviceInfo *device);
void addRecoveryDevice(const iDescriptorRecoveryDevice *device);
void removeRecoveryDevice(uint64_t ecid);
// TODO:udid or uuid ?
void addPendingDevice(const QString &udid, bool locked);
void addPairedDevice(iDescriptorDevice *device);
void removePendingDevice(const QString &udid);
QHBoxLayout *m_mainLayout;
DeviceSidebarWidget *m_sidebar;
QStackedWidget *m_stackedWidget;
@@ -53,6 +51,10 @@ private:
std::pair<DevicePendingWidget *, DevicePendingSidebarItem *>>
m_pendingDeviceWidgets; // Map to store devices by UDID
QMap<uint64_t,
std::pair<RecoveryDeviceInfoWidget *, RecoveryDeviceSidebarItem *>>
m_recoveryDeviceWidgets; // Map to store recovery devices by ECID
std::string m_currentDeviceUuid;
};
+134 -93
View File
@@ -1,6 +1,7 @@
#include "devicesidebarwidget.h"
#include "iDescriptor-ui.h"
#include "loadingspinnerwidget.h"
#include "qprocessindicator.h"
#include <QDebug>
// DeviceSidebarItem Implementation
@@ -26,8 +27,6 @@ void DeviceSidebarItem::setupUI()
QVBoxLayout *headerLayout = new QVBoxLayout(m_headerWidget);
headerLayout->setContentsMargins(0, 0, 0, 0);
headerLayout->setSpacing(2);
m_headerWidget->setStyleSheet(
"ClickableWidget { background-color: #ff0000ff; }");
connect(m_headerWidget, &ClickableWidget::clicked, this,
[this]() { emit deviceSelected(m_uuid); });
@@ -176,15 +175,66 @@ void DeviceSidebarItem::onNavigationButtonClicked()
{
QPushButton *button = qobject_cast<QPushButton *>(sender());
if (button) {
// Only emit navigationRequested - this will handle both device
// selection and tab switching
emit navigationRequested(m_uuid, button->text());
emit deviceSelected(m_uuid);
// Remove this line: emit deviceSelected(m_uuid);
}
}
const std::string &DeviceSidebarItem::getDeviceUuid() const { return m_uuid; }
RecoveryDeviceSidebarItem::RecoveryDeviceSidebarItem(uint64_t ecid,
QWidget *parent)
: QFrame(parent), m_ecid(ecid)
{
setupUI();
}
void RecoveryDeviceSidebarItem::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(5, 5, 5, 5);
mainLayout->setSpacing(5);
ClickableWidget *headerWidget = new ClickableWidget();
connect(headerWidget, &ClickableWidget::clicked, this,
[this]() { emit recoveryDeviceSelected(m_ecid); });
QVBoxLayout *headerLayout = new QVBoxLayout(headerWidget);
headerLayout->setContentsMargins(0, 0, 0, 0);
QLabel *titleLabel = new QLabel("Recovery Mode");
titleLabel->setStyleSheet("QLabel { font-weight: bold; }");
titleLabel->setWordWrap(true);
headerLayout->addWidget(titleLabel);
mainLayout->addWidget(headerWidget);
// Set initial style
// Set initial style
setStyleSheet("RecoveryDeviceSidebarItem { border: "
"1px solid #e0e0e0; border-radius: 5px; }");
}
void RecoveryDeviceSidebarItem::setSelected(bool selected)
{
if (m_selected == selected)
return;
m_selected = selected;
if (selected) {
setStyleSheet("RecoveryDeviceSidebarItem { border: "
"2px solid #2196f3; border-radius: 5px; }");
} else {
setStyleSheet("RecoveryDeviceSidebarItem { border: "
"1px solid #e0e0e0; border-radius: 5px; }");
}
}
// DeviceSidebarWidget Implementation
DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent) : QWidget(parent)
DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent)
: QWidget(parent), m_currentSelection(DeviceSelection(""))
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10);
@@ -219,119 +269,108 @@ DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent) : QWidget(parent)
setMaximumWidth(250);
}
DeviceSidebarItem *DeviceSidebarWidget::addToSidebar(const QString &deviceName,
const std::string &uuid)
DeviceSidebarItem *DeviceSidebarWidget::addDevice(const QString &deviceName,
const std::string &uuid)
{
DeviceSidebarItem *item = new DeviceSidebarItem(deviceName, uuid, this);
// Connect to unified handler
connect(item, &DeviceSidebarItem::deviceSelected, this,
&DeviceSidebarWidget::onDeviceSelected);
[this](const std::string &uuid) {
onItemSelected(DeviceSelection(uuid));
});
connect(item, &DeviceSidebarItem::navigationRequested, this,
&DeviceSidebarWidget::onSidebarNavigationChanged);
[this](const std::string &uuid, const QString &section) {
onItemSelected(DeviceSelection(uuid, section));
});
// TODO
m_currentDeviceUuid = uuid;
// item->setSelected(true);
m_deviceSidebarItems.append(item);
updateSelection();
// m_deviceItems.append(item);
m_contentLayout->insertWidget(m_contentLayout->count() - 1,
item); // Insert before stretch
// Auto-select first device
// if (m_currentDeviceIndex == -1) {
// setCurrentDevice(deviceIndex);
// }
m_deviceItems[uuid] = item;
m_contentLayout->insertWidget(m_contentLayout->count() - 1, item);
return item;
}
void DeviceSidebarWidget::removeFromSidebar(DeviceSidebarItem *item)
{
m_deviceSidebarItems.removeAll(item);
m_contentLayout->removeWidget(item);
item->deleteLater();
}
DevicePendingSidebarItem *
DeviceSidebarWidget::addPendingToSidebar(const QString &uuid)
DeviceSidebarWidget::addPendingDevice(const QString &uuid)
{
DevicePendingSidebarItem *item = new DevicePendingSidebarItem(uuid, this);
m_devicePendingSidebarItems.append(item);
m_contentLayout->insertWidget(m_contentLayout->count() - 1,
item); // Insert before stretch
m_pendingItems[uuid.toStdString()] = item;
m_contentLayout->insertWidget(m_contentLayout->count() - 1, item);
return item;
}
void DeviceSidebarWidget::removePendingFromSidebar(
DevicePendingSidebarItem *item)
RecoveryDeviceSidebarItem *DeviceSidebarWidget::addRecoveryDevice(uint64_t ecid)
{
m_devicePendingSidebarItems.removeAll(item);
m_contentLayout->removeWidget(item);
item->deleteLater();
RecoveryDeviceSidebarItem *item = new RecoveryDeviceSidebarItem(ecid, this);
// Connect to unified handler
connect(item, &RecoveryDeviceSidebarItem::recoveryDeviceSelected, this,
[this](uint64_t ecid) { onItemSelected(DeviceSelection(ecid)); });
m_recoveryItems[ecid] = item;
m_contentLayout->insertWidget(m_contentLayout->count() - 1, item);
return item;
}
void DeviceSidebarWidget::setCurrentDevice(std::string uuid)
void DeviceSidebarWidget::removeDevice(const std::string &uuid)
{
if (m_currentDeviceUuid == uuid)
return;
m_currentDeviceUuid = uuid;
updateSelection();
emit sidebarDeviceChanged(uuid);
}
void DeviceSidebarWidget::setDeviceNavigationSection(int deviceIndex,
const QString &section)
{
// for (DeviceSidebarItem *item : m_deviceItems) {
// if (item->getDeviceIndex() == deviceIndex) {
// // Find and check the appropriate button
// QPushButton *targetButton = nullptr;
// if (section == "Info")
// targetButton = item->findChild<QPushButton *>();
// else if (section == "Apps")
// targetButton = item->findChildren<QPushButton *>().value(1);
// else if (section == "Gallery")
// targetButton = item->findChildren<QPushButton *>().value(2);
// else if (section == "Files")
// targetButton = item->findChildren<QPushButton *>().value(3);
// if (targetButton) {
// targetButton->setChecked(true);
// }
// break;
// }
// }
}
void DeviceSidebarWidget::onDeviceSelected(std::string uuid)
{
setCurrentDevice(uuid);
}
void DeviceSidebarWidget::onSidebarNavigationChanged(std::string uuid,
const QString &section)
{
if (uuid != m_currentDeviceUuid) {
setCurrentDevice(uuid);
if (m_deviceItems.contains(uuid)) {
DeviceSidebarItem *item = m_deviceItems[uuid];
m_deviceItems.remove(uuid);
m_contentLayout->removeWidget(item);
item->deleteLater();
}
emit sidebarNavigationChanged(uuid, section);
}
void DeviceSidebarWidget::updateSidebar(std::string uuid)
void DeviceSidebarWidget::removePendingDevice(const std::string &uuid)
{
// TODO : need a proper check
if (m_deviceSidebarItems.isEmpty())
return;
m_currentDeviceUuid = uuid;
if (m_pendingItems.contains(uuid)) {
DevicePendingSidebarItem *item = m_pendingItems[uuid];
m_pendingItems.remove(uuid);
m_contentLayout->removeWidget(item);
item->deleteLater();
}
}
void DeviceSidebarWidget::removeRecoveryDevice(uint64_t ecid)
{
if (m_recoveryItems.contains(ecid)) {
RecoveryDeviceSidebarItem *item = m_recoveryItems[ecid];
m_recoveryItems.remove(ecid);
m_contentLayout->removeWidget(item);
item->deleteLater();
}
}
void DeviceSidebarWidget::setCurrentSelection(const DeviceSelection &selection)
{
m_currentSelection = selection;
updateSelection();
}
void DeviceSidebarWidget::onItemSelected(const DeviceSelection &selection)
{
setCurrentSelection(selection);
emit deviceSelectionChanged(selection);
}
void DeviceSidebarWidget::updateSelection()
{
for (DeviceSidebarItem *item : m_deviceSidebarItems) {
item->setSelected(item->getDeviceUuid() == m_currentDeviceUuid);
// Clear all selections first
for (auto item : m_deviceItems) {
item->setSelected(false);
}
for (auto item : m_recoveryItems) {
item->setSelected(false);
}
// Set selection based on current selection
if (m_currentSelection.type == DeviceSelection::Normal &&
m_deviceItems.contains(m_currentSelection.uuid)) {
m_deviceItems[m_currentSelection.uuid]->setSelected(true);
} else if (m_currentSelection.type == DeviceSelection::Recovery &&
m_recoveryItems.contains(m_currentSelection.ecid)) {
m_recoveryItems[m_currentSelection.ecid]->setSelected(true);
}
}
@@ -339,15 +378,17 @@ DevicePendingSidebarItem::DevicePendingSidebarItem(const QString &udid,
QWidget *parent)
: QFrame(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(5);
layout->setSpacing(1);
LoadingSpinnerWidget *spinner = new LoadingSpinnerWidget(this);
spinner->setFixedSize(16, 16); // Make it a bit smaller
spinner->setColor(QColor("#0d6efd")); // Use a theme color
QProcessIndicator *spinner = new QProcessIndicator(this);
spinner->setFixedSize(32, 32);
spinner->setType(QProcessIndicator::line_rotate);
spinner->start();
QLabel *label = new QLabel("Pairing...", this);
layout->addWidget(label);
layout->addWidget(spinner);
+61 -17
View File
@@ -69,41 +69,85 @@ signals:
};
#endif // DEVICEPENDINGSIDEBARITEM_H
#ifndef RECOVERYDEVICESIDEBARITEM_H
#define RECOVERYDEVICESIDEBARITEM_H
class RecoveryDeviceSidebarItem : public QFrame
{
Q_OBJECT
public:
explicit RecoveryDeviceSidebarItem(uint64_t ecid,
QWidget *parent = nullptr);
void setSelected(bool selected);
bool isSelected() const { return m_selected; }
private:
void setupUI();
uint64_t m_ecid;
bool m_selected = false;
signals:
void recoveryDeviceSelected(uint64_t ecid);
};
#endif // RECOVERYDEVICESIDEBARITEM_H
// Unified device selection data
struct DeviceSelection {
enum Type { Normal, Recovery, Pending };
Type type;
std::string uuid;
uint64_t ecid = 0;
QString section = "Info";
DeviceSelection(const std::string &deviceUuid, const QString &nav = "")
: type(Normal), uuid(deviceUuid), section(nav)
{
}
DeviceSelection(uint64_t recoveryEcid) : type(Recovery), ecid(recoveryEcid)
{
}
static DeviceSelection pending(const std::string &deviceUuid)
{
DeviceSelection sel(deviceUuid);
sel.type = Pending;
return sel;
}
};
class DeviceSidebarWidget : public QWidget
{
Q_OBJECT
public:
explicit DeviceSidebarWidget(QWidget *parent = nullptr);
std::string getUuid() const;
DeviceSidebarItem *addToSidebar(const QString &deviceName,
const std::string &uuid);
void removeFromSidebar(DeviceSidebarItem *item);
DevicePendingSidebarItem *addPendingToSidebar(const QString &uuid);
void removePendingFromSidebar(DevicePendingSidebarItem *item);
void setDeviceNavigationSection(int deviceIndex, const QString &section);
void updateSidebar(std::string uuid);
// Unified interface
DeviceSidebarItem *addDevice(const QString &deviceName,
const std::string &uuid);
DevicePendingSidebarItem *addPendingDevice(const QString &uuid);
RecoveryDeviceSidebarItem *addRecoveryDevice(uint64_t ecid);
void removeDevice(const std::string &uuid);
void removePendingDevice(const std::string &uuid);
void removeRecoveryDevice(uint64_t ecid);
void setCurrentSelection(const DeviceSelection &selection);
public slots:
void onSidebarNavigationChanged(std::string uuid, const QString &section);
void onItemSelected(const DeviceSelection &selection);
signals:
void sidebarNavigationChanged(std::string uuid, const QString &section);
void deviceNavigationChanged(std::string uuid, const QString &section);
void sidebarDeviceChanged(std::string uuid);
void deviceSelectionChanged(const DeviceSelection &selection);
private:
void updateSelection();
void onDeviceSelected(std::string uuid);
void setCurrentDevice(std::string uuid);
QScrollArea *m_scrollArea;
QWidget *m_contentWidget;
QVBoxLayout *m_contentLayout;
std::string m_currentDeviceUuid;
QList<DeviceSidebarItem *> m_deviceSidebarItems;
QList<DevicePendingSidebarItem *> m_devicePendingSidebarItems;
DeviceSelection m_currentSelection;
QMap<std::string, DeviceSidebarItem *> m_deviceItems;
QMap<std::string, DevicePendingSidebarItem *> m_pendingItems;
QMap<uint64_t, RecoveryDeviceSidebarItem *> m_recoveryItems;
};
#endif // DEVICESIDEBARWIDGET_H
+21 -17
View File
@@ -71,9 +71,9 @@ void DiskUsageWidget::setupUI()
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->setObjectName("diskBarContainer");
m_diskBarLayout = new QHBoxLayout(m_diskBarContainer);
m_diskBarLayout->setContentsMargins(0, 0, 0, 0);
m_diskBarLayout->setSpacing(0);
@@ -92,21 +92,30 @@ void DiskUsageWidget::setupUI()
m_othersBar = new QWidget();
m_freeBar = new QWidget();
#endif
// required for tooltips to have default styling
m_systemBar->setObjectName("systemBar");
m_appsBar->setObjectName("appsBar");
m_mediaBar->setObjectName("mediaBar");
m_othersBar->setObjectName("othersBar");
m_freeBar->setObjectName("freeBar");
// Set colors
m_systemBar->setStyleSheet(
"background-color: #a1384d; border: 1px solid"
"QWidget#systemBar { background-color: #a1384d; border: 1px solid"
"#e64a5b; padding: 0; margin: 0; border-top-left-radius: 3px; "
"border-bottom-left-radius: 3px;");
m_appsBar->setStyleSheet("background-color: #4f869f; border: 1px solid "
"#63b4da; padding: 0; margin: 0;");
m_mediaBar->setStyleSheet(
"background-color: #2ECC71; border: none; padding: 0; margin: 0;");
m_othersBar->setStyleSheet("background-color: #a28729; border: 1px solid "
"#c4a32d; padding: 0; margin: 0;");
"border-bottom-left-radius: 3px; }");
m_appsBar->setStyleSheet(
"QWidget#appsBar { background-color: #4f869f; border: 1px solid "
"#63b4da; padding: 0; margin: 0; }");
m_mediaBar->setStyleSheet("QWidget#mediaBar { background-color: #2ECC71; "
"border: none; padding: 0; margin: 0; }");
m_othersBar->setStyleSheet(
"QWidget#othersBar { background-color: #a28729; border: 1px solid "
"#c4a32d; padding: 0; margin: 0; }");
m_freeBar->setStyleSheet(
"background-color: #474747; border: 1px solid "
"QWidget#freeBar { background-color: #474747; border: 1px solid "
"#4f4f4f; padding: 0; margin: 0; border-top-right-radius: 3px; "
"border-bottom-right-radius: 3px;");
"border-bottom-right-radius: 3px; }");
m_diskBarLayout->addWidget(m_systemBar);
m_diskBarLayout->addWidget(m_appsBar);
@@ -145,6 +154,7 @@ void DiskUsageWidget::setupUI()
m_legendLayout->addStretch();
m_dataLayout->addLayout(m_legendLayout);
// m_dataLayout->addStretch();
m_stackedWidget->addWidget(m_dataPage);
@@ -309,12 +319,6 @@ void DiskUsageWidget::updateUI()
// m_freeBar->setVisible(m_freeSpace > 0);
}
void DiskUsageWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
// No custom painting needed - using widgets and layouts
}
void DiskUsageWidget::fetchData()
{
auto *watcher = new QFutureWatcher<QVariantMap>(this);
-3
View File
@@ -20,9 +20,6 @@ public:
QWidget *parent = nullptr);
QSize sizeHint() const override;
protected:
void paintEvent(QPaintEvent *event) override;
private:
void fetchData();
void setupUI();
+48 -51
View File
@@ -27,7 +27,7 @@
FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device,
QWidget *parent)
: QWidget(parent), device(device), usingAFC2(false)
: QWidget(parent), m_device(device)
{
m_mainSplitter = new ModernSplitter(Qt::Horizontal, this);
@@ -38,16 +38,26 @@ FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device,
setupSidebar();
// Create stacked widget with AFC explorers
m_stackedWidget = new QStackedWidget();
// Add normal AFC explorer (index 0)
m_stackedWidget->addWidget(
new AfcExplorerWidget(m_device->afcClient, nullptr, m_device));
// Add AFC2 explorer (index 1)
m_stackedWidget->addWidget(
new AfcExplorerWidget(m_device->afc2Client, nullptr, m_device));
// Start with normal AFC client
m_stackedWidget->setCurrentIndex(0);
// Add widgets to splitter
m_mainSplitter->addWidget(m_sidebarTree);
m_mainSplitter->addWidget(
new AfcExplorerWidget(device->afcClient, nullptr, device));
m_mainSplitter->addWidget(m_stackedWidget);
m_mainSplitter->setSizes({400, 800});
setLayout(mainLayout);
}
// useAFC2 ,path,
typedef QPair<bool, QString> SidebarItemData;
void FileExplorerWidget::setupSidebar()
{
m_sidebarTree = new QTreeWidget();
@@ -55,63 +65,41 @@ void FileExplorerWidget::setupSidebar()
m_sidebarTree->setMinimumWidth(50);
m_sidebarTree->setMaximumWidth(250);
// AFC Default section
m_afcDefaultItem = new QTreeWidgetItem(m_sidebarTree);
m_afcDefaultItem->setText(0, "Explorer");
m_afcDefaultItem->setIcon(0, QIcon::fromTheme("folder"));
m_afcDefaultItem->setData(0, Qt::UserRole,
QVariant::fromValue(SidebarItemData(false, "/")));
m_afcDefaultItem->setExpanded(true);
QTreeWidgetItem *explorersRoot = new QTreeWidgetItem(m_sidebarTree);
explorersRoot->setText(0, "Explorer");
explorersRoot->setIcon(0, QIcon::fromTheme("folder"));
explorersRoot->setExpanded(true);
// Add root folder under Default
QTreeWidgetItem *rootItem = new QTreeWidgetItem(m_afcDefaultItem);
rootItem->setText(0, "Default");
rootItem->setIcon(0, QIcon::fromTheme("folder"));
rootItem->setData(0, Qt::UserRole,
QVariant::fromValue(SidebarItemData(false, "/")));
rootItem->setData(0, Qt::UserRole + 1, QVariant::fromValue(false));
m_defaultAfcItem = new QTreeWidgetItem(explorersRoot);
m_defaultAfcItem->setText(0, "Default");
m_defaultAfcItem->setIcon(0, QIcon::fromTheme("folder"));
// AFC2 Jailbroken section
m_afcJailbrokenItem = new QTreeWidgetItem(m_afcDefaultItem);
m_afcJailbrokenItem->setText(0, "Jailbroken (AFC2)");
m_afcJailbrokenItem->setIcon(0, QIcon::fromTheme("applications-system"));
m_afcJailbrokenItem->setData(
0, Qt::UserRole, QVariant::fromValue(SidebarItemData(true, "/")));
m_afcJailbrokenItem->setExpanded(false);
m_jailbrokenAfcItem = new QTreeWidgetItem(explorersRoot);
m_jailbrokenAfcItem->setText(0, "Jailbroken (AFC2)");
m_jailbrokenAfcItem->setIcon(0, QIcon::fromTheme("applications-system"));
// Common Places section
m_commonPlacesItem = new QTreeWidgetItem(m_sidebarTree);
m_commonPlacesItem->setText(0, "Common Places");
m_commonPlacesItem->setIcon(0, QIcon::fromTheme("places-bookmarks"));
m_commonPlacesItem->setData(
0, Qt::UserRole,
QVariant::fromValue(
SidebarItemData(false, "../../../var/mobile/Library/Wallpapers")));
m_commonPlacesItem->setExpanded(true);
QTreeWidgetItem *commonPlacesItem = new QTreeWidgetItem(m_sidebarTree);
commonPlacesItem->setText(0, "Common Places");
commonPlacesItem->setIcon(0, QIcon::fromTheme("places-bookmarks"));
commonPlacesItem->setExpanded(true);
QTreeWidgetItem *wallpapersItem = new QTreeWidgetItem(m_commonPlacesItem);
QTreeWidgetItem *wallpapersItem = new QTreeWidgetItem(commonPlacesItem);
wallpapersItem->setText(0, "Wallpapers");
wallpapersItem->setIcon(0, QIcon::fromTheme("image-x-generic"));
wallpapersItem->setData(
0, Qt::UserRole,
QVariant::fromValue(
SidebarItemData(false, "../../../var/mobile/Library/Wallpapers")));
wallpapersItem->setData(0, Qt::UserRole + 1,
QVariant::fromValue(false)); // Default AFC
wallpapersItem->setData(0, Qt::UserRole,
"../../../var/mobile/Library/Wallpapers");
// Favorite Places section
m_favoritePlacesItem = new QTreeWidgetItem(m_sidebarTree);
m_favoritePlacesItem->setText(0, "Favorite Places");
m_favoritePlacesItem->setIcon(0, QIcon::fromTheme("user-bookmarks"));
m_favoritePlacesItem->setData(
// todo:implement
0, Qt::UserRole, QVariant::fromValue(SidebarItemData(false, "/")));
m_favoritePlacesItem->setExpanded(true);
loadFavoritePlaces();
// connect(m_sidebarTree, &QTreeWidget::itemClicked, this,
// &FileExplorerWidget::onSidebarItemClicked);
connect(m_sidebarTree, &QTreeWidget::itemClicked, this,
&FileExplorerWidget::onSidebarItemClicked);
}
void FileExplorerWidget::loadFavoritePlaces()
@@ -128,9 +116,18 @@ void FileExplorerWidget::loadFavoritePlaces()
new QTreeWidgetItem(m_favoritePlacesItem);
favoriteItem->setText(0, alias);
favoriteItem->setIcon(0, QIcon::fromTheme("folder-favorites"));
favoriteItem->setData(
0, Qt::UserRole, QVariant::fromValue(SidebarItemData(false, path)));
favoriteItem->setData(0, Qt::UserRole + 1,
QVariant::fromValue(false)); // Default to AFC
favoriteItem->setData(0, Qt::UserRole, QVariant::fromValue(path));
}
}
void FileExplorerWidget::onSidebarItemClicked(QTreeWidgetItem *item, int column)
{
Q_UNUSED(column);
if (item == m_defaultAfcItem) {
m_stackedWidget->setCurrentIndex(0);
} else if (item == m_jailbrokenAfcItem) {
m_stackedWidget->setCurrentIndex(1);
}
// TODO: implement favorite places
}
+8 -5
View File
@@ -10,6 +10,7 @@
#include <QPushButton>
#include <QSplitter>
#include <QStack>
#include <QStackedWidget>
#include <QString>
#include <QTreeWidget>
#include <QVBoxLayout>
@@ -23,17 +24,19 @@ public:
explicit FileExplorerWidget(iDescriptorDevice *device,
QWidget *parent = nullptr);
private slots:
void onSidebarItemClicked(QTreeWidgetItem *item, int column);
private:
QSplitter *m_mainSplitter;
QStackedWidget *m_stackedWidget;
afc_client_t currentAfcClient;
QTreeWidget *m_sidebarTree;
iDescriptorDevice *device;
bool usingAFC2;
iDescriptorDevice *m_device;
// Tree items
QTreeWidgetItem *m_afcDefaultItem;
QTreeWidgetItem *m_afcJailbrokenItem;
QTreeWidgetItem *m_commonPlacesItem;
QTreeWidgetItem *m_defaultAfcItem;
QTreeWidgetItem *m_jailbrokenAfcItem;
QTreeWidgetItem *m_favoritePlacesItem;
void setupSidebar();
-46
View File
@@ -17,52 +17,6 @@
#include <QVBoxLayout>
#include <QtConcurrent/QtConcurrent>
// todo: create a service
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path)
{
uint64_t fd_handle = 0;
afc_error_t fd_err =
afc_file_open(afcClient, path, AFC_FOPEN_RDONLY, &fd_handle);
if (fd_err != AFC_E_SUCCESS) {
qDebug() << "Could not open file" << path;
return QByteArray();
}
// TODO: is this necessary
char **info = NULL;
afc_get_file_info(afcClient, path, &info);
uint64_t fileSize = 0;
if (info) {
for (int i = 0; info[i]; i += 2) {
if (strcmp(info[i], "st_size") == 0) {
fileSize = std::stoull(info[i + 1]);
break;
}
}
afc_dictionary_free(info);
}
if (fileSize == 0) {
afc_file_close(afcClient, fd_handle);
return QByteArray();
}
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;
return QByteArray(); // Read failed
}
return buffer;
};
void GalleryWidget::load()
{
if (m_loaded)
+1
View File
@@ -123,6 +123,7 @@ public:
: QSplitter(orientation, parent)
{
setHandleWidth(10);
setCursor(Qt::SplitHCursor);
}
protected:
+13 -83
View File
@@ -140,10 +140,6 @@ struct iDescriptorDevice {
idevice_connection_type conn_type;
idevice_t device;
DeviceInfo deviceInfo;
/*
inital afc client to start the file explorer and gallery with
clients are not long lived, so do not assume this will be valid
*/
afc_client_t afcClient;
afc_client_t afc2Client;
bool is_iPhone;
@@ -158,85 +154,12 @@ struct IDescriptorInitDeviceResult {
afc_client_t afc2Client;
};
// Device model identifier to marketing name mapping
const std::unordered_map<std::string, std::string> DEVICE_MAP = {
{"iPhone1,1", "iPhone 2G"},
{"iPhone1,2", "iPhone 3G"},
{"iPhone2,1", "iPhone 3GS"},
{"iPhone3,1", "iPhone 4 (GSM)"},
{"iPhone3,2", "iPhone 4 (GSM Rev A)"},
{"iPhone3,3", "iPhone 4 (CDMA)"},
{"iPhone4,1", "iPhone 4S"},
{"iPhone5,1", "iPhone 5 (GSM)"},
{"iPhone5,2", "iPhone 5 (GSM+CDMA)"},
{"iPhone5,3", "iPhone 5c (GSM)"},
{"iPhone5,4", "iPhone 5c (GSM+CDMA)"},
{"iPhone6,1", "iPhone 5s (GSM)"},
{"iPhone6,2", "iPhone 5s (GSM+CDMA)"},
{"iPhone7,1", "iPhone 6 Plus"},
{"iPhone7,2", "iPhone 6"},
{"iPhone8,1", "iPhone 6s"},
{"iPhone8,2", "iPhone 6s Plus"},
{"iPhone8,4", "iPhone SE (1st generation)"},
{"iPhone9,1", "iPhone 7 (GSM)"},
{"iPhone9,2", "iPhone 7 Plus (GSM)"},
{"iPhone9,3", "iPhone 7 (GSM+CDMA)"},
{"iPhone9,4", "iPhone 7 Plus (GSM+CDMA)"},
{"iPhone10,1", "iPhone 8 (GSM)"},
{"iPhone10,2", "iPhone 8 Plus (GSM)"},
{"iPhone10,3", "iPhone X (GSM)"},
{"iPhone10,4", "iPhone 8 (GSM+CDMA)"},
{"iPhone10,5", "iPhone 8 Plus (GSM+CDMA)"},
{"iPhone10,6", "iPhone X (GSM+CDMA)"},
{"iPhone11,2", "iPhone XS"},
{"iPhone11,4", "iPhone XS Max"},
{"iPhone11,6", "iPhone XS Max (China)"},
{"iPhone11,8", "iPhone XR"},
{"iPhone12,1", "iPhone 11"},
{"iPhone12,3", "iPhone 11 Pro"},
{"iPhone12,5", "iPhone 11 Pro Max"},
{"iPhone12,8", "iPhone SE (2nd generation)"},
{"iPhone13,1", "iPhone 12 mini"},
{"iPhone13,2", "iPhone 12"},
{"iPhone13,3", "iPhone 12 Pro"},
{"iPhone13,4", "iPhone 12 Pro Max"},
{"iPhone14,4", "iPhone 13 mini"},
{"iPhone14,5", "iPhone 13"},
{"iPhone14,2", "iPhone 13 Pro"},
{"iPhone14,3", "iPhone 13 Pro Max"},
{"iPhone14,6", "iPhone SE (3rd generation)"},
{"iPhone15,2", "iPhone 14 Pro"},
{"iPhone15,3", "iPhone 14 Pro Max"},
{"iPad1,1", "iPad 1st generation"},
{"iPad2,1", "iPad 2 (WiFi)"},
{"iPad2,2", "iPad 2 (GSM)"},
{"iPad2,3", "iPad 2 (CDMA)"},
{"iPad2,4", "iPad 2 (Rev A)"},
{"iPad3,1", "iPad 3rd generation (WiFi)"},
{"iPad3,2", "iPad 3rd generation (GSM)"},
};
struct RecoveryDeviceInfo : public QObject {
Q_OBJECT
public:
RecoveryDeviceInfo(const irecv_device_event_t *event,
QObject *parent = nullptr)
: QObject(parent)
{
if (event && event->device_info) {
ecid = event->device_info->ecid;
mode = event->mode;
cpid = event->device_info->cpid;
bdid = event->device_info->bdid;
}
}
struct iDescriptorRecoveryDevice {
uint64_t ecid;
irecv_mode mode;
uint32_t cpid;
uint32_t bdid;
QString product;
QString model;
QString board_id;
const char *displayName;
};
struct TakeScreenshotResult {
@@ -247,8 +170,10 @@ struct TakeScreenshotResult {
struct IDescriptorInitDeviceResultRecovery {
irecv_client_t client = nullptr;
irecv_device_info deviceInfo;
irecv_error_t error;
bool success = false;
irecv_mode mode = IRECV_K_RECOVERY_MODE_1;
const char *displayName = nullptr;
};
void warn(const QString &message, const QString &title = "Warning",
@@ -335,14 +260,14 @@ struct MediaEntry {
bool isDir;
};
struct MediaFileTree {
struct AFCFileTree {
std::vector<MediaEntry> entries;
bool success;
std::string currentPath;
};
MediaFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
const std::string &path = "/");
AFCFileTree get_file_tree(afc_client_t afcClient, idevice_t device,
const std::string &path = "/");
bool detect_jailbroken(afc_client_t afc);
@@ -353,7 +278,7 @@ void get_device_info_xml(const char *udid, int use_network, int simple,
IDescriptorInitDeviceResult init_idescriptor_device(const char *udid);
IDescriptorInitDeviceResultRecovery
init_idescriptor_recovery_device(irecv_device_info *info);
init_idescriptor_recovery_device(uint64_t ecid);
bool set_location(idevice_t device, char *lat, char *lon);
@@ -467,3 +392,8 @@ struct NetworkDevice {
return name == other.name && address == other.address;
}
};
QPixmap load_heic(const QByteArray &data);
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient,
const char *path);
+58
View File
@@ -0,0 +1,58 @@
#include "infolabel.h"
#include <QApplication>
#include <QClipboard>
#include <QMouseEvent>
InfoLabel::InfoLabel(const QString &text, QWidget *parent)
: QLabel(text, parent), m_originalText(text)
{
setCursor(Qt::PointingHandCursor);
setStyleSheet("QLabel:hover { background-color: rgba(255, 255, 255, 0.1); "
"border-radius: 2px; }");
m_restoreTimer = new QTimer(this);
m_restoreTimer->setSingleShot(true);
connect(m_restoreTimer, &QTimer::timeout, this,
&InfoLabel::restoreOriginalText);
}
void InfoLabel::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(m_originalText);
setText("Copied!");
setStyleSheet("QLabel { color: #4CAF50; font-weight: bold; } "
"QLabel:hover { background-color: rgba(255, 255, 255, "
"0.1); border-radius: 2px; }");
m_restoreTimer->start(1000); // Show "Copied!" for 1 second
}
QLabel::mousePressEvent(event);
}
void InfoLabel::enterEvent(QEnterEvent *event)
{
if (!m_restoreTimer->isActive()) {
setStyleSheet("QLabel:hover { background-color: rgba(255, 255, 255, "
"0.1); border-radius: 2px; }");
}
QLabel::enterEvent(event);
}
void InfoLabel::leaveEvent(QEvent *event)
{
if (!m_restoreTimer->isActive()) {
setStyleSheet("QLabel:hover { background-color: rgba(255, 255, 255, "
"0.1); border-radius: 2px; }");
}
QLabel::leaveEvent(event);
}
void InfoLabel::restoreOriginalText()
{
setText(m_originalText);
setStyleSheet("QLabel:hover { background-color: rgba(255, 255, 255, 0.1); "
"border-radius: 2px; }");
}
+28
View File
@@ -0,0 +1,28 @@
#ifndef INFOLABEL_H
#define INFOLABEL_H
#include <QLabel>
#include <QTimer>
class InfoLabel : public QLabel
{
Q_OBJECT
public:
explicit InfoLabel(const QString &text = QString(),
QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void enterEvent(QEnterEvent *event);
void leaveEvent(QEvent *event) override;
private slots:
void restoreOriginalText();
private:
QString m_originalText;
QTimer *m_restoreTimer;
};
#endif // INFOLABEL_H
+10 -14
View File
@@ -1,5 +1,6 @@
#include "jailbrokenwidget.h"
#include "appcontext.h"
#include "responsiveqlabel.h"
#include "sshterminalwidget.h"
#ifdef __linux__
@@ -12,8 +13,6 @@
#include "iDescriptor.h"
#include <QButtonGroup>
#include <QDebug>
#include <QGraphicsPixmapItem>
#include <QGraphicsView>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
@@ -29,18 +28,15 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent}
mainLayout->setContentsMargins(2, 2, 2, 2);
mainLayout->setSpacing(2);
QGraphicsScene *scene = new QGraphicsScene(this);
QGraphicsPixmapItem *pixmapItem =
new QGraphicsPixmapItem(QPixmap(":/resources/iphone.png"));
scene->addItem(pixmapItem);
// 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;");
QGraphicsView *graphicsView = new ResponsiveGraphicsView(scene, this);
graphicsView->setRenderHint(QPainter::Antialiasing);
graphicsView->setMinimumWidth(200);
graphicsView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
graphicsView->setStyleSheet("background:transparent; border: none;");
mainLayout->addWidget(graphicsView, 1);
mainLayout->addWidget(deviceImageLabel, 1);
// Connect to AppContext for device events
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
@@ -48,7 +44,7 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent}
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
&JailbrokenWidget::onWiredDeviceRemoved);
#ifdef __linux____
#ifdef __linux__
m_wirelessProvider = new AvahiService(this);
connect(m_wirelessProvider, &AvahiService::deviceAdded, this,
&JailbrokenWidget::onWirelessDeviceAdded);
+6 -9
View File
@@ -84,18 +84,15 @@ void handleCallbackRecovery(const irecv_device_event_t *event, void *userData)
switch (event->type) {
case IRECV_DEVICE_ADD:
qDebug() << "Recovery device added: ";
// TODO: handle recovery device addition
QMetaObject::invokeMethod(
AppContext::sharedInstance(), "addRecoveryDevice",
Qt::QueuedConnection,
Q_ARG(RecoveryDeviceInfo *, new RecoveryDeviceInfo(event)));
QMetaObject::invokeMethod(AppContext::sharedInstance(),
"addRecoveryDevice", Qt::QueuedConnection,
Q_ARG(uint64_t, event->device_info->ecid));
break;
case IRECV_DEVICE_REMOVE:
qDebug() << "Recovery device removed: ";
QMetaObject::invokeMethod(
AppContext::sharedInstance(), "removeRecoveryDevice",
Qt::QueuedConnection,
Q_ARG(QString, QString::number(event->device_info->ecid)));
QMetaObject::invokeMethod(AppContext::sharedInstance(),
"removeRecoveryDevice", Qt::QueuedConnection,
Q_ARG(uint64_t, event->device_info->ecid));
break;
default:
printf("Unhandled recovery event: %d\n", event->type);
+2 -2
View File
@@ -52,8 +52,8 @@ MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device,
resize(screenSize);
// Add window transparency
setAttribute(Qt::WA_TranslucentBackground);
setWindowOpacity(0.99);
// looks way too bad on linux - maybe enable only on macOS and Windows
// setAttribute(Qt::WA_TranslucentBackground);
setupUI();
loadMedia();
+1 -4
View File
@@ -1,6 +1,7 @@
#include "mediastreamer.h"
#include <QtGlobal>
#include "iDescriptor.h"
#include <QDebug>
#include <QFileInfo>
#include <QHostAddress>
@@ -10,10 +11,6 @@
#include <libimobiledevice/afc.h>
#include <memory>
// Forward declare AFC helper function
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient,
const char *path);
MediaStreamer::MediaStreamer(iDescriptorDevice *device, afc_client_t afcClient,
const QString &filePath, QObject *parent)
: QTcpServer(parent), m_device(device), m_afcClient(afcClient),
+14 -3
View File
@@ -1,5 +1,6 @@
#include "photomodel.h"
#include "iDescriptor.h"
#include "mediastreamermanager.h"
#include <QDebug>
#include <QEventLoop>
@@ -14,9 +15,6 @@
#include <QtConcurrent/QtConcurrent>
#include <algorithm>
// Forward declare your helper function
QByteArray read_afc_file_to_byte_array(afc_client_t afcClient,
const char *path);
PhotoModel::PhotoModel(iDescriptorDevice *device, QObject *parent)
: QAbstractListModel(parent), m_device(device), m_thumbnailSize(256, 256),
m_sortOrder(NewestFirst), m_filterType(All)
@@ -343,6 +341,12 @@ QPixmap PhotoModel::loadThumbnailFromDevice(iDescriptorDevice *device,
return QPixmap(); // Return empty pixmap on error
}
if (filePath.endsWith(".HEIC")) {
qDebug() << "Loading HEIC image from data for:" << filePath;
QPixmap img = load_heic(imageData);
return img.isNull() ? QPixmap() : img;
}
// Load pixmap from data
QPixmap original;
if (!original.loadFromData(imageData)) {
@@ -384,6 +388,13 @@ QPixmap PhotoModel::loadImage(iDescriptorDevice *device,
return QPixmap(); // Return empty pixmap on error
}
if (filePath.endsWith(".HEIC")) {
qDebug() << "Loading HEIC image from data for:" << filePath;
QPixmap img = load_heic(imageData);
return img.isNull() ? QPixmap() : img;
}
// TODO
// Load pixmap from data
QPixmap original;
if (!original.loadFromData(imageData)) {
+15 -9
View File
@@ -3,11 +3,12 @@
#include "libirecovery.h"
#include <QDebug>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(RecoveryDeviceInfo *info,
QWidget *parent)
RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(
const iDescriptorRecoveryDevice *info, QWidget *parent)
: QWidget{parent}
{
ecid = info->ecid; // Assuming ecid is unique for each device
@@ -22,13 +23,13 @@ RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(RecoveryDeviceInfo *info,
QString::fromStdString(parse_recovery_mode(info->mode))));
devLayout->addWidget(new QLabel("ECID: " + QString::number(info->ecid)));
devLayout->addWidget(new QLabel("CPID: " + QString::number(info->cpid)));
devLayout->addWidget(new QLabel("BDID: " + QString::number(info->bdid)));
devLayout->addWidget(new QLabel(info->displayName));
QPushButton *exitRecoveyMode = new QPushButton("Exit Recovery Mode");
connect(exitRecoveyMode, &QPushButton::clicked, this, [this, info]() {
irecv_client_t client = NULL;
irecv_error_t ierr =
irecv_open_with_ecid_and_attempts(&client, info->ecid, 3);
irecv_error_t ierr = irecv_open_with_ecid_and_attempts(
&client, info->ecid, RECOVERY_CLIENT_CONNECTION_TRIES);
irecv_error_t error = IRECV_E_SUCCESS;
if (ierr != IRECV_E_SUCCESS) {
printf("Failed to open device with ECID %llu: %s\n", info->ecid,
@@ -45,12 +46,17 @@ RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(RecoveryDeviceInfo *info,
error = irecv_setenv(client, "auto-boot", "true");
if (error != IRECV_E_SUCCESS) {
QMessageBox::critical(
this, "Error",
"Failed to set environment variable 'auto-boot' to 'true'");
qDebug() << "Failed to set environment variable: "
<< irecv_strerror(error);
}
error = irecv_saveenv(client);
if (error != IRECV_E_SUCCESS) {
QMessageBox::critical(this, "Error",
"Failed to save environment variables");
qDebug() << "Failed to save environment variables: "
<< irecv_strerror(error);
}
@@ -58,13 +64,13 @@ RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(RecoveryDeviceInfo *info,
error = irecv_reboot(client);
if (error != IRECV_E_SUCCESS) {
// debug("%s\n", irecv_strerror(error));
qDebug() << "Failed to send reboot command: "
QMessageBox::critical(this, "Error",
"Failed to send reboot command");
qDebug() << "critical to send reboot command: "
<< irecv_strerror(error);
} else {
// debug("%s\n", irecv_strerror(error));
}
irecv_close(client);
});
devLayout->addWidget(exitRecoveyMode);
devLayout->addWidget(exitRecoveyMode, 0, Qt::AlignCenter);
}
+1 -1
View File
@@ -8,7 +8,7 @@ class RecoveryDeviceInfoWidget : public QWidget
Q_OBJECT
public:
explicit RecoveryDeviceInfoWidget(RecoveryDeviceInfo *info,
explicit RecoveryDeviceInfoWidget(const iDescriptorRecoveryDevice *info,
QWidget *parent = nullptr);
uint64_t ecid; // Assuming ecid is unique for each device
signals:
+56
View File
@@ -0,0 +1,56 @@
#include "responsiveqlabel.h"
#include <QDebug>
#include <QPainter>
ResponsiveQLabel::ResponsiveQLabel(QWidget *parent) : QLabel(parent)
{
setAlignment(Qt::AlignCenter);
setScaledContents(false);
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
setMinimumSize(100, 100);
}
void ResponsiveQLabel::setPixmap(const QPixmap &pixmap)
{
m_originalPixmap = pixmap;
updateScaledPixmap();
}
void ResponsiveQLabel::resizeEvent(QResizeEvent *event)
{
QLabel::resizeEvent(event);
if (!m_originalPixmap.isNull()) {
updateScaledPixmap();
}
}
void ResponsiveQLabel::paintEvent(QPaintEvent *event)
{
if (m_scaledPixmap.isNull()) {
QLabel::paintEvent(event);
return;
}
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// Calculate position to center the pixmap
int x = (width() - m_scaledPixmap.width()) / 2;
int y = (height() - m_scaledPixmap.height()) / 2;
painter.drawPixmap(x, y, m_scaledPixmap);
}
void ResponsiveQLabel::updateScaledPixmap()
{
if (m_originalPixmap.isNull() || size().isEmpty()) {
return;
}
// Scale the pixmap while maintaining aspect ratio
m_scaledPixmap = m_originalPixmap.scaled(size(), Qt::KeepAspectRatio,
Qt::SmoothTransformation);
update();
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef RESPONSIVEQLABEL_H
#define RESPONSIVEQLABEL_H
#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>
class ResponsiveQLabel : public QLabel
{
Q_OBJECT
public:
explicit ResponsiveQLabel(QWidget *parent = nullptr);
void setPixmap(const QPixmap &pixmap);
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
void updateScaledPixmap();
QPixmap m_originalPixmap;
QPixmap m_scaledPixmap;
};
#endif // RESPONSIVEQLABEL_H