diff --git a/CMakeLists.txt b/CMakeLists.txt index c5868ea..bc75b60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 78b219b..0e5cbbe 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -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 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().first; - QString path = item->data(0, Qt::UserRole).value().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(); -} diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h index 375c458..8c592f3 100644 --- a/src/afcexplorerwidget.h +++ b/src/afcexplorerwidget.h @@ -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); diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 25d02d8..50e3a36 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include 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 AppContext::getAllDevices() return m_devices.values(); } -QList AppContext::getAllRecoveryDevices() +QList 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() diff --git a/src/appcontext.h b/src/appcontext.h index d54116d..b5923a7 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -18,30 +18,31 @@ public: bool noDevicesConnected() const; // Returns whether there are any devices connected (regular or recovery) - QList getAllRecoveryDevices(); + QList getAllRecoveryDevices(); ~AppContext(); int getConnectedDeviceCount() const; private: QMap m_devices; - QMap m_recoveryDevices; + QMap 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 diff --git a/src/core/helpers/get_file_tree.cpp b/src/core/helpers/get_file_tree.cpp index c436a8d..e6fb51a 100644 --- a/src/core/helpers/get_file_tree.cpp +++ b/src/core/helpers/get_file_tree.cpp @@ -5,22 +5,22 @@ #include #include -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; } \ No newline at end of file diff --git a/src/core/helpers/parse_product_type.cpp b/src/core/helpers/parse_product_type.cpp deleted file mode 100644 index 1e4053e..0000000 --- a/src/core/helpers/parse_product_type.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "../../iDescriptor.h" -#include - -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 -} \ No newline at end of file diff --git a/src/core/helpers/read_afc_file_to_byte_array.cpp b/src/core/helpers/read_afc_file_to_byte_array.cpp new file mode 100644 index 0000000..c950de3 --- /dev/null +++ b/src/core/helpers/read_afc_file_to_byte_array.cpp @@ -0,0 +1,48 @@ +#include "../../iDescriptor.h" +#include +#include + +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; +}; \ No newline at end of file diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index e1cb872..3100d5c 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -1,9 +1,11 @@ +#include "../../devicedatabase.h" #include "../../iDescriptor.h" #include "libirecovery.h" #include #include #include #include +#include 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; } \ No newline at end of file diff --git a/src/core/services/load_heic.cpp b/src/core/services/load_heic.cpp new file mode 100644 index 0000000..255c265 --- /dev/null +++ b/src/core/services/load_heic.cpp @@ -0,0 +1,64 @@ +#include "../../iDescriptor.h" +#include +#include +#include +#include +#include + +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; +} diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 426f0e5..e254084 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -4,12 +4,10 @@ #include "fileexplorerwidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "infolabel.h" #include #include #include -#include -#include -#include #include #include #include @@ -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> 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() { diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 950a27e..2ef0538 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -2,8 +2,7 @@ #define DEVICEINFOWIDGET_H #include "batterywidget.h" #include "iDescriptor.h" -#include -#include +#include "responsiveqlabel.h" #include #include #include @@ -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 diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp index 7009691..716dc9e 100644 --- a/src/devicemanagerwidget.cpp +++ b/src/devicemanagerwidget.cpp @@ -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( - 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( - // 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 §ion) +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 §ion) -{ - if (deviceUuid != m_currentDeviceUuid) { - setCurrentDevice(deviceUuid); + // Handle navigation section + QWidget *tabWidget = m_deviceWidgets[selection.uuid].first; + DeviceMenuWidget *deviceMenuWidget = + qobject_cast(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(tabWidget); - - if (deviceMenuWidget) { - // Call a method to change the internal tab - deviceMenuWidget->switchToTab(section); - } - // if (deviceIndex != m_currentDeviceIndex) { - // setCurrentDevice(deviceIndex); - // } - // emit sidebarNavigationChanged(deviceUuid, section); -} +} \ No newline at end of file diff --git a/src/devicemanagerwidget.h b/src/devicemanagerwidget.h index 107e00d..e505ed3 100644 --- a/src/devicemanagerwidget.h +++ b/src/devicemanagerwidget.h @@ -5,6 +5,7 @@ #include "devicependingwidget.h" #include "devicesidebarwidget.h" #include "iDescriptor.h" +#include "recoverydeviceinfowidget.h" #include #include #include @@ -20,28 +21,25 @@ public: void setCurrentDevice(const std::string &uuid); std::string getCurrentDevice() const; - // Navigation methods - void setDeviceNavigation(int deviceIndex, const QString §ion); - signals: void deviceChanged(std::string deviceUuid); - void sidebarNavigationChanged(std::string deviceUuid, - const QString §ion); void updateNoDevicesConnected(); + private slots: - void onSidebarDeviceChanged(std::string deviceUuid); - void onSidebarNavigationChanged(std::string deviceUuid, - const QString §ion); + 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> m_pendingDeviceWidgets; // Map to store devices by UDID + QMap> + m_recoveryDeviceWidgets; // Map to store recovery devices by ECID + std::string m_currentDeviceUuid; }; diff --git a/src/devicesidebarwidget.cpp b/src/devicesidebarwidget.cpp index 251dbf7..3aabac1 100644 --- a/src/devicesidebarwidget.cpp +++ b/src/devicesidebarwidget.cpp @@ -1,6 +1,7 @@ #include "devicesidebarwidget.h" #include "iDescriptor-ui.h" #include "loadingspinnerwidget.h" +#include "qprocessindicator.h" #include // 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(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 §ion) { + 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 §ion) -{ - // for (DeviceSidebarItem *item : m_deviceItems) { - // if (item->getDeviceIndex() == deviceIndex) { - // // Find and check the appropriate button - // QPushButton *targetButton = nullptr; - // if (section == "Info") - // targetButton = item->findChild(); - // else if (section == "Apps") - // targetButton = item->findChildren().value(1); - // else if (section == "Gallery") - // targetButton = item->findChildren().value(2); - // else if (section == "Files") - // targetButton = item->findChildren().value(3); - - // if (targetButton) { - // targetButton->setChecked(true); - // } - // break; - // } - // } -} - -void DeviceSidebarWidget::onDeviceSelected(std::string uuid) -{ - setCurrentDevice(uuid); -} - -void DeviceSidebarWidget::onSidebarNavigationChanged(std::string uuid, - const QString §ion) -{ - 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); diff --git a/src/devicesidebarwidget.h b/src/devicesidebarwidget.h index b070b9a..5bcecb8 100644 --- a/src/devicesidebarwidget.h +++ b/src/devicesidebarwidget.h @@ -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 §ion); - 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 §ion); + void onItemSelected(const DeviceSelection &selection); signals: - void sidebarNavigationChanged(std::string uuid, const QString §ion); - void deviceNavigationChanged(std::string uuid, const QString §ion); - 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 m_deviceSidebarItems; - QList m_devicePendingSidebarItems; + DeviceSelection m_currentSelection; + QMap m_deviceItems; + QMap m_pendingItems; + QMap m_recoveryItems; }; #endif // DEVICESIDEBARWIDGET_H \ No newline at end of file diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 9d925e5..938d29f 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -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(this); diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h index 8497b56..23a30b4 100644 --- a/src/diskusagewidget.h +++ b/src/diskusagewidget.h @@ -20,9 +20,6 @@ public: QWidget *parent = nullptr); QSize sizeHint() const override; -protected: - void paintEvent(QPaintEvent *event) override; - private: void fetchData(); void setupUI(); diff --git a/src/fileexplorerwidget.cpp b/src/fileexplorerwidget.cpp index 5aefd31..91b684c 100644 --- a/src/fileexplorerwidget.cpp +++ b/src/fileexplorerwidget.cpp @@ -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 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 } \ No newline at end of file diff --git a/src/fileexplorerwidget.h b/src/fileexplorerwidget.h index 826b927..c684336 100644 --- a/src/fileexplorerwidget.h +++ b/src/fileexplorerwidget.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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(); diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 1d6a8ca..a2aa4a0 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -17,52 +17,6 @@ #include #include -// 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) diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index 98a4cf9..d9c459e 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -123,6 +123,7 @@ public: : QSplitter(orientation, parent) { setHandleWidth(10); + setCursor(Qt::SplitHCursor); } protected: diff --git a/src/iDescriptor.h b/src/iDescriptor.h index 635a99c..3f4b75f 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -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 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 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); diff --git a/src/infolabel.cpp b/src/infolabel.cpp new file mode 100644 index 0000000..7694613 --- /dev/null +++ b/src/infolabel.cpp @@ -0,0 +1,58 @@ +#include "infolabel.h" +#include +#include +#include + +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; }"); +} diff --git a/src/infolabel.h b/src/infolabel.h new file mode 100644 index 0000000..dbbfb14 --- /dev/null +++ b/src/infolabel.h @@ -0,0 +1,28 @@ +#ifndef INFOLABEL_H +#define INFOLABEL_H + +#include +#include + +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 \ No newline at end of file diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index 59c69f7..835093e 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -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 #include -#include -#include #include #include #include @@ -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); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 48819f5..97d8769 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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); diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 04d1f65..277e509 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -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(); diff --git a/src/mediastreamer.cpp b/src/mediastreamer.cpp index 64c7cf4..8bcddee 100644 --- a/src/mediastreamer.cpp +++ b/src/mediastreamer.cpp @@ -1,6 +1,7 @@ #include "mediastreamer.h" #include +#include "iDescriptor.h" #include #include #include @@ -10,10 +11,6 @@ #include #include -// 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), diff --git a/src/photomodel.cpp b/src/photomodel.cpp index 9d9f758..0d2c66a 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -1,5 +1,6 @@ #include "photomodel.h" +#include "iDescriptor.h" #include "mediastreamermanager.h" #include #include @@ -14,9 +15,6 @@ #include #include -// 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)) { diff --git a/src/recoverydeviceinfowidget.cpp b/src/recoverydeviceinfowidget.cpp index ca54223..1597413 100644 --- a/src/recoverydeviceinfowidget.cpp +++ b/src/recoverydeviceinfowidget.cpp @@ -3,11 +3,12 @@ #include "libirecovery.h" #include #include +#include #include #include -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); } diff --git a/src/recoverydeviceinfowidget.h b/src/recoverydeviceinfowidget.h index 7c6ddc3..bb037fa 100644 --- a/src/recoverydeviceinfowidget.h +++ b/src/recoverydeviceinfowidget.h @@ -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: diff --git a/src/responsiveqlabel.cpp b/src/responsiveqlabel.cpp new file mode 100644 index 0000000..3b91d16 --- /dev/null +++ b/src/responsiveqlabel.cpp @@ -0,0 +1,56 @@ +#include "responsiveqlabel.h" +#include +#include + +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(); +} \ No newline at end of file diff --git a/src/responsiveqlabel.h b/src/responsiveqlabel.h new file mode 100644 index 0000000..319e7cf --- /dev/null +++ b/src/responsiveqlabel.h @@ -0,0 +1,27 @@ +#ifndef RESPONSIVEQLABEL_H +#define RESPONSIVEQLABEL_H + +#include +#include +#include + +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 \ No newline at end of file