From c0995006afd472e1f264d568e76b3f7fd682625a Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 21 Dec 2025 05:34:08 +0000 Subject: [PATCH 01/15] WIP: migrate to idevice-rs --- .gitmodules | 3 + .vscode/c_cpp_properties.json | 50 +- .vscode/settings.json | 6 +- CMakeLists.txt | 331 ++++++++---- README.md | 21 +- lib/idevice-rs | 1 + src/afcexplorerwidget.cpp | 2 - src/afcexplorerwidget.h | 3 +- src/appcontext.cpp | 457 ++++++++++------ src/appcontext.h | 34 +- src/core/services/avahi/avahi_service.cpp | 5 +- src/core/services/detect_jailbroken.cpp | 21 +- src/core/services/get-device-info.cpp | 34 +- src/core/services/get_battery_info.cpp | 58 ++- src/core/services/init_device.cpp | 437 +++++++++++----- src/deviceimagewidget.cpp | 6 +- src/deviceinfowidget.cpp | 134 ++--- src/devicemanagerwidget.cpp | 233 +++++---- src/devicemanagerwidget.h | 25 +- src/devicemenuwidget.cpp | 42 +- src/devicemenuwidget.h | 10 +- src/devicemonitor.h | 126 +++++ src/diskusagewidget.cpp | 203 ++++---- src/fileexplorerwidget.cpp | 2 - src/fileexplorerwidget.h | 3 +- src/iDescriptor.h | 291 ++++++----- src/main.cpp | 14 +- src/mainwindow.cpp | 601 ++++++++++++++-------- src/mainwindow.h | 10 +- src/networkdevicemanager.cpp | 28 + src/networkdevicemanager.h | 29 ++ 31 files changed, 2024 insertions(+), 1196 deletions(-) create mode 160000 lib/idevice-rs create mode 100644 src/devicemonitor.h create mode 100644 src/networkdevicemanager.cpp create mode 100644 src/networkdevicemanager.h diff --git a/.gitmodules b/.gitmodules index 1e3396f..13c8d2e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/win-ifuse"] path = lib/win-ifuse url = https://github.com/uncor3/win-ifuse.git +[submodule "lib/idevice-rs"] + path = lib/idevice-rs + url = https://github.com/jkcoxson/idevice.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0b6e02f..cc09ba3 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,25 +1,27 @@ { - "configurations": [ - { - "name": "Linux", - "includePath": [ - "${workspaceFolder}/**", - "/usr/include/qt/**", - "/usr/local/opt/qt/**", - "${workspaceFolder}/build/Desktop-Debug/iDescriptor_autogen/include", - "${workspaceFolder}/build/Desktop-Debug/src/lib/**", - "/usr/local/include/**", - "/usr/include/qt6/**", - "/usr/include/qt6/QtBluetooth/**", - "${workspaceFolder}/lib/zupdater/src", - "/usr/include/qt6/QtConcurrent" - ], - "defines": [], - "compilerPath": "/usr/bin/gcc", - "cStandard": "c17", - "cppStandard": "gnu++17", - "intelliSenseMode": "linux-gcc-x64" - } - ], - "version": 4 -} \ No newline at end of file + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/include/qt/**", + "/usr/local/opt/qt/**", + "${workspaceFolder}/build/Desktop-Debug/iDescriptor_autogen/include", + "${workspaceFolder}/build/Desktop-Debug/src/lib/**", + "/usr/local/include/**", + "/usr/include/qt6/**", + "/usr/include/qt6/QtBluetooth/**", + "${workspaceFolder}/lib/zupdater/src", + "/usr/include/qt6/QtConcurrent", + "/usr/include/qt6", + "${workspaceFolder}/build/Desktop-Release/iDescriptor_autogen/include" + ], + "defines": ["APP_VERSION=\"\""], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ec39cc..d6a8410 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -202,6 +202,10 @@ "qimage": "cpp", "qabstractbutton": "cpp", "qtnetwork": "cpp", - "qtcore": "cpp" + "qtcore": "cpp", + "csetjmp": "cpp", + "qthread": "cpp", + "qregularexpression": "cpp", + "qnetworkaccessmanager": "cpp" } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d2d993..7c794b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,43 +19,42 @@ if (APPLE) endif() # Platform-specific paths for libraries built from source -if(WIN32) - include_directories("C:/msys64/mingw64/include") - link_directories("C:/msys64/mingw64/lib") - set(PKG_CONFIG_EXECUTABLE "C:/msys64/mingw64/bin/pkg-config.exe") - list(APPEND CMAKE_PREFIX_PATH "C:/lxqt") - set(CUSTOM_LIB_PATH "C:/msys64/mingw64/lib") - set(CUSTOM_INCLUDE_PATH "C:/msys64/mingw64/include") - set(CUSTOM_PKGCONFIG_PATH "C:/msys64/mingw64/lib/pkgconfig") - set(ENV{PKG_CONFIG_PATH} "${CUSTOM_PKGCONFIG_PATH};$ENV{PKG_CONFIG_PATH}") -elseif(APPLE) - set(CUSTOM_LIB_PATH "/usr/local/lib") - set(CUSTOM_INCLUDE_PATH "/usr/local/include") - set(CUSTOM_PKGCONFIG_PATH "/usr/local/lib/pkgconfig") - set(ENV{PKG_CONFIG_PATH} "${CUSTOM_PKGCONFIG_PATH}:$ENV{PKG_CONFIG_PATH}") -else () - set(CUSTOM_LIB_PATH "/usr/local/lib") - set(CUSTOM_PKGCONFIG_PATH "/usr/local/lib/pkgconfig") - set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${CUSTOM_PKGCONFIG_PATH}") -endif() +# if(WIN32) +# include_directories("C:/msys64/mingw64/include") +# link_directories("C:/msys64/mingw64/lib") +# set(PKG_CONFIG_EXECUTABLE "C:/msys64/mingw64/bin/pkg-config.exe") +# list(APPEND CMAKE_PREFIX_PATH "C:/lxqt") +# set(CUSTOM_LIB_PATH "C:/msys64/mingw64/lib") +# set(CUSTOM_INCLUDE_PATH "C:/msys64/mingw64/include") +# set(CUSTOM_PKGCONFIG_PATH "C:/msys64/mingw64/lib/pkgconfig") +# set(ENV{PKG_CONFIG_PATH} "${CUSTOM_PKGCONFIG_PATH};$ENV{PKG_CONFIG_PATH}") +# elseif(APPLE) +# set(CUSTOM_LIB_PATH "/usr/local/lib") +# # Remove the problematic include path that's causing conflicts +# # set(CUSTOM_INCLUDE_PATH "/usr/local/include") +# set(CUSTOM_PKGCONFIG_PATH "/usr/local/lib/pkgconfig") +# set(ENV{PKG_CONFIG_PATH} "${CUSTOM_PKGCONFIG_PATH}:$ENV{PKG_CONFIG_PATH}") +# else () +# set(CUSTOM_LIB_PATH "/usr/local/lib") +# set(CUSTOM_PKGCONFIG_PATH "/usr/local/lib/pkgconfig") +# set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${CUSTOM_PKGCONFIG_PATH}") +# endif() -include_directories(${CUSTOM_INCLUDE_PATH}) +# foreach(_ IN LISTS CMAKE_PREFIX_PATH) +# list(APPEND _qt_pkg_dirs +# "${_}/lib/pkgconfig" +# "${_}/lib64/pkgconfig" +# "${_}/lib64/qt/pkgconfig" +# "${_}/lib/qt/pkgconfig" +# ) +# endforeach() -foreach(_ IN LISTS CMAKE_PREFIX_PATH) - list(APPEND _qt_pkg_dirs - "${_}/lib/pkgconfig" - "${_}/lib64/pkgconfig" - "${_}/lib64/qt/pkgconfig" - "${_}/lib/qt/pkgconfig" - ) -endforeach() - - -list(APPEND _qt_pkg_dirs ${CUSTOM_PKGCONFIG_PATH}) +# list(APPEND _qt_pkg_dirs ${CUSTOM_PKGCONFIG_PATH}) find_package(PkgConfig REQUIRED) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Network QuickControls2 SerialPort Positioning Location QuickWidgets) +find_package(Qt6 REQUIRED COMPONENTS Core) # Add QTermWidget # Prefer CMake-native qtermwidget6, fallback to pkg-config if needed @@ -84,36 +83,86 @@ if(WIN32) message(STATUS "Found Qt bin directory: ${QT_BIN_PATH}") endif() -# Define library search behavior based on platform -if(LINUX) - # On Linux (AUR builds), let CMake search default system paths first. - # The custom path /usr/local/lib will be checked if it's in the default search paths. - set(CUSTOM_FIND_LIB_ARGS "") -else() - # On other platforms, only search the custom path for our specific libraries. - set(CUSTOM_FIND_LIB_ARGS - PATHS ${CUSTOM_LIB_PATH} - NO_DEFAULT_PATH +#------------- IDEVICE-RS INTEGRATION ------------- + +find_program(CARGO_EXECUTABLE cargo REQUIRED) +message(STATUS "Using idevice-rs Rust implementation") + +set(IDEVICE_RS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/idevice-rs) +set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/release/libidevice_ffi.a) + +# This command builds the Rust library and declares its output file. +# Any target that uses this output file will automatically depend on this command. +add_custom_command( + OUTPUT ${IDEVICE_RS_LIB_PATH} + COMMAND ${CARGO_EXECUTABLE} build --release --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml + WORKING_DIRECTORY ${IDEVICE_RS_SOURCE_DIR} + COMMENT "Building idevice-rs FFI library" + VERBATIM +) + +# This custom target provides a name to build the Rust library explicitly. +# It depends on the output file, ensuring the custom command is run. +add_custom_target(idevice_rs_build DEPENDS ${IDEVICE_RS_LIB_PATH}) + +# Create an imported target for the Rust static library +add_library(idevice_ffi STATIC IMPORTED GLOBAL) +set_target_properties(idevice_ffi PROPERTIES + IMPORTED_LOCATION "${IDEVICE_RS_LIB_PATH}" +) + +# ---- Build the idevice-rs C++ wrapper library ------------------------------ +set(IDEVICE_CPP_SRC_DIR ${IDEVICE_RS_SOURCE_DIR}/cpp/src) +set(IDEVICE_CPP_INCLUDE_DIR ${IDEVICE_RS_SOURCE_DIR}/cpp/include) +set(IDEVICE_FFI_INCLUDE_DIR ${IDEVICE_RS_SOURCE_DIR}/ffi) +set(PLIST_CPP_INCLUDE_DIR ${IDEVICE_RS_SOURCE_DIR}/plist_ffi/cpp/include) +set(PLIST_CPP_SRC_DIR ${IDEVICE_RS_SOURCE_DIR}/plist_ffi/cpp/src) + +# Collect C++ sources for the wrapper library +file(GLOB_RECURSE IDEVICE_CPP_SOURCES + "${IDEVICE_CPP_SRC_DIR}/*.cpp" +) +file(GLOB PLIST_CPP_SOURCES + "${PLIST_CPP_SRC_DIR}/*.cpp" +) + +add_library(idevice_cpp STATIC ${IDEVICE_CPP_SOURCES} ${PLIST_CPP_SOURCES}) + +target_include_directories(idevice_cpp PUBLIC + ${IDEVICE_CPP_INCLUDE_DIR} + ${PLIST_CPP_INCLUDE_DIR} + PRIVATE + ${IDEVICE_FFI_INCLUDE_DIR} +) + +# Link idevice_cpp to idevice_ffi and add platform-specific dependencies +find_package(Threads REQUIRED) +target_link_libraries(idevice_cpp PUBLIC idevice_ffi Threads::Threads) + +if (UNIX AND NOT APPLE) + pkg_check_modules(UDEV REQUIRED IMPORTED_TARGET udev) + target_link_libraries(idevice_cpp PUBLIC PkgConfig::UDEV dl m) +elseif(APPLE) + find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED) + find_library(IOKIT_FRAMEWORK IOKit REQUIRED) + target_link_libraries(idevice_cpp PUBLIC + ${COREFOUNDATION_FRAMEWORK} + ${IOKIT_FRAMEWORK} + "-framework Security" + "-framework SystemConfiguration" + "-framework CoreServices" + "-framework CFNetwork" ) +elseif(WIN32) + target_link_libraries(idevice_cpp PUBLIC ws2_32 userenv.lib ntdll bcrypt) endif() -find_library(IMOBILEDEVICE_LIBRARY - NAMES imobiledevice-1.0 - ${CUSTOM_FIND_LIB_ARGS} - REQUIRED -) +# Set up variables for linking and includes for the main iDescriptor target +set(IDEVICE_IMPLEMENTATION_LIBS idevice_cpp) +# The C header file generated by cbindgen lives here +set(IDEVICE_IMPLEMENTATION_INCLUDES ${IDEVICE_RS_SOURCE_DIR}/ffi/include) -find_library(IMOBILEDEVICE_GLUE_LIBRARY - NAMES imobiledevice-glue-1.0 - ${CUSTOM_FIND_LIB_ARGS} - REQUIRED -) - -find_library(TATSU_LIBRARY - NAMES tatsu - ${CUSTOM_FIND_LIB_ARGS} - REQUIRED -) +#-------------------------------------------------------------------------------- # Add QR code generation library pkg_check_modules(QRENCODE REQUIRED IMPORTED_TARGET libqrencode) @@ -126,36 +175,21 @@ pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec) pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil) pkg_check_modules(SWSCALE REQUIRED IMPORTED_TARGET libswscale) -if(ENABLE_RECOVERY_DEVICE_SUPPORT) - find_library(IRECOVERY_LIBRARY - NAMES irecovery-1.0 - PATHS ${CUSTOM_LIB_PATH} - NO_DEFAULT_PATH - ) - if(IRECOVERY_LIBRARY) - message(STATUS "Building with recovery device support enabled") - else() - message(WARNING "libirecovery not found. Recovery device support will be disabled. This is to be expected if you are installing from Arch AUR.") - set(ENABLE_RECOVERY_DEVICE_SUPPORT OFF) - endif() -else() - message(STATUS "Recovery device support disabled") -endif() - -find_library(USBMUXD_LIBRARY - NAMES usbmuxd-2.0 - ${CUSTOM_FIND_LIB_ARGS} - REQUIRED -) - -if(WIN32) - # On MSYS2, these are found in the standard mingw64 prefix - find_library(SSL_LIBRARY NAMES ssl PATHS C:/msys64/mingw64/lib REQUIRED) - find_library(CRYPTO_LIBRARY NAMES crypto PATHS C:/msys64/mingw64/lib REQUIRED) -else() - find_library(SSL_LIBRARY NAMES ssl REQUIRED) - find_library(CRYPTO_LIBRARY NAMES crypto REQUIRED) -endif() +# if(ENABLE_RECOVERY_DEVICE_SUPPORT) +# find_library(IRECOVERY_LIBRARY +# NAMES irecovery-1.0 +# PATHS ${CUSTOM_LIB_PATH} +# NO_DEFAULT_PATH +# ) +# if(IRECOVERY_LIBRARY) +# message(STATUS "Building with recovery device support enabled") +# else() +# message(WARNING "libirecovery not found. Recovery device support will be disabled. This is to be expected if you are installing from Arch AUR.") +# set(ENABLE_RECOVERY_DEVICE_SUPPORT OFF) +# endif() +# else() +# message(STATUS "Recovery device support disabled") +# endif() # Add libssh for SSH connections pkg_check_modules(SSH REQUIRED IMPORTED_TARGET libssh) @@ -168,15 +202,73 @@ endif() pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml) -pkg_check_modules(USB REQUIRED IMPORTED_TARGET libusb-1.0) -pkg_check_modules(PLIST REQUIRED IMPORTED_TARGET libplist-2.0) file(GLOB PROJECT_SOURCES -src/*.cpp -src/core/helpers/*.cpp -src/core/services/*.cpp -src/*.h +# src/*.cpp +# src/core/helpers/*.cpp +# src/core/services/*.cpp +# src/*.h +src/mainwindow.cpp +src/mainwindow.h +src/devicemonitor.h +src/main.cpp +src/core/services/init_device.cpp +src/core/services/get_battery_info.cpp +src/core/services/detect_jailbroken.cpp +src/appcontext.cpp +src/appcontext.h +src/devicedatabase.cpp +src/devicedatabase.h +src/core/helpers/compare_product_type.cpp +src/welcomewidget.cpp +src/welcomewidget.h +src/ztabwidget.cpp +src/ztabwidget.h +src/devicemanagerwidget.cpp +src/devicemanagerwidget.h +src/responsiveqlabel.cpp +src/responsiveqlabel.h +src/devicesidebarwidget.cpp +src/devicesidebarwidget.h +src/devicemenuwidget.cpp +src/devicemenuwidget.h +src/deviceinfowidget.cpp +src/deviceinfowidget.h +src/batterywidget.cpp +src/batterywidget.h +src/diskusagewidget.cpp +src/diskusagewidget.h +# src/deviceimagewidget.cpp +# src/deviceimagewidget.h +src/iDescriptor-ui.h +src/infolabel.cpp +src/infolabel.h +src/privateinfolabel.cpp +src/privateinfolabel.h +src/qprocessindicator.cpp +src/qprocessindicator.h +src/diagnosewidget.cpp +src/diagnosewidget.h +src/core/services/get-device-info.cpp +src/deviceimagewidget.cpp +src/deviceimagewidget.h +src/settingsmanager.cpp +src/settingsmanager.h +# src/fileexplorerwidget.cpp +# src/fileexplorerwidget.h +src/settingswidget.cpp +src/settingswidget.h +src/ifusemanager.cpp +src/ifusemanager.h +src/ifusediskunmountbutton.cpp +src/ifusediskunmountbutton.h +src/core/services/get_battery_info.cpp +src/networkdeviceswidget.cpp +src/core/services/avahi/avahi_service.h +src/core/services/avahi/avahi_service.cpp +src/networkdevicemanager.cpp +src/networkdevicemanager.h src/*.ui resources.qrc ) @@ -199,12 +291,12 @@ if (WIN32) list(APPEND PROJECT_SOURCES ${WINDOWS_PLATFORM_SOURCES}) endif() -if(LINUX) - list(APPEND PROJECT_SOURCES - src/core/services/avahi/avahi_service.cpp - src/core/services/avahi/avahi_service.h - ) -endif() +# if(LINUX) +# list(APPEND PROJECT_SOURCES +# src/core/services/avahi/avahi_service.cpp +# src/core/services/avahi/avahi_service.h +# ) +# endif() if (NOT ENABLE_RECOVERY_DEVICE_SUPPORT) list(REMOVE_ITEM PROJECT_SOURCES @@ -253,6 +345,9 @@ else() ) endif() +# Make sure idevice_cpp depends on the Rust library being built +add_dependencies(idevice_cpp idevice_rs_build) + target_link_libraries(iDescriptor PRIVATE Qt6::Widgets Qt6::Multimedia @@ -264,17 +359,17 @@ target_link_libraries(iDescriptor PRIVATE Qt6::Positioning Qt6::QuickWidgets Qt6::QuickControls2 - ${IMOBILEDEVICE_LIBRARY} - ${IMOBILEDEVICE_GLUE_LIBRARY} - ${TATSU_LIBRARY} - ${SSL_LIBRARY} - ${CRYPTO_LIBRARY} + # ${IMOBILEDEVICE_LIBRARY} + # ${IMOBILEDEVICE_GLUE_LIBRARY} + # ${TATSU_LIBRARY} + # ${SSL_LIBRARY} + # ${CRYPTO_LIBRARY} PkgConfig::SSH ${SSH_LIBRARY} - ${USBMUXD_LIBRARY} + # ${USBMUXD_LIBRARY} PkgConfig::PUGIXML - PkgConfig::USB - PkgConfig::PLIST + # PkgConfig::USB + # PkgConfig::PLIST PkgConfig::QRENCODE qtermwidget6 PkgConfig::HEIF @@ -286,17 +381,28 @@ target_link_libraries(iDescriptor PRIVATE airplay ipatool-go ZUpdater + ${IDEVICE_IMPLEMENTATION_LIBS} ) -# Conditionally link libirecovery -if(ENABLE_RECOVERY_DEVICE_SUPPORT) - target_link_libraries(iDescriptor PRIVATE ${IRECOVERY_LIBRARY}) -endif() +# # Conditionally link libirecovery +# if(ENABLE_RECOVERY_DEVICE_SUPPORT) +# target_link_libraries(iDescriptor PRIVATE ${IRECOVERY_LIBRARY}) +# endif() target_include_directories(iDescriptor PRIVATE + # Put idevice-rs includes FIRST + ${IDEVICE_CPP_INCLUDE_DIR} + ${IDEVICE_FFI_INCLUDE_DIR} + ${PLIST_CPP_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib/zupdater/src + # System includes last + ${IDEVICE_IMPLEMENTATION_INCLUDES} ) + + +target_include_directories(iDescriptor PRIVATE ${IDEVICE_IMPLEMENTATION_INCLUDES}) + if(APPLE) find_library(CORE_SERVICES_FRAMEWORK CoreServices REQUIRED) target_link_libraries(iDescriptor PRIVATE @@ -323,6 +429,7 @@ if(APPLE) ${SECURITY_FRAMEWORK} ${COREFOUNDATION_FRAMEWORK} ) +target_link_libraries(iDescriptor PRIVATE Qt6::Core) endif() # Add compile definition for source directory @@ -368,7 +475,7 @@ if (UNIX AND NOT APPLE) ) endif() # Add install rules for the project -include(GNUInstallDirs) +# include(GNUInstallDirs) # Install the main executable install(TARGETS iDescriptor diff --git a/README.md b/README.md index 660092c..f8cb09b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# THIS BRANCH IS UNDER DEVELOPMENT + +## Migrating to new backend -> https://github.com/jkcoxson/idevice + +## Only builds on Linux for now +


@@ -58,9 +64,10 @@ - **Installer (.msi)**: Download and run the installer. Recommended for most users. - **Portable (.zip)**: Extract and run `iDescriptor.exe`. No installation required. - **Choco** : + ```bash choco install idescriptor --version=0.1.0 - ``` +``` #### macOS @@ -72,11 +79,13 @@ Open the `.dmg` and drag iDescriptor to Applications. #### Linux - **AppImage**: Download, unzip, and run. -- **Arch Linux**: Install from AUR: +- **Arch Linux**: Install from AUR: + ```bash sudo pacman -Syu yay -S idescriptor-git ``` + make sure to do "sudo pacman -Syu" otherwise it's not going to find libimobiledevice>=1.4.0
@@ -93,7 +102,7 @@ make sure to do "sudo pacman -Syu" otherwise it's not going to find libimobilede | Feature | Status | Notes | | --------------------------- | -------------------- | --------------------------------------------- | | USB Connection | ✅ Implemented | Fully supported on Windows, macOS, and Linux. | -| Wireless Connection (Wi‑Fi) | ⚠️ To be implemented | - | +| Wireless Connection (Wi‑Fi) | ⚠️ Under development | - | ### Tools @@ -224,9 +233,9 @@ You might get this pop-up on any platform this is because this app uses secure b ## Become a Sponsor -Please support us at - AppImage - +Please support us at +AppImage + ## Thanks diff --git a/lib/idevice-rs b/lib/idevice-rs new file mode 160000 index 0000000..5f1e039 --- /dev/null +++ b/lib/idevice-rs @@ -0,0 +1 @@ +Subproject commit 5f1e03911f0819ceacd366f76f961be407bec2b6 diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index df98b72..b979996 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -43,8 +43,6 @@ #include #include #include -#include -#include AfcExplorerWidget::AfcExplorerWidget(iDescriptorDevice *device, bool favEnabled, afc_client_t afcClient, QString root, diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h index 7c0e8e9..7c7362e 100644 --- a/src/afcexplorerwidget.h +++ b/src/afcexplorerwidget.h @@ -37,7 +37,6 @@ #include #include #include -#include class ExportManager; class ExportProgressDialog; @@ -89,7 +88,7 @@ private: ZIconWidget *m_enterButton; iDescriptorDevice *m_device; bool m_favEnabled; - afc_client_t m_afc; + AfcClientHandle *m_afc; QString m_errorMessage; QString m_root; diff --git a/src/appcontext.cpp b/src/appcontext.cpp index e7b9ab6..25b71b7 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -18,9 +18,11 @@ */ #include "appcontext.h" +#include "devicemonitor.h" #include "iDescriptor.h" #include "mainwindow.h" -#include "settingsmanager.h" +// #include "settingsmanager.h" +#include "networkdevicemanager.h" #include #include #include @@ -37,64 +39,191 @@ AppContext *AppContext::sharedInstance() and does not reconnect them until the user plugs them back in, even if they are still connected */ -AppContext::AppContext(QObject *parent) : QObject{parent} {} +AppContext::AppContext(QObject *parent) : QObject{parent} +{ -void AppContext::addDevice(QString udid, idevice_connection_type conn_type, - AddType addType) + // FIXME: windows and macOS support later + QDir lockdowndir("/var/lib/lockdown"); + if (!lockdowndir.exists()) { + return; + } + + // Load cached pairing files + QStringList pairingFiles = + lockdowndir.entryList(QStringList() << "*.plist", QDir::Files); + + qDebug() << "Parsing cached pairing files in /var/lib/lockdown:"; + for (const QString &fileName : pairingFiles) { + plist_t fileData = nullptr; + plist_read_from_file( + lockdowndir.filePath(fileName).toUtf8().constData(), &fileData, + NULL); + + if (!fileData) { + continue; + } + plist_print(fileData); + const std::string wifiMacAddress = + PlistNavigator(fileData)["WiFiMACAddress"].getString(); + plist_free(fileData); + qDebug() << "Found pairing file for MAC" + << QString::fromStdString(wifiMacAddress); + bool isCompatible = !wifiMacAddress.empty(); + // TODO: !important invalidate old pairing files + // libimobiledevice does not append WIFIMACAddress to the pairing file + if (!isCompatible) { + continue; + } + + IdevicePairingFile *pairing_file = nullptr; + idevice_pairing_file_read( + lockdowndir.filePath(fileName).toUtf8().constData(), &pairing_file); + if (pairing_file) { + qDebug() << "Caching pairing file for MAC" + << QString::fromStdString(wifiMacAddress); + m_pairingFileCache[QString::fromStdString(wifiMacAddress)] = + pairing_file; + } + } +} + +void AppContext::addDevice(QString udid, + DeviceMonitorThread::IdeviceConnectionType conn_type, + AddType addType, QString wifiMacAddress) { try { - iDescriptorInitDeviceResult initResult = - init_idescriptor_device(udid.toStdString().c_str()); + iDescriptorInitDeviceResult initResult; + + if (addType == AddType::UpgradeToWireless) { + const IdevicePairingFile *pairingFile = getCachedPairingFile(udid); + if (!pairingFile) { + qDebug() << "Cannot upgrade to wireless, no pairing file for" + << udid; + return; + } + + QList networkDevices = + NetworkDeviceManager::sharedInstance() + ->m_networkProvider->getNetworkDevices(); + + auto it = std::find_if( + networkDevices.constBegin(), networkDevices.constEnd(), + [wifiMacAddress](const NetworkDevice &device) { + return device.macAddress.compare(wifiMacAddress, + Qt::CaseInsensitive) == 0; + }); + + if (it != networkDevices.constEnd()) { + + IdevicePairingFile *pairing_file = nullptr; + idevice_pairing_file_read(QString("/var/lib/lockdown/%1.plist") + .arg(udid) + .toUtf8() + .constData(), + &pairing_file); + initResult = + init_idescriptor_device(udid, {it->address, pairing_file}); + } else { + qDebug() << "No network device found with MAC address:" + << wifiMacAddress; + return; + } + } else if (addType == AddType::Wireless) { + // FIXME: its not udid here its macAddress + const IdevicePairingFile *pairingFile = getCachedPairingFile(udid); + if (!pairingFile) { + qDebug() + << "Cannot initialize wireless device, no pairing file for" + << udid; + return; + } + + QList networkDevices = + NetworkDeviceManager::sharedInstance() + ->m_networkProvider->getNetworkDevices(); + + auto it = std::find_if( + networkDevices.constBegin(), networkDevices.constEnd(), + [wifiMacAddress](const NetworkDevice &device) { + return device.macAddress.compare(wifiMacAddress, + Qt::CaseInsensitive) == 0; + }); + + if (it != networkDevices.constEnd()) { + initResult = + init_idescriptor_device(udid, {it->address, pairingFile}); + } else { + qDebug() << "No network device found with MAC address:" + << wifiMacAddress; + return; + } + } + + else { + + initResult = init_idescriptor_device(udid, {nullptr, nullptr}); + } qDebug() << "init_idescriptor_device success ?: " << initResult.success; - qDebug() << "init_idescriptor_device error code: " << initResult.error; - + // qDebug() << "init_idescriptor_device error code: " << + // initResult.error; if (!initResult.success) { qDebug() << "Failed to initialize device with UDID: " << udid; - if (initResult.error == LOCKDOWN_E_PASSWORD_PROTECTED) { - if (addType == AddType::Regular) { - m_pendingDevices.append(udid); - emit devicePasswordProtected(udid); - emit deviceChange(); - QTimer::singleShot( - SettingsManager::sharedInstance()->connectionTimeout() * - 1000, - this, [this, udid]() { - if (m_pendingDevices.contains(udid)) { - qDebug() << "Pairing expired for device UDID: " - << udid; - m_pendingDevices.removeAll(udid); - emit devicePairingExpired(udid); - emit deviceChange(); - } - }); - } - } else if (initResult.error == - LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING || - initResult.error == LOCKDOWN_E_INVALID_HOST_ID) { - m_pendingDevices.append(udid); - emit devicePairPending(udid); - emit deviceChange(); - QTimer::singleShot( - SettingsManager::sharedInstance()->connectionTimeout() * - 1000, - this, [this, udid]() { - 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); - emit deviceChange(); - } - }); - } else { - qDebug() << "Unhandled error for device UDID: " << udid - << " Error code: " << initResult.error; - } return; } + // if (!initResult.success) { + // qDebug() << "Failed to initialize device with UDID: " << + // udid; if (initResult.error == LOCKDOWN_E_PASSWORD_PROTECTED) + // { + // if (addType == AddType::Regular) { + // m_pendingDevices.append(udid); + // emit devicePasswordProtected(udid); + // emit deviceChange(); + // QTimer::singleShot( + // SettingsManager::sharedInstance()->connectionTimeout() + // * + // 1000, + // this, [this, udid]() { + // if (m_pendingDevices.contains(udid)) { + // qDebug() << "Pairing expired for device + // UDID: + // " + // << udid; + // m_pendingDevices.removeAll(udid); + // emit devicePairingExpired(udid); + // emit deviceChange(); + // } + // }); + // } + // } else if (initResult.error == + // LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING || + // initResult.error == LOCKDOWN_E_INVALID_HOST_ID) { + // m_pendingDevices.append(udid); + // emit devicePairPending(udid); + // emit deviceChange(); + // QTimer::singleShot( + // SettingsManager::sharedInstance()->connectionTimeout() + // * + // 1000, + // this, [this, udid]() { + // 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); + // emit deviceChange(); + // } + // }); + // } else { + // qDebug() << "Unhandled error for device UDID: " << udid + // << " Error code: " << initResult.error; + // } + // return; + // } qDebug() << "Device initialized: " << udid; iDescriptorDevice *device = new iDescriptorDevice{ @@ -104,17 +233,20 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type, .deviceInfo = initResult.deviceInfo, .afcClient = initResult.afcClient, .afc2Client = initResult.afc2Client, + .lockdown = initResult.lockdown, .mutex = new std::recursive_mutex(), }; m_devices[device->udid] = device; if (addType == AddType::Regular) { - SettingsManager::sharedInstance()->doIfEnabled( - SettingsManager::Setting::AutoRaiseWindow, []() { - if (MainWindow *mainWindow = MainWindow::sharedInstance()) { - mainWindow->raise(); - mainWindow->activateWindow(); - } - }); + qDebug() << "Regular device added: " << udid; + // SettingsManager::sharedInstance()->doIfEnabled( + // SettingsManager::Setting::AutoRaiseWindow, []() { + // if (MainWindow *mainWindow = + // MainWindow::sharedInstance()) { + // mainWindow->raise(); + // mainWindow->activateWindow(); + // } + // }); emit deviceAdded(device); emit deviceChange(); @@ -123,7 +255,6 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type, emit devicePaired(device); emit deviceChange(); m_pendingDevices.removeAll(udid); - } catch (const std::exception &e) { qDebug() << "Exception in onDeviceAdded: " << e.what(); } @@ -131,19 +262,19 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type, int AppContext::getConnectedDeviceCount() const { -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - return m_devices.size() + m_recoveryDevices.size(); -#else + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // return m_devices.size() + m_recoveryDevices.size(); + // #else return m_devices.size(); -#endif + // #endif } /* FIXME: on macOS, sometimes you get wireless disconnects even though we are not - listening for wireless devices it does not have any to do with us, but it - still happens so be aware of that -*/ + listening for wireless devices it does not have any to do with us, but + it still happens so be aware of that + */ void AppContext::removeDevice(QString _udid) { const std::string udid = _udid.toStdString(); @@ -169,41 +300,41 @@ void AppContext::removeDevice(QString _udid) iDescriptorDevice *device = m_devices[udid]; m_devices.remove(udid); - emit deviceRemoved(udid); + emit deviceRemoved(udid, device->deviceInfo.wifiMacAddress); emit deviceChange(); - std::lock_guard lock(*device->mutex); + // std::lock_guard lock(*device->mutex); - if (device->afcClient) - afc_client_free(device->afcClient); - if (device->afc2Client) - afc_client_free(device->afc2Client); - idevice_free(device->device); - delete device->mutex; - delete device; + // if (device->afcClient) + // afc_client_free(device->afcClient); + // if (device->afc2Client) + // afc_client_free(device->afc2Client); + // idevice_free(device->device); + // delete device->mutex; + // delete device; } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void AppContext::removeRecoveryDevice(uint64_t ecid) { - if (!m_recoveryDevices.contains(ecid)) { - qDebug() << "Device with ECID " + QString::number(ecid) + - " not found. Please report this issue."; - return; - } + // 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:" << ecid; + // qDebug() << "Removing recovery device with ECID:" << ecid; - // Fix use-after-free: get pointer before removing from map - iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices.value(ecid); - m_recoveryDevices.remove(ecid); + // // Fix use-after-free: get pointer before removing from map + // iDescriptorRecoveryDevice *deviceInfo = + // m_recoveryDevices.value(ecid); m_recoveryDevices.remove(ecid); - emit recoveryDeviceRemoved(ecid); - emit deviceChange(); + // emit recoveryDeviceRemoved(ecid); + // emit deviceChange(); - std::lock_guard lock(*deviceInfo->mutex); - delete deviceInfo->mutex; - delete deviceInfo; + // std::lock_guard lock(*deviceInfo->mutex); + // delete deviceInfo->mutex; + // delete deviceInfo; } #endif @@ -217,91 +348,121 @@ QList AppContext::getAllDevices() return m_devices.values(); } -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -QList AppContext::getAllRecoveryDevices() -{ - return m_recoveryDevices.values(); -} -#endif +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// QList AppContext::getAllRecoveryDevices() +// { +// // return m_recoveryDevices.values(); +// } +// #endif // Returns whether there are any devices connected (regular or recovery) bool AppContext::noDevicesConnected() const { -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - return (m_devices.isEmpty() && m_recoveryDevices.isEmpty() && - m_pendingDevices.isEmpty()); -#else + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // return (m_devices.isEmpty() && m_recoveryDevices.isEmpty() && + // m_pendingDevices.isEmpty()); + // #else return (m_devices.isEmpty() && m_pendingDevices.isEmpty()); -#endif + // #endif } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void AppContext::addRecoveryDevice(uint64_t ecid) { - iDescriptorInitDeviceResultRecovery res = - init_idescriptor_recovery_device(ecid); + // iDescriptorInitDeviceResultRecovery res = + // init_idescriptor_recovery_device(ecid); - if (!res.success) { - qDebug() << "Failed to initialize recovery device with ECID: " - << QString::number(ecid); - qDebug() << "Error code: " << res.error; - return; - } + // if (!res.success) { + // qDebug() << "Failed to initialize recovery device with ECID: " + // << QString::number(ecid); + // qDebug() << "Error code: " << res.error; + // return; + // } - 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; - recoveryDevice->mutex = new std::recursive_mutex(); + // 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; + // recoveryDevice->mutex = new std::recursive_mutex(); - m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice; - emit recoveryDeviceAdded(recoveryDevice); - emit deviceChange(); + // m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice; + // emit recoveryDeviceAdded(recoveryDevice); + // emit deviceChange(); } #endif AppContext::~AppContext() { - for (auto device : m_devices) { - emit deviceRemoved(device->udid); - if (device->afcClient) - afc_client_free(device->afcClient); - if (device->afc2Client) - afc_client_free(device->afc2Client); - idevice_free(device->device); - delete device->mutex; - delete device; - } + // for (auto device : m_devices) { + // emit deviceRemoved(device->udid); + // if (device->afcClient) + // afc_client_free(device->afcClient); + // if (device->afc2Client) + // afc_client_free(device->afc2Client); + // idevice_free(device->device); + // delete device->mutex; + // delete device; + // } -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - for (auto recoveryDevice : m_recoveryDevices) { - emit recoveryDeviceRemoved(recoveryDevice->ecid); - delete recoveryDevice->mutex; - delete recoveryDevice; - } -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // for (auto recoveryDevice : m_recoveryDevices) { + // emit recoveryDeviceRemoved(recoveryDevice->ecid); + // delete recoveryDevice->mutex; + // delete recoveryDevice; + // } + // #endif } void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) { - qDebug() << "New selection -" - << " Type:" << selection.type - << " UDID:" << QString::fromStdString(selection.udid) - << " ECID:" << selection.ecid << " Section:" << selection.section; - if (m_currentSelection.type == selection.type && - m_currentSelection.udid == selection.udid && - m_currentSelection.ecid == selection.ecid && - m_currentSelection.section == selection.section) { - qDebug() << "setCurrentDeviceSelection: No change in selection"; - return; // No change - } - m_currentSelection = selection; - emit currentDeviceSelectionChanged(m_currentSelection); + // qDebug() << "New selection -" + // << " Type:" << selection.type + // << " UDID:" << QString::fromStdString(selection.udid) + // << " ECID:" << selection.ecid << " Section:" << + // selection.section; + // if (m_currentSelection.type == selection.type && + // m_currentSelection.udid == selection.udid && + // m_currentSelection.ecid == selection.ecid && + // m_currentSelection.section == selection.section) { + // qDebug() << "setCurrentDeviceSelection: No change in selection"; + // return; // No change + // } + // m_currentSelection = selection; + // emit currentDeviceSelectionChanged(m_currentSelection); } const DeviceSelection &AppContext::getCurrentDeviceSelection() const { return m_currentSelection; -} \ No newline at end of file +} + +const iDescriptorDevice * +AppContext::getDeviceByMacAddress(const QString &macAddress) const +{ + for (const iDescriptorDevice *device : m_devices) { + if (device->deviceInfo.wifiMacAddress == macAddress.toStdString()) { + return device; + } + } + return nullptr; +} + +void AppContext::cachePairingFile(const QString &udid, + IdevicePairingFile *pairingFile) +{ + m_pairingFileCache.insert(udid, pairingFile); +} +const IdevicePairingFile * +AppContext::getCachedPairingFile(const QString &udid) const +{ + const IdevicePairingFile *pairingFile = nullptr; + + // Retrieve the pairing file from the cache + if (m_pairingFileCache.contains(udid)) { + pairingFile = m_pairingFileCache.value(udid); + } + + return pairingFile; +} diff --git a/src/appcontext.h b/src/appcontext.h index 3ba8c26..c4d5200 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -20,6 +20,7 @@ #ifndef APPCONTEXT_H #define APPCONTEXT_H +#include "devicemonitor.h" #include "devicesidebarwidget.h" #include "iDescriptor.h" #include @@ -33,32 +34,38 @@ public: QList getAllDevices(); explicit AppContext(QObject *parent = nullptr); bool noDevicesConnected() const; + void cachePairingFile(const QString &udid, IdevicePairingFile *pairingFile); + const IdevicePairingFile *getCachedPairingFile(const QString &udid) const; -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - QList getAllRecoveryDevices(); -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // QList getAllRecoveryDevices(); + // #endif ~AppContext(); int getConnectedDeviceCount() const; void setCurrentDeviceSelection(const DeviceSelection &selection); const DeviceSelection &getCurrentDeviceSelection() const; + const iDescriptorDevice * + getDeviceByMacAddress(const QString &macAddress) const; private: QMap m_devices; -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - QMap m_recoveryDevices; -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // QMap m_recoveryDevices; + // #endif QStringList m_pendingDevices; DeviceSelection m_currentSelection = DeviceSelection(""); + // FIXME: QString can be macAddress or udid - both works fine for now + QMap m_pairingFileCache; signals: void deviceAdded(iDescriptorDevice *device); - void deviceRemoved(const std::string &udid); + void deviceRemoved(const std::string &udid, const std::string &macAddress); void devicePaired(iDescriptorDevice *device); void devicePasswordProtected(const QString &udid); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - void recoveryDeviceAdded(const iDescriptorRecoveryDevice *deviceInfo); - void recoveryDeviceRemoved(uint64_t ecid); -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // void recoveryDeviceAdded(const iDescriptorRecoveryDevice + // *deviceInfo); void recoveryDeviceRemoved(uint64_t ecid); + // #endif void devicePairPending(const QString &udid); void devicePairingExpired(const QString &udid); void systemSleepStarting(); @@ -74,8 +81,9 @@ signals: void currentDeviceSelectionChanged(const DeviceSelection &selection); public slots: void removeDevice(QString udid); - void addDevice(QString udid, idevice_connection_type connType, - AddType addType); + void addDevice(QString udid, + DeviceMonitorThread::IdeviceConnectionType connType, + AddType addType, QString wifiMacAddress = QString()); #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void addRecoveryDevice(uint64_t ecid); void removeRecoveryDevice(uint64_t ecid); diff --git a/src/core/services/avahi/avahi_service.cpp b/src/core/services/avahi/avahi_service.cpp index 585061a..dda167c 100644 --- a/src/core/services/avahi/avahi_service.cpp +++ b/src/core/services/avahi/avahi_service.cpp @@ -217,9 +217,10 @@ void AvahiService::resolveCallback( avahi_free(value); } } - + device.macAddress = device.name.split('@').first(); qDebug() << "Resolved Apple device:" << device.name << "at" - << device.address << ":" << device.port; + << device.address << ":" << device.port + << "MAC:" << device.macAddress; // Add to our list if not already present { diff --git a/src/core/services/detect_jailbroken.cpp b/src/core/services/detect_jailbroken.cpp index ff41ec9..10c9347 100644 --- a/src/core/services/detect_jailbroken.cpp +++ b/src/core/services/detect_jailbroken.cpp @@ -18,7 +18,7 @@ */ #include "../../iDescriptor.h" -#include + // char *possible_jailbreak_paths[] = { // "/Applications/Cydia.app", // "/Library/MobileSubstrate/MobileSubstrate.dylib", @@ -29,18 +29,15 @@ // }; #include -bool detect_jailbroken(afc_client_t afc) +bool detect_jailbroken(AfcClientHandle *afc) { char **dirs = NULL; - if (afc_read_directory(afc, (std::string(POSSIBLE_ROOT) + "bin").c_str(), - &dirs) == AFC_E_SUCCESS) { - // if we can loop through the directory, it means we have access to the - // file system - for (char **dir = dirs; *dir != nullptr; ++dir) { - afc_dictionary_free(dirs); - return true; - } + size_t count = 0; + bool res = false; + if (!afc_list_directory(afc, (std::string(POSSIBLE_ROOT) + "bin").c_str(), + &dirs, &count)) { + free(dirs); } - afc_dictionary_free(dirs); - return false; + + return res > 0; } \ No newline at end of file diff --git a/src/core/services/get-device-info.cpp b/src/core/services/get-device-info.cpp index 2337a35..7c0a029 100644 --- a/src/core/services/get-device-info.cpp +++ b/src/core/services/get-device-info.cpp @@ -52,8 +52,8 @@ #include #endif -#include -#include +#include "../../iDescriptor.h" +#include #include #include @@ -82,35 +82,41 @@ static const char *domains[] = { "com.apple.mobile.iTunes", "com.apple.fmip", "com.apple.Accessibility", NULL}; -plist_t get_device_info(const char *udid, lockdownd_client_t client, - idevice_t device) +plist_t get_device_info(const char *udid, LockdowndClientHandle *client) { - lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; - idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; + IdeviceFfiError *err; plist_t node = NULL; /* run query and output information */ - if (lockdownd_get_value(client, NULL, NULL, &node) != LOCKDOWN_E_SUCCESS) { - fprintf(stderr, "ERROR: Could not get value\n"); + err = lockdownd_get_value(client, NULL, NULL, &node); + + if (err) { + qDebug() << "ERROR: Could not get value"; } plist_t disk_info = nullptr; uint64_t total_space = 0; uint64_t free_space = 0; - if (lockdownd_get_value(client, "com.apple.disk_usage", nullptr, - &disk_info) == LOCKDOWN_E_SUCCESS) { + err = lockdownd_get_value(client, nullptr, "com.apple.disk_usage", + &disk_info); + qDebug() << "Disk usage fetch error:" << (err ? err->message : "none"); + + if (!err) { + qDebug() << "Disk Usage Info:"; + plist_print(disk_info); + // merge dict plist_dict_merge(&node, disk_info); - plist_free(disk_info); + // plist_free(disk_info); } return node; } -void get_device_info_xml(const char *udid, lockdownd_client_t client, - idevice_t device, pugi::xml_document &infoXml) +void get_device_info_xml(const char *udid, LockdowndClientHandle *client, + pugi::xml_document &infoXml) { - plist_t node = get_device_info(udid, client, device); + plist_t node = get_device_info(udid, client); if (!node) return; char *xml_string = nullptr; diff --git a/src/core/services/get_battery_info.cpp b/src/core/services/get_battery_info.cpp index 65cd169..18ab2e5 100644 --- a/src/core/services/get_battery_info.cpp +++ b/src/core/services/get_battery_info.cpp @@ -20,35 +20,45 @@ #include "../../iDescriptor.h" #include "plist/plist.h" #include -#include #include -void get_battery_info(std::string productType, idevice_t idevice, - bool is_iphone, plist_t &diagnostics) +// FIXME: return bool +void get_battery_info(IdeviceProviderHandle *provider, plist_t &diagnostics) { - diagnostics_relay_client_t diagnostics_client = nullptr; - try { + // 1. Connect to the diagnostics_relay service using the raw C function. + DiagnosticsRelayClientHandle *client_handle = nullptr; + IdeviceFfiError *err = + ::diagnostics_relay_client_connect(provider, &client_handle); - if (diagnostics_relay_client_start_service(idevice, &diagnostics_client, - nullptr) != - DIAGNOSTICS_RELAY_E_SUCCESS) { - qDebug() << "Failed to start diagnostics relay service."; - return; - } + if (err) { + qDebug() << "Failed to create diagnostics relay client:" + << err->message; + idevice_error_free(err); + return; + } - if (diagnostics_relay_query_ioregistry_entry( - diagnostics_client, nullptr, "IOPMPowerSource", &diagnostics) != - DIAGNOSTICS_RELAY_E_SUCCESS && - !diagnostics) { + // 2. Adopt the raw handle into the C++ RAII wrapper. + // The client will now be automatically freed when it goes out of scope. + auto diagnostics_client = + IdeviceFFI::DiagnosticsRelay::adopt(client_handle); - qDebug() - << "Failed to query diagnostics relay for AppleARMPMUCharger."; - if (diagnostics_client) - diagnostics_relay_client_free(diagnostics_client); - } - } catch (const std::exception &e) { - if (diagnostics_client) - diagnostics_relay_client_free(diagnostics_client); - qDebug() << "Exception in get_battery_info: " << e.what(); + // 3. Query IORegistry for battery info. + auto ioreg_result = diagnostics_client.ioregistry( + IdeviceFFI::None, // current_plane + IdeviceFFI::None, // entry_name + IdeviceFFI::Some(std::string("IOPMPowerSource")) // entry_class + ); + + if (!ioreg_result.is_ok()) { + qDebug() << "Failed to query IORegistry:" + << ioreg_result.unwrap_err().message.c_str(); + return; + } + + // 4. Unwrap the result and handle the optional plist. + auto plist_opt = std::move(ioreg_result).unwrap(); + if (plist_opt.is_some()) { + // The caller of get_battery_info is responsible for freeing this plist. + diagnostics = std::move(plist_opt).unwrap(); } } \ No newline at end of file diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 0062a21..8ef71bc 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -19,16 +19,19 @@ #include "../../devicedatabase.h" #include "../../iDescriptor.h" -#include "../../servicemanager.h" +// #include "../../servicemanager.h" +#include "../../appcontext.h" #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT #include "libirecovery.h" #endif #include -#include -#include -#include #include +#include +#include +#include + +#include std::string safeGetXML(const char *key, pugi::xml_node dict) { for (pugi::xml_node child = dict.first_child(); child; @@ -150,7 +153,8 @@ void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d) } DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, - afc_client_t &afcClient, + AfcClientHandle *afcClient, + IdeviceProviderHandle *provider, iDescriptorInitDeviceResult &result) { pugi::xml_node dict = doc.child("plist").child("dict"); @@ -194,6 +198,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, d.bluetoothAddress = safeGet("BluetoothAddress"); d.firmwareVersion = safeGet("FirmwareVersion"); d.productVersion = safeGet("ProductVersion"); + d.wifiMacAddress = safeGet("WiFiAddress"); QString q_version = QString::fromStdString(d.productVersion); QStringList parts = q_version.split('.'); @@ -206,17 +211,25 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, /*DiskInfo*/ try { - d.diskInfo.totalDiskCapacity = - std::stoull(safeGet("TotalDiskCapacity")); - d.diskInfo.totalDataCapacity = - std::stoull(safeGet("TotalDataCapacity")); - d.diskInfo.totalSystemCapacity = - std::stoull(safeGet("TotalSystemCapacity")); + auto safeParseU64 = [&](const char *key) -> uint64_t { + std::string s = safeGet(key); + if (s.empty()) + return 0; + try { + return std::stoull(s); + } catch (...) { + qDebug() << "Failed to parse key to uint64_t:" << key + << "value:" << QString::fromStdString(s); + return 0; + } + }; + d.diskInfo.totalDiskCapacity = safeParseU64("TotalDiskCapacity"); + d.diskInfo.totalDataCapacity = safeParseU64("TotalDataCapacity"); + d.diskInfo.totalSystemCapacity = safeParseU64("TotalSystemCapacity"); /* For some reason this is way inaccrutate for iOS 17 and up */ - d.diskInfo.totalDataAvailable = - std::stoull(safeGet("TotalDataAvailable")); + d.diskInfo.totalDataAvailable = safeParseU64("TotalDataAvailable"); try { /* @@ -226,13 +239,19 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, // "FSTotalBytes: 63966400512" // "FSFreeBytes: 2867101696" // "FSBlockSize: 4096" - char **info = NULL; - afc_get_device_info(afcClient, &info); - if (info && info[6]) { - d.diskInfo.totalDataAvailable = - std::stoull(std::string(info[5])); + AfcDeviceInfo *info = new AfcDeviceInfo(); + IdeviceFfiError *err = afc_get_device_info(afcClient, info); + if (err) { + qDebug() << "AFC get device info error code: " << err->message; + return d; } - afc_dictionary_free(info); + if (info) { + + qDebug() << "AFC Disk Info" << info->free_bytes; + d.diskInfo.totalDataAvailable = info->free_bytes; + } + // FIXME: free + afc_device_info_free(info); } catch (const std::exception &e) { qDebug() << "Error parsing disk info: " << e.what(); } @@ -281,14 +300,14 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, /*BatteryInfo*/ plist_t diagnostics = nullptr; - get_battery_info(rawProductType, result.device, d.is_iPhone, diagnostics); + get_battery_info(provider, diagnostics); if (!diagnostics) { qDebug() << "Failed to get diagnostics plist."; return d; } try { - PlistNavigator ioreg = PlistNavigator(diagnostics)["IORegistry"]; + PlistNavigator ioreg = PlistNavigator(diagnostics); // old devices do not have "BatteryData" d.oldDevice = !ioreg["BatteryData"]; @@ -329,6 +348,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, d.batteryInfo.serialNumber = !batterySerialNumber.empty() ? batterySerialNumber : "Error retrieving serial number"; + qDebug() << "Cycle count: " << cycleCount; parseDeviceBattery(ioreg, d); plist_free(diagnostics); diagnostics = nullptr; @@ -340,156 +360,289 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, } } -iDescriptorInitDeviceResult init_idescriptor_device(const char *udid) +// [DeviceMonitor] Device connected: "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" +// Device added: "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" +// Initializing iDescriptor device with UDID: +// "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" Failed to create idevice handle +// Initialization failed, cleaning up resources. FfiInvalidArg +// init_idescriptor_device success ?: false +// Failed to initialize device with UDID: +// "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" +iDescriptorInitDeviceResult +init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) { - qDebug() << "Initializing iDescriptor device with UDID: " - << QString::fromUtf8(udid); + const bool isWireless = + !wirelessArgs.ip.isEmpty() && wirelessArgs.pairing_file; + qDebug() << "Initializing iDescriptor device with UDID: " << udid + << (isWireless ? "over wireless" : "over USB"); + iDescriptorInitDeviceResult result = {}; - // 1. Initialize all resource handles to nullptr - idevice_t device = nullptr; - lockdownd_client_t client = nullptr; - lockdownd_service_descriptor_t lockdownService = nullptr; - afc_client_t afcClient = nullptr; - afc_client_t afc2Client = nullptr; + UsbmuxdConnectionHandle *usbmuxd_conn = nullptr; + UsbmuxdAddrHandle *addr_handle = nullptr; + IdeviceProviderHandle *provider = nullptr; + LockdowndClientHandle *lockdown = nullptr; + IdeviceSocketHandle *socket = nullptr; + AfcClientHandle *afc_client = nullptr; + AfcClientHandle *afc2_client = nullptr; pugi::xml_document infoXml; + uint32_t actual_device_id = 0; + IdevicePairingFile *pairing_file = nullptr; + IdeviceHandle *deviceHandle = nullptr; + // FIXME: remove debug + std::stringstream ss; - idevice_error_t ret = - idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX); + // 1. Connect to usbmuxd + IdeviceFfiError *err = + idevice_usbmuxd_new_default_connection(0, &usbmuxd_conn); + if (err) { + if (!isWireless) { + qDebug() << "Failed to connect to usbmuxd"; + goto cleanup; + } + } - if (ret != IDEVICE_E_SUCCESS) { - qDebug() << "Failed to connect to device: " << ret; - // result.error is not set here as idevice_error_t is different + // 2. Create default address handle + err = idevice_usbmuxd_default_addr_new(&addr_handle); + if (err) { + qDebug() << "Failed to create address handle"; goto cleanup; } - lockdownd_error_t ldret; - if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake( - device, &client, APP_LABEL))) { - result.error = ldret; - qDebug() << "Failed to create lockdown client: " << ldret; - goto cleanup; - } - - if (LOCKDOWN_E_SUCCESS != - (ldret = lockdownd_start_service(client, "com.apple.afc", - &lockdownService))) { - result.error = ldret; - qDebug() << "Failed to start AFC service: " << ldret; - goto cleanup; - } - - if (afc_client_new(device, lockdownService, &afcClient) != AFC_E_SUCCESS) { - qDebug() << "Failed to create AFC client."; - - goto cleanup; - } - - // AFC2 is optional, so we don't goto cleanup on failure - afc_error_t afc2_err; - if ((afc2_err = afc2_client_new(device, &afc2Client)) != AFC_E_SUCCESS) { - qDebug() << "AFC2 client not available. Error:" << afc2_err; - afc2Client = nullptr; + if (isWireless) { + // Create IPv4 sockaddr + struct sockaddr_in addr_in; + memset(&addr_in, 0, sizeof(addr_in)); + addr_in.sin_family = AF_INET; + addr_in.sin_port = htons(0); // Port doesn't matter for provider + inet_pton(AF_INET, wirelessArgs.ip.toUtf8().constData(), + &addr_in.sin_addr); + // IdevicePairingFile *pairing_file = nullptr; + // idevice_pairing_file_read( + // wirelessArgs.pairing_file.toUtf8().constData(), &pairing_file); + err = idevice_tcp_provider_new( + (const idevice_sockaddr *)&addr_in, + const_cast(wirelessArgs.pairing_file), + APP_LABEL, &provider); + if (err) { + qDebug() << "Failed to create wireless provider"; + goto cleanup; + } + // err = heartbeat_new(); + // heartbeat_connect } else { - qDebug() << "AFC2 client created successfully."; + + UsbmuxdDeviceHandle **devices; + int device_count; + err = + idevice_usbmuxd_get_devices(usbmuxd_conn, &devices, &device_count); + + // Find by UDID and get its device_id + for (size_t i = 0; i < device_count; i++) { + const char *device_udid = + idevice_usbmuxd_device_get_udid(devices[i]); + if (strcmp(device_udid, udid.toUtf8().constData()) == 0) { + actual_device_id = + idevice_usbmuxd_device_get_device_id(devices[i]); + break; + } + } + + // 3. Create provider for the device (actual function name) + err = usbmuxd_provider_new(addr_handle, 0, udid.toUtf8().constData(), + actual_device_id, APP_LABEL, &provider); } - get_device_info_xml(udid, client, device, infoXml); - - if (infoXml.empty()) { - qDebug() << "Failed to retrieve device info XML for UDID: " - << QString::fromUtf8(udid); + if (err) { + qDebug() << "Failed to create provider"; goto cleanup; } - // If we got this far, the core initialization is successful + // 4. Connect to lockdown (actual function name) + err = lockdownd_connect(provider, &lockdown); + if (err) { + qDebug() << "Failed to connect to lockdown"; + goto cleanup; + } + // err = idevice_new(socket, "iDescriptor", &deviceHandle); + // if (err) { + // qDebug() << "Failed to create idevice handle"; + // goto cleanup; + // } + + err = idevice_provider_get_pairing_file(provider, &pairing_file); + if (err) { + qDebug() << "Failed to get pairing file"; + goto cleanup; + } + + err = lockdownd_start_session(lockdown, pairing_file); + if (err) { + qDebug() << "Failed to start lockdown session"; + goto cleanup; + } + + uint16_t heartbeat_port; + bool heartbeat_ssl; + if (isWireless) { + // err = lockdownd_start_service(lockdown, "com.apple.heartbeat", + // &heartbeat_port, &heartbeat_ssl); + // if (err) { + // qDebug() << "Failed to start Heartbeat service"; + // goto cleanup; + // } + + // Start heartbeat client to keep connection alive + HeartbeatClientHandle *heartbeat = nullptr; + err = heartbeat_connect(provider, &heartbeat); + + if (err) { + qDebug() << "Failed to connect to Heartbeat client"; + goto cleanup; + } + + // // After getting the heartbeat port from lockdown + // IdeviceHandle *deviceHandle = nullptr; + + // // Then create IdeviceHandle from the socket + // err = idevice_new(socket, "heartbeat", &deviceHandle); + // if (err) { + // qDebug() << "Failed to create idevice handle"; + // goto cleanup; + // } + + // // Now use with heartbeat_new + // HeartbeatClientHandle *heartbeat = nullptr; + // err = heartbeat_new(deviceHandle, &heartbeat); + // if (err) { + // qDebug() << "Failed to create Heartbeat client"; + // goto cleanup; + // } + + qDebug() << "Heartbeat client created successfully"; + } + + // 5. Start AFC service + uint16_t afc_port; + bool afc_ssl; + err = + lockdownd_start_service(lockdown, "com.apple.afc", &afc_port, &afc_ssl); + if (err) { + qDebug() << "Failed to start AFC service"; + goto cleanup; + } + + // 6. Create AFC client from provider + err = afc_client_connect(provider, &afc_client); + if (err) { + qDebug() << "Failed to create AFC client"; + goto cleanup; + } + + // 7. AFC2 is optional + uint16_t afc2_port; + bool afc2_ssl; + err = lockdownd_start_service(lockdown, "com.apple.afc2", &afc2_port, + &afc2_ssl); + if (!err) { + err = afc_client_connect(provider, &afc2_client); + } + + get_device_info_xml(udid.toUtf8().constData(), lockdown, infoXml); + infoXml.print(ss, " "); // " " for indentation + qDebug().noquote() << "--- Full Device Info XML ---" + << QString::fromStdString(ss.str()); + + result.device = provider; result.success = true; - result.device = device; - result.afcClient = afcClient; - result.afc2Client = afc2Client; - fullDeviceInfo(infoXml, afcClient, result); + result.afcClient = afc_client; + result.afc2Client = afc2_client; + result.lockdown = lockdown; + AppContext::sharedInstance()->cachePairingFile(udid, pairing_file); + fullDeviceInfo(infoXml, afc_client, provider, result); cleanup: - if (lockdownService) { - lockdownd_service_descriptor_free(lockdownService); - } - if (client) { - lockdownd_client_free(client); - } - - // free on error + // Cleanup on error + // FIXME: implement proper cleanup + // one of them causes a crash here, needs investigation if (!result.success) { - if (afc2Client) { - afc_client_free(afc2Client); - } - if (afcClient) { - afc_client_free(afcClient); - } - if (device) { - idevice_free(device); - } + qDebug() << "Initialization failed, cleaning up resources." + << err->message; + // if (afc2_client) + // afc_client_free(afc2_client); + // if (afc_client) + // afc_client_free(afc_client); + // if (lockdown) + // lockdownd_client_free(lockdown); + // if (provider) + // idevice_provider_free(provider); + // if (addr_handle) + // idevice_usbmuxd_addr_free(addr_handle); + // if (usbmuxd_conn) + // idevice_usbmuxd_connection_free(usbmuxd_conn); } return result; } -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -iDescriptorInitDeviceResultRecovery -init_idescriptor_recovery_device(uint64_t ecid) -{ - qDebug() << "Initializing iDescriptor recovery device with ECID: " << ecid; - iDescriptorInitDeviceResultRecovery result = {}; - irecv_client_t client = nullptr; - const irecv_device_info *deviceInfo = nullptr; - irecv_device_t device = nullptr; - const DeviceDatabaseInfo *info = nullptr; +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// iDescriptorInitDeviceResultRecovery +// init_idescriptor_recovery_device(uint64_t ecid) +// { +// qDebug() << "Initializing iDescriptor recovery device with ECID: " << +// ecid; iDescriptorInitDeviceResultRecovery result = {}; - irecv_error_t ret = irecv_open_with_ecid_and_attempts( - &client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES); +// irecv_client_t client = nullptr; +// const irecv_device_info *deviceInfo = nullptr; +// irecv_device_t device = nullptr; +// const DeviceDatabaseInfo *info = nullptr; - if (ret != IRECV_E_SUCCESS) { - qDebug() << "Failed to open recovery client with ECID:" << ecid - << "Error:" << ret; - result.error = ret; - goto cleanup; - } +// irecv_error_t ret = irecv_open_with_ecid_and_attempts( +// &client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES); - ret = irecv_get_mode(client, (int *)&result.mode); - if (ret != IRECV_E_SUCCESS) { - qDebug() << "Failed to get recovery mode. Error:" << ret; - result.error = ret; - goto cleanup; - } +// if (ret != IRECV_E_SUCCESS) { +// qDebug() << "Failed to open recovery client with ECID:" << ecid +// << "Error:" << ret; +// result.error = ret; +// goto cleanup; +// } - deviceInfo = irecv_get_device_info(client); - if (!deviceInfo) { - qDebug() << "Failed to get device info from recovery client"; - result.error = IRECV_E_UNKNOWN_ERROR; - goto cleanup; - } +// ret = irecv_get_mode(client, (int *)&result.mode); +// if (ret != IRECV_E_SUCCESS) { +// qDebug() << "Failed to get recovery mode. Error:" << ret; +// result.error = ret; +// goto cleanup; +// } - 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."; - } +// deviceInfo = irecv_get_device_info(client); +// if (!deviceInfo) { +// qDebug() << "Failed to get device info from recovery client"; +// result.error = IRECV_E_UNKNOWN_ERROR; +// goto cleanup; +// } - result.displayName = - info ? (info->displayName ? info->displayName : info->marketingName) - : "Unknown Device"; - result.deviceInfo = *deviceInfo; - result.success = true; +// 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."; +// } -cleanup: - if (client) { - irecv_close(client); - } +// result.displayName = +// info ? (info->displayName ? info->displayName : info->marketingName) +// : "Unknown Device"; +// result.deviceInfo = *deviceInfo; +// result.success = true; - return result; -} -#endif // ENABLE_RECOVERY_DEVICE_SUPPORT \ No newline at end of file +// cleanup: +// if (client) { +// irecv_close(client); +// } + +// return result; +// } +// #endif // ENABLE_RECOVERY_DEVICE_SUPPORT \ No newline at end of file diff --git a/src/deviceimagewidget.cpp b/src/deviceimagewidget.cpp index 6723686..8b361ef 100644 --- a/src/deviceimagewidget.cpp +++ b/src/deviceimagewidget.cpp @@ -18,13 +18,13 @@ */ #include "deviceimagewidget.h" +#include "iDescriptor.h" #include #include #include #include #include #include -#include DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) @@ -161,7 +161,7 @@ QString DeviceImageWidget::getMockupNameFromDisplayName( int DeviceImageWidget::getIosVersionFromDevice() const { - unsigned int version = idevice_get_device_version(m_device->device); + unsigned int version = m_device->deviceInfo.parsedDeviceVersion; if (version > 0) { int majorVersion = (version >> 16) & 0xFF; @@ -180,7 +180,7 @@ int DeviceImageWidget::getIosVersionFromDevice() const } } - // If all else fails, return unknown version (will use ios26 wallpaper) + // return unknown version (will use ios26 wallpaper) return 0; } diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 7f02ea0..047f226 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -20,12 +20,12 @@ #include "deviceinfowidget.h" #include "batterywidget.h" #include "diskusagewidget.h" -#include "fileexplorerwidget.h" +// #include "fileexplorerwidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "infolabel.h" #include "privateinfolabel.h" -#include "toolboxwidget.h" +// #include "toolboxwidget.h" #include #include #include @@ -78,20 +78,20 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown", this); shutdownBtn->setIconSize(QSize(20, 20)); - connect(shutdownBtn, &ZIconWidget::clicked, this, - [device]() { ToolboxWidget::shutdownDevice(device); }); + // connect(shutdownBtn, &ZIconWidget::clicked, this, + // [device]() { ToolboxWidget::shutdownDevice(device); }); ZIconWidget *restartBtn = new ZIconWidget( QIcon(":/resources/icons/IcTwotoneRestartAlt.png"), "Restart", this); restartBtn->setIconSize(QSize(20, 20)); - connect(restartBtn, &ZIconWidget::clicked, this, - [device]() { ToolboxWidget::restartDevice(device); }); + // connect(restartBtn, &ZIconWidget::clicked, this, + // [device]() { ToolboxWidget::restartDevice(device); }); ZIconWidget *recoveryBtn = new ZIconWidget( QIcon(":/resources/icons/HugeiconsWrench01.png"), "Recovery", this); recoveryBtn->setIconSize(QSize(20, 20)); - connect(recoveryBtn, &ZIconWidget::clicked, this, - [device]() { ToolboxWidget::_enterRecoveryMode(device); }); + // connect(recoveryBtn, &ZIconWidget::clicked, this, + // [device]() { ToolboxWidget::_enterRecoveryMode(device); }); actionsLayout->addWidget(shutdownBtn); actionsLayout->addWidget(restartBtn); @@ -259,25 +259,27 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) infoItems.append( {"Hardware Platform:", createValueLabel(QString::fromStdString( device->deviceInfo.hardwarePlatform))}); - infoItems.append( - {"Battery Cycle:", createValueLabel(QString::number( - m_device->deviceInfo.batteryInfo.cycleCount))}); + // infoItems.append( + // {"Battery Cycle:", createValueLabel(QString::number( + // m_device->deviceInfo.batteryInfo.cycleCount))}); infoItems.append( {"Firmware Version:", createValueLabel(QString::fromStdString( device->deviceInfo.firmwareVersion))}); - // Battery Info - QWidget *batteryWidget = new QWidget(); - QHBoxLayout *batteryLayout = new QHBoxLayout(batteryWidget); - batteryLayout->setContentsMargins(0, 0, 0, 0); - batteryLayout->setSpacing(5); - batteryLayout->addWidget(new QLabel(device->deviceInfo.batteryInfo.health)); - QPushButton *moreButton = new QPushButton("More"); - connect(moreButton, &QPushButton::clicked, this, - &DeviceInfoWidget::onBatteryMoreClicked); - batteryLayout->addWidget(moreButton); - batteryLayout->addStretch(); - infoItems.append({"Battery Health:", batteryWidget}); + // // FIXME: Battery Info + // // QWidget *batteryWidget = new QWidget(); + // // QHBoxLayout *batteryLayout = new QHBoxLayout(batteryWidget); + // // batteryLayout->setContentsMargins(0, 0, 0, 0); + // // batteryLayout->setSpacing(5); + // // batteryLayout->addWidget(new + // // QLabel(device->deviceInfo.batteryInfo.health)); QPushButton + // *moreButton = + // // new QPushButton("More"); connect(moreButton, &QPushButton::clicked, + // this, + // // &DeviceInfoWidget::onBatteryMoreClicked); + // // batteryLayout->addWidget(moreButton); + // // batteryLayout->addStretch(); + // // infoItems.append({"Battery Health:", batteryWidget}); infoItems.append( {"Production Device:", @@ -322,7 +324,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) } infoLayout->addWidget(gridContainer); - // infoLayout->addStretch(); // Pushes footer to the bottom + infoLayout->addStretch(); // Pushes footer to the bottom // Footer QLabel *footerLabel = @@ -341,16 +343,16 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) rightSideLayout->addWidget(new DiskUsageWidget(device, this)); rightSideLayout->addStretch(); - // TODO: layout shift cause ? - // rightSideLayout->setAlignment(Qt::AlignCenter); + // // TODO: layout shift cause ? + // // rightSideLayout->setAlignment(Qt::AlignCenter); mainLayout->addLayout(rightSideLayout); mainLayout->addStretch(); - m_updateTimer = new QTimer(this); - connect(m_updateTimer, &QTimer::timeout, this, - &DeviceInfoWidget::updateBatteryInfo); - m_updateTimer->start(30000); // Update every 30 seconds + // m_updateTimer = new QTimer(this); + // connect(m_updateTimer, &QTimer::timeout, this, + // &DeviceInfoWidget::updateBatteryInfo); + // m_updateTimer->start(30000); // Update every 30 seconds } DeviceInfoWidget::~DeviceInfoWidget() {} @@ -370,47 +372,47 @@ void DeviceInfoWidget::onBatteryMoreClicked() void DeviceInfoWidget::updateBatteryInfo() { - qDebug() << "Updating battery info..."; - plist_t diagnostics = nullptr; - get_battery_info(m_device->deviceInfo.rawProductType, m_device->device, - m_device->deviceInfo.is_iPhone, diagnostics); + // qDebug() << "Updating battery info..."; + // plist_t diagnostics = nullptr; + // get_battery_info(m_device->deviceInfo.rawProductType, m_device->device, + // m_device->deviceInfo.is_iPhone, diagnostics); - if (!diagnostics) { - qDebug() << "Failed to get diagnostics plist."; - return; - } - /*DATA*/ - DeviceInfo &d = m_device->deviceInfo; - qDebug() << "old device" << d.oldDevice; - PlistNavigator ioreg = PlistNavigator(diagnostics)["IORegistry"]; - if (d.oldDevice) - parseOldDeviceBattery(ioreg, d); - else - parseDeviceBattery(ioreg, d); - /*UI*/ - updateChargingStatusIcon(); - m_chargingWattsWithCableTypeLabel->setText( - QString::number(d.batteryInfo.watts) + "W" + "/" + - (d.batteryInfo.usbConnectionType == BatteryInfo::ConnectionType::USB - ? "USB" - : "USB-C")); + // if (!diagnostics) { + // qDebug() << "Failed to get diagnostics plist."; + // return; + // } + // /*DATA*/ + // DeviceInfo &d = m_device->deviceInfo; + // qDebug() << "old device" << d.oldDevice; + // PlistNavigator ioreg = PlistNavigator(diagnostics)["IORegistry"]; + // // if (d.oldDevice) + // // parseOldDeviceBattery(ioreg, d); + // // else + // // parseDeviceBattery(ioreg, d); + // /*UI*/ + // updateChargingStatusIcon(); + // m_chargingWattsWithCableTypeLabel->setText( + // QString::number(d.batteryInfo.watts) + "W" + "/" + + // (d.batteryInfo.usbConnectionType == BatteryInfo::ConnectionType::USB + // ? "USB" + // : "USB-C")); - m_batteryWidget->updateContext( - d.batteryInfo.isCharging, - qBound(1, d.batteryInfo.currentBatteryLevel, 100)); + // m_batteryWidget->updateContext( + // d.batteryInfo.isCharging, + // qBound(1, d.batteryInfo.currentBatteryLevel, 100)); } void DeviceInfoWidget::updateChargingStatusIcon() { - if (m_device->deviceInfo.batteryInfo.isCharging) { - m_chargingStatusLabel->setText("Charging"); - m_chargingStatusLabel->setStyleSheet( - QString("color: %1;").arg(COLOR_GREEN.name())); - m_lightningIconLabel->show(); + // if (m_device->deviceInfo.batteryInfo.isCharging) { + // m_chargingStatusLabel->setText("Charging"); + // m_chargingStatusLabel->setStyleSheet( + // QString("color: %1;").arg(COLOR_GREEN.name())); + // m_lightningIconLabel->show(); - } else { - m_chargingStatusLabel->setText("Not Charging"); - m_chargingStatusLabel->setStyleSheet(""); - m_lightningIconLabel->hide(); - } + // } else { + // m_chargingStatusLabel->setText("Not Charging"); + // m_chargingStatusLabel->setStyleSheet(""); + // m_lightningIconLabel->hide(); + // } } \ No newline at end of file diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp index 508f209..9a9accd 100644 --- a/src/devicemanagerwidget.cpp +++ b/src/devicemanagerwidget.cpp @@ -20,10 +20,10 @@ #include "devicemanagerwidget.h" #include "appcontext.h" #include "devicemenuwidget.h" -#include "devicependingwidget.h" -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include "recoverydeviceinfowidget.h" -#endif +// #include "devicependingwidget.h" +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// #include "recoverydeviceinfowidget.h" +// #endif #include "settingsmanager.h" #include @@ -37,12 +37,12 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent) addDevice(device); // Apply settings-based behavior for switching to new device - SettingsManager::sharedInstance()->doIfEnabled( - SettingsManager::Setting::SwitchToNewDevice, - [this, device]() { - AppContext::sharedInstance()->setCurrentDeviceSelection( - DeviceSelection(device->udid)); - }); + // SettingsManager::sharedInstance()->doIfEnabled( + // SettingsManager::Setting::SwitchToNewDevice, + // [this, device]() { + // AppContext::sharedInstance()->setCurrentDeviceSelection( + // DeviceSelection(device->udid)); + // }); emit updateNoDevicesConnected(); }); @@ -72,29 +72,32 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent) connect(AppContext::sharedInstance(), &AppContext::devicePaired, this, [this](iDescriptorDevice *device) { addPairedDevice(device); - SettingsManager::sharedInstance()->doIfEnabled( - SettingsManager::Setting::SwitchToNewDevice, - [this, device]() { - AppContext::sharedInstance()->setCurrentDeviceSelection( - DeviceSelection(device->udid)); - }); + // SettingsManager::sharedInstance()->doIfEnabled( + // SettingsManager::Setting::SwitchToNewDevice, + // [this, device]() { + // AppContext::sharedInstance()->setCurrentDeviceSelection( + // DeviceSelection(device->udid)); + // }); emit updateNoDevicesConnected(); }); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceAdded, - this, [this](const iDescriptorRecoveryDevice *recoveryDeviceInfo) { - addRecoveryDevice(recoveryDeviceInfo); - emit updateNoDevicesConnected(); - }); + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // connect(AppContext::sharedInstance(), + // &AppContext::recoveryDeviceAdded, + // this, [this](const iDescriptorRecoveryDevice + // *recoveryDeviceInfo) { + // addRecoveryDevice(recoveryDeviceInfo); + // emit updateNoDevicesConnected(); + // }); - connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved, - this, [this](uint64_t ecid) { - removeRecoveryDevice(ecid); - emit updateNoDevicesConnected(); - }); -#endif + // connect(AppContext::sharedInstance(), + // &AppContext::recoveryDeviceRemoved, + // this, [this](uint64_t ecid) { + // removeRecoveryDevice(ecid); + // emit updateNoDevicesConnected(); + // }); + // #endif connect(AppContext::sharedInstance(), &AppContext::devicePairingExpired, this, [this](const QString &udid) { @@ -146,94 +149,97 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device) std::pair{deviceWidget, m_sidebar->addDevice(tabTitle, device->udid)}; } -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -void DeviceManagerWidget::addRecoveryDevice( - const iDescriptorRecoveryDevice *device) -{ - 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); +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// void DeviceManagerWidget::addRecoveryDevice( +// const iDescriptorRecoveryDevice *device) +// { +// 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"; - } -} +// } 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; - } +// 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 = - m_recoveryDeviceWidgets[ecid].first; - RecoveryDeviceSidebarItem *sidebarItem = - m_recoveryDeviceWidgets[ecid].second; +// RecoveryDeviceInfoWidget *deviceWidget = +// m_recoveryDeviceWidgets[ecid].first; +// RecoveryDeviceSidebarItem *sidebarItem = +// m_recoveryDeviceWidgets[ecid].second; - if (deviceWidget != nullptr && sidebarItem != nullptr) { - qDebug() << "Recovery device exists removing:" << QString::number(ecid); +// if (deviceWidget != nullptr && sidebarItem != nullptr) { +// qDebug() << "Recovery device exists removing:" << +// QString::number(ecid); - m_recoveryDeviceWidgets.remove(ecid); - m_stackedWidget->removeWidget(deviceWidget); - m_sidebar->removeRecoveryDevice(ecid); - deviceWidget->deleteLater(); +// m_recoveryDeviceWidgets.remove(ecid); +// m_stackedWidget->removeWidget(deviceWidget); +// m_sidebar->removeRecoveryDevice(ecid); +// deviceWidget->deleteLater(); - emit updateNoDevicesConnected(); - } -} -#endif +// emit updateNoDevicesConnected(); +// } +// } +// #endif void DeviceManagerWidget::addPendingDevice(const QString &udid, bool locked) { - qDebug() << "Adding pending device:" << udid; - if (m_pendingDeviceWidgets.contains(udid.toStdString()) && !locked) { - qDebug() << "Pending device already exists, moving to next state:" - << udid; - m_pendingDeviceWidgets[udid.toStdString()].first->next(); - return; - } else if (m_pendingDeviceWidgets.contains(udid.toStdString()) && locked) { - // Already exists and still locked, do nothing - qDebug() - << "Pending device already exists and is locked, doing nothing:" - << udid; - return; - } + // qDebug() << "Adding pending device:" << udid; + // if (m_pendingDeviceWidgets.contains(udid.toStdString()) && !locked) { + // qDebug() << "Pending device already exists, moving to next state:" + // << udid; + // m_pendingDeviceWidgets[udid.toStdString()].first->next(); + // return; + // } else if (m_pendingDeviceWidgets.contains(udid.toStdString()) && locked) + // { + // // Already exists and still locked, do nothing + // qDebug() + // << "Pending device already exists and is locked, doing nothing:" + // << udid; + // return; + // } - qDebug() << "Created pending widget for:" << udid << "Locked:" << locked; - DevicePendingWidget *pendingWidget = new DevicePendingWidget(locked, this); - m_stackedWidget->addWidget(pendingWidget); - m_pendingDeviceWidgets[udid.toStdString()] = - std::pair{pendingWidget, m_sidebar->addPendingDevice(udid)}; + // qDebug() << "Created pending widget for:" << udid << "Locked:" << locked; + // DevicePendingWidget *pendingWidget = new DevicePendingWidget(locked, + // this); m_stackedWidget->addWidget(pendingWidget); + // m_pendingDeviceWidgets[udid.toStdString()] = + // std::pair{pendingWidget, m_sidebar->addPendingDevice(udid)}; } 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; + // 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(); - } + // 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) @@ -335,18 +341,19 @@ void DeviceManagerWidget::onDeviceSelectionChanged( } break; -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - 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; -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // 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; + // #endif case DeviceSelection::Pending: if (m_pendingDeviceWidgets.contains(selection.udid)) { diff --git a/src/devicemanagerwidget.h b/src/devicemanagerwidget.h index 8f9f574..deb8a83 100644 --- a/src/devicemanagerwidget.h +++ b/src/devicemanagerwidget.h @@ -24,9 +24,9 @@ #include "devicependingwidget.h" #include "devicesidebarwidget.h" #include "iDescriptor.h" -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include "recoverydeviceinfowidget.h" -#endif +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// #include "recoverydeviceinfowidget.h" +// #endif #include #include #include @@ -53,10 +53,10 @@ private: void addDevice(iDescriptorDevice *device); void removeDevice(const std::string &uuid); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - void addRecoveryDevice(const iDescriptorRecoveryDevice *device); - void removeRecoveryDevice(uint64_t ecid); -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // void addRecoveryDevice(const iDescriptorRecoveryDevice *device); + // void removeRecoveryDevice(uint64_t ecid); + // #endif void addPendingDevice(const QString &udid, bool locked); void addPairedDevice(iDescriptorDevice *device); void removePendingDevice(const QString &udid); @@ -72,11 +72,12 @@ private: std::pair> m_pendingDeviceWidgets; // Map to store devices by UDID -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - QMap> - m_recoveryDeviceWidgets; // Map to store recovery devices by ECID -#endif + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // QMap> + // m_recoveryDeviceWidgets; // Map to store recovery devices by ECID + // #endif std::string m_currentDeviceUuid; }; diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index aa051b9..def6524 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -55,30 +55,30 @@ void DeviceMenuWidget::init() // Create and add widgets to the stacked widget m_deviceInfoWidget = new DeviceInfoWidget(device, this); - m_installedAppsWidget = new InstalledAppsWidget(device, this); - m_galleryWidget = new GalleryWidget(device, this); - m_fileExplorerWidget = new FileExplorerWidget(device, this); + // m_installedAppsWidget = new InstalledAppsWidget(device, this); + // m_galleryWidget = new GalleryWidget(device, this); + // m_fileExplorerWidget = new FileExplorerWidget(device, this); // Set minimum heights - m_galleryWidget->setMinimumHeight(300); - m_fileExplorerWidget->setMinimumHeight(300); + // m_galleryWidget->setMinimumHeight(300); + // m_fileExplorerWidget->setMinimumHeight(300); - stackedWidget->addWidget(m_deviceInfoWidget); // Index 0 - Info - stackedWidget->addWidget(m_installedAppsWidget); // Index 1 - Apps - stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery - stackedWidget->addWidget(m_fileExplorerWidget); // Index 3 - Files + stackedWidget->addWidget(m_deviceInfoWidget); // Index 0 - Info + // stackedWidget->addWidget(m_installedAppsWidget); // Index 1 - Apps + // stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery + // stackedWidget->addWidget(m_fileExplorerWidget); // Index 3 - Files // Set default to Info tab stackedWidget->setCurrentWidget(m_deviceInfoWidget); // Connect to current changed signal for lazy loading - connect(stackedWidget, &QStackedWidget::currentChanged, this, - [this](int index) { - if (index == 2) { // Gallery tab - qDebug() << "Switched to Gallery tab"; - m_galleryWidget->load(); - } - }); + // connect(stackedWidget, &QStackedWidget::currentChanged, this, + // [this](int index) { + // if (index == 2) { // Gallery tab + // qDebug() << "Switched to Gallery tab"; + // m_galleryWidget->load(); + // } + // }); QWidget *loadingWidget = stackedWidget->widget(0); stackedWidget->removeWidget(loadingWidget); @@ -89,12 +89,12 @@ void DeviceMenuWidget::switchToTab(const QString &tabName) { if (tabName == "Info") { stackedWidget->setCurrentWidget(m_deviceInfoWidget); - } else if (tabName == "Apps") { - stackedWidget->setCurrentWidget(m_installedAppsWidget); - } else if (tabName == "Gallery") { - stackedWidget->setCurrentWidget(m_galleryWidget); + // } else if (tabName == "Apps") { + // stackedWidget->setCurrentWidget(m_installedAppsWidget); + // } else if (tabName == "Gallery") { + // stackedWidget->setCurrentWidget(m_galleryWidget); } else if (tabName == "Files") { - stackedWidget->setCurrentWidget(m_fileExplorerWidget); + // stackedWidget->setCurrentWidget(m_fileExplorerWidget); } else { qDebug() << "Tab not found:" << tabName; } diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index cf0480a..ea94647 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -20,10 +20,10 @@ #ifndef DEVICEMENUWIDGET_H #define DEVICEMENUWIDGET_H #include "deviceinfowidget.h" -#include "fileexplorerwidget.h" +// #include "fileexplorerwidget.h" #include "gallerywidget.h" #include "iDescriptor.h" -#include "installedappswidget.h" +// #include "installedappswidget.h" #include #include @@ -41,9 +41,9 @@ private: QStackedWidget *stackedWidget; // Pointer to the stacked widget iDescriptorDevice *device; // Pointer to the iDescriptor device DeviceInfoWidget *m_deviceInfoWidget; - InstalledAppsWidget *m_installedAppsWidget; - GalleryWidget *m_galleryWidget; - FileExplorerWidget *m_fileExplorerWidget; + // InstalledAppsWidget *m_installedAppsWidget; + // GalleryWidget *m_galleryWidget; + // FileExplorerWidget *m_fileExplorerWidget; signals: }; diff --git a/src/devicemonitor.h b/src/devicemonitor.h new file mode 100644 index 0000000..c2898aa --- /dev/null +++ b/src/devicemonitor.h @@ -0,0 +1,126 @@ +#ifndef DEVICEMONITOR_H +#define DEVICEMONITOR_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace IdeviceFFI; + +class DeviceMonitorThread : public QThread +{ + Q_OBJECT +public: + DeviceMonitorThread(QObject *parent = nullptr) : QThread(parent) {} + + void run() override + { + while (!isInterruptionRequested()) { + // Create connection + UsbmuxdConnectionHandle *usbmuxd_conn; + IdeviceFfiError *err = + idevice_usbmuxd_new_default_connection(0, &usbmuxd_conn); + if (err) { + idevice_error_free(err); + QThread::msleep(1000); // Wait before retry + continue; + } + + UsbmuxdListenerHandle *listener; + err = idevice_usbmuxd_listen(usbmuxd_conn, &listener); + if (err) { + idevice_usbmuxd_connection_free(usbmuxd_conn); + idevice_error_free(err); + QThread::msleep(1000); + continue; + } + + // Monitor loop + while (!isInterruptionRequested()) { + bool connect = false; + UsbmuxdDeviceHandle *device; + uint32_t disconnect_id; + + err = idevice_usbmuxd_listener_next(listener, &connect, &device, + &disconnect_id); + qDebug() << "Listening for device connections..." << err + << connect; + if (err) { + // Check if it's just the stream ending (no devices) + if (err->code == -1) { // Socket error code + qDebug() << "Listener stream ended, reconnecting..."; + idevice_error_free(err); + break; // Break inner loop to reconnect + } else { + qDebug() << "Real error:" << err->message; + idevice_error_free(err); + break; + } + } + + if (connect) { + const char *udid = idevice_usbmuxd_device_get_udid(device); + uint32_t device_id = + idevice_usbmuxd_device_get_device_id(device); + uint8_t conn_type = + idevice_usbmuxd_device_get_connection_type(device); + + // Skip network devices (same as original callback) + if (conn_type == CONNECTION_NETWORK) { + qDebug() << "Skipping network device:" << QString(udid); + idevice_usbmuxd_device_free(device); + continue; + } + + deviceMap[device_id] = QString(udid); + qDebug() + << "[DeviceMonitor] Device connected:" << QString(udid); + + emit deviceEvent(IDEVICE_DEVICE_ADD, QString(udid), + conn_type, AddType::Regular); + idevice_usbmuxd_device_free(device); + } else { + QString udid = deviceMap.value(disconnect_id, QString()); + if (!udid.isEmpty()) { + emit deviceEvent(IDEVICE_DEVICE_REMOVE, udid, + CONNECTION_USB, AddType::Regular); + deviceMap.remove(disconnect_id); + } else { + qDebug() << "Unknown device disconnected please report " + "this issue: " + << disconnect_id; + } + } + } + + // Cleanup before reconnecting + idevice_usbmuxd_listener_handle_free(listener); + idevice_usbmuxd_connection_free(usbmuxd_conn); + } + } + + enum IdeviceEventType { + IDEVICE_DEVICE_ADD = 1, + IDEVICE_DEVICE_REMOVE = 2, + IDEVICE_DEVICE_PAIRED = 3 + }; + + enum IdeviceConnectionType { CONNECTION_USB = 1, CONNECTION_NETWORK = 2 }; + + enum AddType { Regular = 0, Pairing = 1 }; + +signals: + void deviceEvent(int event, const QString &udid, int conn_type, + int addType); + +private: + QMap deviceMap; +}; +#endif // DEVICEMONITOR_H \ No newline at end of file diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 64eea78..0561698 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -27,9 +27,9 @@ #include #include -#include -#include -#include +// #include +// #include +// #include DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0), @@ -95,31 +95,33 @@ void DiskUsageWidget::setupUI() m_diskBarLayout->setContentsMargins(0, 0, 0, 0); m_diskBarLayout->setSpacing(0); -/* - FIXME: There is bug with qt, related to NSPopover on macOS - need to revisit this when we find a fix -*/ -// #ifdef Q_OS_MAC -// m_systemBar = new DiskUsageBar(); -// m_appsBar = new DiskUsageBar(); -// m_mediaBar = new DiskUsageBar(); -// m_othersBar = new DiskUsageBar(); -// m_freeBar = new DiskUsageBar(); + /* + FIXME: There is bug with qt, related to NSPopover on macOS + need to revisit this when we find a fix + */ + // #ifdef Q_OS_MAC + // m_systemBar = new DiskUsageBar(); + // m_appsBar = new DiskUsageBar(); + // m_mediaBar = new DiskUsageBar(); + // m_othersBar = new DiskUsageBar(); + // m_freeBar = new DiskUsageBar(); -// m_systemBar->setStyleSheet( -// " 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; "); -// m_freeBar->setStyleSheet( -// "background-color: #6e6d6d; border: 1px solid " -// "#4f4f4f; padding: 0; margin: 0; border-top-right-radius: 3px; " -// "border-bottom-right-radius: 3px; "); + // m_systemBar->setStyleSheet( + // " 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; "); + // m_freeBar->setStyleSheet( + // "background-color: #6e6d6d; border: 1px solid " + // "#4f4f4f; padding: 0; margin: 0; border-top-right-radius: 3px; " + // "border-bottom-right-radius: 3px; "); m_systemBar = new QWidget(); m_appsBar = new QWidget(); @@ -221,6 +223,7 @@ void DiskUsageWidget::updateUI() if (m_totalCapacity == 0) { m_processIndicator->stop(); + m_processIndicator->hide(); m_statusLabel->setText("No disk information available."); m_stackedWidget->setCurrentWidget(m_loadingErrorPage); return; @@ -318,19 +321,22 @@ void DiskUsageWidget::updateUI() m_diskBarLayout->setStretchFactor(m_othersBar, othersStretch); m_diskBarLayout->setStretchFactor(m_freeBar, freeStretch); -/* FIXME: NSPopover bug */ + /* FIXME: NSPopover bug */ // #ifdef Q_OS_MAC -// m_systemBar->setUsageInfo("System", formatSize(m_systemUsage), "#a1384d", -// (double)m_systemUsage / m_totalCapacity); -// m_appsBar->setUsageInfo("Apps", formatSize(m_appsUsage), "#3498DB", -// (double)m_appsUsage / m_totalCapacity); -// m_mediaBar->setUsageInfo("Media", formatSize(m_mediaUsage), "#2ECC71", -// (double)m_mediaUsage / m_totalCapacity); -// m_othersBar->setUsageInfo("Others", formatSize(m_othersUsage), "#F39C12", -// (double)m_othersUsage / m_totalCapacity); -// m_freeBar->setUsageInfo("Free", formatSize(m_freeSpace), "#BDC3C7", -// (double)m_freeSpace / m_totalCapacity); -// #else + // m_systemBar->setUsageInfo("System", formatSize(m_systemUsage), + // "#a1384d", + // (double)m_systemUsage / m_totalCapacity); + // m_appsBar->setUsageInfo("Apps", formatSize(m_appsUsage), "#3498DB", + // (double)m_appsUsage / m_totalCapacity); + // m_mediaBar->setUsageInfo("Media", formatSize(m_mediaUsage), + // "#2ECC71", + // (double)m_mediaUsage / m_totalCapacity); + // m_othersBar->setUsageInfo("Others", formatSize(m_othersUsage), + // "#F39C12", + // (double)m_othersUsage / m_totalCapacity); + // m_freeBar->setUsageInfo("Free", formatSize(m_freeSpace), "#BDC3C7", + // (double)m_freeSpace / m_totalCapacity); + // #else m_systemBar->setToolTip( QString("System: %1 (%2%)") .arg(formatSize(m_systemUsage)) @@ -357,7 +363,6 @@ void DiskUsageWidget::updateUI() .arg(QString::number((double)m_freeSpace / m_totalCapacity * 100, 'f', 1))); - // Hide segments with zero usage // m_systemBar->setVisible(m_systemUsage > 0); // m_appsBar->setVisible(m_appsUsage > 0); @@ -365,7 +370,6 @@ void DiskUsageWidget::updateUI() // m_othersBar->setVisible(m_othersUsage > 0); // m_freeBar->setVisible(m_freeSpace > 0); } - void DiskUsageWidget::fetchData() { auto *watcher = new QFutureWatcher(this); @@ -376,6 +380,7 @@ void DiskUsageWidget::fetchData() m_state = Error; m_errorMessage = result["error"].toString(); } else { + qDebug() << "Disk usage data fetched:" << result; m_totalCapacity = result["totalCapacity"].toULongLong(); m_systemUsage = result["systemUsage"].toULongLong(); m_appsUsage = result["appsUsage"].toULongLong(); @@ -393,17 +398,18 @@ void DiskUsageWidget::fetchData() m_state = Ready; } - updateUI(); // Update the UI instead of triggering repaint + updateUI(); watcher->deleteLater(); }); - QFuture future = QtConcurrent::run([this]() { + QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; if (!m_device || !m_device->device) { result["error"] = "Invalid device."; return result; } + // Pre-populate with known info result["totalCapacity"] = QVariant::fromValue( m_device->deviceInfo.diskInfo.totalDiskCapacity); result["freeSpace"] = QVariant::fromValue( @@ -411,41 +417,21 @@ void DiskUsageWidget::fetchData() result["systemUsage"] = QVariant::fromValue( m_device->deviceInfo.diskInfo.totalSystemCapacity); + // Create provider wrapper from existing handle + Provider provider = Provider::adopt(m_device->device); + // Apps usage uint64_t totalAppsSpace = 0; - instproxy_client_t instproxy = nullptr; - lockdownd_client_t lockdownClient = nullptr; - lockdownd_service_descriptor_t lockdowndService = nullptr; - - if (lockdownd_client_new_with_handshake(m_device->device, - &lockdownClient, APP_LABEL) != - LOCKDOWN_E_SUCCESS) { - result["error"] = "Could not connect to lockdown service."; + auto instproxy_res = IdeviceFFI::InstallationProxy::connect(provider); + if (instproxy_res.is_err()) { + result["error"] = + "Could not connect to installation proxy: " + + QString::fromStdString(instproxy_res.unwrap_err().message); return result; } + auto instproxy = std::move(instproxy_res.unwrap()); - if (lockdownd_start_service(lockdownClient, - "com.apple.mobile.installation_proxy", - &lockdowndService) != LOCKDOWN_E_SUCCESS) { - result["error"] = "Could not start installation proxy service."; - lockdownd_client_free(lockdownClient); - return result; - } - - if (instproxy_client_new(m_device->device, lockdowndService, - &instproxy) != INSTPROXY_E_SUCCESS) { - result["error"] = "Could not connect to installation proxy."; - lockdownd_service_descriptor_free(lockdowndService); - lockdownd_client_free(lockdownClient); - return result; - } - - // The service descriptor is no longer needed after the client is - // created - lockdownd_service_descriptor_free(lockdowndService); - lockdowndService = nullptr; - - plist_t client_opts = instproxy_client_options_new(); + plist_t client_opts = plist_new_dict(); plist_dict_set_item(client_opts, "ApplicationType", plist_new_string("User")); @@ -456,56 +442,51 @@ void DiskUsageWidget::fetchData() plist_new_string("DynamicDiskUsage")); plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); - plist_t apps = nullptr; - if (instproxy_browse(instproxy, client_opts, &apps) == - INSTPROXY_E_SUCCESS && - apps) { - if (plist_get_node_type(apps) == PLIST_ARRAY) { - for (uint32_t i = 0; i < plist_array_get_size(apps); i++) { - plist_t app_info = plist_array_get_item(apps, i); - if (!app_info) - continue; + auto apps_result = instproxy.browse(client_opts); + if (apps_result.is_ok()) { + auto apps = std::move(apps_result.unwrap()); + for (const auto &app_info : apps) { + plist_t static_usage = + plist_dict_get_item(app_info, "StaticDiskUsage"); + if (static_usage && + plist_get_node_type(static_usage) == PLIST_UINT) { + uint64_t static_size = 0; + plist_get_uint_val(static_usage, &static_size); + totalAppsSpace += static_size; + } - plist_t static_usage = - plist_dict_get_item(app_info, "StaticDiskUsage"); - if (static_usage && - plist_get_node_type(static_usage) == PLIST_UINT) { - uint64_t static_size = 0; - plist_get_uint_val(static_usage, &static_size); - totalAppsSpace += static_size; - } - - plist_t dynamic_usage = - plist_dict_get_item(app_info, "DynamicDiskUsage"); - if (dynamic_usage && - plist_get_node_type(dynamic_usage) == PLIST_UINT) { - uint64_t dynamic_size = 0; - plist_get_uint_val(dynamic_usage, &dynamic_size); - totalAppsSpace += dynamic_size; - } + plist_t dynamic_usage = + plist_dict_get_item(app_info, "DynamicDiskUsage"); + if (dynamic_usage && + plist_get_node_type(dynamic_usage) == PLIST_UINT) { + uint64_t dynamic_size = 0; + plist_get_uint_val(dynamic_usage, &dynamic_size); + totalAppsSpace += dynamic_size; } } - plist_free(apps); } result["appsUsage"] = QVariant::fromValue(totalAppsSpace); - plist_free(client_opts); - instproxy_client_free(instproxy); + plist_free(client_opts); // client_opts is consumed by browse, but // Media usage uint64_t mediaSpace = 0; - plist_t node = nullptr; - if (lockdownd_get_value(lockdownClient, "com.apple.mobile.iTunes", - nullptr, &node) == LOCKDOWN_E_SUCCESS && - node) { - plist_t mediaNode = plist_dict_get_item(node, "MediaLibrarySize"); - if (mediaNode && plist_get_node_type(mediaNode) == PLIST_UINT) { - plist_get_uint_val(mediaNode, &mediaSpace); + IdeviceFFI::Lockdown lockdown = + IdeviceFFI::Lockdown::adopt(m_device->lockdown); + auto itunes_info_res = + lockdown.get_value("com.apple.mobile.iTunes", nullptr); + if (itunes_info_res.is_ok()) { + auto itunes_dict = std::move(itunes_info_res.unwrap()); + if (itunes_dict) { + plist_t media_node = + plist_dict_get_item(itunes_dict, "MediaLibrarySize"); + if (media_node && + plist_get_node_type(media_node) == PLIST_UINT) { + plist_get_uint_val(media_node, &mediaSpace); + } } - plist_free(node); } result["mediaUsage"] = QVariant::fromValue(mediaSpace); - lockdownd_client_free(lockdownClient); return result; }); watcher->setFuture(future); diff --git a/src/fileexplorerwidget.cpp b/src/fileexplorerwidget.cpp index 32da91a..0b7c024 100644 --- a/src/fileexplorerwidget.cpp +++ b/src/fileexplorerwidget.cpp @@ -41,8 +41,6 @@ #include #include #include -#include -#include FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device, QWidget *parent) diff --git a/src/fileexplorerwidget.h b/src/fileexplorerwidget.h index a2eb418..7aa68d8 100644 --- a/src/fileexplorerwidget.h +++ b/src/fileexplorerwidget.h @@ -34,7 +34,6 @@ #include #include #include -#include class FileExplorerWidget : public QWidget { @@ -49,7 +48,7 @@ private slots: private: QSplitter *m_mainSplitter; QStackedWidget *m_stackedWidget; - afc_client_t currentAfcClient; + AfcClientHandle *currentAfcClient; QTreeWidget *m_sidebarTree; iDescriptorDevice *m_device; diff --git a/src/iDescriptor.h b/src/iDescriptor.h index defb1cf..cd89726 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -24,15 +24,20 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include -#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -54,8 +59,11 @@ "https://raw.githubusercontent.com/iDescriptor/iDescriptor/refs/heads/" \ "main/DeveloperDiskImages.json" -// This is because afc_read_directory accepts "/var/mobile/Media" as "/" +// This is because afc_list_directory accepts "/var/mobile/Media" as "/" #define POSSIBLE_ROOT "../../../../" +#define IDEVICE_DEVICE_VERSION(maj, min, patch) \ + ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF)) +#include "devicemonitor.h" struct BatteryInfo { QString health; @@ -172,59 +180,62 @@ struct DeviceInfo { std::string regionRaw; std::string region; unsigned int parsedDeviceVersion; + std::string wifiMacAddress; }; struct iDescriptorDevice { std::string udid; - idevice_connection_type conn_type; - idevice_t device; + DeviceMonitorThread::IdeviceConnectionType conn_type; + IdeviceProviderHandle *device; DeviceInfo deviceInfo; - afc_client_t afcClient; - afc_client_t afc2Client; + AfcClientHandle *afcClient; + AfcClientHandle *afc2Client; + LockdowndClientHandle *lockdown; bool is_iPhone; std::recursive_mutex *mutex; }; struct iDescriptorInitDeviceResult { bool success = false; - lockdownd_error_t error; - idevice_t device; + IdeviceFfiError error; + IdeviceProviderHandle *device; DeviceInfo deviceInfo; - afc_client_t afcClient; - afc_client_t afc2Client; + AfcClientHandle *afcClient; + AfcClientHandle *afc2Client; + LockdowndClientHandle *lockdown; }; -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -struct iDescriptorRecoveryDevice { - uint64_t ecid; - irecv_mode mode; - uint32_t cpid; - uint32_t bdid; - std::string displayName; - std::recursive_mutex *mutex; -}; -#endif +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// struct iDescriptorRecoveryDevice { +// uint64_t ecid; +// irecv_mode mode; +// uint32_t cpid; +// uint32_t bdid; +// std::string displayName; +// std::recursive_mutex *mutex; +// }; +// #endif struct TakeScreenshotResult { bool success = false; QImage img; }; -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -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; -}; +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// 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; +// }; -#endif +// #endif void warn(const QString &message, const QString &title = "Warning", QWidget *parent = nullptr); -enum class AddType { Regular, Pairing }; +enum class AddType { Regular, Pairing, Wireless, UpgradeToWireless }; class PlistNavigator { @@ -293,14 +304,14 @@ public: plist_t getNode() const { return current_node; } }; -afc_error_t safe_afc_read_directory(afc_client_t afcClient, idevice_t device, - const char *path, char ***dirs); +// afc_error_t safe_afc_read_directory(afc_client_t afcClient, idevice_t device, +// const char *path, char ***dirs); std::string parse_product_type(const std::string &productType); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -std::string parse_recovery_mode(irecv_mode productType); -#endif +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// std::string parse_recovery_mode(irecv_mode productType); +// #endif struct MediaEntry { std::string name; @@ -313,114 +324,122 @@ struct AFCFileTree { std::string currentPath; }; -AFCFileTree get_file_tree(afc_client_t afcClient, - const std::string &path = "/"); +// AFCFileTree get_file_tree(afc_client_t afcClient, +// const std::string &path = "/"); -bool detect_jailbroken(afc_client_t afc); +bool detect_jailbroken(AfcClientHandle *afc); -void get_device_info_xml(const char *udid, lockdownd_client_t client, - idevice_t device, pugi::xml_document &infoXml); +void get_device_info_xml(const char *udid, LockdowndClientHandle *client, + pugi::xml_document &infoXml); -iDescriptorInitDeviceResult init_idescriptor_device(const char *udid); - -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -iDescriptorInitDeviceResultRecovery -init_idescriptor_recovery_device(uint64_t ecid); -#endif -bool set_location(idevice_t device, char *lat, char *lon); - -bool shutdown(idevice_t device); - -TakeScreenshotResult take_screenshot(screenshotr_client_t shotr); - -mobile_image_mounter_error_t mount_dev_image(idevice_t device, - unsigned int device_version, - const char *image_dir_path); -struct GetMountedImageResult { - bool success; - std::string sig; - std::string message; +struct WirelessInitArgs { + QString ip; + const IdevicePairingFile *pairing_file; }; +iDescriptorInitDeviceResult +init_idescriptor_device(const QString &udid, + WirelessInitArgs wirelessArgs = {nullptr, nullptr}); -plist_t _get_mounted_image(const char *udid); +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// iDescriptorInitDeviceResultRecovery +// init_idescriptor_recovery_device(uint64_t ecid); +// #endif +// bool set_location(idevice_t device, char *lat, char *lon); -bool restart(std::string udid); +// bool shutdown(idevice_t device); -enum class ImageCompatibility { - Compatible, // Exact match or known compatible version - MaybeCompatible, // Major version matches but minor doesn't - NotCompatible // Not compatible -}; +// TakeScreenshotResult take_screenshot(screenshotr_client_t shotr); -struct ImageInfo { - QString version; - QString dmgPath; - QString sigPath; - ImageCompatibility compatibility = ImageCompatibility::NotCompatible; - bool isDownloaded = false; - bool isMounted = false; -}; +// mobile_image_mounter_error_t mount_dev_image(idevice_t device, +// unsigned int device_version, +// const char *image_dir_path); +// struct GetMountedImageResult { +// bool success; +// std::string sig; +// std::string message; +// }; -/** - * @brief Compare two iPhone product types to determine which is newer - * @param productType First iPhone product type (e.g., "iPhone8,1") - * @param otherProductType Second iPhone product type (e.g., "iPhone7,2") - * @return true if productType is newer than otherProductType, false otherwise - * - * Examples: - * - compare_product_type("iPhone8,1", "iPhone7,2") returns true - * - compare_product_type("iPhone6,1", "iPhone8,1") returns false - * - compare_product_type("iPhone8,2", "iPhone8,1") returns true - */ -bool compare_product_type(std::string productType, - std::string otherProductType); +// plist_t _get_mounted_image(const char *udid); -/** - * @brief Check if two iPhone product types are exactly equal - * @param productType First iPhone product type - * @param otherProductType Second iPhone product type - * @return true if both product types are identical - */ -bool are_product_types_equal(const std::string &productType, - const std::string &otherProductType); +// bool restart(std::string udid); -/** - * @brief Check if first product type is newer than second - * @param productType First iPhone product type - * @param otherProductType Second iPhone product type - * @return true if productType is newer than otherProductType - */ +// enum class ImageCompatibility { +// Compatible, // Exact match or known compatible version +// MaybeCompatible, // Major version matches but minor doesn't +// NotCompatible // Not compatible +// }; + +// struct ImageInfo { +// QString version; +// QString dmgPath; +// QString sigPath; +// ImageCompatibility compatibility = ImageCompatibility::NotCompatible; +// bool isDownloaded = false; +// bool isMounted = false; +// }; + +// /** +// * @brief Compare two iPhone product types to determine which is newer +// * @param productType First iPhone product type (e.g., "iPhone8,1") +// * @param otherProductType Second iPhone product type (e.g., "iPhone7,2") +// * @return true if productType is newer than otherProductType, false +// otherwise +// * +// * Examples: +// * - compare_product_type("iPhone8,1", "iPhone7,2") returns true +// * - compare_product_type("iPhone6,1", "iPhone8,1") returns false +// * - compare_product_type("iPhone8,2", "iPhone8,1") returns true +// */ +// bool compare_product_type(std::string productType, +// std::string otherProductType); + +// /** +// * @brief Check if two iPhone product types are exactly equal +// * @param productType First iPhone product type +// * @param otherProductType Second iPhone product type +// * @return true if both product types are identical +// */ +// bool are_product_types_equal(const std::string &productType, +// const std::string &otherProductType); + +// /** +// * @brief Check if first product type is newer than second +// * @param productType First iPhone product type +// * @param otherProductType Second iPhone product type +// * @return true if productType is newer than otherProductType +// */ bool is_product_type_newer(const std::string &productType, const std::string &otherProductType); -/** - * @brief Check if first product type is older than second - * @param productType First iPhone product type - * @param otherProductType Second iPhone product type - * @return true if productType is older than otherProductType - */ -bool is_product_type_older(const std::string &productType, - const std::string &otherProductType); +// /** +// * @brief Check if first product type is older than second +// * @param productType First iPhone product type +// * @param otherProductType Second iPhone product type +// * @return true if productType is older than otherProductType +// */ +// bool is_product_type_older(const std::string &productType, +// const std::string &otherProductType); -bool query_mobile_gestalt(iDescriptorDevice *id_device, const QStringList &keys, - uint32_t &xml_size, char *&xml_data); -; +// bool query_mobile_gestalt(iDescriptorDevice *id_device, const QStringList +// &keys, +// uint32_t &xml_size, char *&xml_data); +// ; -std::string safeGetXML(const char *key, pugi::xml_node dict); +// std::string safeGetXML(const char *key, pugi::xml_node dict); -void get_battery_info(std::string productType, idevice_t idevice, - bool is_iphone, plist_t &diagnostics); +void get_battery_info(IdeviceProviderHandle *provider, plist_t &diagnostics); -void parseOldDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d); -void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d); +// void parseOldDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d); +// void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d); -void fetchAppIconFromApple(QNetworkAccessManager *manager, - const QString &bundleId, - std::function callback); +// void fetchAppIconFromApple(QNetworkAccessManager *manager, +// const QString &bundleId, +// std::function +// callback); -afc_error_t afc2_client_new(idevice_t device, afc_client_t *afc); +// afc_error_t afc2_client_new(idevice_t device, afc_client_t *afc); -void get_cable_info(idevice_t device, plist_t &response); +// void get_cable_info(idevice_t device, plist_t &response); struct NetworkDevice { QString name; // service name @@ -428,7 +447,7 @@ struct NetworkDevice { QString address; // IPv4 or IPv6 address uint16_t port = 22; // SSH port std::map txt; // TXT records - + QString macAddress; // MAC address if available bool operator==(const NetworkDevice &other) const { return name == other.name && address == other.address; @@ -437,13 +456,13 @@ struct NetworkDevice { QPixmap load_heic(const QByteArray &data); -QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, - const char *path); +// QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, +// const char *path); bool isDarkMode(); -instproxy_error_t install_IPA(idevice_t device, afc_client_t afc, - const char *filePath); +// instproxy_error_t install_IPA(idevice_t device, afc_client_t afc, +// const char *filePath); // Helper struct for semantic version comparison struct AppVersion { diff --git a/src/main.cpp b/src/main.cpp index 7cb2815..e16ea84 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ */ #include "mainwindow.h" -#include "settingsmanager.h" +// #include "settingsmanager.h" #include #include #include @@ -37,12 +37,12 @@ int main(int argc, char *argv[]) QCoreApplication::setApplicationName("iDescriptor"); QCoreApplication::setApplicationVersion(APP_VERSION); - if (a.arguments().contains("--reset-settings")) { - SettingsManager::sharedInstance()->clear(); - QMessageBox::information(nullptr, "Settings Reset", - "All application settings have been reset to " - "their default values."); - } + // if (a.arguments().contains("--reset-settings")) { + // SettingsManager::sharedInstance()->clear(); + // QMessageBox::information(nullptr, "Settings Reset", + // "All application settings have been reset to + // " "their default values."); + // } #ifdef WIN32 QString appPath = QCoreApplication::applicationDirPath(); QString gstPluginPath = diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6ccdcdc..ec28c5e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -19,19 +19,19 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" -#include "detailwindow.h" -#include "ifusediskunmountbutton.h" -#include "ifusemanager.h" -#include "settingswidget.h" +// #include "detailwindow.h" +// #include "ifusediskunmountbutton.h" +// #include "ifusemanager.h" +// #include "settingswidget.h" #include "appswidget.h" #include "devicemanagerwidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "jailbrokenwidget.h" -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include "libirecovery.h" -#endif +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// #include "libirecovery.h" +// #endif #include "toolboxwidget.h" #include "welcomewidget.h" #include @@ -42,90 +42,99 @@ #include #include "appcontext.h" -#include "settingsmanager.h" +// #include "settingsmanager.h" +// #include "devicemonitor.h" +#include "networkdevicemanager.h" +#include "networkdeviceswidget.h" #include #include #include #include #include -#ifdef WIN32 -#include "platform/windows/check_deps.h" -#endif +// #ifdef WIN32 +// #include "platform/windows/check_deps.h" +// #endif -void handleCallback(const idevice_event_t *event, void *userData) -{ - printf("Device event received: "); +using namespace IdeviceFFI; - switch (event->event) { - case IDEVICE_DEVICE_ADD: { - /* this should never happen iDescriptor does not support network devices - but for some reason even though we are only listening for USB devices, - we still get network devices on macOS*/ - if (event->conn_type == CONNECTION_NETWORK) { - return; - } - qDebug() << "Device added: " << QString::fromUtf8(event->udid); +// void handleCallback(const idevice_event_t *event, void *userData) +// { +// printf("Device event received: "); - QMetaObject::invokeMethod( - AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, - Q_ARG(QString, QString::fromUtf8(event->udid)), - Q_ARG(idevice_connection_type, event->conn_type), - Q_ARG(AddType, AddType::Regular)); - break; - } +// switch (event->event) { +// case IDEVICE_DEVICE_ADD: { +// /* this should never happen iDescriptor does not support network +// devices but for some reason even though we are only listening for USB +// devices, we still get network devices on macOS*/ if (event->conn_type +// == CONNECTION_NETWORK) { +// return; +// } +// qDebug() << "Device added: " << QString::fromUtf8(event->udid); - case IDEVICE_DEVICE_REMOVE: { - QMetaObject::invokeMethod(AppContext::sharedInstance(), "removeDevice", - Qt::QueuedConnection, - Q_ARG(QString, QString(event->udid))); - break; - } +// QMetaObject::invokeMethod( +// AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, +// Q_ARG(QString, QString::fromUtf8(event->udid)), +// Q_ARG(idevice_connection_type, event->conn_type), +// Q_ARG(AddType, AddType::Regular)); +// break; +// } - case IDEVICE_DEVICE_PAIRED: { - if (event->conn_type == CONNECTION_NETWORK) { - qDebug() - << "Network devices are not supported but a network device was " - "received in event listener. Please report this issue."; - return; - } - qDebug() << "Device paired: " << QString::fromUtf8(event->udid); +// case IDEVICE_DEVICE_REMOVE: { +// QMetaObject::invokeMethod(AppContext::sharedInstance(), +// "removeDevice", +// Qt::QueuedConnection, +// Q_ARG(QString, QString(event->udid))); +// break; +// } - QMetaObject::invokeMethod( - AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, - Q_ARG(QString, QString::fromUtf8(event->udid)), - Q_ARG(idevice_connection_type, event->conn_type), - Q_ARG(AddType, AddType::Pairing)); - break; - } - default: - qDebug() << "Unhandled event: " << event->event; - } -} +// case IDEVICE_DEVICE_PAIRED: { +// if (event->conn_type == CONNECTION_NETWORK) { +// qDebug() +// << "Network devices are not supported but a network device +// was " +// "received in event listener. Please report this issue."; +// return; +// } +// qDebug() << "Device paired: " << QString::fromUtf8(event->udid); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -void handleCallbackRecovery(const irecv_device_event_t *event, void *userData) -{ +// QMetaObject::invokeMethod( +// AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, +// Q_ARG(QString, QString::fromUtf8(event->udid)), +// Q_ARG(idevice_connection_type, event->conn_type), +// Q_ARG(AddType, AddType::Pairing)); +// break; +// } +// default: +// qDebug() << "Unhandled event: " << event->event; +// } +// } - switch (event->type) { - case IRECV_DEVICE_ADD: - qDebug() << "Recovery device added: "; - 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(uint64_t, event->device_info->ecid)); - break; - default: - printf("Unhandled recovery event: %d\n", event->type); - } -} -irecv_device_event_context_t context; -#endif +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// void handleCallbackRecovery(const irecv_device_event_t *event, void +// *userData) +// { + +// switch (event->type) { +// case IRECV_DEVICE_ADD: +// qDebug() << "Recovery device added: "; +// 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(uint64_t, +// event->device_info->ecid)); +// break; +// default: +// printf("Unhandled recovery event: %d\n", event->type); +// } +// } +// irecv_device_event_context_t context; +// #endif MainWindow *MainWindow::sharedInstance() { @@ -162,27 +171,32 @@ MainWindow::MainWindow(QWidget *parent) this, &MainWindow::updateNoDevicesConnected); m_ZTabWidget->addTab(m_mainStackedWidget, "iDevice"); - auto *appsWidgetTab = - m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); - m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); + // auto *appsWidgetTab = + // m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); + // m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); + m_ZTabWidget->addTab(new QWidget(), "Apps"); // Placeholder for Apps tab + m_ZTabWidget->addTab(new QWidget(), + "Toolbox"); // Placeholder for Toolbox tab + m_ZTabWidget->addTab(new QWidget(), + "Jailbroken"); // Placeholder for Jailbroken tab - auto *jailbrokenWidget = new JailbrokenWidget(this); - m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); + // auto *jailbrokenWidget = new JailbrokenWidget(this); + // m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); m_ZTabWidget->finalizeStyles(); - connect( - appsWidgetTab, &ZTab::clicked, this, - [this](int index) { AppsWidget::sharedInstance()->init(); }, - Qt::SingleShotConnection); + // connect( + // appsWidgetTab, &ZTab::clicked, this, + // [this](int index) { AppsWidget::sharedInstance()->init(); }, + // Qt::SingleShotConnection); - // settings button - ZIconWidget *settingsButton = new ZIconWidget( - QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings"); - settingsButton->setCursor(Qt::PointingHandCursor); - settingsButton->setFixedSize(24, 24); - connect(settingsButton, &ZIconWidget::clicked, this, [this]() { - SettingsManager::sharedInstance()->showSettingsDialog(); - }); + // // settings button + // ZIconWidget *settingsButton = new ZIconWidget( + // QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings"); + // settingsButton->setCursor(Qt::PointingHandCursor); + // settingsButton->setFixedSize(24, 24); + // connect(settingsButton, &ZIconWidget::clicked, this, [this]() { + // SettingsManager::sharedInstance()->showSettingsDialog(); + // }); ZIconWidget *githubButton = new ZIconWidget( QIcon(":/resources/icons/MdiGithub.png"), "iDescriptor on GitHub"); @@ -204,57 +218,62 @@ MainWindow::MainWindow(QWidget *parent) "QLabel:hover { background-color : #13131319; }"); ui->statusbar->addPermanentWidget(appVersionLabel); ui->statusbar->addPermanentWidget(githubButton); - ui->statusbar->addPermanentWidget(settingsButton); + // ui->statusbar->addPermanentWidget(settingsButton); -#ifdef __linux__ - QList mounted_iFusePaths = iFuseManager::getMountPoints(); + // #ifdef __linux__ + // QList mounted_iFusePaths = iFuseManager::getMountPoints(); - for (const QString &path : mounted_iFusePaths) { - auto *p = new iFuseDiskUnmountButton(path); + // for (const QString &path : mounted_iFusePaths) { + // auto *p = new iFuseDiskUnmountButton(path); - ui->statusbar->addPermanentWidget(p); - connect(p, &iFuseDiskUnmountButton::clicked, this, [this, p, path]() { - bool ok = iFuseManager::linuxUnmount(path); - if (!ok) { - QMessageBox::warning(nullptr, "Unmount Failed", - "Failed to unmount iFuse at " + path + - ". Please try again."); - return; - } - ui->statusbar->removeWidget(p); - p->deleteLater(); - }); - } -#endif + // ui->statusbar->addPermanentWidget(p); + // connect(p, &iFuseDiskUnmountButton::clicked, this, [this, p, + // path]() { + // bool ok = iFuseManager::linuxUnmount(path); + // if (!ok) { + // QMessageBox::warning(nullptr, "Unmount Failed", + // "Failed to unmount iFuse at " + path + // + + // ". Please try again."); + // return; + // } + // ui->statusbar->removeWidget(p); + // p->deleteLater(); + // }); + // } + // #endif -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - irecv_error_t res_recovery = - irecv_device_event_subscribe(&context, handleCallbackRecovery, nullptr); + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // irecv_error_t res_recovery = + // irecv_device_event_subscribe(&context, handleCallbackRecovery, + // nullptr); - if (res_recovery != IRECV_E_SUCCESS) { - qDebug() << "ERROR: Unable to subscribe to recovery device events. " - "Error code:" - << res_recovery; - } - qDebug() << "Subscribed to recovery device events successfully."; -#endif + // if (res_recovery != IRECV_E_SUCCESS) { + // qDebug() << "ERROR: Unable to subscribe to recovery device + // events. " + // "Error code:" + // << res_recovery; + // } + // qDebug() << "Subscribed to recovery device events successfully."; + // #endif - idevice_error_t res = idevice_event_subscribe(handleCallback, nullptr); - if (res != IDEVICE_E_SUCCESS) { - qDebug() << "ERROR: Unable to subscribe to device events. Error code:" - << res; - } - qDebug() << "Subscribed to device events successfully."; - createMenus(); + // idevice_error_t res = idevice_event_subscribe(handleCallback, + // nullptr); if (res != IDEVICE_E_SUCCESS) { + // qDebug() << "ERROR: Unable to subscribe to device events. Error + // code:" + // << res; + // } + // qDebug() << "Subscribed to device events successfully."; + // createMenus(); - UpdateProcedure updateProcedure; - bool packageManagerManaged = false; - bool isPortable = false; - bool skipPrerelease = true; -#ifdef WIN32 - isPortable = !is_iDescriptorInstalled(); - qDebug() << "isPortable=" << isPortable; -#endif + // UpdateProcedure updateProcedure; + // bool packageManagerManaged = false; + // bool isPortable = false; + // bool skipPrerelease = true; + // #ifdef WIN32 + // isPortable = !is_iDescriptorInstalled(); + // qDebug() << "isPortable=" << isPortable; + // #endif /* struct UpdateProcedure { @@ -265,71 +284,225 @@ MainWindow::MainWindow(QWidget *parent) QString boxText; }; */ - switch (ZUpdater::detectPlatform()) { - case Platform::Windows: - updateProcedure = UpdateProcedure{ - !isPortable, - isPortable, - !isPortable, - isPortable ? "New portable version downloaded, app location will " - "be shown after this message" - : "The application will now quit to install the update.", - isPortable ? "New portable version downloaded" - : "Do you want to install the downloaded update now?", - }; - break; - // todo: adjust for pkg managers - case Platform::MacOS: - updateProcedure = UpdateProcedure{ - true, - false, - true, - "The application will now quit and open .dmg file downloaded to " - "\"Downloads\" from there you can drag it to Applications to " - "install.", - "Update downloaded would you like to quit and install the update?", - }; - break; - case Platform::Linux: - // currently only on linux (arch aur) is enabled -#ifdef PACKAGE_MANAGER_MANAGED - packageManagerManaged = true; -#endif - updateProcedure = UpdateProcedure{ - true, - false, - true, - "AppImages we ship are not updateable. New version is downloaded " - "to " - "\"Downloads\". You can start using the new version by launching " - "it " - "from there. You can delete this AppImage version if you like.", - "Update downloaded would you like to quit and open the new " - "version?", - }; - break; - default: - updateProcedure = UpdateProcedure{ - false, false, false, "", "", - }; - } + // switch (ZUpdater::detectPlatform()) { + // case Platform::Windows: + // updateProcedure = UpdateProcedure{ + // !isPortable, + // isPortable, + // !isPortable, + // isPortable ? "New portable version downloaded, app location + // will " + // "be shown after this message" + // : "The application will now quit to install the + // update.", + // isPortable ? "New portable version downloaded" + // : "Do you want to install the downloaded update + // now?", + // }; + // break; + // // todo: adjust for pkg managers + // case Platform::MacOS: + // updateProcedure = UpdateProcedure{ + // true, + // false, + // true, + // "The application will now quit and open .dmg file downloaded + // to " + // "\"Downloads\" from there you can drag it to Applications to + // " "install.", "Update downloaded would you like to quit and + // install the update?", + // }; + // break; + // case Platform::Linux: + // // currently only on linux (arch aur) is enabled + // #ifdef PACKAGE_MANAGER_MANAGED + // packageManagerManaged = true; + // #endif + // updateProcedure = UpdateProcedure{ + // true, + // false, + // true, + // "AppImages we ship are not updateable. New version is + // downloaded " "to " + // "\"Downloads\". You can start using the new version by + // launching " "it " "from there. You can delete this AppImage + // version if you like.", "Update downloaded would you like to + // quit and open the new " "version?", + // }; + // break; + // default: + // updateProcedure = UpdateProcedure{ + // false, false, false, "", "", + // }; + // } - m_updater = new ZUpdater("iDescriptor/iDescriptor", APP_VERSION, - "iDescriptor", updateProcedure, isPortable, - packageManagerManaged, skipPrerelease, this); -#if defined(PACKAGE_MANAGER_MANAGED) && defined(__linux__) - m_updater->setPackageManagerManagedMessage( - QString( - "You seem to have installed iDescriptor using a package manager. " - "Please use %1 to update it.") - .arg(PACKAGE_MANAGER_HINT)); -#endif + // m_updater = new ZUpdater("iDescriptor/iDescriptor", APP_VERSION, + // "iDescriptor", updateProcedure, isPortable, + // packageManagerManaged, skipPrerelease, + // this); + // #if defined(PACKAGE_MANAGER_MANAGED) && defined(__linux__) + // m_updater->setPackageManagerManagedMessage( + // QString( + // "You seem to have installed iDescriptor using a package + // manager. " "Please use %1 to update it.") + // .arg(PACKAGE_MANAGER_HINT)); + // #endif - SettingsManager::sharedInstance()->doIfEnabled( - SettingsManager::Setting::AutoCheckUpdates, [this]() { - qDebug() << "Checking for updates..."; - m_updater->checkForUpdates(); + // SettingsManager::sharedInstance()->doIfEnabled( + // SettingsManager::Setting::AutoCheckUpdates, [this]() { + // qDebug() << "Checking for updates..."; + // m_updater->checkForUpdates(); + // }); + + // Usage in main thread: + m_deviceMonitor = new DeviceMonitorThread(this); + connect( + m_deviceMonitor, &DeviceMonitorThread::deviceEvent, this, + [this](int event, const QString &udid, int conn_type, int addType) { + // Handle device connection + switch (event) { + case DeviceMonitorThread::IDEVICE_DEVICE_ADD: { + /* this should never happen iDescriptor does not + support network devices but for some reason even + though we are only listening for USB devices, we + still get network devices on macOS*/ + if (conn_type == DeviceMonitorThread::CONNECTION_NETWORK) { + return; + } + qDebug() << "Device added: " << udid; + + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", + Qt::QueuedConnection, Q_ARG(QString, udid), + Q_ARG( + DeviceMonitorThread::IdeviceConnectionType, + static_cast( + conn_type)), + Q_ARG(AddType, AddType::Regular)); + break; + } + + case DeviceMonitorThread::IDEVICE_DEVICE_REMOVE: { + QMetaObject::invokeMethod(AppContext::sharedInstance(), + "removeDevice", Qt::QueuedConnection, + Q_ARG(QString, udid)); + break; + } + + case DeviceMonitorThread::IDEVICE_DEVICE_PAIRED: { + if (conn_type == DeviceMonitorThread::CONNECTION_NETWORK) { + qDebug() << "Network devices are not supported but a " + "network device was " + "received in event listener. Please report " + "this issue."; + return; + } + qDebug() << "Device paired: " << udid; + + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", + Qt::QueuedConnection, Q_ARG(QString, udid), + Q_ARG( + DeviceMonitorThread::IdeviceConnectionType, + static_cast( + conn_type)), + Q_ARG(AddType, AddType::Pairing)); + break; + } + default: + qDebug() << "Unhandled event: " << event; + } }); + + m_deviceMonitor->start(); + + connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, + [](const std::string &udid, const std::string &wifiMacAddress) { + const IdevicePairingFile *pairingFile = + AppContext::sharedInstance()->getCachedPairingFile( + QString::fromStdString(udid)); + + if (pairingFile) { + // qDebug() << "Device removed, pairing file for UDID" + // << QString::fromStdString(udid) << "MAC" + // << QString::fromStdString(wifiMacAddress) + // << "exists in cache."; + // try to upgrade device to wireless if possible + qDebug() + << "Upgrading device to wireless connection for UDID" + << QString::fromStdString(udid); + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", + Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(udid)), + Q_ARG(DeviceMonitorThread::IdeviceConnectionType, + DeviceMonitorThread::CONNECTION_NETWORK), + Q_ARG(AddType, AddType::UpgradeToWireless), + Q_ARG(QString, QString::fromStdString(wifiMacAddress))); + + } else { + qDebug() + << "Device removed, no cached pairing file for UDID" + << QString::fromStdString(udid); + } + }); + + connect(NetworkDeviceManager::sharedInstance(), + &NetworkDeviceManager::deviceAdded, this, + [this](const NetworkDevice &device) { + // const iDescriptorDevice *idevice = + // AppContext::sharedInstance()->getDeviceByMacAddress( + // device.macAddress); + // if (idevice) { + // qDebug() << "Network device matched to connected device:" + // << QString::fromStdString( + // idevice->deviceInfo.deviceName) + // << "MAC:" << device.macAddress; + // // You can now use 'idevice' as needed + // } + // FIXME: both macAddress and udid can be used to get pairing + // file + + if (AppContext::sharedInstance()->getDeviceByMacAddress( + device.macAddress)) { + qDebug() << "Prefering wired connection on device MAC:" + << device.macAddress; + return; + } + + const IdevicePairingFile *pairingFile = + AppContext::sharedInstance()->getCachedPairingFile( + device.macAddress); + + if (!pairingFile) { + qDebug() << "No cached pairing file for network device MAC:" + << device.macAddress + << "Cannot add as wireless device."; + return; + } + + qDebug() << "Trying to add network device with MAC:" + << device.macAddress; + + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", + Qt::QueuedConnection, Q_ARG(QString, device.macAddress), + Q_ARG(DeviceMonitorThread::IdeviceConnectionType, + DeviceMonitorThread::CONNECTION_NETWORK), + Q_ARG(AddType, AddType::Wireless), + Q_ARG(QString, device.macAddress)); + + // Handle network device addition if needed + }); + + // NetworkDevicesWidget *m_networkDevicesWidget = new + // NetworkDevicesWidget(); + // m_networkDevicesWidget->setAttribute(Qt::WA_DeleteOnClose); + // m_networkDevicesWidget->setWindowFlag(Qt::Window); + // m_networkDevicesWidget->resize(500, 600); + // // connect(m_networkDevicesWidget, &QObject::destroyed, this, + // // [this]() { m_networkDevicesWidget = nullptr; }); + // m_networkDevicesWidget->show(); } void MainWindow::createMenus() @@ -339,9 +512,9 @@ void MainWindow::createMenus() QAction *aboutAct = new QAction("&About iDescriptor", this); connect(aboutAct, &QAction::triggered, this, [this]() { - QMessageBox::about( - this, "iDescriptor", - "A free, open-source, and cross-platform iDevice management tool."); + QMessageBox::about(this, "iDescriptor", + "A free, open-source, and cross-platform " + "iDevice management tool."); }); actionsMenu->addAction(aboutAct); #endif @@ -357,19 +530,23 @@ void MainWindow::updateNoDevicesConnected() return m_mainStackedWidget->setCurrentIndex(0); // Show Welcome page } int deviceCount = AppContext::sharedInstance()->getConnectedDeviceCount(); - m_connectedDeviceCountLabel->setText( - "iDescriptor: " + QString::number(deviceCount) + - (deviceCount == 1 ? " device" : " devices") + " connected"); + // m_connectedDeviceCountLabel->setText( + // "iDescriptor: " + QString::number(deviceCount) + + // (deviceCount == 1 ? " device" : " devices") + " connected"); m_mainStackedWidget->setCurrentIndex(1); // Show device list page } MainWindow::~MainWindow() { - idevice_event_unsubscribe(); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - irecv_device_event_unsubscribe(context); -#endif + // idevice_event_unsubscribe(); + // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + // irecv_device_event_unsubscribe(context); + // #endif delete ui; - delete m_updater; - sleep(2); + m_deviceMonitor->requestInterruption(); + // FIXME:QThread: Destroyed while thread '' is still running + // m_deviceMonitor->wait(); + delete m_deviceMonitor; + // delete m_updater; + // sleep(2); } diff --git a/src/mainwindow.h b/src/mainwindow.h index db2938c..85d1bbb 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -23,12 +23,13 @@ #include "ZUpdater.h" #include "devicemanagerwidget.h" #include "iDescriptor.h" -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include "libirecovery.h" -#endif +#include +// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +// #include "libirecovery.h" +// #endif +#include "devicemonitor.h" #include "ztabwidget.h" #include -#include #include QT_BEGIN_NAMESPACE @@ -58,5 +59,6 @@ private: DeviceManagerWidget *m_deviceManager; QStackedWidget *m_mainStackedWidget; QLabel *m_connectedDeviceCountLabel; + DeviceMonitorThread *m_deviceMonitor; }; #endif // MAINWINDOW_H diff --git a/src/networkdevicemanager.cpp b/src/networkdevicemanager.cpp new file mode 100644 index 0000000..1177fb8 --- /dev/null +++ b/src/networkdevicemanager.cpp @@ -0,0 +1,28 @@ +#include "networkdevicemanager.h" + +NetworkDeviceManager *NetworkDeviceManager::sharedInstance() +{ + static NetworkDeviceManager instance; + return &instance; +} + +NetworkDeviceManager::NetworkDeviceManager(QObject *parent) : QObject{parent} +{ + +#ifdef __linux__ + m_networkProvider = new AvahiService(this); + connect(m_networkProvider, &AvahiService::deviceAdded, this, + &NetworkDeviceManager::deviceAdded); + connect(m_networkProvider, &AvahiService::deviceRemoved, this, + &NetworkDeviceManager::deviceRemoved); +#else + m_networkProvider = new DnssdService(this); + connect(m_networkProvider, &DnssdService::deviceAdded, this, + &NetworkDeviceManager::deviceAdded); + connect(m_networkProvider, &DnssdService::deviceRemoved, this, + &NetworkDeviceManager::deviceRemoved); +#endif + + // Start scanning for network devices + m_networkProvider->startBrowsing(); +} diff --git a/src/networkdevicemanager.h b/src/networkdevicemanager.h new file mode 100644 index 0000000..5dfab9b --- /dev/null +++ b/src/networkdevicemanager.h @@ -0,0 +1,29 @@ +#ifndef NETWORKDEVICEMANAGER_H +#define NETWORKDEVICEMANAGER_H + +#include + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +class NetworkDeviceManager : public QObject +{ + Q_OBJECT +public: + explicit NetworkDeviceManager(QObject *parent = nullptr); + +#ifdef __linux__ + AvahiService *m_networkProvider = nullptr; +#else + DnssdService *m_networkProvider = nullptr; +#endif + static NetworkDeviceManager *sharedInstance(); +signals: + void deviceAdded(const NetworkDevice &device); + void deviceRemoved(const QString &deviceName); +}; + +#endif // NETWORKDEVICEMANAGER_H From 9b2cc67b9cba1549a1413679762960570dc18a58 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 29 Dec 2025 21:31:42 +0000 Subject: [PATCH 02/15] WIP: refactor remaining code - Initial fixes for the error "peer closed connection without sending TLS close_notify" - ServiceManager still need a lot work - Not prod ready still experimental --- CMakeLists.txt | 18 +- src/appcontext.cpp | 31 ++- .../helpers/read_afc_file_to_byte_array.cpp | 98 ++++---- src/core/services/get_file_tree.cpp | 113 ++++++--- src/core/services/init_device.cpp | 97 ++++---- src/devicemenuwidget.cpp | 25 +- src/devicemenuwidget.h | 2 +- src/diskusagewidget.cpp | 6 +- src/gallerywidget.cpp | 221 ++++++++++-------- src/gallerywidget.h | 5 +- src/heartbeat.h | 72 ++++++ src/iDescriptor.h | 13 +- src/mainwindow.cpp | 20 +- src/mediapreviewdialog.h | 3 +- src/mediastreamer.h | 3 +- src/mediastreamermanager.h | 3 +- src/photomodel.cpp | 176 ++++++-------- src/servicemanager.cpp | 192 ++++++++------- src/servicemanager.h | 158 ++++++++----- src/zloadingwidget.cpp | 37 +++ src/zloadingwidget.h | 19 ++ 21 files changed, 757 insertions(+), 555 deletions(-) create mode 100644 src/heartbeat.h create mode 100644 src/zloadingwidget.cpp create mode 100644 src/zloadingwidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c794b5..df8f2aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,6 @@ endif() find_package(PkgConfig REQUIRED) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Network QuickControls2 SerialPort Positioning Location QuickWidgets) -find_package(Qt6 REQUIRED COMPONENTS Core) # Add QTermWidget # Prefer CMake-native qtermwidget6, fallback to pkg-config if needed @@ -89,13 +88,13 @@ find_program(CARGO_EXECUTABLE cargo REQUIRED) message(STATUS "Using idevice-rs Rust implementation") set(IDEVICE_RS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/idevice-rs) -set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/release/libidevice_ffi.a) +set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/debug/libidevice_ffi.a) # This command builds the Rust library and declares its output file. # Any target that uses this output file will automatically depend on this command. add_custom_command( OUTPUT ${IDEVICE_RS_LIB_PATH} - COMMAND ${CARGO_EXECUTABLE} build --release --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml + COMMAND ${CARGO_EXECUTABLE} build --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml WORKING_DIRECTORY ${IDEVICE_RS_SOURCE_DIR} COMMENT "Building idevice-rs FFI library" VERBATIM @@ -271,6 +270,18 @@ src/networkdevicemanager.cpp src/networkdevicemanager.h src/*.ui resources.qrc +src/gallerywidget.cpp +src/gallerywidget.h +src/photomodel.cpp +src/photomodel.h +src/core/services/load_heic.cpp +src/servicemanager.cpp +src/servicemanager.h +src/core/services/get_file_tree.cpp +src/core/helpers/read_afc_file_to_byte_array.cpp +src/heartbeat.h +src/zloadingwidget.h +src/zloadingwidget.cpp ) if(APPLE) @@ -429,7 +440,6 @@ if(APPLE) ${SECURITY_FRAMEWORK} ${COREFOUNDATION_FRAMEWORK} ) -target_link_libraries(iDescriptor PRIVATE Qt6::Core) endif() # Add compile definition for source directory diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 25b71b7..591d3e2 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -65,7 +65,7 @@ AppContext::AppContext(QObject *parent) : QObject{parent} plist_print(fileData); const std::string wifiMacAddress = PlistNavigator(fileData)["WiFiMACAddress"].getString(); - plist_free(fileData); + // plist_free(fileData); qDebug() << "Found pairing file for MAC" << QString::fromStdString(wifiMacAddress); bool isCompatible = !wifiMacAddress.empty(); @@ -229,7 +229,7 @@ void AppContext::addDevice(QString udid, iDescriptorDevice *device = new iDescriptorDevice{ .udid = udid.toStdString(), .conn_type = conn_type, - .device = initResult.device, + .provider = initResult.provider, .deviceInfo = initResult.deviceInfo, .afcClient = initResult.afcClient, .afc2Client = initResult.afc2Client, @@ -417,20 +417,19 @@ AppContext::~AppContext() void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) { - // qDebug() << "New selection -" - // << " Type:" << selection.type - // << " UDID:" << QString::fromStdString(selection.udid) - // << " ECID:" << selection.ecid << " Section:" << - // selection.section; - // if (m_currentSelection.type == selection.type && - // m_currentSelection.udid == selection.udid && - // m_currentSelection.ecid == selection.ecid && - // m_currentSelection.section == selection.section) { - // qDebug() << "setCurrentDeviceSelection: No change in selection"; - // return; // No change - // } - // m_currentSelection = selection; - // emit currentDeviceSelectionChanged(m_currentSelection); + qDebug() << "New selection -" + << " Type:" << selection.type + << " UDID:" << QString::fromStdString(selection.udid) + << " ECID:" << selection.ecid << " Section:" << selection.section; + if (m_currentSelection.type == selection.type && + m_currentSelection.udid == selection.udid && + m_currentSelection.ecid == selection.ecid && + m_currentSelection.section == selection.section) { + qDebug() << "setCurrentDeviceSelection: No change in selection"; + return; // No change + } + m_currentSelection = selection; + emit currentDeviceSelectionChanged(m_currentSelection); } const DeviceSelection &AppContext::getCurrentDeviceSelection() const diff --git a/src/core/helpers/read_afc_file_to_byte_array.cpp b/src/core/helpers/read_afc_file_to_byte_array.cpp index 3ab98d4..9739de7 100644 --- a/src/core/helpers/read_afc_file_to_byte_array.cpp +++ b/src/core/helpers/read_afc_file_to_byte_array.cpp @@ -18,75 +18,77 @@ */ #include "../../iDescriptor.h" +#include "../../servicemanager.h" #include #include -QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, const char *path) +QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, + const char *path) { - uint64_t fd_handle = 0; - afc_error_t fd_err = - afc_file_open(afcClient, path, AFC_FOPEN_RDONLY, &fd_handle); + AfcFileHandle *handle = nullptr; + IdeviceFfiError *err_open = // Use distinct variable name + ServiceManager::safeAfcFileOpen(device, path, AfcRdOnly, &handle); - if (fd_err != AFC_E_SUCCESS) { - qDebug() << "Could not open file" << path << "Error:" << fd_err; + if (err_open) { + qDebug() << "Could not open file" << path + << "Error:" << err_open->message; + idevice_error_free(err_open); // Free the error object return QByteArray(); } - // TODO:Maybe use afc_get_file_info_plist instead? - 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); - } + AfcFileInfo info = {}; + IdeviceFfiError *err_info = // Use distinct variable name + ServiceManager::safeAfcGetFileInfo(device, path, &info); + if (err_info) { + qDebug() << "Could not get file info for file" << path + << "Error:" << err_info->message; + idevice_error_free(err_info); // Free the error object + ServiceManager::safeAfcFileClose(device, handle); // Close handle + return QByteArray(); + } + // Note: afc_file_info_free will be called later if the function returns + // successfully or when returning early after the file size check. + + qDebug() << "File size of" << path << "is" << info.size; + size_t fileSize = info.size; if (fileSize == 0) { - afc_file_close(afcClient, fd_handle); + ServiceManager::safeAfcFileClose(device, handle); + afc_file_info_free(&info); // Free internal strings of info return QByteArray(); } QByteArray buffer; - buffer.resize(fileSize); + buffer.reserve(fileSize); - uint64_t totalBytesRead = 0; - const uint32_t CHUNK_SIZE = 1024 * 1024; // Read in 1MB chunks - char *p = buffer.data(); + uint8_t *chunkData = nullptr; + size_t bytesRead = 0; + IdeviceFfiError *read_err = ServiceManager::safeAfcFileRead( + device, handle, &chunkData, fileSize, &bytesRead); - while (totalBytesRead < fileSize) { - uint32_t bytesToRead = - std::min((uint64_t)CHUNK_SIZE, fileSize - totalBytesRead); - uint32_t bytesReadThisChunk = 0; - afc_error_t read_err = - afc_file_read(afcClient, fd_handle, p + totalBytesRead, bytesToRead, - &bytesReadThisChunk); - - if (read_err != AFC_E_SUCCESS) { - qDebug() << "AFC Error: Read failed for file" << path - << "Error:" << read_err; - afc_file_close(afcClient, fd_handle); - return QByteArray(); - } - - if (bytesReadThisChunk == 0) { - // Premature end of file - break; - } - totalBytesRead += bytesReadThisChunk; + if (read_err) { + qDebug() << "AFC Error: Read failed for file" << path + << "Error:" << read_err->message; + idevice_error_free(read_err); + ServiceManager::safeAfcFileClose(device, handle); + afc_file_info_free(&info); // Free internal strings of info + return QByteArray(); } - afc_file_close(afcClient, fd_handle); + // Only append and free `chunkData` if `afc_file_read` was successful + buffer.append(reinterpret_cast(chunkData), bytesRead); + afc_file_read_data_free(chunkData, + bytesRead); // Free memory owned by Rust FFI - if (totalBytesRead != fileSize) { + ServiceManager::safeAfcFileClose(device, handle); + + if (bytesRead != fileSize) { qDebug() << "AFC Error: Read mismatch for file" << path - << "Read:" << totalBytesRead << "Expected:" << fileSize; - return QByteArray(); // Read failed + << "Read:" << bytesRead << "Expected:" << fileSize; + afc_file_info_free(&info); // Free internal strings of info + return QByteArray(); // Read failed } + afc_file_info_free(&info); // Free internal strings of info on success path return buffer; } \ No newline at end of file diff --git a/src/core/services/get_file_tree.cpp b/src/core/services/get_file_tree.cpp index 9796205..e53e5bc 100644 --- a/src/core/services/get_file_tree.cpp +++ b/src/core/services/get_file_tree.cpp @@ -18,68 +18,113 @@ */ #include "../../iDescriptor.h" +#include "../../servicemanager.h" #include #include -#include -#include #include -AFCFileTree get_file_tree(afc_client_t afcClient, const std::string &path) +AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path) { - + qDebug() << "Getting file tree for path:" << QString::fromStdString(path); AFCFileTree result; result.currentPath = path; + result.success = false; - char **dirs = NULL; - if (afc_read_directory(afcClient, path.c_str(), &dirs) != AFC_E_SUCCESS) { - result.success = false; + char **dirs = nullptr; + size_t count = 0; + + // Use safe wrapper to read directory + IdeviceFfiError *err = + ServiceManager::safeAfcReadDirectory(device, path.c_str(), &dirs); + + if (err) { + qDebug() << "Failed to read directory:" << path.c_str() + << "Error:" << err->message << "Code:" << err->code; + idevice_error_free(err); return result; } + if (!dirs) { + result.success = true; + return result; + } + + // Iterate through directory entries for (int i = 0; dirs[i]; i++) { + qDebug() << "Found entry:" << dirs[i]; std::string entryName = dirs[i]; if (entryName == "." || entryName == "..") continue; - char **info = NULL; std::string fullPath = path; if (fullPath.back() != '/') fullPath += "/"; fullPath += entryName; + + if (!checkDir) { + result.entries.push_back({entryName, false}); + continue; + } + + // Get file info using safe wrapper + AfcFileInfo info = {}; + IdeviceFfiError *info_err = + ServiceManager::safeAfcGetFileInfo(device, fullPath.c_str(), &info); + + if (info_err) { + qDebug() << "Failed to get file info for:" << fullPath.c_str() + << "Error:" << info_err->message; + } + 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) { - 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; + if (!info_err) { + qDebug() << "Entry:" << entryName.c_str() << "Type:" << info.st_ifmt + << "Size:" << info.size; + if (strcmp(info.st_ifmt, "S_IFDIR") == 0) { + isDir = true; + } else if (strcmp(info.st_ifmt, "S_IFLNK") == 0) { + // Check if symlink points to a directory + char **dir_contents = nullptr; + IdeviceFfiError *link_err = + ServiceManager::safeAfcReadDirectory( + device, fullPath.c_str(), &dir_contents); + + if (!link_err) { + isDir = true; + // if (dir_contents) { + // // FIXME: is this ok ? + // for (int j = 0; dir_contents[j]; j++) { + // free(dir_contents[j]); + // } + // free(dir_contents); + // } + } + + if (link_err) { + idevice_error_free(link_err); } } - afc_dictionary_free(info); + + // Free file info + // afc_file_info_free(&info); } + + if (info_err) { + idevice_error_free(info_err); + } + result.entries.push_back({entryName, isDir}); } + + // Free the directory list if (dirs) { - afc_dictionary_free(dirs); + // for (int i = 0; dirs[i]; i++) { + // free(dirs[i]); + // } + // free(dirs); } + result.success = true; return result; } \ No newline at end of file diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 8ef71bc..49fdd65 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -27,11 +27,13 @@ #include #include +#include "../../heartbeat.h" #include #include #include #include +#include std::string safeGetXML(const char *key, pugi::xml_node dict) { for (pugi::xml_node child = dict.first_child(); child; @@ -360,14 +362,9 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, } } -// [DeviceMonitor] Device connected: "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" -// Device added: "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" -// Initializing iDescriptor device with UDID: -// "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" Failed to create idevice handle -// Initialization failed, cleaning up resources. FfiInvalidArg -// init_idescriptor_device success ?: false -// Failed to initialize device with UDID: -// "a5c08c1dfdc9fcf81366bd6159c81bba73deaa27" +// FIXME:spawn on a new thread? +// wireless connections sometimes take more than 10sec to connect +// and ofc it freezes the ui iDescriptorInitDeviceResult init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) { @@ -389,6 +386,9 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) uint32_t actual_device_id = 0; IdevicePairingFile *pairing_file = nullptr; IdeviceHandle *deviceHandle = nullptr; + HeartbeatClientHandle *heartbeat = nullptr; + HeartBeatThread *heartbeatThread = nullptr; + // FIXME: remove debug std::stringstream ss; @@ -484,43 +484,30 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) uint16_t heartbeat_port; bool heartbeat_ssl; - if (isWireless) { - // err = lockdownd_start_service(lockdown, "com.apple.heartbeat", - // &heartbeat_port, &heartbeat_ssl); - // if (err) { - // qDebug() << "Failed to start Heartbeat service"; - // goto cleanup; - // } + // if (isWireless) { + // err = lockdownd_start_service(lockdown, "com.apple.heartbeat", + // &heartbeat_port, &heartbeat_ssl); - // Start heartbeat client to keep connection alive - HeartbeatClientHandle *heartbeat = nullptr; - err = heartbeat_connect(provider, &heartbeat); - - if (err) { - qDebug() << "Failed to connect to Heartbeat client"; - goto cleanup; - } - - // // After getting the heartbeat port from lockdown - // IdeviceHandle *deviceHandle = nullptr; - - // // Then create IdeviceHandle from the socket - // err = idevice_new(socket, "heartbeat", &deviceHandle); - // if (err) { - // qDebug() << "Failed to create idevice handle"; - // goto cleanup; - // } - - // // Now use with heartbeat_new - // HeartbeatClientHandle *heartbeat = nullptr; - // err = heartbeat_new(deviceHandle, &heartbeat); - // if (err) { - // qDebug() << "Failed to create Heartbeat client"; - // goto cleanup; - // } - - qDebug() << "Heartbeat client created successfully"; + // Start heartbeat client to keep connection alive + err = heartbeat_connect(provider, &heartbeat); + if (err) { + qDebug() << "Failed to start Heartbeat service"; + goto cleanup; } + heartbeatThread = new HeartBeatThread(heartbeat); + heartbeatThread->start(); + + // while (!heartbeatThread->initialCompleted()) { + // sleep(1); + // } + + if (err) { + qDebug() << "Failed to connect to Heartbeat client"; + goto cleanup; + } + + qDebug() << "Heartbeat client created successfully"; + // } // 5. Start AFC service uint16_t afc_port; @@ -539,21 +526,21 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) goto cleanup; } - // 7. AFC2 is optional - uint16_t afc2_port; - bool afc2_ssl; - err = lockdownd_start_service(lockdown, "com.apple.afc2", &afc2_port, - &afc2_ssl); - if (!err) { - err = afc_client_connect(provider, &afc2_client); - } + // // 7. AFC2 is optional + // uint16_t afc2_port; + // bool afc2_ssl; + // err = lockdownd_start_service(lockdown, "com.apple.afc2", &afc2_port, + // &afc2_ssl); + // if (!err) { + // err = afc_client_connect(provider, &afc2_client); + // } get_device_info_xml(udid.toUtf8().constData(), lockdown, infoXml); - infoXml.print(ss, " "); // " " for indentation - qDebug().noquote() << "--- Full Device Info XML ---" - << QString::fromStdString(ss.str()); + // infoXml.print(ss, " "); // " " for indentation + // qDebug().noquote() << "--- Full Device Info XML ---" + // << QString::fromStdString(ss.str()); - result.device = provider; + result.provider = provider; result.success = true; result.afcClient = afc_client; result.afc2Client = afc2_client; diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index def6524..d6e5f3a 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -56,29 +56,29 @@ void DeviceMenuWidget::init() // Create and add widgets to the stacked widget m_deviceInfoWidget = new DeviceInfoWidget(device, this); // m_installedAppsWidget = new InstalledAppsWidget(device, this); - // m_galleryWidget = new GalleryWidget(device, this); + m_galleryWidget = new GalleryWidget(device, this); // m_fileExplorerWidget = new FileExplorerWidget(device, this); // Set minimum heights - // m_galleryWidget->setMinimumHeight(300); + m_galleryWidget->setMinimumHeight(300); // m_fileExplorerWidget->setMinimumHeight(300); stackedWidget->addWidget(m_deviceInfoWidget); // Index 0 - Info // stackedWidget->addWidget(m_installedAppsWidget); // Index 1 - Apps - // stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery + stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery // stackedWidget->addWidget(m_fileExplorerWidget); // Index 3 - Files // Set default to Info tab stackedWidget->setCurrentWidget(m_deviceInfoWidget); // Connect to current changed signal for lazy loading - // connect(stackedWidget, &QStackedWidget::currentChanged, this, - // [this](int index) { - // if (index == 2) { // Gallery tab - // qDebug() << "Switched to Gallery tab"; - // m_galleryWidget->load(); - // } - // }); + connect(stackedWidget, &QStackedWidget::currentChanged, this, + [this](int index) { + if (stackedWidget->widget(index) == + m_galleryWidget) { // Gallery tab + m_galleryWidget->load(); + } + }); QWidget *loadingWidget = stackedWidget->widget(0); stackedWidget->removeWidget(loadingWidget); @@ -91,8 +91,9 @@ void DeviceMenuWidget::switchToTab(const QString &tabName) stackedWidget->setCurrentWidget(m_deviceInfoWidget); // } else if (tabName == "Apps") { // stackedWidget->setCurrentWidget(m_installedAppsWidget); - // } else if (tabName == "Gallery") { - // stackedWidget->setCurrentWidget(m_galleryWidget); + } else if (tabName == "Gallery") { + qDebug() << "Switching to Gallery tab"; + stackedWidget->setCurrentWidget(m_galleryWidget); } else if (tabName == "Files") { // stackedWidget->setCurrentWidget(m_fileExplorerWidget); } else { diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index ea94647..326bb8d 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -42,7 +42,7 @@ private: iDescriptorDevice *device; // Pointer to the iDescriptor device DeviceInfoWidget *m_deviceInfoWidget; // InstalledAppsWidget *m_installedAppsWidget; - // GalleryWidget *m_galleryWidget; + GalleryWidget *m_galleryWidget; // FileExplorerWidget *m_fileExplorerWidget; signals: }; diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 0561698..b2fc4cf 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -404,7 +404,7 @@ void DiskUsageWidget::fetchData() QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; - if (!m_device || !m_device->device) { + if (!m_device || !m_device->provider) { result["error"] = "Invalid device."; return result; } @@ -418,7 +418,7 @@ void DiskUsageWidget::fetchData() m_device->deviceInfo.diskInfo.totalSystemCapacity); // Create provider wrapper from existing handle - Provider provider = Provider::adopt(m_device->device); + Provider provider = Provider::adopt(m_device->provider); // Apps usage uint64_t totalAppsSpace = 0; @@ -466,7 +466,7 @@ void DiskUsageWidget::fetchData() } } result["appsUsage"] = QVariant::fromValue(totalAppsSpace); - plist_free(client_opts); // client_opts is consumed by browse, but + // plist_free(client_opts); // client_opts is consumed by browse, but // Media usage uint64_t mediaSpace = 0; diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index de862f5..924a85e 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -18,9 +18,9 @@ */ #include "gallerywidget.h" -#include "exportmanager.h" +// #include "exportmanager.h" #include "iDescriptor.h" -#include "mediapreviewdialog.h" +// #include "mediapreviewdialog.h" #include "photomodel.h" #include "servicemanager.h" #include @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -53,20 +54,6 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) m_stackedWidget(nullptr), m_albumSelectionWidget(nullptr), m_albumListView(nullptr), m_photoGalleryWidget(nullptr), m_listView(nullptr), m_backButton(nullptr) -{ -} -/*Load is called when the tab is active*/ -void GalleryWidget::load() -{ - if (m_loaded) - return; - - m_loaded = true; - - setupUI(); -} - -void GalleryWidget::setupUI() { m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(0, 0, 0, 0); @@ -86,10 +73,36 @@ void GalleryWidget::setupUI() // Add stacked widget to main layout m_mainLayout->addWidget(m_stackedWidget); setLayout(m_mainLayout); + m_loadingWidget = new ZLoadingWidget(true, this); + m_stackedWidget->addWidget(m_loadingWidget); + m_stackedWidget->setCurrentWidget(m_loadingWidget); - // Start with album selection view and load albums - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + QVBoxLayout *errorLayout = new QVBoxLayout(); + errorLayout->setAlignment(Qt::AlignCenter); + QLabel *errorLabel = new QLabel("Failed to load albums."); + errorLabel->setStyleSheet("font-weight: bold; color: red;"); + errorLayout->addWidget(errorLabel); + m_retryButton = new QPushButton("Retry", this); + connect(m_retryButton, &QPushButton::clicked, this, [this]() { + m_stackedWidget->setCurrentWidget(m_loadingWidget); + QTimer::singleShot(100, this, &GalleryWidget::loadAlbumList); + }); + errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); + m_errorWidget = new QWidget(); + m_errorWidget->setLayout(errorLayout); + + m_stackedWidget->addWidget(m_errorWidget); + m_stackedWidget->setCurrentWidget(m_loadingWidget); setControlsEnabled(false); // Disable controls until album is selected +} +/*Load is called when the tab is active*/ +void GalleryWidget::load() +{ + if (m_loaded) + return; + + m_loaded = true; + loadAlbumList(); } @@ -115,7 +128,8 @@ void GalleryWidget::setupControlsLayout() QLabel *filterLabel = new QLabel("Filter:"); filterLabel->setStyleSheet("font-weight: bold;"); m_filterComboBox = new QComboBox(); - m_filterComboBox->addItem("All Media", static_cast(PhotoModel::All)); + m_filterComboBox->addItem("All Media", + static_cast(PhotoModel::ImagesOnly)); m_filterComboBox->addItem("Images Only", static_cast(PhotoModel::ImagesOnly)); m_filterComboBox->addItem("Videos Only", @@ -208,39 +222,39 @@ void GalleryWidget::onExportSelected() return; } - if (ExportManager::sharedInstance()->isExporting()) { - QMessageBox::information(this, "Export in Progress", - "An export is already in progress."); - return; - } + // if (ExportManager::sharedInstance()->isExporting()) { + // QMessageBox::information(this, "Export in Progress", + // "An export is already in progress."); + // return; + // } - QModelIndexList selectedIndexes = - m_listView->selectionModel()->selectedIndexes(); - QStringList filePaths = m_model->getSelectedFilePaths(selectedIndexes); + // QModelIndexList selectedIndexes = + // m_listView->selectionModel()->selectedIndexes(); + // QStringList filePaths = m_model->getSelectedFilePaths(selectedIndexes); - if (filePaths.isEmpty()) { - QMessageBox::information(this, "No Items", - "No valid items selected for export."); - return; - } + // if (filePaths.isEmpty()) { + // QMessageBox::information(this, "No Items", + // "No valid items selected for export."); + // return; + // } - QString exportDir = selectExportDirectory(); - if (exportDir.isEmpty()) { - return; - } + // QString exportDir = selectExportDirectory(); + // if (exportDir.isEmpty()) { + // return; + // } - // Convert QStringList to QList - QList exportItems; - for (const QString &filePath : filePaths) { - QString fileName = filePath.split('/').last(); - exportItems.append(ExportItem(filePath, fileName)); - } + // // Convert QStringList to QList + // QList exportItems; + // for (const QString &filePath : filePaths) { + // QString fileName = filePath.split('/').last(); + // exportItems.append(ExportItem(filePath, fileName)); + // } - qDebug() << "Starting export of selected files:" << exportItems.size() - << "items to" << exportDir; + // qDebug() << "Starting export of selected files:" << exportItems.size() + // << "items to" << exportDir; - ExportManager::sharedInstance()->startExport(m_device, exportItems, - exportDir); + // ExportManager::sharedInstance()->startExport(m_device, exportItems, + // exportDir); } void GalleryWidget::onExportAll() @@ -248,47 +262,49 @@ void GalleryWidget::onExportAll() if (!m_model) return; - if (ExportManager::sharedInstance()->isExporting()) { - QMessageBox::information(this, "Export in Progress", - "An export is already in progress."); - return; - } + // if (ExportManager::sharedInstance()->isExporting()) { + // QMessageBox::information(this, "Export in Progress", + // "An export is already in progress."); + // return; + // } - QStringList filePaths = m_model->getFilteredFilePaths(); + // QStringList filePaths = m_model->getFilteredFilePaths(); - if (filePaths.isEmpty()) { - QMessageBox::information(this, "No Items", "No items to export."); - return; - } + // if (filePaths.isEmpty()) { + // QMessageBox::information(this, "No Items", "No items to export."); + // return; + // } - QString message = - QString("Export all %1 items currently shown?").arg(filePaths.size()); - int reply = QMessageBox::question(this, "Export All", message, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No); + // QString message = + // QString("Export all %1 items currently + // shown?").arg(filePaths.size()); + // int reply = QMessageBox::question(this, "Export All", message, + // QMessageBox::Yes | QMessageBox::No, + // QMessageBox::No); - if (reply != QMessageBox::Yes) { - return; - } + // if (reply != QMessageBox::Yes) { + // return; + // } - QString exportDir = selectExportDirectory(); - if (exportDir.isEmpty()) { - return; - } + // QString exportDir = selectExportDirectory(); + // if (exportDir.isEmpty()) { + // return; + // } - // Convert QStringList to QList - QList exportItems; - for (const QString &filePath : filePaths) { - QString fileName = filePath.split('/').last(); - exportItems.append(ExportItem(filePath, fileName)); - } + // // Convert QStringList to QList + // QList exportItems; + // for (const QString &filePath : filePaths) { + // QString fileName = filePath.split('/').last(); + // exportItems.append(ExportItem(filePath, fileName)); + // } - qDebug() << "Starting export of all filtered files:" << exportItems.size() - << "items to" << exportDir; + // qDebug() << "Starting export of all filtered files:" << + // exportItems.size() + // << "items to" << exportDir; - // Start export and the manager will show its own dialog - ExportManager::sharedInstance()->startExport(m_device, exportItems, - exportDir); + // // Start export and the manager will show its own dialog + // ExportManager::sharedInstance()->startExport(m_device, exportItems, + // exportDir); } QString GalleryWidget::selectExportDirectory() @@ -392,11 +408,11 @@ void GalleryWidget::setupPhotoGalleryView() if (filePath.isEmpty()) return; - qDebug() << "Opening preview for" << filePath; - auto *previewDialog = new MediaPreviewDialog( - m_device, m_device->afcClient, filePath, this); - previewDialog->setAttribute(Qt::WA_DeleteOnClose); - previewDialog->show(); + // qDebug() << "Opening preview for" << filePath; + // auto *previewDialog = new MediaPreviewDialog( + // m_device, m_device->afcClient, filePath, this); + // previewDialog->setAttribute(Qt::WA_DeleteOnClose); + // previewDialog->show(); }); connect(m_listView, &QListView::customContextMenuRequested, this, @@ -405,15 +421,20 @@ void GalleryWidget::setupPhotoGalleryView() void GalleryWidget::loadAlbumList() { - AFCFileTree dcimTree = ServiceManager::safeGetFileTree(m_device, "/DCIM"); + qDebug() << "Before reading DCIM directory"; + AFCFileTree dcimTree = + ServiceManager::safeGetFileTree(m_device, "/DCIM", true); if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; + m_stackedWidget->setCurrentWidget(m_errorWidget); QMessageBox::warning(this, "Error", "Could not access DCIM directory on device."); return; } + m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + qDebug() << "DCIM directory read successfully, found" << dcimTree.entries.size() << "entries"; @@ -505,8 +526,8 @@ void GalleryWidget::setControlsEnabled(bool enabled) QIcon GalleryWidget::loadAlbumThumbnail(const QString &albumPath) { // Get album directory contents - AFCFileTree albumTree = - ServiceManager::safeGetFileTree(m_device, albumPath.toStdString()); + AFCFileTree albumTree = ServiceManager::safeGetFileTree( + m_device, albumPath.toStdString(), false); if (!albumTree.success) { qDebug() << "Failed to read album directory:" << albumPath; @@ -606,21 +627,21 @@ void GalleryWidget::onPhotoContextMenu(const QPoint &pos) exportAction->setEnabled(m_listView->selectionModel()->hasSelection()); - connect(previewAction, &QAction::triggered, this, [this, index]() { - // Re-use the double-click logic - if (!index.isValid()) - return; + // connect(previewAction, &QAction::triggered, this, [this, index]() { + // // Re-use the double-click logic + // if (!index.isValid()) + // return; - QString filePath = m_model->data(index, Qt::UserRole).toString(); - if (filePath.isEmpty()) - return; + // QString filePath = m_model->data(index, Qt::UserRole).toString(); + // if (filePath.isEmpty()) + // return; - qDebug() << "Opening preview for" << filePath; - auto *previewDialog = new MediaPreviewDialog( - m_device, m_device->afcClient, filePath, this); - previewDialog->setAttribute(Qt::WA_DeleteOnClose); - previewDialog->show(); - }); + // qDebug() << "Opening preview for" << filePath; + // auto *previewDialog = new MediaPreviewDialog( + // m_device, m_device->afcClient, filePath, this); + // previewDialog->setAttribute(Qt::WA_DeleteOnClose); + // previewDialog->show(); + // }); connect(exportAction, &QAction::triggered, this, &GalleryWidget::onExportSelected); diff --git a/src/gallerywidget.h b/src/gallerywidget.h index 4dae062..d8073e0 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -22,6 +22,7 @@ #include "iDescriptor.h" #include "photomodel.h" +#include "zloadingwidget.h" #include QT_BEGIN_NAMESPACE @@ -57,7 +58,6 @@ private slots: void onBackToAlbums(); private: - void setupUI(); void setupControlsLayout(); void setupAlbumSelectionView(); void setupPhotoGalleryView(); @@ -77,6 +77,9 @@ private: QVBoxLayout *m_mainLayout; QHBoxLayout *m_controlsLayout; QStackedWidget *m_stackedWidget; + ZLoadingWidget *m_loadingWidget; + QWidget *m_errorWidget; + QPushButton *m_retryButton; // Album selection view QWidget *m_albumSelectionWidget; diff --git a/src/heartbeat.h b/src/heartbeat.h new file mode 100644 index 0000000..7026d4b --- /dev/null +++ b/src/heartbeat.h @@ -0,0 +1,72 @@ +#ifndef HEARTBEATTHREAD_H +#define HEARTBEATTHREAD_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace IdeviceFFI; + +class HeartBeatThread : public QThread +{ + Q_OBJECT +public: + HeartBeatThread(HeartbeatClientHandle *heartbeat, QObject *parent = nullptr) + : QThread(parent), m_hb(Heartbeat::adopt(heartbeat)) + { + } + + void run() override + { + qDebug() << "Heartbeat thread started"; + try { + // Start with initial interval (15 seconds as per the tool example) + u_int64_t interval = 15; + + while (!isInterruptionRequested()) { + // 1. Wait for Marco with current interval + Result result = m_hb.get_marco(interval); + if (result.is_err()) { + qDebug() + << "Failed to get marco:" + << QString::fromStdString(result.unwrap_err().message); + break; + } + + // 2. Get the new interval from device + interval = result.unwrap(); + qDebug() << "Received marco, new interval:" << interval; + + // 3. Send Polo response + Result polo_result = m_hb.send_polo(); + if (polo_result.is_err()) { + qDebug() << "Failed to send polo:" + << QString::fromStdString( + polo_result.unwrap_err().message); + break; + } + qDebug() << "Sent polo successfully"; + + interval += 5; + m_initialCompleted = true; + } + } catch (const std::exception &e) { + qDebug() << "Heartbeat error:" << e.what(); + } + } + + bool initialCompleted() const { return m_initialCompleted; } + +private: + Heartbeat m_hb; + bool m_initialCompleted = false; +}; +#endif // HEARTBEATTHREAD_H \ No newline at end of file diff --git a/src/iDescriptor.h b/src/iDescriptor.h index cd89726..dd52673 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -25,6 +25,7 @@ #include #include +// #include "idevice.h" #include #include #include @@ -186,7 +187,7 @@ struct DeviceInfo { struct iDescriptorDevice { std::string udid; DeviceMonitorThread::IdeviceConnectionType conn_type; - IdeviceProviderHandle *device; + IdeviceProviderHandle *provider; DeviceInfo deviceInfo; AfcClientHandle *afcClient; AfcClientHandle *afc2Client; @@ -198,7 +199,7 @@ struct iDescriptorDevice { struct iDescriptorInitDeviceResult { bool success = false; IdeviceFfiError error; - IdeviceProviderHandle *device; + IdeviceProviderHandle *provider; DeviceInfo deviceInfo; AfcClientHandle *afcClient; AfcClientHandle *afc2Client; @@ -324,8 +325,8 @@ struct AFCFileTree { std::string currentPath; }; -// AFCFileTree get_file_tree(afc_client_t afcClient, -// const std::string &path = "/"); +AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path = "/"); bool detect_jailbroken(AfcClientHandle *afc); @@ -456,8 +457,8 @@ struct NetworkDevice { QPixmap load_heic(const QByteArray &data); -// QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, -// const char *path); +QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, + const char *path); bool isDarkMode(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ec28c5e..19291ff 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -450,16 +450,16 @@ MainWindow::MainWindow(QWidget *parent) connect(NetworkDeviceManager::sharedInstance(), &NetworkDeviceManager::deviceAdded, this, [this](const NetworkDevice &device) { - // const iDescriptorDevice *idevice = - // AppContext::sharedInstance()->getDeviceByMacAddress( - // device.macAddress); - // if (idevice) { - // qDebug() << "Network device matched to connected device:" - // << QString::fromStdString( - // idevice->deviceInfo.deviceName) - // << "MAC:" << device.macAddress; - // // You can now use 'idevice' as needed - // } + const iDescriptorDevice *idevice = + AppContext::sharedInstance()->getDeviceByMacAddress( + device.macAddress); + if (idevice) { + qDebug() << "Network device matched to connected device:" + << QString::fromStdString( + idevice->deviceInfo.deviceName) + << "MAC:" << device.macAddress; + // You can now use 'idevice' as needed + } // FIXME: both macAddress and udid can be used to get pairing // file diff --git a/src/mediapreviewdialog.h b/src/mediapreviewdialog.h index 5aca232..5cf897d 100644 --- a/src/mediapreviewdialog.h +++ b/src/mediapreviewdialog.h @@ -36,7 +36,6 @@ #include #include #include -#include /** * @brief A dialog for previewing images and videos from iOS devices @@ -149,4 +148,4 @@ private: afc_client_t m_afcClient; }; -#endif // MEDIAPREVIEWDIALOG_H \ No newline at end of file +#endif // MEDIAPREVIEWDIALOG_H diff --git a/src/mediastreamer.h b/src/mediastreamer.h index be59d71..0c8eba6 100644 --- a/src/mediastreamer.h +++ b/src/mediastreamer.h @@ -25,7 +25,6 @@ #include #include #include -#include QT_BEGIN_NAMESPACE class QTcpSocket; @@ -117,4 +116,4 @@ private: afc_client_t m_afcClient; }; -#endif // MEDIASTREAMER_H \ No newline at end of file +#endif // MEDIASTREAMER_H diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h index 587f710..0e53c90 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -26,7 +26,6 @@ #include #include #include -#include /** * @brief Singleton manager for MediaStreamer instances @@ -81,4 +80,4 @@ private: QMutex m_streamersMutex; }; -#endif // MEDIASTREAMERMANAGER_H \ No newline at end of file +#endif // MEDIASTREAMERMANAGER_H diff --git a/src/photomodel.cpp b/src/photomodel.cpp index dfb12c7..eb5e688 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -19,7 +19,7 @@ #include "photomodel.h" #include "iDescriptor.h" -#include "mediastreamermanager.h" +// #include "mediastreamermanager.h" #include "servicemanager.h" #include #include @@ -84,30 +84,32 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, { QPixmap thumbnail; - uint64_t fileHandle = 0; + AfcFileHandle *fileHandle = nullptr; - afc_error_t openResult = ServiceManager::safeAfcFileOpen( - device, filePath.toUtf8().constData(), AFC_FOPEN_RDONLY, &fileHandle); + IdeviceFfiError *err = + ServiceManager::safeAfcFileOpen(device, filePath.toUtf8().constData(), + AfcFopenMode::AfcRdOnly, &fileHandle); - if (openResult != AFC_E_SUCCESS || fileHandle == 0) { + if (err || fileHandle == nullptr) { qWarning() << "Failed to open video file for thumbnail:" << filePath; + if (err) { + // idevice_error_free(err); + } return {}; } // Get file size - char **fileInfo = nullptr; - afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( - device, filePath.toUtf8().constData(), &fileInfo); + AfcFileInfo *info = nullptr; + err = ServiceManager::safeAfcGetFileInfo( + device, filePath.toUtf8().constData(), info); uint64_t fileSize = 0; - if (infoResult == AFC_E_SUCCESS && fileInfo) { - for (int i = 0; fileInfo[i]; i += 2) { - if (strcmp(fileInfo[i], "st_size") == 0) { - fileSize = strtoull(fileInfo[i + 1], nullptr, 10); - break; - } + if (!err && info) { + fileSize = info->size; + // afc_file_info_free(info); + if (err) { + // idevice_error_free(err); } - afc_dictionary_free(fileInfo); } if (fileSize == 0) { @@ -127,7 +129,7 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, // Context for streaming read from device struct StreamContext { iDescriptorDevice *device; - uint64_t fileHandle; + AfcFileHandle *fileHandle; uint64_t fileSize; uint64_t currentPos; }; @@ -137,27 +139,31 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, // Custom read function that reads from device on-demand auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int { - StreamContext *ctx = static_cast(opaque); + // StreamContext *ctx = static_cast(opaque); - if (ctx->currentPos >= ctx->fileSize) { - return AVERROR_EOF; - } + // if (ctx->currentPos >= ctx->fileSize) { + // return AVERROR_EOF; + // } - uint32_t toRead = - std::min(static_cast(bufSize), - static_cast(ctx->fileSize - ctx->currentPos)); - uint32_t bytesRead = 0; + // uint32_t toRead = + // std::min(static_cast(bufSize), + // static_cast(ctx->fileSize - ctx->currentPos)); + // uint32_t bytesRead = 0; - afc_error_t result = ServiceManager::safeAfcFileRead( - ctx->device, ctx->fileHandle, reinterpret_cast(buf), toRead, - &bytesRead); + // IdeviceFfiError *err = ServiceManager::safeAfcFileRead( + // ctx->device, ctx->fileHandle, reinterpret_cast(buf), + // toRead, &bytesRead); - if (result != AFC_E_SUCCESS || bytesRead == 0) { - return AVERROR(EIO); - } + // if (err || bytesRead == 0) { + // if (err) { + // idevice_error_free(err); + // } + // return AVERROR(EIO); + // } - ctx->currentPos += bytesRead; - return static_cast(bytesRead); + // ctx->currentPos += bytesRead; + // return static_cast(bytesRead); + return 0; }; // Custom seek function using AFC seek @@ -188,11 +194,12 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, return -1; } - // Use AFC seek - afc_error_t result = ServiceManager::safeAfcFileSeek( + IdeviceFfiError *err = ServiceManager::safeAfcFileSeek( ctx->device, ctx->fileHandle, newPos, seekWhence); - if (result != AFC_E_SUCCESS) { + if (err) { + qDebug() << "AFC seek error:" << err->message; + // idevice_error_free(err); return -1; } @@ -615,16 +622,19 @@ void PhotoModel::populatePhotoPaths() QByteArray albumPathBytes = m_albumPath.toUtf8(); const char *albumPathCStr = albumPathBytes.constData(); - char **albumInfo = nullptr; - afc_error_t infoResult = + AfcFileInfo albumInfo = {}; + + IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo(m_device, albumPathCStr, &albumInfo); - if (infoResult != AFC_E_SUCCESS) { + if (err) { qDebug() << "Album path does not exist or cannot be accessed:" - << m_albumPath << "Error:" << infoResult; + << m_albumPath << "Error:" << err->message; + idevice_error_free(err); return; } - if (albumInfo) { - afc_dictionary_free(albumInfo); + // FIXME: should we continue if albumInfo is null? + if (albumInfo.size) { + afc_file_info_free(&albumInfo); } // Fix: Store the QByteArray to keep the C string valid @@ -633,13 +643,12 @@ void PhotoModel::populatePhotoPaths() qDebug() << "Photo directory:" << m_albumPath; qDebug() << "Photo directory C string:" << photoDir; - // Use ServiceManager for thread-safe AFC operations char **files = nullptr; - afc_error_t readResult = - ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files); - if (readResult != AFC_E_SUCCESS) { + err = ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files); + if (err) { qDebug() << "Failed to read photo directory:" << photoDir - << "Error:" << readResult; + << "Error:" << err->message; + idevice_error_free(err); return; } @@ -663,7 +672,8 @@ void PhotoModel::populatePhotoPaths() m_allPhotos.append(info); } } - afc_dictionary_free(files); + // free(files); + // afc_dictionary_free(files); } // Apply initial filtering and sorting, which will also reset the model @@ -792,74 +802,22 @@ QStringList PhotoModel::getFilteredFilePaths() const // Helper methods QDateTime PhotoModel::extractDateTimeFromFile(const QString &filePath) const { - plist_t info = nullptr; - afc_error_t afc_err = ServiceManager::safeAfcGetFileInfoPlist( - m_device, filePath.toUtf8().constData(), &info); + AfcFileInfo *info = nullptr; + IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo( + m_device, filePath.toUtf8().constData(), info); + if (!err && info) { + uint64_t birthtime_ns = info->st_birthtime; + // Convert nanoseconds since epoch to QDateTime + // The timestamp appears to be in nanoseconds since Unix epoch + uint64_t seconds = birthtime_ns / 1000000000ULL; + QDateTime dateTime = QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); - if (afc_err == AFC_E_SUCCESS && info) { - plist_t birthtime_node = plist_dict_get_item(info, "st_birthtime"); - if (birthtime_node && - plist_get_node_type(birthtime_node) == PLIST_UINT) { - uint64_t birthtime_ns = 0; - plist_get_uint_val(birthtime_node, &birthtime_ns); - - // Convert nanoseconds since epoch to QDateTime - // The timestamp appears to be in nanoseconds since Unix epoch - uint64_t seconds = birthtime_ns / 1000000000ULL; - QDateTime dateTime = - QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); - - plist_free(info); - if (dateTime.isValid()) { - return dateTime; - } - } - - // Fallback to st_mtime (modification time) if birthtime not available - plist_t mtime_node = plist_dict_get_item(info, "st_mtime"); - if (mtime_node && plist_get_node_type(mtime_node) == PLIST_UINT) { - uint64_t mtime_ns = 0; - plist_get_uint_val(mtime_node, &mtime_ns); - - // Convert nanoseconds since epoch to QDateTime - uint64_t seconds = mtime_ns / 1000000000ULL; - QDateTime dateTime = - QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); - - plist_free(info); - if (dateTime.isValid()) { - return dateTime; - } - } - - plist_free(info); - } - - // Final fallback: try to extract date from filename pattern like - // IMG_20231025_143052.jpg - QFileInfo fileInfo(filePath); - QString baseName = fileInfo.baseName(); - - QRegularExpression dateRegex( - R"((\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2}))"); - QRegularExpressionMatch match = dateRegex.match(baseName); - - if (match.hasMatch()) { - int year = match.captured(1).toInt(); - int month = match.captured(2).toInt(); - int day = match.captured(3).toInt(); - int hour = match.captured(4).toInt(); - int minute = match.captured(5).toInt(); - int second = match.captured(6).toInt(); - - QDateTime dateTime(QDate(year, month, day), - QTime(hour, minute, second)); + // afc_file_info_free(info); if (dateTime.isValid()) { return dateTime; } } - // Ultimate fallback: return current time return QDateTime::currentDateTime(); } diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index 1fe4772..d006064 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -18,146 +18,164 @@ */ #include "servicemanager.h" +#include "iDescriptor.h" -afc_error_t -ServiceManager::safeAfcReadDirectory(iDescriptorDevice *device, +IdeviceFfiError * +ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device, const char *path, char ***dirs, - std::optional altAfc) + std::optional altAfc) { - return executeAfcOperation( + size_t count = 0; + return executeAfcClientOperation( device, - [path, dirs](afc_client_t client) { - return afc_read_directory(client, path, dirs); + [path, dirs, &count, device](AfcClientHandle *client) { + IdeviceFfiError *err = nullptr; + err = afc_list_directory(client, path, dirs, &count); + /*TODO -1 is unknown error*/ + if (err && (err->code == -1 || err->code == -11)) { + qDebug() << "Reconnecting AFC client for path:" << path; + // afc_client_free(client); + // err = afc_client_connect(device->provider, &client); + err = afc_client_connect( + device->provider, + &const_cast(device)->afcClient); + + err = afc_list_directory( + const_cast(device)->afcClient, path, + dirs, &count); + } + return err; }, altAfc); } -afc_error_t -ServiceManager::safeAfcGetFileInfo(iDescriptorDevice *device, const char *path, - char ***info, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcGetFileInfo(const iDescriptorDevice *device, + const char *path, AfcFileInfo *info, + std::optional altAfc) { - return executeAfcOperation( + return executeAfcClientOperation( device, - [path, info](afc_client_t client) { - return afc_get_file_info(client, path, info); + [path, info, device](AfcClientHandle *client) { + IdeviceFfiError *err = nullptr; + err = afc_get_file_info(client, path, info); + /*TODO -1 is unknown error*/ + if (err && err->code == -1) { + // afc_client_free(client); + // err = afc_client_connect(device->provider, &client); + err = afc_client_connect( + device->provider, + &const_cast(device)->afcClient); + + err = afc_get_file_info( + const_cast(device)->afcClient, path, + info); + } + return err; }, altAfc); } -afc_error_t -ServiceManager::safeAfcGetFileInfoPlist(iDescriptorDevice *device, - const char *path, plist_t *info, - std::optional altAfc) +IdeviceFfiError *ServiceManager::safeAfcFileOpen( + const iDescriptorDevice *device, const char *path, AfcFopenMode mode, + AfcFileHandle **handle, std::optional altAfc) { - return executeAfcOperation( + return executeAfcClientOperation( device, - [path, info](afc_client_t client) { - return afc_get_file_info_plist(client, path, info); + [path, mode, handle, device](AfcClientHandle *client) { + IdeviceFfiError *err = nullptr; + err = afc_file_open(client, path, mode, handle); + /*TODO -1 is unknown error*/ + if (err && err->code == -1) { + // afc_client_free(client); + err = afc_client_connect( + device->provider, + &const_cast(device)->afcClient); + err = afc_file_open( + const_cast(device)->afcClient, path, + mode, handle); + } + return err; }, altAfc); } -afc_error_t ServiceManager::safeAfcFileOpen(iDescriptorDevice *device, - const char *path, - afc_file_mode_t mode, - uint64_t *handle, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileRead(const iDescriptorDevice *device, + AfcFileHandle *handle, uint8_t **data, + uintptr_t length, size_t *bytes_read) { return executeAfcOperation( device, - [path, mode, handle](afc_client_t client) { - return afc_file_open(client, path, mode, handle); + [data, length, bytes_read](AfcFileHandle *handle) { + return afc_file_read(handle, data, length, bytes_read); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileRead(iDescriptorDevice *device, - uint64_t handle, char *data, - uint32_t length, - uint32_t *bytes_read, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileWrite(const iDescriptorDevice *device, + AfcFileHandle *handle, const uint8_t *data, + uint32_t length) { return executeAfcOperation( device, - [handle, data, length, bytes_read](afc_client_t client) { - return afc_file_read(client, handle, data, length, bytes_read); + [data, length](AfcFileHandle *handle) { + return afc_file_write(handle, data, length); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileWrite(iDescriptorDevice *device, - uint64_t handle, const char *data, - uint32_t length, - uint32_t *bytes_written, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileClose(const iDescriptorDevice *device, + AfcFileHandle *handle) { return executeAfcOperation( - device, - [handle, data, length, bytes_written](afc_client_t client) { - return afc_file_write(client, handle, data, length, bytes_written); - }, - altAfc); + device, [](AfcFileHandle *handle) { return afc_file_close(handle); }, + handle); } -afc_error_t ServiceManager::safeAfcFileClose(iDescriptorDevice *device, - uint64_t handle, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileSeek(const iDescriptorDevice *device, + AfcFileHandle *handle, int64_t offset, + int whence) { + off_t *newPos = nullptr; return executeAfcOperation( device, - [handle](afc_client_t client) { - return afc_file_close(client, handle); + [offset, whence, newPos](AfcFileHandle *handle) { + return afc_file_seek(handle, offset, whence, newPos); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileSeek(iDescriptorDevice *device, - uint64_t handle, int64_t offset, - int whence, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileTell(const iDescriptorDevice *device, + AfcFileHandle *handle, off_t *position) { return executeAfcOperation( device, - [handle, offset, whence](afc_client_t client) { - return afc_file_seek(client, handle, offset, whence); + [position](AfcFileHandle *handle) { + return afc_file_tell(handle, position); }, - altAfc); -} - -afc_error_t ServiceManager::safeAfcFileTell(iDescriptorDevice *device, - uint64_t handle, uint64_t *position, - std::optional altAfc) -{ - return executeAfcOperation( - device, - [handle, position](afc_client_t client) { - return afc_file_tell(client, handle, position); - }, - altAfc); + handle); } QByteArray -ServiceManager::safeReadAfcFileToByteArray(iDescriptorDevice *device, - const char *path, - std::optional altAfc) +ServiceManager::safeReadAfcFileToByteArray(const iDescriptorDevice *device, + const char *path) { - return executeOperation( - device, - [path](afc_client_t client) -> QByteArray { - return read_afc_file_to_byte_array(client, path); - }, - altAfc); + return executeOperation(device, [path, device]() -> QByteArray { + return read_afc_file_to_byte_array(device, path); + }); } -AFCFileTree ServiceManager::safeGetFileTree(iDescriptorDevice *device, +AFCFileTree ServiceManager::safeGetFileTree(const iDescriptorDevice *device, const std::string &path, - std::optional altAfc) + bool checkDir) { return executeOperation( - device, - [path](afc_client_t client) -> AFCFileTree { - return get_file_tree(client, path.c_str()); - }, - altAfc); + device, [path, device, checkDir]() -> AFCFileTree { + return get_file_tree(device, checkDir, path); + }); } \ No newline at end of file diff --git a/src/servicemanager.h b/src/servicemanager.h index 446489a..27227ad 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -23,7 +23,6 @@ #include "iDescriptor.h" #include #include -#include #include #include @@ -39,9 +38,10 @@ class ServiceManager { public: template - static T executeOperation(iDescriptorDevice *device, - std::function operation, - std::optional altAfc = std::nullopt) + static T + executeOperation(const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { return T{}; // Return default-constructed value for the type @@ -61,16 +61,19 @@ public: } // Determine which client to use - afc_client_t client = altAfc ? *altAfc : device->afcClient; + AfcClientHandle *client = altAfc ? *altAfc : device->afcClient; + return operation(client); } template - static T executeOperation(iDescriptorDevice *device, - std::function operation, - std::optional altAfc = std::nullopt) + static T + executeOperation(const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { + qDebug() << "[executeOperation] Device or mutex is null"; return T{}; // Return default-constructed value for the type } @@ -78,21 +81,24 @@ public: // Double-check device is still valid after acquiring lock if (!device->afcClient) { + qDebug() << "[executeOperation] AFC client is null"; return T{}; } if (altAfc && !*altAfc) { // altAfc was explicitly provided but is null, which is an // invalid state. + qDebug() << "[executeOperation] altAfc is null"; return T{}; } return operation(); } template - static T executeOperation(iDescriptorDevice *device, - std::function operation, T failureValue, - std::optional altAfc = std::nullopt) + static T + executeOperation(const iDescriptorDevice *device, + std::function operation, T failureValue, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { return failureValue; @@ -115,8 +121,9 @@ public: } static void - executeOperation(iDescriptorDevice *device, std::function operation, - std::optional altAfc = std::nullopt) + executeOperation(const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) { if (!device || !device->mutex) { return; @@ -138,84 +145,109 @@ public: operation(); } - static afc_error_t - executeAfcOperation(iDescriptorDevice *device, - std::function operation, - std::optional altAfc = std::nullopt) + static IdeviceFfiError *executeAfcOperation( + const iDescriptorDevice *device, + std::function operation, + AfcFileHandle *handle) { try { if (!device || !device->mutex) { - return AFC_E_UNKNOWN_ERROR; + // FIXME: we have to free error + return new IdeviceFfiError{1, "DEVICE_OR_MUTEX_IS_NULL"}; } std::lock_guard lock(*device->mutex); // Double-check device is still valid after acquiring lock if (!device->afcClient) { - return AFC_E_UNKNOWN_ERROR; + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; + } + + if (!handle) { + return new IdeviceFfiError{1, "FILE HANDLE IS NULL"}; + } + + return operation(handle); + } catch (const std::exception &e) { + qDebug() << "Exception in executeAfcOperation:" << e.what(); + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; + } + } + + static IdeviceFfiError *executeAfcClientOperation( + const iDescriptorDevice *device, + std::function operation, + std::optional altAfc = std::nullopt) + { + try { + if (!device || !device->mutex) { + // FIXME: we have to free error + qDebug() + << "[executeAfcClientOperation] Device or mutex is null"; + return new IdeviceFfiError{1, "DEVICE_OR_MUTEX_IS_NULL"}; + } + + std::lock_guard lock(*device->mutex); + + // Double-check device is still valid after acquiring lock + if (!device->afcClient) { + qDebug() << "[executeAfcClientOperation] AFC client is null"; + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; } if (altAfc && !*altAfc) { // altAfc was explicitly provided but is null, which is an // invalid state. - return AFC_E_INVALID_ARG; + qDebug() << "[executeAfcClientOperation] altAfc is null"; + return new IdeviceFfiError{1, "ALT_AFC_CLIENT_IS_NULL"}; } // Determine which client to use - afc_client_t client = altAfc ? *altAfc : device->afcClient; + AfcClientHandle *client = altAfc ? *altAfc : device->afcClient; return operation(client); } catch (const std::exception &e) { qDebug() << "Exception in executeAfcOperation:" << e.what(); - return AFC_E_UNKNOWN_ERROR; + return new IdeviceFfiError{1, "AFC_CLIENT_IS_NULL"}; } } // Specific AFC operation wrappers - static afc_error_t - safeAfcReadDirectory(iDescriptorDevice *device, const char *path, - char ***dirs, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcGetFileInfo(iDescriptorDevice *device, const char *path, - char ***info, - std::optional altAfc = std::nullopt); + static IdeviceFfiError *safeAfcReadDirectory( + const iDescriptorDevice *device, const char *path, char ***dirs, + std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcGetFileInfoPlist(iDescriptorDevice *device, const char *path, - plist_t *info, - std::optional altAfc = std::nullopt); + static IdeviceFfiError * + safeAfcGetFileInfo(const iDescriptorDevice *device, const char *path, + AfcFileInfo *info, + std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileOpen(iDescriptorDevice *device, const char *path, - afc_file_mode_t mode, uint64_t *handle, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileRead(iDescriptorDevice *device, uint64_t handle, char *data, - uint32_t length, uint32_t *bytes_read, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileWrite(iDescriptorDevice *device, uint64_t handle, - const char *data, uint32_t length, uint32_t *bytes_written, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileClose(iDescriptorDevice *device, uint64_t handle, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileSeek(iDescriptorDevice *device, uint64_t handle, int64_t offset, - int whence, - std::optional altAfc = std::nullopt); - static afc_error_t - safeAfcFileTell(iDescriptorDevice *device, uint64_t handle, - uint64_t *position, - std::optional altAfc = std::nullopt); + static IdeviceFfiError * + safeAfcFileOpen(const iDescriptorDevice *device, const char *path, + AfcFopenMode mode, AfcFileHandle **handle, + std::optional altAfc = std::nullopt); + static IdeviceFfiError *safeAfcFileRead(const iDescriptorDevice *device, + AfcFileHandle *handle, + uint8_t **data, uintptr_t length, + size_t *bytes_read); + static IdeviceFfiError *safeAfcFileWrite(const iDescriptorDevice *device, + AfcFileHandle *handle, + const uint8_t *data, + uint32_t length); + static IdeviceFfiError *safeAfcFileClose(const iDescriptorDevice *device, + AfcFileHandle *handle); + static IdeviceFfiError *safeAfcFileSeek(const iDescriptorDevice *device, + AfcFileHandle *handle, + int64_t offset, int whence); + static IdeviceFfiError *safeAfcFileTell(const iDescriptorDevice *device, + AfcFileHandle *handle, + off_t *position); // Utility functions - static QByteArray safeReadAfcFileToByteArray( - iDescriptorDevice *device, const char *path, - std::optional altAfc = std::nullopt); - static AFCFileTree - safeGetFileTree(iDescriptorDevice *device, const std::string &path = "/", - std::optional altAfc = std::nullopt); + static QByteArray + safeReadAfcFileToByteArray(const iDescriptorDevice *device, + const char *path); + static AFCFileTree safeGetFileTree(const iDescriptorDevice *device, + const std::string &path, bool checkDir); }; #endif // SERVICEMANAGER_H diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp new file mode 100644 index 0000000..65debd0 --- /dev/null +++ b/src/zloadingwidget.cpp @@ -0,0 +1,37 @@ +#include "zloadingwidget.h" + +#include "qprocessindicator.h" +#include +#include + +ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) : QWidget{parent} +{ + m_loadingIndicator = new QProcessIndicator(); + m_loadingIndicator->setType(QProcessIndicator::line_rotate); + m_loadingIndicator->setFixedSize(64, 32); + if (start) { + m_loadingIndicator->start(); + } + + QHBoxLayout *loadingLayout = new QHBoxLayout(); + loadingLayout->setSpacing(1); + + loadingLayout->addWidget(m_loadingIndicator); + setLayout(loadingLayout); +} + +void ZLoadingWidget::stop() +{ + if (m_loadingIndicator) { + m_loadingIndicator->stop(); + } +} + +ZLoadingWidget::~ZLoadingWidget() +{ + if (m_loadingIndicator) { + m_loadingIndicator->stop(); + delete m_loadingIndicator; + m_loadingIndicator = nullptr; + } +} \ No newline at end of file diff --git a/src/zloadingwidget.h b/src/zloadingwidget.h new file mode 100644 index 0000000..1c8050e --- /dev/null +++ b/src/zloadingwidget.h @@ -0,0 +1,19 @@ +#ifndef ZLOADINGWIDGET_H +#define ZLOADINGWIDGET_H + +#include + +class ZLoadingWidget : public QWidget +{ + Q_OBJECT +public: + explicit ZLoadingWidget(bool start = true, QWidget *parent = nullptr); + ~ZLoadingWidget(); + void stop(); + +private: + class QProcessIndicator *m_loadingIndicator = nullptr; +signals: +}; + +#endif // ZLOADINGWIDGET_H From 868efa4525d95cb9d45dfc253c89d21611b6a5c2 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Tue, 6 Jan 2026 16:42:00 +0000 Subject: [PATCH 03/15] WIP: migrate to idevice-rs (90% done) --- .gitmodules | 3 + CMakeLists.txt | 192 +++--- lib/ztoast | 1 + src/afcexplorerwidget.cpp | 131 ++-- src/afcexplorerwidget.h | 6 +- src/appcontext.cpp | 340 +++++----- src/appdownloadbasedialog.cpp | 15 +- src/appdownloadbasedialog.h | 4 +- src/appinstalldialog.cpp | 145 ++-- src/appinstalldialog.h | 8 +- src/cableinfowidget.cpp | 10 +- src/cableinfowidget.h | 1 - src/core/helpers/mounted_image_info_free.cpp | 11 + src/core/services/afc2_client_new.cpp | 54 -- src/core/services/detect-afc2.cpp | 2 +- .../detect_has_been_jailbroken_before.cpp | 37 +- src/core/services/detect_jailbroken.cpp | 13 +- src/core/services/get_battery_info.cpp | 25 +- src/core/services/get_cable_info.cpp | 55 +- src/core/services/get_file_tree.cpp | 21 +- src/core/services/get_mounted_image.cpp | 109 +-- src/core/services/init_device.cpp | 132 ++-- src/core/services/install_ipa.cpp | 583 ++-------------- src/core/services/mount_dev_image.cpp | 615 ++--------------- src/core/services/query_mobilegestalt.cpp | 70 -- src/core/services/restart.cpp | 90 --- src/core/services/set_location.cpp | 145 ++-- src/core/services/shutdown.cpp | 88 --- src/core/services/take_screenshot.cpp | 61 -- src/devdiskimagehelper.cpp | 107 +-- src/devdiskimageswidget.cpp | 217 +++--- src/devdiskimageswidget.h | 2 +- src/devdiskmanager.cpp | 168 ++--- src/devdiskmanager.h | 6 +- src/deviceimagewidget.cpp | 18 +- src/devicemenuwidget.cpp | 63 +- src/devicemenuwidget.h | 10 +- src/devicesidebarwidget.h | 12 + src/diskusagewidget.cpp | 152 +++-- src/exportmanager.cpp | 181 +++-- src/exportmanager.h | 6 +- src/gallerywidget.cpp | 40 +- src/iDescriptor.h | 191 +++++- src/installedappswidget.cpp | 626 +++++++++--------- src/installedappswidget.h | 6 +- src/livescreenwidget.cpp | 201 +++--- src/livescreenwidget.h | 3 - src/main.cpp | 3 +- src/mainwindow.cpp | 97 ++- src/mediapreviewdialog.cpp | 19 +- src/mediapreviewdialog.h | 5 +- src/mediastreamer.cpp | 117 ++-- src/mediastreamer.h | 9 +- src/mediastreamermanager.cpp | 2 +- src/mediastreamermanager.h | 2 +- src/photomodel.cpp | 116 ++-- src/qballoontip.cpp | 299 +++++++++ src/qballoontip.h | 44 ++ src/querymobilegestaltwidget.cpp | 50 +- src/recoverydeviceinfowidget.cpp | 229 +++---- src/recoverydeviceinfowidget.h | 2 +- src/servicemanager.cpp | 110 +-- src/servicemanager.h | 11 + src/toolboxwidget.cpp | 165 ++--- src/toolboxwidget.h | 1 - src/virtuallocationwidget.cpp | 111 ++-- 66 files changed, 2822 insertions(+), 3546 deletions(-) create mode 160000 lib/ztoast create mode 100644 src/core/helpers/mounted_image_info_free.cpp delete mode 100644 src/core/services/afc2_client_new.cpp delete mode 100644 src/core/services/query_mobilegestalt.cpp delete mode 100644 src/core/services/restart.cpp delete mode 100644 src/core/services/shutdown.cpp delete mode 100644 src/core/services/take_screenshot.cpp create mode 100644 src/qballoontip.cpp create mode 100644 src/qballoontip.h diff --git a/.gitmodules b/.gitmodules index 13c8d2e..ca79437 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/idevice-rs"] path = lib/idevice-rs url = https://github.com/jkcoxson/idevice.git +[submodule "lib/ztoast"] + path = lib/ztoast + url = https://github.com/niklashenning/qt-toast.git diff --git a/CMakeLists.txt b/CMakeLists.txt index df8f2aa..573e0bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ add_custom_command( OUTPUT ${IDEVICE_RS_LIB_PATH} COMMAND ${CARGO_EXECUTABLE} build --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml WORKING_DIRECTORY ${IDEVICE_RS_SOURCE_DIR} - COMMENT "Building idevice-rs FFI library" + COMMENT "Building idevice-rs FFI libraryy" VERBATIM ) @@ -204,84 +204,106 @@ pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml) file(GLOB PROJECT_SOURCES -# src/*.cpp -# src/core/helpers/*.cpp -# src/core/services/*.cpp -# src/*.h -src/mainwindow.cpp -src/mainwindow.h -src/devicemonitor.h -src/main.cpp -src/core/services/init_device.cpp -src/core/services/get_battery_info.cpp -src/core/services/detect_jailbroken.cpp -src/appcontext.cpp -src/appcontext.h -src/devicedatabase.cpp -src/devicedatabase.h -src/core/helpers/compare_product_type.cpp -src/welcomewidget.cpp -src/welcomewidget.h -src/ztabwidget.cpp -src/ztabwidget.h -src/devicemanagerwidget.cpp -src/devicemanagerwidget.h -src/responsiveqlabel.cpp -src/responsiveqlabel.h -src/devicesidebarwidget.cpp -src/devicesidebarwidget.h -src/devicemenuwidget.cpp -src/devicemenuwidget.h -src/deviceinfowidget.cpp -src/deviceinfowidget.h -src/batterywidget.cpp -src/batterywidget.h -src/diskusagewidget.cpp -src/diskusagewidget.h +src/*.cpp +src/core/helpers/*.cpp +src/core/services/*.cpp +src/*.h +# src/mainwindow.cpp +# src/mainwindow.h +# src/devicemonitor.h +# src/main.cpp +# src/core/services/init_device.cpp +# src/core/services/get_battery_info.cpp +# src/core/services/detect_jailbroken.cpp +# src/appcontext.cpp +# src/appcontext.h +# src/devicedatabase.cpp +# src/devicedatabase.h +# src/core/helpers/compare_product_type.cpp +# src/welcomewidget.cpp +# src/welcomewidget.h +# src/ztabwidget.cpp +# src/ztabwidget.h +# src/devicemanagerwidget.cpp +# src/devicemanagerwidget.h +# src/responsiveqlabel.cpp +# src/responsiveqlabel.h +# src/devicesidebarwidget.cpp +# src/devicesidebarwidget.h +# src/devicemenuwidget.cpp +# src/devicemenuwidget.h +# src/deviceinfowidget.cpp +# src/deviceinfowidget.h +# src/batterywidget.cpp +# src/batterywidget.h +# src/diskusagewidget.cpp +# src/diskusagewidget.h +# # src/deviceimagewidget.cpp +# # src/deviceimagewidget.h +# src/iDescriptor-ui.h +# src/infolabel.cpp +# src/infolabel.h +# src/privateinfolabel.cpp +# src/privateinfolabel.h +# src/qprocessindicator.cpp +# src/qprocessindicator.h +# src/diagnosewidget.cpp +# src/diagnosewidget.h +# src/core/services/get-device-info.cpp # src/deviceimagewidget.cpp # src/deviceimagewidget.h -src/iDescriptor-ui.h -src/infolabel.cpp -src/infolabel.h -src/privateinfolabel.cpp -src/privateinfolabel.h -src/qprocessindicator.cpp -src/qprocessindicator.h -src/diagnosewidget.cpp -src/diagnosewidget.h -src/core/services/get-device-info.cpp -src/deviceimagewidget.cpp -src/deviceimagewidget.h -src/settingsmanager.cpp -src/settingsmanager.h -# src/fileexplorerwidget.cpp -# src/fileexplorerwidget.h -src/settingswidget.cpp -src/settingswidget.h -src/ifusemanager.cpp -src/ifusemanager.h -src/ifusediskunmountbutton.cpp -src/ifusediskunmountbutton.h -src/core/services/get_battery_info.cpp -src/networkdeviceswidget.cpp -src/core/services/avahi/avahi_service.h -src/core/services/avahi/avahi_service.cpp -src/networkdevicemanager.cpp -src/networkdevicemanager.h -src/*.ui +# src/settingsmanager.cpp +# src/settingsmanager.h +# # src/fileexplorerwidget.cpp +# # src/fileexplorerwidget.h +# src/settingswidget.cpp +# src/settingswidget.h +# src/ifusemanager.cpp +# src/ifusemanager.h +# src/ifusediskunmountbutton.cpp +# src/ifusediskunmountbutton.h +# src/core/services/get_battery_info.cpp +# src/networkdeviceswidget.cpp +# src/core/services/avahi/avahi_service.h +# src/core/services/avahi/avahi_service.cpp +# src/networkdevicemanager.cpp +# src/networkdevicemanager.h +# src/*.ui resources.qrc -src/gallerywidget.cpp -src/gallerywidget.h -src/photomodel.cpp -src/photomodel.h -src/core/services/load_heic.cpp -src/servicemanager.cpp -src/servicemanager.h -src/core/services/get_file_tree.cpp -src/core/helpers/read_afc_file_to_byte_array.cpp -src/heartbeat.h -src/zloadingwidget.h -src/zloadingwidget.cpp +# src/gallerywidget.cpp +# src/gallerywidget.h +# src/photomodel.cpp +# src/photomodel.h +# src/core/services/load_heic.cpp +# src/servicemanager.cpp +# src/servicemanager.h +# src/core/services/get_file_tree.cpp +# src/core/helpers/read_afc_file_to_byte_array.cpp +# src/heartbeat.h +# src/zloadingwidget.h +# src/zloadingwidget.cpp +# src/mediapreviewdialog.h +# src/mediapreviewdialog.cpp +# src/mediastreamer.h +# src/mediastreamer.cpp +# src/mediastreamermanager.h +# src/mediastreamermanager.cpp +# src/querymobilegestaltwidget.h +# src/querymobilegestaltwidget.cpp +# src/core/services/mount_dev_image.cpp +# src/core/services/get_mounted_image.cpp +# src/devdiskimageswidget.h +# src/devdiskimageswidget.cpp +# src/devdiskmanager.h +# src/devdiskmanager.cpp +# src/core/helpers/mounted_image_info_free.cpp +# src/core/services/get_cable_info.cpp +# src/cableinfowidget.h +# src/cableinfowidget.cpp +# src/livescreenwidget.h +# src/livescreenwidget.cpp +# src/virtuallocationwidget.h +# src/virtuallocationwidget.cpp ) if(APPLE) @@ -290,9 +312,7 @@ if(APPLE) src/core/services/dnssd/dnssd_service.cpp src/core/services/dnssd/dnssd_service.h ) -endif() - -if (WIN32) +elseif (WIN32) list(APPEND PROJECT_SOURCES src/core/services/dnssd/dnssd_service.cpp src/core/services/dnssd/dnssd_service.h @@ -300,15 +320,13 @@ if (WIN32) file(GLOB WINDOWS_PLATFORM_SOURCES src/platform/windows/*.cpp src/platform/windows/*.h) list(APPEND PROJECT_SOURCES ${WINDOWS_PLATFORM_SOURCES}) +else() + list(APPEND PROJECT_SOURCES + src/core/services/avahi/avahi_service.cpp + src/core/services/avahi/avahi_service.h + ) endif() -# if(LINUX) -# list(APPEND PROJECT_SOURCES -# src/core/services/avahi/avahi_service.cpp -# src/core/services/avahi/avahi_service.h -# ) -# endif() - if (NOT ENABLE_RECOVERY_DEVICE_SUPPORT) list(REMOVE_ITEM PROJECT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/recoverydeviceinfowidget.cpp @@ -323,6 +341,7 @@ endif() add_subdirectory(lib/airplay) add_subdirectory(lib/ipatool-go) add_subdirectory(lib/zupdater) +add_subdirectory(lib/ztoast) if (WIN32) set(NO_DEPLOY_WIN_IFUSE ON) @@ -392,6 +411,7 @@ target_link_libraries(iDescriptor PRIVATE airplay ipatool-go ZUpdater + ZToast ${IDEVICE_IMPLEMENTATION_LIBS} ) diff --git a/lib/ztoast b/lib/ztoast new file mode 160000 index 0000000..c7ebd75 --- /dev/null +++ b/lib/ztoast @@ -0,0 +1 @@ +Subproject commit c7ebd7514efe138e45d6d1a0ad6c33fd14ae579e diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index b979996..386d796 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -45,7 +45,7 @@ #include AfcExplorerWidget::AfcExplorerWidget(iDescriptorDevice *device, bool favEnabled, - afc_client_t afcClient, QString root, + AfcClientHandle *afcClient, QString root, QWidget *parent) : QWidget(parent), m_device(device), m_favEnabled(favEnabled), m_afc(afcClient), m_errorMessage("Failed to load directory"), m_root(root) @@ -373,6 +373,7 @@ void AfcExplorerWidget::exportSelectedFile(QListWidgetItem *item, // Save to selected directory QString savePath = directory + "/" + fileName; + // FIXME: this should be async int result = exportFileToPath(m_afc, devicePath.toStdString().c_str(), savePath.toStdString().c_str()); @@ -401,34 +402,81 @@ void AfcExplorerWidget::exportSelectedFile(QListWidgetItem *item, even though we are using safe wrappers, we better move this to services */ -int AfcExplorerWidget::exportFileToPath(afc_client_t afc, +// FIXME: this should be async +// use connect to signals/slots to notify progress +// create a progress dialog to show progress +// dont do this on the main thread +int AfcExplorerWidget::exportFileToPath(AfcClientHandle *afc, const char *device_path, const char *local_path) { - uint64_t handle = 0; - if (ServiceManager::safeAfcFileOpen(m_device, device_path, AFC_FOPEN_RDONLY, - &handle, m_afc) != AFC_E_SUCCESS) { - qDebug() << "Failed to open file on device:" << device_path; + AfcFileHandle *afcHandle = nullptr; + IdeviceFfiError *err_open = ServiceManager::safeAfcFileOpen( + m_device, device_path, AfcRdOnly, &afcHandle); + if (err_open != nullptr) { + qDebug() << "Failed to open file on device:" << device_path + << "Error Code:" << err_open->code + << "Message:" << err_open->message; + idevice_error_free(err_open); return -1; } + FILE *out = fopen(local_path, "wb"); if (!out) { qDebug() << "Failed to open local file:" << local_path; - afc_file_close(afc, handle); + IdeviceFfiError *err_close = + ServiceManager::safeAfcFileClose(m_device, afcHandle); + if (err_close != nullptr) { + idevice_error_free(err_close); + } return -1; } - char buffer[4096]; - uint32_t bytes_read = 0; - while (ServiceManager::safeAfcFileRead(m_device, handle, buffer, - sizeof(buffer), &bytes_read, - m_afc) == AFC_E_SUCCESS && - bytes_read > 0) { - fwrite(buffer, 1, bytes_read, out); + const size_t CHUNK_SIZE = 256 * 1024; // 256KB chunks + uint8_t *chunkData = nullptr; + size_t bytesRead = 0; + + // Read file in chunks + while (true) { + IdeviceFfiError *read_err = ServiceManager::safeAfcFileRead( + m_device, afcHandle, &chunkData, CHUNK_SIZE, &bytesRead); + + if (read_err != nullptr) { + qDebug() << "Error reading file:" << read_err->message; + idevice_error_free(read_err); + break; + } + + if (bytesRead == 0) { + // End of file reached + break; + } + + // Write chunk to local file + size_t written = fwrite(chunkData, 1, bytesRead, out); + + // Free the memory allocated by afc_file_read + afc_file_read_data_free(chunkData, bytesRead); + chunkData = nullptr; + + if (written != bytesRead) { + qDebug() << "Failed to write all bytes to local file"; + fclose(out); + ServiceManager::safeAfcFileClose(m_device, afcHandle); + return -1; + } } fclose(out); - ServiceManager::safeAfcFileClose(m_device, handle, m_afc); + + IdeviceFfiError *err_close = + ServiceManager::safeAfcFileClose(m_device, afcHandle); + if (err_close != nullptr) { + qDebug() << "Failed to close AFC file:" << err_close->message; + idevice_error_free(err_close); + return -1; + } + return 0; } @@ -464,39 +512,40 @@ void AfcExplorerWidget::onImportClicked() /* FIXME : move to services */ -int AfcExplorerWidget::importFileToDevice(afc_client_t afc, +int AfcExplorerWidget::importFileToDevice(AfcClientHandle *afc, const char *device_path, const char *local_path) { QFile in(local_path); - if (!in.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to open local file for import:" << local_path; - return -1; - } + // if (!in.open(QIODevice::ReadOnly)) { + // qDebug() << "Failed to open local file for import:" << local_path; + // return -1; + // } - uint64_t handle = 0; - if (ServiceManager::safeAfcFileOpen(m_device, device_path, AFC_FOPEN_WRONLY, - &handle, m_afc) != AFC_E_SUCCESS) { - qDebug() << "Failed to open file on device for writing:" << device_path; - return -1; - } + // uint64_t handle = 0; + // if (ServiceManager::safeAfcFileOpen(m_device, device_path, + // AFC_FOPEN_WRONLY, + // &handle, m_afc) != AFC_E_SUCCESS) { + // qDebug() << "Failed to open file on device for writing:" << + // device_path; return -1; + // } - char buffer[4096]; - qint64 bytesRead; - while ((bytesRead = in.read(buffer, sizeof(buffer))) > 0) { - uint32_t bytesWritten = 0; - if (ServiceManager::safeAfcFileWrite( - m_device, handle, buffer, static_cast(bytesRead), - &bytesWritten, m_afc) != AFC_E_SUCCESS || - bytesWritten != bytesRead) { - qDebug() << "Failed to write to device file:" << device_path; - ServiceManager::safeAfcFileClose(m_device, handle, m_afc); - in.close(); - return -1; - } - } + // char buffer[4096]; + // qint64 bytesRead; + // while ((bytesRead = in.read(buffer, sizeof(buffer))) > 0) { + // uint32_t bytesWritten = 0; + // if (ServiceManager::safeAfcFileWrite( + // m_device, handle, buffer, static_cast(bytesRead), + // &bytesWritten, m_afc) != AFC_E_SUCCESS || + // bytesWritten != bytesRead) { + // qDebug() << "Failed to write to device file:" << device_path; + // ServiceManager::safeAfcFileClose(m_device, handle, m_afc); + // in.close(); + // return -1; + // } + // } - ServiceManager::safeAfcFileClose(m_device, handle, m_afc); + // ServiceManager::safeAfcFileClose(m_device, handle, m_afc); in.close(); return 0; } diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h index 7c7362e..9b7250a 100644 --- a/src/afcexplorerwidget.h +++ b/src/afcexplorerwidget.h @@ -47,7 +47,7 @@ class AfcExplorerWidget : public QWidget public: explicit AfcExplorerWidget(iDescriptorDevice *device = nullptr, bool favEnabled = false, - afc_client_t afcClient = nullptr, + AfcClientHandle *afcClient = nullptr, QString root = "/", QWidget *parent = nullptr); void navigateToPath(const QString &path); void goHome(); @@ -109,9 +109,9 @@ private: void setupContextMenu(); void exportSelectedFile(QListWidgetItem *item); void exportSelectedFile(QListWidgetItem *item, const QString &directory); - int exportFileToPath(afc_client_t afc, const char *device_path, + int exportFileToPath(AfcClientHandle *afc, const char *device_path, const char *local_path); - int importFileToDevice(afc_client_t afc, const char *device_path, + int importFileToDevice(AfcClientHandle *afc, const char *device_path, const char *local_path); void updateNavStyles(); void updateButtonStates(); diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 591d3e2..13b41eb 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -91,170 +91,194 @@ void AppContext::addDevice(QString udid, DeviceMonitorThread::IdeviceConnectionType conn_type, AddType addType, QString wifiMacAddress) { + try { - iDescriptorInitDeviceResult initResult; + // iDescriptorInitDeviceResult initResult; + auto initResult = std::make_shared(); + QFuture future = QtConcurrent::run([this, udid, conn_type, + addType, wifiMacAddress, + initResult]() { + if (addType == AddType::UpgradeToWireless) { + const IdevicePairingFile *pairingFile = + getCachedPairingFile(udid); + if (!pairingFile) { + qDebug() + << "Cannot upgrade to wireless, no pairing file for" + << udid; + return; + } - if (addType == AddType::UpgradeToWireless) { - const IdevicePairingFile *pairingFile = getCachedPairingFile(udid); - if (!pairingFile) { - qDebug() << "Cannot upgrade to wireless, no pairing file for" - << udid; - return; + QList networkDevices = + NetworkDeviceManager::sharedInstance() + ->m_networkProvider->getNetworkDevices(); + + auto it = std::find_if( + networkDevices.constBegin(), networkDevices.constEnd(), + [wifiMacAddress](const NetworkDevice &device) { + return device.macAddress.compare( + wifiMacAddress, Qt::CaseInsensitive) == 0; + }); + + if (it != networkDevices.constEnd()) { + + IdevicePairingFile *pairing_file = nullptr; + idevice_pairing_file_read( + QString("/var/lib/lockdown/%1.plist") + .arg(udid) + .toUtf8() + .constData(), + &pairing_file); + *initResult = init_idescriptor_device( + udid, {it->address, pairing_file}); + } else { + qDebug() << "No network device found with MAC address:" + << wifiMacAddress; + return; + } + } else if (addType == AddType::Wireless) { + // FIXME: its not udid here its macAddress + const IdevicePairingFile *pairingFile = + getCachedPairingFile(udid); + if (!pairingFile) { + qDebug() << "Cannot initialize wireless device, no pairing " + "file for" + << udid; + return; + } + + QList networkDevices = + NetworkDeviceManager::sharedInstance() + ->m_networkProvider->getNetworkDevices(); + + auto it = std::find_if( + networkDevices.constBegin(), networkDevices.constEnd(), + [wifiMacAddress](const NetworkDevice &device) { + return device.macAddress.compare( + wifiMacAddress, Qt::CaseInsensitive) == 0; + }); + + if (it != networkDevices.constEnd()) { + *initResult = init_idescriptor_device( + udid, {it->address, pairingFile}); + } else { + qDebug() << "No network device found with MAC address:" + << wifiMacAddress; + return; + } } - QList networkDevices = - NetworkDeviceManager::sharedInstance() - ->m_networkProvider->getNetworkDevices(); + else { - auto it = std::find_if( - networkDevices.constBegin(), networkDevices.constEnd(), - [wifiMacAddress](const NetworkDevice &device) { - return device.macAddress.compare(wifiMacAddress, - Qt::CaseInsensitive) == 0; + *initResult = init_idescriptor_device(udid, {nullptr, nullptr}); + } + }); + QFutureWatcher *watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, this, + [this, udid, initResult, addType, conn_type, watcher]() { + watcher->deleteLater(); + qDebug() << "init_idescriptor_device success ?: " + << initResult->success; + // qDebug() << "init_idescriptor_device error code: " << + // initResult.error; + if (!initResult->success) { + qDebug() << "Failed to initialize device with UDID: " + << udid; + return; + } + // if (!initResult.success) { + // qDebug() << "Failed to initialize device with UDID: " + // << udid; if (initResult.error == + // LOCKDOWN_E_PASSWORD_PROTECTED) + // { + // if (addType == AddType::Regular) { + // m_pendingDevices.append(udid); + // emit devicePasswordProtected(udid); + // emit deviceChange(); + // QTimer::singleShot( + // SettingsManager::sharedInstance()->connectionTimeout() + // * + // 1000, + // this, [this, udid]() { + // if (m_pendingDevices.contains(udid)) + // { + // qDebug() << "Pairing expired for + // device UDID: + // " + // << udid; + // m_pendingDevices.removeAll(udid); + // emit devicePairingExpired(udid); + // emit deviceChange(); + // } + // }); + // } + // } else if (initResult.error == + // LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING + // || + // initResult.error == + // LOCKDOWN_E_INVALID_HOST_ID) { + // m_pendingDevices.append(udid); + // emit devicePairPending(udid); + // emit deviceChange(); + // QTimer::singleShot( + // SettingsManager::sharedInstance()->connectionTimeout() + // * + // 1000, + // this, [this, udid]() { + // 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); + // emit deviceChange(); + // } + // }); + // } else { + // qDebug() << "Unhandled error for device UDID: " + // << udid + // << " Error code: " << initResult.error; + // } + // return; + // } + qDebug() << "Device initialized: " << udid; + + iDescriptorDevice *device = new iDescriptorDevice{ + .udid = udid.toStdString(), + .conn_type = conn_type, + .provider = initResult->provider, + .deviceInfo = initResult->deviceInfo, + .afcClient = initResult->afcClient, + .afc2Client = initResult->afc2Client, + .lockdown = initResult->lockdown, + .mutex = new std::recursive_mutex(), + .imageMounter = initResult->imageMounter, + .diagRelay = initResult->diagRelay, + .screenshotrClient = initResult->screenshotrClient, + .locationSimulation = initResult->locationSimulation}; + m_devices[device->udid] = device; + if (addType == AddType::Regular) { + qDebug() << "Regular device added: " << udid; + // SettingsManager::sharedInstance()->doIfEnabled( + // SettingsManager::Setting::AutoRaiseWindow, []() { + // if (MainWindow *mainWindow = + // MainWindow::sharedInstance()) { + // mainWindow->raise(); + // mainWindow->activateWindow(); + // } + // }); + + emit deviceAdded(device); + emit deviceChange(); + return; + } + emit devicePaired(device); + emit deviceChange(); + m_pendingDevices.removeAll(udid); }); - - if (it != networkDevices.constEnd()) { - - IdevicePairingFile *pairing_file = nullptr; - idevice_pairing_file_read(QString("/var/lib/lockdown/%1.plist") - .arg(udid) - .toUtf8() - .constData(), - &pairing_file); - initResult = - init_idescriptor_device(udid, {it->address, pairing_file}); - } else { - qDebug() << "No network device found with MAC address:" - << wifiMacAddress; - return; - } - } else if (addType == AddType::Wireless) { - // FIXME: its not udid here its macAddress - const IdevicePairingFile *pairingFile = getCachedPairingFile(udid); - if (!pairingFile) { - qDebug() - << "Cannot initialize wireless device, no pairing file for" - << udid; - return; - } - - QList networkDevices = - NetworkDeviceManager::sharedInstance() - ->m_networkProvider->getNetworkDevices(); - - auto it = std::find_if( - networkDevices.constBegin(), networkDevices.constEnd(), - [wifiMacAddress](const NetworkDevice &device) { - return device.macAddress.compare(wifiMacAddress, - Qt::CaseInsensitive) == 0; - }); - - if (it != networkDevices.constEnd()) { - initResult = - init_idescriptor_device(udid, {it->address, pairingFile}); - } else { - qDebug() << "No network device found with MAC address:" - << wifiMacAddress; - return; - } - } - - else { - - initResult = init_idescriptor_device(udid, {nullptr, nullptr}); - } - - qDebug() << "init_idescriptor_device success ?: " << initResult.success; - // qDebug() << "init_idescriptor_device error code: " << - // initResult.error; - if (!initResult.success) { - qDebug() << "Failed to initialize device with UDID: " << udid; - return; - } - // if (!initResult.success) { - // qDebug() << "Failed to initialize device with UDID: " << - // udid; if (initResult.error == LOCKDOWN_E_PASSWORD_PROTECTED) - // { - // if (addType == AddType::Regular) { - // m_pendingDevices.append(udid); - // emit devicePasswordProtected(udid); - // emit deviceChange(); - // QTimer::singleShot( - // SettingsManager::sharedInstance()->connectionTimeout() - // * - // 1000, - // this, [this, udid]() { - // if (m_pendingDevices.contains(udid)) { - // qDebug() << "Pairing expired for device - // UDID: - // " - // << udid; - // m_pendingDevices.removeAll(udid); - // emit devicePairingExpired(udid); - // emit deviceChange(); - // } - // }); - // } - // } else if (initResult.error == - // LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING || - // initResult.error == LOCKDOWN_E_INVALID_HOST_ID) { - // m_pendingDevices.append(udid); - // emit devicePairPending(udid); - // emit deviceChange(); - // QTimer::singleShot( - // SettingsManager::sharedInstance()->connectionTimeout() - // * - // 1000, - // this, [this, udid]() { - // 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); - // emit deviceChange(); - // } - // }); - // } else { - // qDebug() << "Unhandled error for device UDID: " << udid - // << " Error code: " << initResult.error; - // } - // return; - // } - qDebug() << "Device initialized: " << udid; - - iDescriptorDevice *device = new iDescriptorDevice{ - .udid = udid.toStdString(), - .conn_type = conn_type, - .provider = initResult.provider, - .deviceInfo = initResult.deviceInfo, - .afcClient = initResult.afcClient, - .afc2Client = initResult.afc2Client, - .lockdown = initResult.lockdown, - .mutex = new std::recursive_mutex(), - }; - m_devices[device->udid] = device; - if (addType == AddType::Regular) { - qDebug() << "Regular device added: " << udid; - // SettingsManager::sharedInstance()->doIfEnabled( - // SettingsManager::Setting::AutoRaiseWindow, []() { - // if (MainWindow *mainWindow = - // MainWindow::sharedInstance()) { - // mainWindow->raise(); - // mainWindow->activateWindow(); - // } - // }); - - emit deviceAdded(device); - emit deviceChange(); - return; - } - emit devicePaired(device); - emit deviceChange(); - m_pendingDevices.removeAll(udid); } catch (const std::exception &e) { qDebug() << "Exception in onDeviceAdded: " << e.what(); } diff --git a/src/appdownloadbasedialog.cpp b/src/appdownloadbasedialog.cpp index 04d508b..2f60bdc 100644 --- a/src/appdownloadbasedialog.cpp +++ b/src/appdownloadbasedialog.cpp @@ -72,7 +72,8 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName, void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, const QString &outputDir, int index, - bool promptToOpenDir) + bool promptToOpenDir, + bool close) { if (bundleId.isEmpty()) { @@ -86,12 +87,12 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, m_actionButton->setEnabled(false); m_operationInProgress = true; - tryToDownload(bundleId, outputDir, promptToOpenDir); + tryToDownload(bundleId, outputDir, promptToOpenDir, close); } void AppDownloadBaseDialog::tryToDownload(const QString &bundleId, const QString &outputDir, - bool promptToOpenDir) + bool promptToOpenDir, bool close) { AppStoreManager *manager = AppStoreManager::sharedInstance(); if (!manager) { @@ -117,7 +118,7 @@ void AppDownloadBaseDialog::tryToDownload(const QString &bundleId, manager->downloadApp( bundleId, outputDir, "", acquireLicense, - [safeThis, promptToOpenDir, outputDir](int result) { + [safeThis, promptToOpenDir, outputDir, close](int result) { if (!safeThis) { return; } @@ -146,7 +147,8 @@ void AppDownloadBaseDialog::tryToDownload(const QString &bundleId, } } } - safeThis->accept(); + if (close) + safeThis->accept(); } else { // Failure // 3 attempts if (safeThis->m_tries < 3) { @@ -166,7 +168,8 @@ void AppDownloadBaseDialog::tryToDownload(const QString &bundleId, "in. Error code: %2") .arg(safeThis->m_appName) .arg(result)); - safeThis->reject(); + if (close) + safeThis->reject(); } }, progressCallback); diff --git a/src/appdownloadbasedialog.h b/src/appdownloadbasedialog.h index 79bbe2e..2810f1b 100644 --- a/src/appdownloadbasedialog.h +++ b/src/appdownloadbasedialog.h @@ -44,13 +44,13 @@ protected: void reject() override; void startDownloadProcess(const QString &bundleId, const QString &workingDir, int index, - bool promptToOpenDir = true); + bool promptToOpenDir = true, bool close = false); void checkDownloadProgress(const QString &logFilePath, const QString &appName, const QString &outputDir); void addProgressBar(int index); void tryToDownload(const QString &bundleId, const QString &outputDir, - bool promptToOpenDir); + bool promptToOpenDir, bool close = false); QProgressBar *m_progressBar; QTimer *m_progressTimer; QProcess *m_downloadProcess; diff --git a/src/appinstalldialog.cpp b/src/appinstalldialog.cpp index 0493cf6..97afa78 100644 --- a/src/appinstalldialog.cpp +++ b/src/appinstalldialog.cpp @@ -21,6 +21,7 @@ #include "appcontext.h" #include "appdownloadbasedialog.h" #include "iDescriptor.h" +#include "servicemanager.h" #include #include #include @@ -151,46 +152,59 @@ void AppInstallDialog::updateDeviceList() } void AppInstallDialog::performInstallation(const QString &ipaPath, + const QString &ipaName, const QString &deviceUdid) { m_statusLabel->setText("Installing app..."); // Setup install watcher - m_installWatcher = new QFutureWatcher(this); - connect(m_installWatcher, &QFutureWatcher::finished, this, [this]() { - int result = m_installWatcher->result(); - m_installWatcher->deleteLater(); - m_installWatcher = nullptr; + m_installWatcher = new QFutureWatcher(this); + connect( + m_installWatcher, &QFutureWatcher::finished, this, + [this]() { + IdeviceFfiError *result = m_installWatcher->result(); + m_installWatcher->deleteLater(); + m_installWatcher = nullptr; - if (result == 0) { - m_statusLabel->setText("Installation completed successfully!"); - m_statusLabel->setStyleSheet( - "font-size: 14px; color: #34C759; padding: 5px;"); - QMessageBox::information(this, "Success", - "App installed successfully!"); - accept(); - } else { - m_statusLabel->setText("Installation failed"); - m_statusLabel->setStyleSheet( - "font-size: 14px; color: #FF3B30; padding: 5px;"); - QMessageBox::critical( - this, "Error", - QString("Installation failed with error code: %1").arg(result)); - } - }); + if (result == nullptr) { + m_statusLabel->setText("Installation completed successfully!"); + m_statusLabel->setStyleSheet( + "font-size: 14px; color: #34C759; padding: 5px;"); + QMessageBox::information(this, "Success", + "App installed successfully!"); + accept(); + } else { + m_statusLabel->setText("Installation failed"); + m_statusLabel->setStyleSheet( + "font-size: 14px; color: #FF3B30; padding: 5px;"); + QMessageBox::critical( + this, "Error", + QString( + "Installation failed with message %1 and error code %2") + .arg(QString::fromUtf8(result->message)) + .arg(result->code)); + idevice_error_free(result); + reject(); + } + }); // Run installation in background thread - QFuture future = QtConcurrent::run([ipaPath, deviceUdid]() -> int { - iDescriptorDevice *device = - AppContext::sharedInstance()->getDevice(deviceUdid.toStdString()); - if (!device) { - return -1; - } + QFuture future = QtConcurrent::run( + [ipaPath, ipaName, deviceUdid]() -> IdeviceFfiError * { + iDescriptorDevice *device = AppContext::sharedInstance()->getDevice( + deviceUdid.toStdString()); + if (!device) { + return nullptr; + } - instproxy_error_t ret = install_IPA(device->device, device->afcClient, - ipaPath.toStdString().c_str()); - return static_cast(ret); - }); + IdeviceFfiError *err = ServiceManager::install_IPA( + device, ipaPath.toStdString().c_str(), + ipaName.toStdString().c_str()); + if (err != nullptr) { + return err; + } + return nullptr; + }); m_installWatcher->setFuture(future); } @@ -229,43 +243,44 @@ void AppInstallDialog::onInstallClicked() return; } - startDownloadProcess(m_bundleId, m_tempDir->path(), buttonIndex, false); - connect(this, &AppDownloadBaseDialog::downloadFinished, this, - [this, selectedDevice](bool success) { - if (success) { - qDebug() << "Download finished, starting installation..."; - /* - FIXME: libipatool generates random id and appends that - to the downloaded IPA filename, so we need to search for - it. - */ - // Find the actual downloaded IPA file - QDir outDir(m_tempDir->path()); - QStringList filters; - filters << m_bundleId + "*.ipa"; - QStringList matches = - outDir.entryList(filters, QDir::Files, QDir::Time); - if (matches.isEmpty()) { - m_statusLabel->setText( - "Download failed - IPA not found"); - m_statusLabel->setStyleSheet( - "font-size: 14px; color: #FF3B30; padding: 5px;"); - QMessageBox::critical( - this, "Error", - QString("Downloaded IPA not found in %1") - .arg(outDir.absolutePath())); - return; - } - - QString ipaFile = outDir.filePath(matches.first()); - performInstallation(ipaFile, selectedDevice); - - } else { - m_statusLabel->setText("Download failed"); + startDownloadProcess(m_bundleId, m_tempDir->path(), buttonIndex, false, + false); + connect( + this, &AppDownloadBaseDialog::downloadFinished, this, + [this, selectedDevice](bool success) { + if (success) { + qDebug() << "Download finished, starting installation..."; + /* + FIXME: libipatool generates random id and appends that + to the downloaded IPA filename, so we need to search for + it. + */ + // Find the actual downloaded IPA file + QDir outDir(m_tempDir->path()); + QStringList filters; + filters << m_bundleId + "*.ipa"; + QStringList matches = + outDir.entryList(filters, QDir::Files, QDir::Time); + if (matches.isEmpty()) { + m_statusLabel->setText("Download failed - IPA not found"); m_statusLabel->setStyleSheet( "font-size: 14px; color: #FF3B30; padding: 5px;"); + QMessageBox::critical( + this, "Error", + QString("Downloaded IPA not found in %1") + .arg(outDir.absolutePath())); + return; } - }); + qDebug() << "Found downloaded IPA:" << matches.first(); + QString ipaFile = outDir.filePath(matches.first()); + performInstallation(ipaFile, matches.first(), selectedDevice); + + } else { + m_statusLabel->setText("Download failed"); + m_statusLabel->setStyleSheet( + "font-size: 14px; color: #FF3B30; padding: 5px;"); + } + }); } void AppInstallDialog::reject() diff --git a/src/appinstalldialog.h b/src/appinstalldialog.h index 5d38fd7..b91ab5d 100644 --- a/src/appinstalldialog.h +++ b/src/appinstalldialog.h @@ -25,8 +25,9 @@ #include #include #include -#include #include +#include +#include "iDescriptor.h" class AppInstallDialog : public AppDownloadBaseDialog { @@ -48,11 +49,12 @@ private: QComboBox *m_deviceCombo; QString m_bundleId; QLabel *m_statusLabel; - QFutureWatcher *m_installWatcher; + QFutureWatcher *m_installWatcher; QTemporaryDir *m_tempDir = nullptr; QNetworkAccessManager *m_manager = nullptr; void updateDeviceList(); - void performInstallation(const QString &ipaPath, const QString &deviceUdid); + void performInstallation(const QString &ipaPath, const QString &ipaName, + const QString &deviceUdid); }; #endif // APPINSTALLDIALOG_H diff --git a/src/cableinfowidget.cpp b/src/cableinfowidget.cpp index 7b93109..0c4a336 100644 --- a/src/cableinfowidget.cpp +++ b/src/cableinfowidget.cpp @@ -19,6 +19,7 @@ #include "cableinfowidget.h" #include "appcontext.h" +#include "servicemanager.h" #include #include #include @@ -74,7 +75,7 @@ void CableInfoWidget::setupUI() void CableInfoWidget::initCableInfo() { - if (!m_device || !m_device->device) { + if (!m_device) { m_statusLabel->setText("Something went wrong (no device ?)"); m_statusLabel->setStyleSheet( "QLabel { color: #dc3545; font-size: 18px; font-weight: bold; }"); @@ -82,7 +83,7 @@ void CableInfoWidget::initCableInfo() } m_statusLabel->setText("Analyzing cable..."); - get_cable_info(m_device->device, m_response); + ServiceManager::getCableInfo(m_device, m_response); analyzeCableInfo(); updateUI(); @@ -97,9 +98,8 @@ void CableInfoWidget::analyzeCableInfo() if (!m_response) { return; } - - PlistNavigator nav(m_response); - PlistNavigator ioreg = nav["IORegistry"]; + plist_print(m_response); + PlistNavigator ioreg(m_response); if (!ioreg.valid()) { return; diff --git a/src/cableinfowidget.h b/src/cableinfowidget.h index bd28801..f9aee59 100644 --- a/src/cableinfowidget.h +++ b/src/cableinfowidget.h @@ -29,7 +29,6 @@ #include #include #include -#include class CableInfoWidget : public QWidget { diff --git a/src/core/helpers/mounted_image_info_free.cpp b/src/core/helpers/mounted_image_info_free.cpp new file mode 100644 index 0000000..06cb0b6 --- /dev/null +++ b/src/core/helpers/mounted_image_info_free.cpp @@ -0,0 +1,11 @@ +#include "../../iDescriptor.h" + +void mounted_image_info_free(MountedImageInfo &info) +{ + if (info.err) { + idevice_error_free(info.err); + } + if (info.signature) { + idevice_data_free(info.signature, info.signature_len); + } +} \ No newline at end of file diff --git a/src/core/services/afc2_client_new.cpp b/src/core/services/afc2_client_new.cpp deleted file mode 100644 index 06ee21f..0000000 --- a/src/core/services/afc2_client_new.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * iDescriptor: A free and open-source idevice management tool. - * - * Copyright (C) 2025 Uncore - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include "../../iDescriptor.h" -#include -#include -#include -#include - -afc_error_t afc2_client_new(idevice_t device, afc_client_t *afc) -{ - - lockdownd_service_descriptor_t service = NULL; - // TODO: should free service ? - lockdownd_client_t client = NULL; - - if (lockdownd_client_new_with_handshake(device, &client, APP_LABEL) != - LOCKDOWN_E_SUCCESS) { - qDebug() << "Could not connect to lockdownd"; - return AFC_E_UNKNOWN_ERROR; - } - if (lockdownd_start_service(client, AFC2_SERVICE_NAME, &service) != - LOCKDOWN_E_SUCCESS) { - qDebug() << "Could not start AFC service"; - lockdownd_client_free(client); - return AFC_E_UNKNOWN_ERROR; - } - - return afc_client_new(device, service, afc); - - // char **dirs = NULL; - // if (afc_read_directory(afc, argv[1], &dirs) == AFC_E_SUCCESS) { - // for (int i = 0; dirs[i]; i++) { - // printf("Entry: %s\n", dirs[i]); - // } - // // free(dirs); - // } -} \ No newline at end of file diff --git a/src/core/services/detect-afc2.cpp b/src/core/services/detect-afc2.cpp index 79e832d..09c1b7b 100644 --- a/src/core/services/detect-afc2.cpp +++ b/src/core/services/detect-afc2.cpp @@ -17,4 +17,4 @@ * along with this program. If not, see . */ -// Detect AFC2 (Apple File Conduit 2) support \ No newline at end of file +// TODO:Detect AFC2 (Apple File Conduit 2) support \ No newline at end of file diff --git a/src/core/services/detect_has_been_jailbroken_before.cpp b/src/core/services/detect_has_been_jailbroken_before.cpp index 02d4314..9d7ff4a 100644 --- a/src/core/services/detect_has_been_jailbroken_before.cpp +++ b/src/core/services/detect_has_been_jailbroken_before.cpp @@ -18,7 +18,6 @@ */ #include "../../iDescriptor.h" -#include #include #include @@ -27,25 +26,29 @@ struct JailbreakDetectionResult { std::vector found_folders; }; -JailbreakDetectionResult detect_has_jailbroken_before(afc_client_t afc) +// char *possible_jailbreak_paths[] = { +// "/Applications/Cydia.app", +// "/Library/MobileSubstrate/MobileSubstrate.dylib", +// "/bin/bash", +// "/usr/sbin/sshd", +// "/etc/apt", +// NULL +// }; + +JailbreakDetectionResult detect_has_jailbroken_before(AfcClientHandle *afc) { - std::vector jailbreak_folders = {".installed_palera1n", - ".procursus_strapped"}; + // std::vector jailbreak_folders = {".installed_palera1n", + // ".procursus_strapped"}; JailbreakDetectionResult result = {false, {}}; - char **dirs = NULL; - if (afc_read_directory(afc, POSSIBLE_ROOT, &dirs) == AFC_E_SUCCESS) { - for (char **dir = dirs; *dir != nullptr; ++dir) { - std::string dirname = *dir; - for (const auto &jb_folder : jailbreak_folders) { - if (dirname == jb_folder) { - result.found_folders.push_back(jb_folder); - result.is_jailbroken = true; - } - } - } - } - afc_dictionary_free(dirs); + // char **dirs = NULL; + // size_t count = 0; + // if (!afc_list_directory(afc, (std::string(POSSIBLE_ROOT) + + // "bin").c_str(), + // &dirs, &count)) { + // free(dirs); + // } + // afc_dictionary_free(dirs); return result; } \ No newline at end of file diff --git a/src/core/services/detect_jailbroken.cpp b/src/core/services/detect_jailbroken.cpp index 10c9347..788e219 100644 --- a/src/core/services/detect_jailbroken.cpp +++ b/src/core/services/detect_jailbroken.cpp @@ -19,25 +19,16 @@ #include "../../iDescriptor.h" -// char *possible_jailbreak_paths[] = { -// "/Applications/Cydia.app", -// "/Library/MobileSubstrate/MobileSubstrate.dylib", -// "/bin/bash", -// "/usr/sbin/sshd", -// "/etc/apt", -// NULL -// }; #include bool detect_jailbroken(AfcClientHandle *afc) { char **dirs = NULL; size_t count = 0; - bool res = false; if (!afc_list_directory(afc, (std::string(POSSIBLE_ROOT) + "bin").c_str(), &dirs, &count)) { - free(dirs); + free_directory_listing(dirs, count); } - return res > 0; + return count > 0; } \ No newline at end of file diff --git a/src/core/services/get_battery_info.cpp b/src/core/services/get_battery_info.cpp index 18ab2e5..2afb964 100644 --- a/src/core/services/get_battery_info.cpp +++ b/src/core/services/get_battery_info.cpp @@ -23,27 +23,10 @@ #include // FIXME: return bool -void get_battery_info(IdeviceProviderHandle *provider, plist_t &diagnostics) +void get_battery_info(DiagnosticsRelay *diag_client, plist_t &diagnostics) { - // 1. Connect to the diagnostics_relay service using the raw C function. - DiagnosticsRelayClientHandle *client_handle = nullptr; - IdeviceFfiError *err = - ::diagnostics_relay_client_connect(provider, &client_handle); - - if (err) { - qDebug() << "Failed to create diagnostics relay client:" - << err->message; - idevice_error_free(err); - return; - } - - // 2. Adopt the raw handle into the C++ RAII wrapper. - // The client will now be automatically freed when it goes out of scope. - auto diagnostics_client = - IdeviceFFI::DiagnosticsRelay::adopt(client_handle); - - // 3. Query IORegistry for battery info. - auto ioreg_result = diagnostics_client.ioregistry( + qDebug() << "Fetching battery info via DiagnosticsRelay"; + auto ioreg_result = diag_client->ioregistry( IdeviceFFI::None, // current_plane IdeviceFFI::None, // entry_name IdeviceFFI::Some(std::string("IOPMPowerSource")) // entry_class @@ -55,10 +38,8 @@ void get_battery_info(IdeviceProviderHandle *provider, plist_t &diagnostics) return; } - // 4. Unwrap the result and handle the optional plist. auto plist_opt = std::move(ioreg_result).unwrap(); if (plist_opt.is_some()) { - // The caller of get_battery_info is responsible for freeing this plist. diagnostics = std::move(plist_opt).unwrap(); } } \ No newline at end of file diff --git a/src/core/services/get_cable_info.cpp b/src/core/services/get_cable_info.cpp index 68532ef..61dc781 100644 --- a/src/core/services/get_cable_info.cpp +++ b/src/core/services/get_cable_info.cpp @@ -18,51 +18,24 @@ */ #include "../../iDescriptor.h" -#include -#include -#include -void get_cable_info(idevice_t device, plist_t &response) +void _get_cable_info(const iDescriptorDevice *device, plist_t &response) { - lockdownd_client_t lockdown_client = NULL; - diagnostics_relay_client_t diagnostics_client = NULL; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - lockdownd_service_descriptor_t service = NULL; - int use_network = 0; - if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake( - device, &lockdown_client, TOOL_NAME))) { - idevice_free(device); - printf("ERROR: Could not connect to lockdownd, error code %d\n", ret); + auto ioreg_result = device->diagRelay->ioregistry( + IdeviceFFI::None, // current_plane + IdeviceFFI::None, // entry_name + IdeviceFFI::Some(std::string("AppleTriStarBuiltIn")) // entry_class + ); + + if (!ioreg_result.is_ok()) { + qDebug() << "Failed to query IORegistry:" + << ioreg_result.unwrap_err().message.c_str(); + return; } - /* attempt to use newer diagnostics service available on iOS 5 and later */ - ret = lockdownd_start_service( - lockdown_client, "com.apple.mobile.diagnostics_relay", &service); - if (ret == LOCKDOWN_E_INVALID_SERVICE) { - /* attempt to use older diagnostics service */ - ret = lockdownd_start_service( - lockdown_client, "com.apple.iosdiagnostics.relay", &service); + auto plist_opt = std::move(ioreg_result).unwrap(); + if (plist_opt.is_some()) { + response = std::move(plist_opt).unwrap(); } - lockdownd_client_free(lockdown_client); - - if (ret != LOCKDOWN_E_SUCCESS) { - idevice_free(device); - printf("ERROR: Could not start diagnostics relay service: %s\n", - lockdownd_strerror(ret)); - } - - if ((ret == LOCKDOWN_E_SUCCESS) && service && (service->port > 0)) { - if (diagnostics_relay_client_new(device, service, - &diagnostics_client) != - DIAGNOSTICS_RELAY_E_SUCCESS) { - printf("ERROR: Could not connect to diagnostics_relay!\n"); - } - } - - diagnostics_relay_error_t err = diagnostics_relay_query_ioregistry_entry( - diagnostics_client, NULL, "AppleTriStarBuiltIn", &response); - - if (diagnostics_client) - diagnostics_relay_client_free(diagnostics_client); } \ No newline at end of file diff --git a/src/core/services/get_file_tree.cpp b/src/core/services/get_file_tree.cpp index e53e5bc..1bf3021 100644 --- a/src/core/services/get_file_tree.cpp +++ b/src/core/services/get_file_tree.cpp @@ -74,7 +74,8 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, if (info_err) { qDebug() << "Failed to get file info for:" << fullPath.c_str() - << "Error:" << info_err->message; + << "Error:" << info_err->message + << "Code:" << info_err->code; } bool isDir = false; @@ -86,19 +87,15 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, } else if (strcmp(info.st_ifmt, "S_IFLNK") == 0) { // Check if symlink points to a directory char **dir_contents = nullptr; + // FIXME: recursively call safeAfcGetFileInfo to figure out if + // it's a dir IdeviceFfiError *link_err = ServiceManager::safeAfcReadDirectory( device, fullPath.c_str(), &dir_contents); if (!link_err) { isDir = true; - // if (dir_contents) { - // // FIXME: is this ok ? - // for (int j = 0; dir_contents[j]; j++) { - // free(dir_contents[j]); - // } - // free(dir_contents); - // } + free_directory_listing(dir_contents, count); } if (link_err) { @@ -106,8 +103,7 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, } } - // Free file info - // afc_file_info_free(&info); + afc_file_info_free(&info); } if (info_err) { @@ -119,10 +115,7 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, // Free the directory list if (dirs) { - // for (int i = 0; dirs[i]; i++) { - // free(dirs[i]); - // } - // free(dirs); + free_directory_listing(dirs, count); } result.success = true; diff --git a/src/core/services/get_mounted_image.cpp b/src/core/services/get_mounted_image.cpp index a37221f..ff52f42 100644 --- a/src/core/services/get_mounted_image.cpp +++ b/src/core/services/get_mounted_image.cpp @@ -18,101 +18,30 @@ */ #include "../../iDescriptor.h" -#include -#define _GNU_SOURCE 1 -#define __USE_GNU 1 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef _WIN32 -#include -#endif - -plist_t _get_mounted_image(const char *udid) +MountedImageInfo _get_mounted_image(const iDescriptorDevice *device) { - mobile_image_mounter_client_t mim = NULL; - lockdownd_client_t lckd = NULL; - lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; - afc_client_t afc = NULL; - lockdownd_service_descriptor_t service = NULL; - idevice_t device = NULL; - - mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; - plist_t result = NULL; - size_t sig_length = 0; - const char *imagetype = "Developer"; - - if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, - - IDEVICE_LOOKUP_USBMUX)) { - qDebug() << "ERROR: Could not create idevice!"; + uint8_t *signature = NULL; + size_t signature_len = 0; + IdeviceFfiError *err = nullptr; + qDebug() << "_get_mounted_image"; + if (err) { + qDebug() << "Failed to connect to image mounter:" << err->message; goto leave; } - if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake( - device, &lckd, TOOL_NAME))) { - qDebug() << "ERROR: Could not connect to lockdownd service!"; - goto leave; + err = image_mounter_lookup_image(device->imageMounter, + DISK_IMAGE_TYPE_DEVELOPER, &signature, + &signature_len); + if (err) { + qDebug() << "Failed to lookup image:" << err->message + << "Code:" << err->code; } - lockdownd_start_service(lckd, "com.apple.mobile.mobile_image_mounter", - &service); - - if (!service || service->port == 0) { - printf("ERROR: Could not start mobile_image_mounter service!\n"); - goto leave; - } - - if (mobile_image_mounter_new(device, service, &mim) != - MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - printf("ERROR: Could not connect to mobile_image_mounter!\n"); - goto leave; - } - - if (!service || service->port == 0) { - qDebug() << "ERROR: Could not start mobile_image_mounter service!"; - goto leave; - } - - // will sometimes return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the device - // is locked - mostly on older devices - err = mobile_image_mounter_lookup_image(mim, imagetype, &result); - leave: - if (mim) { - mobile_image_mounter_free(mim); - } - if (afc) { - afc_client_free(afc); - } - if (lckd) { - lockdownd_client_free(lckd); - } - if (device) { - idevice_free(device); - } - - return result; -} - -// int main(){return 0;} \ No newline at end of file + return { + .err = err, + .signature = signature, + .signature_len = signature_len, + }; +} \ No newline at end of file diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 49fdd65..bfc94d7 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -156,7 +156,7 @@ void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d) DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, AfcClientHandle *afcClient, - IdeviceProviderHandle *provider, + DiagnosticsRelay *diagRelay, iDescriptorInitDeviceResult &result) { pugi::xml_node dict = doc.child("plist").child("dict"); @@ -205,11 +205,12 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, QString q_version = QString::fromStdString(d.productVersion); QStringList parts = q_version.split('.'); - int major = (parts.length() > 0) ? parts[0].toInt() : 0; - int minor = (parts.length() > 1) ? parts[1].toInt() : 0; - int patch = (parts.length() > 2) ? parts[2].toInt() : 0; + unsigned int major = (parts.length() > 0) ? parts[0].toInt() : 0; + unsigned int minor = (parts.length() > 1) ? parts[1].toInt() : 0; + unsigned int patch = (parts.length() > 2) ? parts[2].toInt() : 0; - d.parsedDeviceVersion = IDEVICE_DEVICE_VERSION(major, minor, patch); + d.parsedDeviceVersion = + DeviceVersion{.major = major, .minor = minor, .patch = patch}; /*DiskInfo*/ try { @@ -241,7 +242,9 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, // "FSTotalBytes: 63966400512" // "FSFreeBytes: 2867101696" // "FSBlockSize: 4096" + // FIXME: it's too slow on older devices? AfcDeviceInfo *info = new AfcDeviceInfo(); + qDebug() << "afc_get_device_info..."; IdeviceFfiError *err = afc_get_device_info(afcClient, info); if (err) { qDebug() << "AFC get device info error code: " << err->message; @@ -302,7 +305,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, /*BatteryInfo*/ plist_t diagnostics = nullptr; - get_battery_info(provider, diagnostics); + get_battery_info(diagRelay, diagnostics); if (!diagnostics) { qDebug() << "Failed to get diagnostics plist."; @@ -365,6 +368,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, // FIXME:spawn on a new thread? // wireless connections sometimes take more than 10sec to connect // and ofc it freezes the ui +// TODO:idevice_start_session ? iDescriptorInitDeviceResult init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) { @@ -388,9 +392,13 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) IdeviceHandle *deviceHandle = nullptr; HeartbeatClientHandle *heartbeat = nullptr; HeartBeatThread *heartbeatThread = nullptr; - + ImageMounterHandle *image_mounter = nullptr; + DiagnosticsRelayClientHandle *diagnostics_relay = nullptr; + ScreenshotrClientHandle *screenshotr_client = nullptr; + LocationSimulationHandle *location_simulation = nullptr; // FIXME: remove debug std::stringstream ss; + plist_t val = nullptr; // 1. Connect to usbmuxd IdeviceFfiError *err = @@ -428,8 +436,18 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) qDebug() << "Failed to create wireless provider"; goto cleanup; } - // err = heartbeat_new(); - // heartbeat_connect + err = heartbeat_connect(provider, &heartbeat); + if (err) { + qDebug() << "Failed to start Heartbeat service"; + goto cleanup; + } + heartbeatThread = new HeartBeatThread(heartbeat); + heartbeatThread->start(); + + while (!heartbeatThread->initialCompleted()) { + sleep(1); + } + } else { UsbmuxdDeviceHandle **devices; @@ -458,17 +476,11 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) goto cleanup; } - // 4. Connect to lockdown (actual function name) err = lockdownd_connect(provider, &lockdown); if (err) { qDebug() << "Failed to connect to lockdown"; goto cleanup; } - // err = idevice_new(socket, "iDescriptor", &deviceHandle); - // if (err) { - // qDebug() << "Failed to create idevice handle"; - // goto cleanup; - // } err = idevice_provider_get_pairing_file(provider, &pairing_file); if (err) { @@ -482,71 +494,79 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) goto cleanup; } - uint16_t heartbeat_port; - bool heartbeat_ssl; - // if (isWireless) { - // err = lockdownd_start_service(lockdown, "com.apple.heartbeat", - // &heartbeat_port, &heartbeat_ssl); - - // Start heartbeat client to keep connection alive - err = heartbeat_connect(provider, &heartbeat); - if (err) { - qDebug() << "Failed to start Heartbeat service"; - goto cleanup; - } - heartbeatThread = new HeartBeatThread(heartbeat); - heartbeatThread->start(); - - // while (!heartbeatThread->initialCompleted()) { - // sleep(1); - // } - if (err) { qDebug() << "Failed to connect to Heartbeat client"; goto cleanup; } - qDebug() << "Heartbeat client created successfully"; - // } - - // 5. Start AFC service - uint16_t afc_port; - bool afc_ssl; - err = - lockdownd_start_service(lockdown, "com.apple.afc", &afc_port, &afc_ssl); - if (err) { - qDebug() << "Failed to start AFC service"; - goto cleanup; - } - - // 6. Create AFC client from provider err = afc_client_connect(provider, &afc_client); if (err) { qDebug() << "Failed to create AFC client"; goto cleanup; } - // // 7. AFC2 is optional - // uint16_t afc2_port; - // bool afc2_ssl; - // err = lockdownd_start_service(lockdown, "com.apple.afc2", &afc2_port, - // &afc2_ssl); - // if (!err) { - // err = afc_client_connect(provider, &afc2_client); + err = image_mounter_connect(provider, &image_mounter); + if (err) { + qDebug() << "Failed to create Image Mounter client"; + goto cleanup; + } + + err = diagnostics_relay_client_connect(provider, &diagnostics_relay); + + if (err) { + qDebug() << "Failed to create Diagnostics Relay client"; + goto cleanup; + } + + // err = screenshotr_connect(provider, &screenshotr_client); + + // if (err) { + // qDebug() << "Failed to create Screenshotr client"; + // goto cleanup; // } + err = afc2_client_connect(provider, &afc2_client); + if (err) { + qDebug() << "Failed to create AFC2 client"; + // dont cleanup here, afc2 is optional + } + + // FIXME: will probably not work on iOS 17 and above + // requires dev image disk + // err = location_simulation_connect(provider, &location_simulation); + // if (err) { + // qDebug() << "Failed to create Location Simulation client"; + // goto cleanup; + // } get_device_info_xml(udid.toUtf8().constData(), lockdown, infoXml); // infoXml.print(ss, " "); // " " for indentation // qDebug().noquote() << "--- Full Device Info XML ---" // << QString::fromStdString(ss.str()); + // Received plist: { + // Domain: "com.apple.mobile.wireless_lockdown", + // Key: "EnableWifiConnections", + // Request: "GetValue", + // Value: true + // } + lockdownd_get_value(lockdown, "EnableWifiConnections", + "com.apple.mobile.wireless_lockdown", &val); + if (val) + plist_print(val); + result.provider = provider; result.success = true; result.afcClient = afc_client; result.afc2Client = afc2_client; result.lockdown = lockdown; + result.imageMounter = image_mounter; + result.screenshotrClient = screenshotr_client; + result.diagRelay = std::make_shared( + DiagnosticsRelay::adopt(diagnostics_relay)); + result.locationSimulation = location_simulation; AppContext::sharedInstance()->cachePairingFile(udid, pairing_file); - fullDeviceInfo(infoXml, afc_client, provider, result); + result.deviceInfo.isWireless = isWireless; + fullDeviceInfo(infoXml, afc_client, result.diagRelay.get(), result); cleanup: // Cleanup on error diff --git a/src/core/services/install_ipa.cpp b/src/core/services/install_ipa.cpp index 01a2765..761b416 100644 --- a/src/core/services/install_ipa.cpp +++ b/src/core/services/install_ipa.cpp @@ -17,538 +17,95 @@ * along with this program. If not, see . */ -#ifdef HAVE_CONFIG_H -#include -#endif -#include -#define _GNU_SOURCE 1 -#define __USE_GNU 1 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "../../iDescriptor.h" -#ifdef HAVE_UNISTD_H -#include -#endif -#ifndef WIN32 -#include -#endif - -#include -#include -#include -#include -#include - - -#include - -#include - -#ifdef WIN32 -#include -#define wait_ms(x) Sleep(x) -#else -#define wait_ms(x) \ - { \ - struct timespec ts; \ - ts.tv_sec = 0; \ - ts.tv_nsec = x * 1000000; \ - nanosleep(&ts, NULL); \ - } -#endif - -#define ITUNES_METADATA_PLIST_FILENAME "iTunesMetadata.plist" - -const char PKG_PATH[] = "PublicStaging"; - -struct install_status_data { - int command_completed; - int err_occurred; - char *last_status; -}; - -static void status_cb(plist_t command, plist_t status, void *user_data) +IdeviceFfiError *_install_IPA(const iDescriptorDevice *device, + const char *filePath, const char *filename) { - struct install_status_data *isd = (struct install_status_data *)user_data; - if (command && status) { - char *command_name = NULL; - instproxy_command_get_name(command, &command_name); + IdeviceFfiError *err = nullptr; + InstallationProxyClientHandle *instproxy_client = NULL; + AfcFileHandle *file = NULL; + std::string dest_path_str = "/PublicStaging/" + std::string(filename); + AfcFileInfo info = {}; + uint8_t *data = NULL; + size_t length = 0; - /* get status */ - char *status_name = NULL; - instproxy_status_get_name(status, &status_name); + qDebug() << "Uploading " << filePath << " to " << dest_path_str.c_str() + << "..."; - if (status_name) { - if (!strcmp(status_name, "Complete")) { - isd->command_completed = 1; - } + err = afc_get_file_info(device->afcClient, "/PublicStaging", &info); + // -33 /* No such file or directory */ + if (err != NULL && err->code == -33) { + qDebug() << "/PublicStaging does not exist, creating..."; + err = afc_make_directory(device->afcClient, "/PublicStaging"); + if (err != NULL) { + qDebug() << "Failed to create /PublicStaging: [" << err->code + << "] " << err->message; + goto cleanup; } - - /* get error if any */ - char *error_name = NULL; - char *error_description = NULL; - uint64_t error_code = 0; - instproxy_status_get_error(status, &error_name, &error_description, - &error_code); - - /* output/handling */ - if (!error_name) { - if (status_name) { - /* get progress if any */ - int percent = -1; - instproxy_status_get_percent_complete(status, &percent); - - if (isd->last_status && - (strcmp(isd->last_status, status_name))) { - printf("\n"); - } - - if (percent >= 0) { - printf("\r%s: %s (%d%%)", command_name, status_name, - percent); - } else { - printf("\r%s: %s", command_name, status_name); - } - if (isd->command_completed) { - printf("\n"); - } - } - } else { - /* report error to the user */ - if (error_description) - fprintf(stderr, - "ERROR: %s failed. Got error \"%s\" with code " - "0x%08" PRIx64 ": %s\n", - command_name, error_name, error_code, - error_description ? error_description : "N/A"); - else - fprintf(stderr, "ERROR: %s failed. Got error \"%s\".\n", - command_name, error_name); - isd->err_occurred = 1; - } - - /* clean up */ - free(error_name); - free(error_description); - - free(isd->last_status); - isd->last_status = status_name; - - free(command_name); - command_name = NULL; - } else { - fprintf(stderr, "ERROR: %s was called with invalid arguments!\n", - __func__); - } -} - -static int zip_get_contents(struct zip *zf, const char *filename, - int locate_flags, char **buffer, uint32_t *len) -{ - struct zip_stat zs; - struct zip_file *zfile; - int zindex = zip_name_locate(zf, filename, locate_flags); - - *buffer = NULL; - *len = 0; - - if (zindex < 0) { - return -1; + qDebug() << "/PublicStaging created successfully"; + } else if (err != NULL) { + qDebug() << "Failed to get info for /PublicStaging: [" << err->code + << "] " << err->message; + goto cleanup; } - zip_stat_init(&zs); - - if (zip_stat_index(zf, zindex, 0, &zs) != 0) { - fprintf(stderr, "ERROR: zip_stat_index '%s' failed!\n", filename); - return -2; + if (!read_file(filePath, &data, &length)) { + err = new IdeviceFfiError{-1, "Failed to read IPA file"}; + goto cleanup; } - if (zs.size > 10485760) { - fprintf(stderr, "ERROR: file '%s' is too large!\n", filename); - return -3; + // todo should we use from service manager safe afc functions ? + err = afc_file_open(device->afcClient, dest_path_str.c_str(), AfcWrOnly, + &file); + if (err != NULL) { + qDebug() << "Failed to open destination file: [" << err->code << "] " + << err->message; + goto cleanup; } - zfile = zip_fopen_index(zf, zindex, 0); - if (!zfile) { - fprintf(stderr, "ERROR: zip_fopen '%s' failed!\n", filename); - return -4; + err = afc_file_write(file, data, length); + + if (err != NULL) { + qDebug() << "Failed to write file: [" << err->code << "] " + << err->message; + goto cleanup; } - *buffer = (char *)malloc(zs.size); - if (zs.size > LLONG_MAX || - zip_fread(zfile, *buffer, zs.size) != (zip_int64_t)zs.size) { - fprintf(stderr, "ERROR: zip_fread %" PRIu64 " bytes from '%s'\n", - (uint64_t)zs.size, filename); - free(*buffer); - *buffer = NULL; - zip_fclose(zfile); - return -5; - } - *len = zs.size; - zip_fclose(zfile); - return 0; -} + qDebug() << "Upload completed successfully"; -static int zip_get_app_directory(struct zip *zf, char **path) -{ - zip_int64_t i = 0; - zip_int64_t c = (zip_int64_t)zip_get_num_entries(zf, 0); - int len = 0; - const char *name = NULL; - - /* look through all filenames in the archive */ - do { - /* get filename at current index */ - name = zip_get_name(zf, i++, 0); - if (name != NULL) { - /* check if we have a "Payload/.../" name */ - len = strlen(name); - if (!strncmp(name, "Payload/", 8) && (len > 8)) { - /* skip hidden files */ - if (name[8] == '.') - continue; - - /* locate the second directory delimiter */ - const char *p = name + 8; - do { - if (*p == '/') { - break; - } - } while (p++ != NULL); - - /* try next entry if not found */ - if (p == NULL) - continue; - - len = p - name + 1; - - /* make sure app directory endwith .app */ - if (len < 12 || strncmp(p - 4, ".app", 4)) { - continue; - } - - if (path != NULL) { - free(*path); - *path = NULL; - } - - /* allocate and copy filename */ - *path = (char *)malloc(len + 1); - strncpy(*path, name, len); - - /* add terminating null character */ - char *t = *path + len; - *t = '\0'; - break; - } - } - } while (i < c); - - if (*path == NULL) { - return -1; + err = installation_proxy_connect(device->provider, &instproxy_client); + if (err != NULL) { + qDebug() << "Failed to connect to installation proxy:" << err->message + << "Code:" << err->code; + goto cleanup; } - return 0; -} - -static int afc_upload_file(afc_client_t afc, const char *filename, - const char *dstfn) -{ - FILE *f = NULL; - uint64_t af = 0; - char buf[1048576]; - - f = fopen(filename, "rb"); - if (!f) { - fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno)); - return -1; - } - - if ((afc_file_open(afc, dstfn, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || - !af) { - fclose(f); - fprintf(stderr, "afc_file_open on '%s' failed!\n", dstfn); - return -1; - } - - size_t amount = 0; - do { - amount = fread(buf, 1, sizeof(buf), f); - if (amount > 0) { - uint32_t written, total = 0; - while (total < amount) { - written = 0; - afc_error_t aerr = - afc_file_write(afc, af, buf, amount, &written); - if (aerr != AFC_E_SUCCESS) { - fprintf(stderr, "AFC Write error: %d\n", aerr); - break; - } - total += written; - } - if (total != amount) { - fprintf(stderr, "Error: wrote only %u of %u\n", total, - (uint32_t)amount); - afc_file_close(afc, af); - fclose(f); - return -1; - } - } - } while (amount > 0); - - afc_file_close(afc, af); - fclose(f); - - return 0; -} - -instproxy_error_t install_IPA(idevice_t device, afc_client_t afc, - const char *filePath) -{ - lockdownd_client_t client = NULL; - instproxy_client_t ipc = NULL; - lockdownd_service_descriptor_t service = NULL; - instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; - char *bundleidentifier = NULL; - struct install_status_data status_data = {0, 0, NULL}; - plist_t sinf = NULL; - plist_t meta = NULL; - char *pkgname = NULL; - struct stat fst; - char **strs = NULL; - plist_t client_opts = instproxy_client_options_new(); - char *zbuf = NULL; - uint32_t len = 0; - plist_t meta_dict = NULL; - int errp = 0; - struct zip *zf = zip_open(filePath, 0, &errp); - plist_t info = NULL; - char *filename = NULL; - char *app_directory_name = NULL; - char *bundleexecutable = NULL; - plist_t bname = NULL; - char *sinfname = NULL; - - if (!device || !filePath || !afc) { - fprintf(stderr, "ERROR: Invalid arguments passed to install_IPA.\n"); - return INSTPROXY_E_INVALID_ARG; - } - - lockdownd_error_t lerr = lockdownd_client_new_with_handshake( - device, &client, "ideviceinstaller"); - if (lerr != LOCKDOWN_E_SUCCESS) { - fprintf(stderr, "Could not connect to lockdownd: %s. Exiting.\n", - lockdownd_strerror(lerr)); - return INSTPROXY_E_OP_FAILED; - } - - lerr = lockdownd_start_service( - client, "com.apple.mobile.installation_proxy", &service); - if (lerr != LOCKDOWN_E_SUCCESS) { - fprintf(stderr, - "Could not start com.apple.mobile.installation_proxy: %s\n", - lockdownd_strerror(lerr)); - lockdownd_client_free(client); - return INSTPROXY_E_OP_FAILED; - } - - err = instproxy_client_new(device, service, &ipc); - if (service) { - lockdownd_service_descriptor_free(service); - service = NULL; - } - - if (err != INSTPROXY_E_SUCCESS) { - fprintf(stderr, "Could not connect to installation_proxy!\n"); - lockdownd_client_free(client); - return err; - } - - setbuf(stdout, NULL); - - if (stat(filePath, &fst) != 0) { - fprintf(stderr, "ERROR: stat: %s: %s\n", filePath, strerror(errno)); - err = INSTPROXY_E_INVALID_ARG; - goto leave_cleanup; - } - - if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) { - if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) { - fprintf(stderr, - "WARNING: Could not create directory '%s' on device!\n", - PKG_PATH); - } - } - if (strs) { - int i = 0; - while (strs[i]) { - free(strs[i]); - i++; - } - free(strs); - } - - if (!zf) { - fprintf(stderr, "ERROR: zip_open: %s: %d\n", filePath, errp); - err = INSTPROXY_E_INVALID_ARG; - goto leave_cleanup; - } - - /* extract iTunesMetadata.plist from package */ - if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == - 0) { - meta = plist_new_data(zbuf, len); - plist_from_memory(zbuf, len, &meta_dict, NULL); - } - if (!meta_dict) { - plist_free(meta); - meta = NULL; - fprintf(stderr, "WARNING: could not locate %s in archive!\n", - ITUNES_METADATA_PLIST_FILENAME); - } - free(zbuf); - - /* determine .app directory in archive */ - zbuf = NULL; - len = 0; - - if (zip_get_app_directory(zf, &app_directory_name)) { - fprintf(stderr, "ERROR: Unable to locate .app directory in archive. " - "Make sure it is inside a 'Payload' directory.\n"); - err = INSTPROXY_E_INVALID_ARG; - goto zip_cleanup; - } - - /* construct full filename to Info.plist */ - filename = (char *)malloc(strlen(app_directory_name) + 10 + 1); - strcpy(filename, app_directory_name); - free(app_directory_name); - app_directory_name = NULL; - strcat(filename, "Info.plist"); - - if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) { - fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename); - free(filename); - err = INSTPROXY_E_INVALID_ARG; - goto zip_cleanup; - } - free(filename); - plist_from_memory(zbuf, len, &info, NULL); - free(zbuf); - - if (!info) { - fprintf(stderr, "Could not parse Info.plist!\n"); - err = INSTPROXY_E_INVALID_ARG; - goto zip_cleanup; - } - - bname = plist_dict_get_item(info, "CFBundleExecutable"); - if (bname) { - plist_get_string_val(bname, &bundleexecutable); - } - - bname = plist_dict_get_item(info, "CFBundleIdentifier"); - if (bname) { - plist_get_string_val(bname, &bundleidentifier); - } - plist_free(info); - info = NULL; - - if (!bundleexecutable) { - fprintf(stderr, "Could not determine value for CFBundleExecutable!\n"); - err = INSTPROXY_E_INVALID_ARG; - goto zip_cleanup; - } - - if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, - bundleexecutable) < 0) { - fprintf(stderr, "Out of memory!?\n"); - err = INSTPROXY_E_UNKNOWN_ERROR; - goto zip_cleanup; - } - free(bundleexecutable); - - /* extract .sinf from package */ - zbuf = NULL; - len = 0; - if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) { - sinf = plist_new_data(zbuf, len); - } else { - fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname); - } - free(sinfname); - free(zbuf); - - /* copy archive to device */ - pkgname = NULL; - if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) { - fprintf(stderr, "Out of memory!?\n"); - err = INSTPROXY_E_UNKNOWN_ERROR; - goto zip_cleanup; - } - - printf("Copying '%s' to device... ", filePath); - - if (afc_upload_file(afc, filePath, pkgname) < 0) { - printf("FAILED\n"); - free(pkgname); - err = INSTPROXY_E_OP_FAILED; - goto zip_cleanup; - } - - printf("DONE.\n"); - - if (bundleidentifier) { - instproxy_client_options_add(client_opts, "CFBundleIdentifier", - bundleidentifier, NULL); - } - if (sinf) { - instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, + qDebug() << "Installing the ipa on idevice, path on device is " + << dest_path_str.c_str(); + err = installation_proxy_install(instproxy_client, dest_path_str.c_str(), NULL); - } - if (meta) { - instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL); - } - -zip_cleanup: - if (zf) { - zip_unchange_all(zf); - zip_close(zf); - } - if (err != INSTPROXY_E_SUCCESS) { - goto leave_cleanup; - } - - /* perform installation */ - printf("Installing '%s'\n", bundleidentifier); - instproxy_install(ipc, pkgname, client_opts, status_cb, &status_data); - - instproxy_client_options_free(client_opts); - free(pkgname); - - while (!status_data.command_completed && !status_data.err_occurred) { - wait_ms(50); - } - - if (status_data.err_occurred) { - err = INSTPROXY_E_OP_FAILED; + if (err != NULL) { + qDebug() << "Installation failed: [" << err->code << "] " + << err->message; + goto cleanup; } else { - err = INSTPROXY_E_SUCCESS; + qDebug() << "Installation completed successfully"; } -leave_cleanup: - instproxy_client_free(ipc); - lockdownd_client_free(client); - free(bundleidentifier); - free(status_data.last_status); +cleanup: + + if (data) { + free(data); + } + + if (file) { + afc_file_close(file); + } + + if (instproxy_client) { + installation_proxy_client_free(instproxy_client); + } return err; -} \ No newline at end of file +} diff --git a/src/core/services/mount_dev_image.cpp b/src/core/services/mount_dev_image.cpp index 5b9a9b3..542d994 100644 --- a/src/core/services/mount_dev_image.cpp +++ b/src/core/services/mount_dev_image.cpp @@ -19,596 +19,61 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#define _GNU_SOURCE 1 #include "../../iDescriptor.h" -#include -#define __USE_GNU 1 -#include -#include -#include -#include #include -#include -#include -#include -#include -#ifndef _WIN32 -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef _WIN32 -#include -#endif +#include -typedef enum { - DISK_IMAGE_UPLOAD_TYPE_AFC, - DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE -} disk_image_upload_type_t; - -static const char *imagetype = NULL; - -static const char PKG_PATH[] = "PublicStaging"; -static const char PATH_PREFIX[] = "/private/var/mobile/Media"; - -static ssize_t mim_upload_cb(void *buf, size_t size, void *userdata) +// Failed to mount developer image: [-21] DeviceLockedMount image result: false +IdeviceFfiError *mount_dev_image(const iDescriptorDevice *device, + const char *image_file, + const char *signature_file) { - return fread(buf, 1, size, (FILE *)userdata); -} -// extend the mobile_image_mounter_error_t type and return sucess if there is -// already a disk image -mobile_image_mounter_error_t mount_dev_image(idevice_t device, - unsigned int device_version, - const char *image_dir_path) -{ - mobile_image_mounter_client_t mim = NULL; - int res = -1; - size_t image_size = 0; - lockdownd_client_t lckd = NULL; - lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; - afc_client_t afc = NULL; - lockdownd_service_descriptor_t service = NULL; - char *image_path = NULL; - char *image_sig_path = NULL; - FILE *f = NULL; - unsigned char *sig = NULL; - plist_t mount_options = NULL; - char *targetname = NULL; - char *mountname = NULL; - disk_image_upload_type_t disk_image_upload_type = - DISK_IMAGE_UPLOAD_TYPE_AFC; - mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; - plist_t result = NULL; - size_t sig_length = 0; - - if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake( - device, &lckd, TOOL_NAME))) { - qDebug() << "ERROR: Could not connect to lockdownd service!"; - res = -1; - goto leave; + if (!device || !device->provider || !device->imageMounter) { + qDebug() + << "Error: Invalid device or provider passed to mount_dev_image"; + return new IdeviceFfiError{// FIXME: whats the code ? + .code = -1, + .message = "Invalid device or provider"}; } - if (device_version >= IDEVICE_DEVICE_VERSION(7, 0, 0)) { - disk_image_upload_type = DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE; + size_t image_len = 0; + size_t signature_len = 0; + uint8_t *image_data = nullptr; + uint8_t *signature_data = nullptr; + IdeviceFfiError *err = nullptr; + + if (err) { + goto cleanup; } - if (disk_image_upload_type == DISK_IMAGE_UPLOAD_TYPE_AFC) { - lockdownd_error_t lerr = - lockdownd_start_service(lckd, "com.apple.afc", &service); - if (lerr != LOCKDOWN_E_SUCCESS) { - qDebug() << "ERROR: Could not start AFC service!" - << lockdownd_strerror(lerr) << "(" << lerr << ")"; - res = -1; - goto leave; - } - - afc_error_t rafc = afc_client_new(device, service, &afc); - if (rafc != AFC_E_SUCCESS) { - qDebug() << "ERROR: Could not connect to AFC!" << afc_strerror(rafc) - << "(" << rafc << ")"; - res = -1; - goto leave; - } - lockdownd_service_descriptor_free(service); - service = NULL; - } - - if (asprintf(&image_path, "%s/DeveloperDiskImage.dmg", image_dir_path) < - 0) { - qDebug() << "Out of memory constructing image path!"; - res = -1; - goto leave; - } - - if (asprintf(&image_sig_path, "%s/DeveloperDiskImage.dmg.signature", - image_dir_path) < 0) { - qDebug() << "Out of memory constructing signature path!"; - res = -1; - goto leave; - } - - qDebug() << "Using image:" << image_path; - qDebug() << "Using signature:" << image_sig_path; - - if (device_version >= IDEVICE_DEVICE_VERSION(16, 0, 0)) { - uint8_t dev_mode_status = 0; - plist_t val = NULL; - ldret = lockdownd_get_value(lckd, "com.apple.security.mac.amfi", - "DeveloperModeStatus", &val); - if (ldret == LOCKDOWN_E_SUCCESS) { - plist_get_bool_val(val, &dev_mode_status); - plist_free(val); - } - if (!dev_mode_status) { - qDebug() << "ERROR: You have to enable Developer Mode on the given " - "device in order to allowing mounting a developer disk " - "image."; - res = -1; - goto leave; - } - } - - lockdownd_start_service(lckd, "com.apple.mobile.mobile_image_mounter", - &service); - - if (!service || service->port == 0) { - qDebug() << "ERROR: Could not start mobile_image_mounter service!"; - res = -1; - goto leave; - } - - if (mobile_image_mounter_new(device, service, &mim) != - MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - qDebug() << "ERROR: Could not connect to mobile_image_mounter!"; - res = -1; - goto leave; - } - lockdownd_service_descriptor_free(service); - service = NULL; - - struct stat fst; - if (stat(image_path, &fst) != 0) { - qDebug() << "ERROR: stat:" << image_path << ":" << strerror(errno); - res = -1; - goto leave; - } - image_size = fst.st_size; - if (device_version < IDEVICE_DEVICE_VERSION(17, 0, 0) && - stat(image_sig_path, &fst) != 0) { - qDebug() << "ERROR: stat:" << image_sig_path << ":" << strerror(errno); - res = -1; - goto leave; - } - - if (device_version < IDEVICE_DEVICE_VERSION(17, 0, 0)) { - f = fopen(image_sig_path, "rb"); - if (!f) { - qDebug() << "Error opening signature file" << image_sig_path << ":" - << strerror(errno); - res = -1; - goto leave; - } - if (fstat(fileno(f), &fst) != 0) { - qDebug() << "Error: fstat:" << strerror(errno); - res = -1; - goto leave; - } - sig = (unsigned char *)malloc(fst.st_size); - sig_length = fread(sig, 1, fst.st_size, f); - fclose(f); - f = NULL; - if (sig_length == 0) { - qDebug() << "Could not read signature from file" << image_sig_path; - res = -1; - goto leave; - } - - f = fopen(image_path, "rb"); - if (!f) { - qDebug() << "Error opening image file" << image_path << ":" - << strerror(errno); - res = -1; - goto leave; - } + if (!read_file(image_file, &image_data, &image_len)) { + err = new IdeviceFfiError{.code = -1, + .message = "Failed to read image file"}; + goto cleanup; + } else if (!read_file(signature_file, &signature_data, &signature_len)) { + err = new IdeviceFfiError{.code = -1, + .message = "Failed to read signature file"}; + goto cleanup; } else { - char *build_manifest_path = - string_build_path(image_path, "BuildManifest.plist", NULL); - plist_t build_manifest = NULL; - if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) != - 0) { - free(build_manifest_path); - build_manifest_path = string_build_path( - image_path, "Restore", "BuildManifest.plist", NULL); - if (plist_read_from_file(build_manifest_path, &build_manifest, - NULL) == 0) { - char *image_path_new = - string_build_path(image_path, "Restore", NULL); - free(image_path); - image_path = image_path_new; - } - } - if (!build_manifest) { - qDebug() << "Error: Could not locate BuildManifest.plist inside " - "given disk image path!"; - res = -1; - goto leave; - } - - plist_t identifiers = NULL; - mobile_image_mounter_error_t merr = - mobile_image_mounter_query_personalization_identifiers( - mim, NULL, &identifiers); - if (merr != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - qDebug() << "Failed to query personalization identifiers:" << merr; - res = -1; - goto leave; - } - - unsigned int board_id = plist_dict_get_uint(identifiers, "BoardId"); - unsigned int chip_id = plist_dict_get_uint(identifiers, "ChipID"); - - plist_t build_identities = - plist_dict_get_item(build_manifest, "BuildIdentities"); - plist_array_iter iter; - plist_array_new_iter(build_identities, &iter); - plist_t item = NULL; - plist_t build_identity = NULL; - do { - plist_array_next_item(build_identities, iter, &item); - if (!item) { - break; - } - unsigned int bi_board_id = - (unsigned int)plist_dict_get_uint(item, "ApBoardID"); - unsigned int bi_chip_id = - (unsigned int)plist_dict_get_uint(item, "ApChipID"); - if (bi_chip_id == chip_id && bi_board_id == board_id) { - build_identity = item; - break; - } - } while (item); - plist_mem_free(iter); - if (!build_identity) { - qDebug() << "Error: The given disk image is not compatible with " - "the current device."; - res = -1; - goto leave; - } - plist_t p_tc_path = - plist_access_path(build_identity, 4, "Manifest", - "LoadableTrustCache", "Info", "Path"); - if (!p_tc_path) { - qDebug() << "Error: Could not determine path for trust cache!"; - res = -1; - goto leave; - } - plist_t p_dmg_path = plist_access_path( - build_identity, 4, "Manifest", "PersonalizedDMG", "Info", "Path"); - if (!p_dmg_path) { - qDebug() << "Error: Could not determine path for disk image!"; - res = -1; - goto leave; - } - char *tc_path = string_build_path( - image_path, plist_get_string_ptr(p_tc_path, NULL), NULL); - unsigned char *trust_cache = NULL; - uint64_t trust_cache_size = 0; - if (!buffer_read_from_filename(tc_path, (char **)&trust_cache, - &trust_cache_size)) { - qDebug() << "Error: Trust cache does not exist at" << tc_path - << "!"; - res = -1; - goto leave; - } - mount_options = plist_new_dict(); - plist_dict_set_item( - mount_options, "ImageTrustCache", - plist_new_data((char *)trust_cache, trust_cache_size)); - free(trust_cache); - char *dmg_path = string_build_path( - image_path, plist_get_string_ptr(p_dmg_path, NULL), NULL); - free(image_path); - image_path = dmg_path; - f = fopen(image_path, "rb"); - if (!f) { - qDebug() << "Error opening image file" << image_path << ":" - << strerror(errno); - res = -1; - goto leave; - } - - unsigned char buf[8192]; - unsigned char sha384_digest[48]; - sha384_context ctx; - sha384_init(&ctx); - fstat(fileno(f), &fst); - image_size = fst.st_size; - while (!feof(f)) { - ssize_t fr = fread(buf, 1, sizeof(buf), f); - if (fr <= 0) { - break; - } - sha384_update(&ctx, buf, fr); - } - rewind(f); - sha384_final(&ctx, sha384_digest); - unsigned char *manifest = NULL; - unsigned int manifest_size = 0; - /* check if the device already has a personalization manifest for this - * image */ - if (mobile_image_mounter_query_personalization_manifest( - mim, "DeveloperDiskImage", sha384_digest, sizeof(sha384_digest), - &manifest, &manifest_size) == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - qDebug() << "Using existing personalization manifest from device."; + err = image_mounter_mount_developer(device->imageMounter, image_data, + image_len, signature_data, + signature_len); + if (err == NULL) { + printf("Developer image mounted successfully\n"); } else { - /* we need to re-connect in this case */ - mobile_image_mounter_free(mim); - mim = NULL; - if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != - MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - res = -1; - goto leave; - } - qDebug() << "No personalization manifest, requesting from TSS..."; - unsigned char *nonce = NULL; - unsigned int nonce_size = 0; - - /* create new TSS request and fill parameters */ - plist_t request = tss_request_new(NULL); - plist_t params = plist_new_dict(); - tss_parameters_add_from_manifest(params, build_identity, 1); - - /* copy all `Ap,*` items from identifiers */ - plist_dict_iter di = NULL; - plist_dict_new_iter(identifiers, &di); - plist_t node = NULL; - do { - char *key = NULL; - plist_dict_next_item(identifiers, di, &key, &node); - if (node) { - if (!strncmp(key, "Ap,", 3)) { - plist_dict_set_item(request, key, plist_copy(node)); - } - } - free(key); - } while (node); - plist_mem_free(di); - - plist_dict_copy_uint(params, identifiers, "ApECID", "UniqueChipID"); - plist_dict_set_item(params, "ApProductionMode", plist_new_bool(1)); - plist_dict_set_item(params, "ApSecurityMode", plist_new_bool(1)); - plist_dict_set_item(params, "ApSupportsImg4", plist_new_bool(1)); - - /* query nonce from image mounter service */ - merr = mobile_image_mounter_query_nonce(mim, "DeveloperDiskImage", - &nonce, &nonce_size); - if (merr == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - plist_dict_set_item(params, "ApNonce", - plist_new_data((char *)nonce, nonce_size)); - } else { - qDebug() - << "ERROR: Failed to query nonce for developer disk image:" - << merr; - res = -1; - goto leave; - } - mobile_image_mounter_free(mim); - mim = NULL; - - plist_dict_set_item( - params, "ApSepNonce", - plist_new_data("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 20)); - plist_dict_set_item(params, "UID_MODE", plist_new_bool(0)); - tss_request_add_ap_tags(request, params, NULL); - tss_request_add_common_tags(request, params, NULL); - tss_request_add_ap_img4_tags(request, params); - plist_free(params); - - /* request IM4M from TSS */ - plist_t response = tss_request_send(request, NULL); - plist_free(request); - - plist_t p_manifest = plist_dict_get_item(response, "ApImg4Ticket"); - if (!PLIST_IS_DATA(p_manifest)) { - qDebug() << "Failed to get Img4Ticket"; - res = -1; - goto leave; - } - - uint64_t m4m_len = 0; - plist_get_data_val(p_manifest, (char **)&manifest, &m4m_len); - manifest_size = m4m_len; - plist_free(response); - qDebug() << "Done."; + fprintf(stderr, "Failed to mount developer image: [%d] %s", + err->code, err->message); + goto cleanup; } - sig = manifest; - sig_length = manifest_size; - - imagetype = "Personalized"; } - if (asprintf(&targetname, "%s/%s", PKG_PATH, "staging.dimage") < 0) { - qDebug() << "Out of memory!?"; - res = -1; - goto leave; - } - if (asprintf(&mountname, "%s/%s", PATH_PREFIX, targetname) < 0) { - qDebug() << "Out of memory!?"; - res = -1; - goto leave; - } +cleanup: - if (!imagetype) { - imagetype = "Developer"; - } - - switch (disk_image_upload_type) { - case DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE: - qDebug() << "Uploading" << image_path; - err = mobile_image_mounter_upload_image(mim, imagetype, image_size, sig, - sig_length, mim_upload_cb, f); - break; - case DISK_IMAGE_UPLOAD_TYPE_AFC: - default: - qDebug() << "Uploading" << image_path << "--> afc:///" << targetname; - plist_t fileinfo = NULL; - if (afc_get_file_info_plist(afc, PKG_PATH, &fileinfo) != - AFC_E_SUCCESS) { - if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) { - qDebug() << "WARNING: Could not create directory" << PKG_PATH - << "on device!"; - } - } - plist_free(fileinfo); - - uint64_t af = 0; - if ((afc_file_open(afc, targetname, AFC_FOPEN_WRONLY, &af) != - AFC_E_SUCCESS) || - !af) { - qDebug() << "afc_file_open on" << targetname << "failed!"; - res = -1; - goto leave; - } - - char buf[8192]; - size_t amount = 0; - do { - amount = fread(buf, 1, sizeof(buf), f); - if (amount > 0) { - uint32_t written, total = 0; - while (total < amount) { - written = 0; - if (afc_file_write(afc, af, buf + total, amount - total, - &written) != AFC_E_SUCCESS) { - qDebug() << "AFC Write error!"; - break; - } - total += written; - } - if (total != amount) { - qDebug() << "Error: wrote only" << total << "of" - << (unsigned int)amount; - afc_file_close(afc, af); - res = -1; - goto leave; - } - } - } while (amount > 0); - - afc_file_close(afc, af); - break; - } - - if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - qDebug() << "ERROR: Device is locked, can't mount. Unlock device " - "and try again."; - } else { - qDebug() << "ERROR: Unknown error occurred, can't mount."; - } - res = -1; - goto leave; - } - - qDebug() << "Mounting..."; - err = mobile_image_mounter_mount_image_with_options( - mim, mountname, sig, sig_length, imagetype, mount_options, &result); - if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - if (result) { - plist_t node = plist_dict_get_item(result, "Status"); - if (node) { - char *status = NULL; - plist_get_string_val(node, &status); - if (status) { - if (!strcmp(status, "Complete")) { - qDebug() << "Done."; - res = 0; - } else { - err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; - } - free(status); - } else { - err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; - } - } else { - err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; - } - if (res != 0) { // If not complete, log the error - err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; - node = plist_dict_get_item(result, "Error"); - if (node) { - char *error = NULL; - plist_get_string_val(node, &error); - if (error) { - qDebug() << "Error:" << error; - free(error); - } - node = plist_dict_get_item(result, "DetailedError"); - if (node) { - const char *str = plist_get_string_ptr(node, NULL); - auto sd_str = std::string(str); - if (sd_str.find("already mounted at /Developer") != - std::string::npos) { - // FIXME: need an error code for this - qDebug() << "Image is already mounted"; - // res = 0; - // err = MOBILE_IMAGE_MOUNTER_E_SUCCESS; - } else { - qDebug() << "DetailedError:" << str; - } - } - } - } - } - } else { - qDebug() << "Error: mount_image returned" << err; - } - -leave: - if (f) { - fclose(f); - } - if (result) { - plist_free(result); - } - if (mim) { - mobile_image_mounter_free(mim); - } - if (afc) { - afc_client_free(afc); - } - if (lckd) { - lockdownd_client_free(lckd); - } - if (image_path) { - free(image_path); - } - if (image_sig_path) { - free(image_sig_path); - } - if (sig) { - free(sig); - } - if (mount_options) { - plist_free(mount_options); - } - if (targetname) { - free(targetname); - } - if (mountname) { - free(mountname); - } + if (image_data) + free(image_data); + if (signature_data) + free(signature_data); return err; } \ No newline at end of file diff --git a/src/core/services/query_mobilegestalt.cpp b/src/core/services/query_mobilegestalt.cpp deleted file mode 100644 index 82e2a4e..0000000 --- a/src/core/services/query_mobilegestalt.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * iDescriptor: A free and open-source idevice management tool. - * - * Copyright (C) 2025 Uncore - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include "../../iDescriptor.h" -#include "libimobiledevice/diagnostics_relay.h" -#include -#include - -bool query_mobile_gestalt(iDescriptorDevice *id_device, const QStringList &keys, - uint32_t &xml_size, char *&xml_data) -{ - if (!id_device) { - qDebug() << "Invalid device"; - return false; - } - - diagnostics_relay_client_t diagnostics_client = nullptr; - if (diagnostics_relay_client_start_service(id_device->device, - &diagnostics_client, nullptr) != - DIAGNOSTICS_RELAY_E_SUCCESS) { - qDebug() << "Failed to start diagnostics service"; - return false; - } - - plist_t result = nullptr; - plist_t keys_array = plist_new_array(); - for (const QString &key : keys) { - plist_t key_node = plist_new_string(key.toStdString().c_str()); - plist_array_append_item(keys_array, key_node); - } - - diagnostics_relay_error_t err = diagnostics_relay_query_mobilegestalt( - diagnostics_client, keys_array, &result); - - plist_free(keys_array); // Free the keys array - - if (err != DIAGNOSTICS_RELAY_E_SUCCESS) { - qDebug() << "Failed to query mobile gestalt"; - diagnostics_relay_client_free(diagnostics_client); - return false; - } - - if (!result) { - qDebug() << "No result from mobile gestalt query"; - diagnostics_relay_client_free(diagnostics_client); - return false; - } - - plist_to_xml(result, &xml_data, &xml_size); - plist_free(result); // Free the result plist - diagnostics_relay_client_free(diagnostics_client); - - return true; -} diff --git a/src/core/services/restart.cpp b/src/core/services/restart.cpp deleted file mode 100644 index a829009..0000000 --- a/src/core/services/restart.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * idevicediagnostics.c - * Retrieves diagnostics information from device - * - * Copyright (c) 2012 Martin Szulecki All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "../../iDescriptor.h" -#include -#include -#include - -bool restart(std::string _udid) -{ - idevice_t device = NULL; - lockdownd_client_t lockdown_client = NULL; - diagnostics_relay_client_t diagnostics_client = NULL; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - lockdownd_service_descriptor_t service = NULL; - const char *udid = _udid.c_str(); - int use_network = 0; - - if (idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX) != - IDEVICE_E_SUCCESS) { - printf("ERROR: No device found, is it plugged in?\n"); - return false; - } - - if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake( - device, &lockdown_client, TOOL_NAME))) { - idevice_free(device); - printf("ERROR: Could not connect to lockdownd, error code %d\n", ret); - return false; - } - - /* attempt to use newer diagnostics service available on iOS 5 and later */ - ret = lockdownd_start_service( - lockdown_client, "com.apple.mobile.diagnostics_relay", &service); - if (ret == LOCKDOWN_E_INVALID_SERVICE) { - /* attempt to use older diagnostics service */ - ret = lockdownd_start_service( - lockdown_client, "com.apple.iosdiagnostics.relay", &service); - } - lockdownd_client_free(lockdown_client); - - if (ret != LOCKDOWN_E_SUCCESS) { - idevice_free(device); - printf("ERROR: Could not start diagnostics relay service: %s\n", - lockdownd_strerror(ret)); - return false; - } - - if ((ret == LOCKDOWN_E_SUCCESS) && service && (service->port > 0)) { - if (diagnostics_relay_client_new(device, service, - &diagnostics_client) != - DIAGNOSTICS_RELAY_E_SUCCESS) { - printf("ERROR: Could not connect to diagnostics_relay!\n"); - } else { - - if (diagnostics_relay_restart( - diagnostics_client, - DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) == - DIAGNOSTICS_RELAY_E_SUCCESS) { - printf("Restarting device.\n"); - return true; - } else { - printf("ERROR: Failed to restart device.\n"); - } - } - - diagnostics_relay_goodbye(diagnostics_client); - diagnostics_relay_client_free(diagnostics_client); - } - - return false; -} \ No newline at end of file diff --git a/src/core/services/set_location.cpp b/src/core/services/set_location.cpp index cfe8b0d..952ef8e 100644 --- a/src/core/services/set_location.cpp +++ b/src/core/services/set_location.cpp @@ -20,101 +20,80 @@ */ #include "../../iDescriptor.h" -#define DT_SIMULATELOCATION_SERVICE "com.apple.dt.simulatelocation" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(__APPLE__) -#include -#include -#define htobe32(x) OSSwapHostToBigInt32(x) -#elif defined(_WIN32) -#include -#define htobe32(x) htonl(x) -#else -#include -#endif -#include enum { SET_LOCATION = 0, RESET_LOCATION = 1 }; -bool set_location(idevice_t device, char *lat, char *lon) +bool set_location(void *device, char *lat, char *lon) { - uint32_t mode = 0; - lockdownd_client_t lockdown = NULL; - lockdownd_error_t lerr = - lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); - try { - /* code */ + return false; + // uint32_t mode = 0; + // lockdownd_client_t lockdown = NULL; + // lockdownd_error_t lerr = + // lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME); + // try { + // /* code */ - if (lerr != LOCKDOWN_E_SUCCESS) { - idevice_free(device); - printf("ERROR: Could not connect to lockdownd: %s (%d)\n", - lockdownd_strerror(lerr), lerr); - return false; - } + // if (lerr != LOCKDOWN_E_SUCCESS) { + // idevice_free(device); + // printf("ERROR: Could not connect to lockdownd: %s (%d)\n", + // lockdownd_strerror(lerr), lerr); + // return false; + // } - lockdownd_service_descriptor_t svc = NULL; - lerr = lockdownd_start_service(lockdown, DT_SIMULATELOCATION_SERVICE, - &svc); - if (lerr != LOCKDOWN_E_SUCCESS) { - unsigned int device_version = idevice_get_device_version(device); - lockdownd_client_free(lockdown); - idevice_free(device); + // lockdownd_service_descriptor_t svc = NULL; + // lerr = lockdownd_start_service(lockdown, DT_SIMULATELOCATION_SERVICE, + // &svc); + // if (lerr != LOCKDOWN_E_SUCCESS) { + // unsigned int device_version = idevice_get_device_version(device); + // lockdownd_client_free(lockdown); + // idevice_free(device); - return false; - } - lockdownd_client_free(lockdown); + // return false; + // } + // lockdownd_client_free(lockdown); - service_client_t service = NULL; + // service_client_t service = NULL; - service_error_t serr = service_client_new(device, svc, &service); + // service_error_t serr = service_client_new(device, svc, &service); - lockdownd_service_descriptor_free(svc); + // lockdownd_service_descriptor_free(svc); - if (serr != SERVICE_E_SUCCESS) { - idevice_free(device); - return false; - } + // if (serr != SERVICE_E_SUCCESS) { + // idevice_free(device); + // return false; + // } - uint32_t l; - uint32_t s = 0; + // uint32_t l; + // uint32_t s = 0; - l = htobe32(mode); - service_send(service, (const char *)&l, 4, &s); - if (mode == SET_LOCATION) { - int len = 4 + strlen(lat) + 4 + strlen(lon); - char *buf = (char *)malloc(len); - uint32_t latlen; - latlen = strlen(lat); - l = htobe32(latlen); - memcpy(buf, &l, 4); - memcpy(buf + 4, lat, latlen); - uint32_t longlen = strlen(lon); - l = htobe32(longlen); - memcpy(buf + 4 + latlen, &l, 4); - memcpy(buf + 4 + latlen + 4, lon, longlen); + // l = htobe32(mode); + // service_send(service, (const char *)&l, 4, &s); + // if (mode == SET_LOCATION) { + // int len = 4 + strlen(lat) + 4 + strlen(lon); + // char *buf = (char *)malloc(len); + // uint32_t latlen; + // latlen = strlen(lat); + // l = htobe32(latlen); + // memcpy(buf, &l, 4); + // memcpy(buf + 4, lat, latlen); + // uint32_t longlen = strlen(lon); + // l = htobe32(longlen); + // memcpy(buf + 4 + latlen, &l, 4); + // memcpy(buf + 4 + latlen + 4, lon, longlen); - s = 0; - service_send(service, buf, len, &s); - free(buf); // <-- free the buffer after use - } + // s = 0; + // service_send(service, buf, len, &s); + // free(buf); // <-- free the buffer after use + // } - return true; - } catch (...) { - if (lockdown) { - lockdownd_client_free(lockdown); - } - if (device) { - idevice_free(device); - } - qDebug() << "Exception occurred while setting location."; - return false; - } + // return true; + // } catch (...) { + // if (lockdown) { + // lockdownd_client_free(lockdown); + // } + // if (device) { + // idevice_free(device); + // } + // qDebug() << "Exception occurred while setting location."; + // return false; + // } } \ No newline at end of file diff --git a/src/core/services/shutdown.cpp b/src/core/services/shutdown.cpp deleted file mode 100644 index c0d3478..0000000 --- a/src/core/services/shutdown.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - * idevicediagnostics.c - * Retrieves diagnostics information from device - * - * Copyright (c) 2012 Martin Szulecki All Rights Reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "../../iDescriptor.h" -#include -#include -#include - -bool shutdown(idevice_t device) -{ - lockdownd_client_t lockdown_client = NULL; - diagnostics_relay_client_t diagnostics_client = NULL; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - lockdownd_service_descriptor_t service = NULL; - const char *udid = NULL; - int use_network = 0; - - if (LOCKDOWN_E_SUCCESS != (ret = lockdownd_client_new_with_handshake( - device, &lockdown_client, TOOL_NAME))) { - idevice_free(device); - printf("ERROR: Could not connect to lockdownd, error code %d\n", ret); - return false; - } - - /* attempt to use newer diagnostics service available on iOS 5 and later */ - ret = lockdownd_start_service( - lockdown_client, "com.apple.mobile.diagnostics_relay", &service); - if (ret == LOCKDOWN_E_INVALID_SERVICE) { - /* attempt to use older diagnostics service */ - ret = lockdownd_start_service( - lockdown_client, "com.apple.iosdiagnostics.relay", &service); - } - lockdownd_client_free(lockdown_client); - - if (ret != LOCKDOWN_E_SUCCESS) { - idevice_free(device); - printf("ERROR: Could not start diagnostics relay service: %s\n", - lockdownd_strerror(ret)); - return false; - } - - if ((ret == LOCKDOWN_E_SUCCESS) && service && (service->port > 0)) { - if (diagnostics_relay_client_new(device, service, - &diagnostics_client) != - DIAGNOSTICS_RELAY_E_SUCCESS) { - printf("ERROR: Could not connect to diagnostics_relay!\n"); - } else { - - if (diagnostics_relay_shutdown( - diagnostics_client, - DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) == - DIAGNOSTICS_RELAY_E_SUCCESS) { - printf("Shutting down device.\n"); - return true; - } else { - printf("ERROR: Failed to shut down device.\n"); - } - } - - diagnostics_relay_goodbye(diagnostics_client); - diagnostics_relay_client_free(diagnostics_client); - } - - if (service) { - lockdownd_service_descriptor_free(service); - service = NULL; - } - - return false; -} \ No newline at end of file diff --git a/src/core/services/take_screenshot.cpp b/src/core/services/take_screenshot.cpp deleted file mode 100644 index 3e41254..0000000 --- a/src/core/services/take_screenshot.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * idevicescreenshot.c - * Gets a screenshot from a device - * - * Copyright (C) 2010 Nikias Bassen - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include "../../iDescriptor.h" -#include -#include -#include -#include -#include -#include - -QImage get_image(const char *imgdata, uint64_t imgsize) -{ - QByteArray byteArray(imgdata, static_cast(imgsize)); - QImage image; - image.loadFromData(byteArray); - return image; -} - -TakeScreenshotResult take_screenshot(screenshotr_client_t shotr) -{ - TakeScreenshotResult result; - try { - char *imgdata = NULL; - uint64_t imgsize = 0; - if (screenshotr_take_screenshot(shotr, &imgdata, &imgsize) == - SCREENSHOTR_E_SUCCESS) { - QImage image = get_image(imgdata, imgsize); - if (image.isNull()) { - printf("Could not decode screenshot image!\n"); - } else { - result.img = image; - result.success = true; - } - // Always free imgdata after use! - free(imgdata); - } - } catch (const std::exception &e) { - qDebug() << "Exception occurred while taking screenshot:" << e.what(); - } - // Always return result! - return result; -} diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index 56bb589..62e165b 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -97,9 +97,8 @@ void DevDiskImageHelper::start() m_loadingIndicator->start(); showStatus("Please wait..."); - unsigned int device_version = idevice_get_device_version(m_device->device); - unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; - unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF; + unsigned int deviceMajorVersion = + m_device->deviceInfo.parsedDeviceVersion.major; // FIXME:we dont have developer disk images for ios 6 and below if (deviceMajorVersion > 5) { @@ -124,23 +123,22 @@ void DevDiskImageHelper::start() void DevDiskImageHelper::checkAndMount() { - GetMountedImageResult result = - DevDiskManager::sharedInstance()->getMountedImage( - m_device->udid.c_str()); - qDebug() << "checkAndMount result:" << result.success - << result.message.c_str() << QString::fromStdString(result.sig); - if (!result.success) { - showRetryUI(QString::fromStdString(result.message)); - return; - } + // GetMountedImageResult result = + // DevDiskManager::sharedInstance()->getMountedImage(m_device); + // qDebug() << "checkAndMount result:" << result.success + // << result.message.c_str() << QString::fromStdString(result.sig); + // if (!result.success) { + // showRetryUI(QString::fromStdString(result.message)); + // return; + // } - // If image is already mounted - if (!result.sig.empty()) { - finishWithSuccess(); - return; - } + // // If image is already mounted + // if (!result.sig.empty()) { + // finishWithSuccess(); + // return; + // } - onMountButtonClicked(); + // onMountButtonClicked(); } void DevDiskImageHelper::onMountButtonClicked() @@ -151,9 +149,10 @@ void DevDiskImageHelper::onMountButtonClicked() m_isMounting = true; // Check if we need to download first - unsigned int device_version = idevice_get_device_version(m_device->device); - unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; - unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF; + unsigned int deviceMajorVersion = + m_device->deviceInfo.parsedDeviceVersion.major; + unsigned int deviceMinorVersion = + m_device->deviceInfo.parsedDeviceVersion.minor; QList images = DevDiskManager::sharedInstance()->parseImageList( path, deviceMajorVersion, deviceMinorVersion, "", 0); @@ -174,27 +173,28 @@ void DevDiskImageHelper::onMountButtonClicked() } if (hasDownloadedImage) { - // Mount directly - showStatus("Mounting developer disk image..."); + // // Mount directly + // showStatus("Mounting developer disk image..."); - mobile_image_mounter_error_t err = - DevDiskManager::sharedInstance()->mountImage(versionToMount, - m_device); + // mobile_image_mounter_error_t err = + // DevDiskManager::sharedInstance()->mountImage(versionToMount, + // m_device); - m_isMounting = false; - if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - showStatus("Developer disk image mounted successfully"); - finishWithSuccess(); - } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - showRetryUI( - "Device is locked. Please unlock your device and try again."); - } else { - showRetryUI("Failed to mount developer disk image.\n" - "Please ensure:\n" - "• Device is unlocked\n" - "• Using a genuine cable\n" - "• Developer mode is enabled (iOS 16+)"); - } + // m_isMounting = false; + // if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + // showStatus("Developer disk image mounted successfully"); + // finishWithSuccess(); + // } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { + // showRetryUI( + // "Device is locked. Please unlock your device and try + // again."); + // } else { + // showRetryUI("Failed to mount developer disk image.\n" + // "Please ensure:\n" + // "• Device is unlocked\n" + // "• Using a genuine cable\n" + // "• Developer mode is enabled (iOS 16+)"); + // } } else { // Need to download first showStatus( @@ -237,20 +237,21 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version, // Download successful, now mount showStatus("Download complete. Mounting..."); - mobile_image_mounter_error_t err = - DevDiskManager::sharedInstance()->mountImage(version, m_device); + // mobile_image_mounter_error_t err = + // DevDiskManager::sharedInstance()->mountImage(version, m_device); - if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - showStatus("Developer disk image mounted successfully"); - finishWithSuccess(); - } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - showRetryUI( - "Device is locked. Please unlock your device and try again."); - } else { - showRetryUI( - "Failed to mount developer disk image.\n" - "Please ensure the device is unlocked and using a genuine cable."); - } + // if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + // showStatus("Developer disk image mounted successfully"); + // finishWithSuccess(); + // } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { + // showRetryUI( + // "Device is locked. Please unlock your device and try again."); + // } else { + // showRetryUI( + // "Failed to mount developer disk image.\n" + // "Please ensure the device is unlocked and using a genuine + // cable."); + // } } void DevDiskImageHelper::showRetryUI(const QString &errorMessage) diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index 3606463..5404f58 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -22,6 +22,7 @@ #include "devdiskmanager.h" #include "iDescriptor.h" #include "qprocessindicator.h" +#include "servicemanager.h" #include "settingsmanager.h" #include #include @@ -46,12 +47,13 @@ #include #include #include -#include #include DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device, QWidget *parent) - : QWidget{parent}, m_currentDevice(device) + : QWidget{parent}, + m_currentDeviceUdid( + device != nullptr ? QString::fromStdString(device->udid) : QString()) { setupUi(); connect(DevDiskManager::sharedInstance(), &DevDiskManager::imageListFetched, @@ -171,34 +173,34 @@ void DevDiskImagesWidget::onDeviceSelectionChanged(int index) if (device == nullptr) return; - m_currentDevice = device; + m_currentDeviceUdid = QString::fromStdString(device->udid); displayImages(); } void DevDiskImagesWidget::displayImages() { + qDebug() << "Displaying images for device"; m_imageListWidget->clear(); - int deviceMajorVersion = 0; - int deviceMinorVersion = 0; - bool hasConnectedDevice = false; - - if (m_currentDevice && m_currentDevice->device) { - unsigned int device_version = - idevice_get_device_version(m_currentDevice->device); - deviceMajorVersion = (device_version >> 16) & 0xFF; - deviceMinorVersion = (device_version >> 8) & 0xFF; - hasConnectedDevice = true; + // Look up device by UDID + iDescriptorDevice *currentDevice = nullptr; + if (!m_currentDeviceUdid.isEmpty()) { + currentDevice = AppContext::sharedInstance()->getDevice( + m_currentDeviceUdid.toStdString()); } + bool hasConnectedDevice = (currentDevice != nullptr); + + int major = hasConnectedDevice + ? currentDevice->deviceInfo.parsedDeviceVersion.major + : 0; + int minor = hasConnectedDevice + ? currentDevice->deviceInfo.parsedDeviceVersion.minor + : 0; - qDebug() << "Device version:" << deviceMajorVersion << "." - << deviceMinorVersion << "displayImages"; - // Parse images using manager QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); QList allImages = DevDiskManager::sharedInstance()->parseImageList( - path, deviceMajorVersion, deviceMinorVersion, m_mounted_sig.c_str(), - m_mounted_sig_len); + path, major, minor, m_mounted_sig.c_str(), m_mounted_sig_len); qDebug() << "Total images:" << allImages.size(); @@ -307,8 +309,7 @@ void DevDiskImagesWidget::displayImages() // Show device info if available if (hasConnectedDevice) { - QString deviceVersion = - QString("%1.%2").arg(deviceMajorVersion).arg(deviceMinorVersion); + QString deviceVersion = QString("%1.%2").arg(major).arg(minor); m_statusLabel->setText( QString("Connected device: iOS %1 - Compatible images shown at top") .arg(deviceVersion)); @@ -499,7 +500,7 @@ void DevDiskImagesWidget::updateDeviceList() auto devices = AppContext::sharedInstance()->getAllDevices(); if (devices.isEmpty()) { - m_currentDevice = nullptr; + m_currentDeviceUdid.clear(); m_check_mountedButton->setEnabled(false); m_deviceComboBox->setEnabled(false); } else { @@ -511,6 +512,8 @@ void DevDiskImagesWidget::updateDeviceList() if (m_deviceComboBox->count() > 0 && m_deviceComboBox->currentIndex() >= 0) { currentUdid = m_deviceComboBox->currentData().toString(); + } else if (!m_currentDeviceUdid.isEmpty()) { + currentUdid = m_currentDeviceUdid; } m_deviceComboBox->clear(); @@ -582,61 +585,87 @@ void DevDiskImagesWidget::mountImage(const QString &version) m_mountButton->setEnabled(false); m_mountButton->setText("Mounting..."); - mobile_image_mounter_error_t err = - DevDiskManager::sharedInstance()->mountImage(version, m_currentDevice); - auto updateUI = [&]() { m_mountButton->setEnabled(true); m_mountButton->setText("Mount"); m_deviceComboBox->setEnabled(true); }; - switch (err) { - case MOBILE_IMAGE_MOUNTER_E_INVALID_ARG: - QMessageBox::critical(this, "Mount Failed", - "Invalid argument provided for mounting."); - updateUI(); - return; - case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED: - QMessageBox::critical(this, "Mount Failed", - "The device is locked. Please unlock it and try " - "again."); - updateUI(); - return; - case MOBILE_IMAGE_MOUNTER_E_SUCCESS: + auto paths = DevDiskManager::sharedInstance()->getPathsForVersion(version); + + MountedImageInfo info = ServiceManager::getMountedImage( + AppContext::sharedInstance()->getDevice(udid.toStdString())); + + if (info.err == nullptr && info.signature && info.signature_len) { + qDebug() << "Mount image: already mounted sig found" + << QString::fromStdString(std::string((char *)info.signature, + info.signature_len)); + QMessageBox::information(this, "Already Mounted", + QString("A developer disk image is already " + "mounted on %1.") + .arg(m_deviceComboBox->currentText())); + return updateUI(); + } else if (info.err->code == DeviceLockedMountErrorCode) { + QMessageBox::critical(this, "Device Locked", + "The device is locked. Please unlock it and try" + " again."); + mounted_image_info_free(info); + return updateUI(); + } else if (info.err->code == NotFoundErrorCode) { + QMessageBox::critical( + this, "No Mounted Image", + "No developer disk image is mounted on the device."); + mounted_image_info_free(info); + return updateUI(); + } else { + QMessageBox::critical( + this, "Mount Check Failed", + QString("Failed to check mounted image on %1. Try with a " + "genuine cable.") + .arg(m_deviceComboBox->currentText())); + mounted_image_info_free(info); + return updateUI(); + } + + mounted_image_info_free(info); + + iDescriptorDevice *currentDevice = + m_currentDeviceUdid.isEmpty() ? nullptr + : AppContext::sharedInstance()->getDevice( + m_currentDeviceUdid.toStdString()); + + if (!currentDevice) { + QMessageBox::warning(this, "No Device", + "Device is no longer connected."); + return updateUI(); + } + + IdeviceFfiError *err = ServiceManager::mountImage( + currentDevice, paths.first.toStdString().c_str(), + paths.second.toStdString().c_str()); + + if (err == nullptr) { QMessageBox::information(this, "Success", QString("Image mounted successfully on %1.") .arg(m_deviceComboBox->currentText())); - displayImages(); // Refresh to show mounted status - updateUI(); - break; - default: - GetMountedImageResult result = - DevDiskManager::sharedInstance()->getMountedImage( - udid.toStdString().c_str()); - /* - * FIXME: there is no error enum like - * MOBILE_IMAGE_MOUNTER_E_ALREADY_MOUNTED so we work around here - */ - qDebug() << "Mount result:" << result.success << result.message.c_str() - << QString::fromStdString(result.sig); - if (result.success && !result.sig.empty()) { - m_mounted_sig = result.sig; - m_mounted_sig_len = result.sig.size(); - updateUI(); - displayImages(); - QMessageBox::information(this, "Already Mounted", - "There is already a developer disk image " - "mounted on the device."); - return; - } + return updateUI(); + } + qDebug() << "Mount image result:" << err->code + << QString::fromStdString(err->message); + + if (err->code == DeviceLockedMountErrorCode) { + QMessageBox::critical(this, "Mount Failed", + "The device is locked. Please unlock it and try" + " again."); + } else { QMessageBox::critical( this, "Mount Failed", QString("Failed to mount image on %1. Try with a genuine cable.") .arg(m_deviceComboBox->currentText())); - updateUI(); } + idevice_error_free(err); + updateUI(); } void DevDiskImagesWidget::closeEvent(QCloseEvent *event) @@ -674,35 +703,63 @@ void DevDiskImagesWidget::closeEvent(QCloseEvent *event) void DevDiskImagesWidget::checkMountedImage() { - // just in case - if (!m_currentDevice || !m_currentDevice->device) { + iDescriptorDevice *currentDevice = + m_currentDeviceUdid.isEmpty() ? nullptr + : AppContext::sharedInstance()->getDevice( + m_currentDeviceUdid.toStdString()); + + if (!currentDevice) { + qDebug() << "No device selected"; + auto devices = AppContext::sharedInstance()->getAllDevices(); + for (const auto &dev : devices) { + qDebug() << "Device:" + << QString::fromStdString(dev->deviceInfo.deviceName) + << "UDID:" << QString::fromStdString(dev->udid); + } return; } if (m_deviceComboBox->currentIndex() < 0) { + qDebug() << "No device selected in combo box"; return; } - GetMountedImageResult result = - DevDiskManager::sharedInstance()->getMountedImage( - m_currentDevice->udid.c_str()); + /* + older devices return something like this: + { + "ImagePresent": true, + "ImageSignature": <7b16200b 2ead1830 a59809d1 51e9060b ... 8a 9844eb07 + e0b8e0>, "Status": "Complete" + } + */ + MountedImageInfo info = ServiceManager::getMountedImage(currentDevice); - qDebug() << "checkMountedImage result:" << result.success - << result.message.c_str() << QString::fromStdString(result.sig); - - if (result.success && !result.sig.empty()) { - m_mounted_sig = result.sig; - m_mounted_sig_len = result.sig.size(); + if (info.err == nullptr && info.signature != nullptr && + info.signature_len > 0) { + m_mounted_sig = std::string( + reinterpret_cast(info.signature), info.signature_len); + m_mounted_sig_len = info.signature_len; displayImages(); // Refresh to show mounted status QMessageBox::information( this, "Check Mounted Image", "There is already a developer disk image mounted on the device."); - return; + mounted_image_info_free(info); + } else if (info.err->code == DeviceLockedMountErrorCode) { + QMessageBox::critical(this, "Device Locked", + "The device is locked. Please unlock it and try" + " again."); + mounted_image_info_free(info); + } else if (info.err->code == NotFoundErrorCode) { + QMessageBox::critical( + this, "No Mounted Image", + "No developer disk image is mounted on the device."); + mounted_image_info_free(info); + } else { + QMessageBox::critical( + this, "Check Mounted Image Failed", + QString("Failed to check mounted image on %1. Try with a " + "genuine cable. Error message: %2") + .arg(m_deviceComboBox->currentText()) + .arg(QString::fromStdString(info.err->message))); + mounted_image_info_free(info); } - - QString errorMsg = QString::fromStdString(result.message); - if (errorMsg.isEmpty()) { - errorMsg = "Unknown error occurred while checking mounted image"; - } - - QMessageBox::warning(this, "Check Mounted Image Failed", errorMsg); } diff --git a/src/devdiskimageswidget.h b/src/devdiskimageswidget.h index d1feb60..c6db239 100644 --- a/src/devdiskimageswidget.h +++ b/src/devdiskimageswidget.h @@ -86,7 +86,7 @@ private: QPushButton *m_check_mountedButton; QProcessIndicator *m_processIndicator; - iDescriptorDevice *m_currentDevice; + QString m_currentDeviceUdid; QStringList m_compatibleVersions; QStringList m_otherVersions; diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp index d2d110d..3bdf20a 100644 --- a/src/devdiskmanager.cpp +++ b/src/devdiskmanager.cpp @@ -19,6 +19,7 @@ #include "devdiskmanager.h" #include "iDescriptor.h" +#include "servicemanager.h" #include "settingsmanager.h" #include #include @@ -31,7 +32,6 @@ #include #include #include -#include DevDiskManager *DevDiskManager::sharedInstance() { @@ -326,9 +326,10 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device, std::function callback) { QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); - unsigned int device_version = idevice_get_device_version(device->device); - unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; - unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF; + unsigned int deviceMajorVersion = + device->deviceInfo.parsedDeviceVersion.major; + unsigned int deviceMinorVersion = + device->deviceInfo.parsedDeviceVersion.minor; qDebug() << "Device version:" << deviceMajorVersion << "." << deviceMinorVersion; QList images = @@ -405,37 +406,36 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device, bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device) { QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); - unsigned int device_version = idevice_get_device_version(device->device); - unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; - unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF; QList images = - parseImageList(path, deviceMajorVersion, deviceMinorVersion, "", 0); + parseImageList(path, device->deviceInfo.parsedDeviceVersion.major, + device->deviceInfo.parsedDeviceVersion.minor, "", 0); + return false; // 1. Try to mount an already downloaded compatible image - for (const ImageInfo &info : images) { - if (info.compatibility != ImageCompatibility::Compatible && - info.compatibility != ImageCompatibility::MaybeCompatible) { - continue; - } - if (info.isDownloaded) { - qDebug() << "There is a compatible image already downloaded:" - << info.version; - qDebug() << "Attempting to mount image version" << info.version - << "on device:" << device->udid.c_str(); - if (MOBILE_IMAGE_MOUNTER_E_SUCCESS == - mountImage(info.version, device)) { - qDebug() << "Mounted existing image version" << info.version - << "on device:" << device->udid.c_str(); - return true; - } else { - qDebug() << "Failed to mount existing image version" - << info.version - << "on device:" << device->udid.c_str(); - return false; - } - } - } + // for (const ImageInfo &info : images) { + // if (info.compatibility != ImageCompatibility::Compatible && + // info.compatibility != ImageCompatibility::MaybeCompatible) { + // continue; + // } + // if (info.isDownloaded) { + // qDebug() << "There is a compatible image already downloaded:" + // << info.version; + // qDebug() << "Attempting to mount image version" << info.version + // << "on device:" << device->udid.c_str(); + // if (MOBILE_IMAGE_MOUNTER_E_SUCCESS == + // mountImage(info.version, device)) { + // qDebug() << "Mounted existing image version" << info.version + // << "on device:" << device->udid.c_str(); + // return true; + // } else { + // qDebug() << "Failed to mount existing image version" + // << info.version + // << "on device:" << device->udid.c_str(); + // return false; + // } + // } + // } // 2. If none are downloaded, download the newest compatible one for (const ImageInfo &info : images) { @@ -492,19 +492,38 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device) return false; } -mobile_image_mounter_error_t -DevDiskManager::mountImage(const QString &version, iDescriptorDevice *device) +bool DevDiskManager::mountImage(const QString &version, + iDescriptorDevice *device) { const QString downloadPath = SettingsManager::sharedInstance()->devdiskimgpath(); if (!isImageDownloaded(version, downloadPath)) { - return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + return false; } QString versionPath = QDir(downloadPath).filePath(version); - return mount_dev_image(device->device, - device->deviceInfo.parsedDeviceVersion, - versionPath.toUtf8().constData()); + return mount_dev_image(device, + QDir(versionPath) + .filePath("DeveloperDiskImage.dmg") + .toUtf8() + .constData(), + QDir(versionPath) + .filePath("DeveloperDiskImage.dmg.signature") + .toUtf8() + .constData()); +} + +std::pair +DevDiskManager::getPathsForVersion(const QString &version) +{ + const QString downloadPath = + SettingsManager::sharedInstance()->devdiskimgpath(); + QString versionPath = QDir(downloadPath).filePath(version); + QString dmgPath = QDir(versionPath).filePath("DeveloperDiskImage.dmg"); + QString sigPath = + QDir(versionPath).filePath("DeveloperDiskImage.dmg.signature"); + + return {dmgPath, sigPath}; } void DevDiskManager::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) @@ -624,79 +643,4 @@ bool DevDiskManager::compareSignatures(const char *signature_file_path, free(local_sig); return matches; -} - -/* - older devices return something like this: - { - "ImagePresent": true, - "ImageSignature": <7b16200b 2ead1830 a59809d1 51e9060b ... 8a 9844eb07 - e0b8e0>, "Status": "Complete" - } -*/ -GetMountedImageResult DevDiskManager::getMountedImage(const char *udid) -{ - /* - FIXME: _get_mounted_image can return MOBILE_IMAGE_MOUNTER_E_SUCCESS even - if the device is locked so we are going to go off of the result - dictionary - */ - plist_t result = _get_mounted_image(udid); - plist_print(result); - const char *lockedErr = "DeviceLocked"; - - PlistNavigator r = PlistNavigator(result); - - std::string error = r["Error"].getString(); - - if (!error.empty()) { - plist_free(result); - if (error == lockedErr) { - return GetMountedImageResult{ - false, "", "Device is locked, please unlock it and try again."}; - } else - return GetMountedImageResult{false, "", "Unknown error"}; - } - - // for older devices - bool image_present = r["ImagePresent"].getBool(); - - /* FIXME: returning a madeup sig because iDescriptorTool::MountDevImage - * depends on it in toolboxwidget.cpp we can read the actual signature from - * the device but it’s not really necessary since we - * just need to know if there is an image mounted or - * not also we would need to check the ios version - * to know whether to treat ImageSignature as plist array or data - */ - if (image_present) { - plist_free(result); - return GetMountedImageResult{true, "FIXME", - "There is already an image mounted."}; - } - - plist_t sig_array_node = r["ImageSignature"].getNode(); - - if (sig_array_node == NULL) { - plist_free(result); - return GetMountedImageResult{true, "", "No disk image mounted"}; - } - - char *mounted_sig = nullptr; - uint64_t mounted_sig_len = 0; - // get the signature - if (sig_array_node && plist_get_node_type(sig_array_node) == PLIST_ARRAY && - plist_array_get_size(sig_array_node) > 0) { - plist_t sig_data_node = plist_array_get_item(sig_array_node, 0); - if (sig_data_node && plist_get_node_type(sig_data_node) == PLIST_DATA) { - plist_get_data_val(sig_data_node, &mounted_sig, &mounted_sig_len); - } - } - std::string mounted_sig_str(mounted_sig ? mounted_sig : ""); - free(mounted_sig); - plist_free(result); - if (mounted_sig_str.empty()) { - return GetMountedImageResult{ - true, "", "No disk image mounted (No signature found)"}; - } - return GetMountedImageResult{true, mounted_sig_str, "Success"}; } \ No newline at end of file diff --git a/src/devdiskmanager.h b/src/devdiskmanager.h index ba995d3..6d49071 100644 --- a/src/devdiskmanager.h +++ b/src/devdiskmanager.h @@ -27,7 +27,6 @@ #include #include #include -#include class DevDiskManager : public QObject { @@ -50,16 +49,15 @@ public: // Mount operations - mobile_image_mounter_error_t mountImage(const QString &version, - iDescriptorDevice *device); + bool mountImage(const QString &version, iDescriptorDevice *device); bool unmountImage(); + std::pair getPathsForVersion(const QString &version); // Signature comparison bool compareSignatures(const char *signature_file_path, const char *mounted_sig, uint64_t mounted_sig_len); QByteArray getImageListData() const { return m_imageListJsonData; } - GetMountedImageResult getMountedImage(const char *udid); bool mountCompatibleImage(iDescriptorDevice *device); bool downloadCompatibleImage(iDescriptorDevice *device, std::function callback); diff --git a/src/deviceimagewidget.cpp b/src/deviceimagewidget.cpp index 8b361ef..fab01cb 100644 --- a/src/deviceimagewidget.cpp +++ b/src/deviceimagewidget.cpp @@ -161,26 +161,12 @@ QString DeviceImageWidget::getMockupNameFromDisplayName( int DeviceImageWidget::getIosVersionFromDevice() const { - unsigned int version = m_device->deviceInfo.parsedDeviceVersion; + unsigned int version = m_device->deviceInfo.parsedDeviceVersion.major; if (version > 0) { - int majorVersion = (version >> 16) & 0xFF; - return majorVersion; + return version; } - // Fallback: parse from productVersion string - QString versionString = - QString::fromStdString(m_device->deviceInfo.productVersion); - QStringList parts = versionString.split('.'); - if (!parts.isEmpty()) { - bool ok; - int majorVersion = parts.first().toInt(&ok); - if (ok) { - return majorVersion; - } - } - - // return unknown version (will use ios26 wallpaper) return 0; } diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index d6e5f3a..444b303 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -18,14 +18,21 @@ */ #include "devicemenuwidget.h" +#include "Toast.h" +#include "cableinfowidget.h" +#include "devdiskimageswidget.h" #include "iDescriptor.h" +#include "livescreenwidget.h" #include "qprocessindicator.h" +#include "querymobilegestaltwidget.h" +#include "servicemanager.h" +#include "virtuallocationwidget.h" #include #include #include DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent) - : QWidget{parent}, device(device) + : QWidget{parent}, m_device(device) { QVBoxLayout *mainLayout = new QVBoxLayout(this); setContentsMargins(0, 0, 0, 0); @@ -54,19 +61,19 @@ void DeviceMenuWidget::init() { // Create and add widgets to the stacked widget - m_deviceInfoWidget = new DeviceInfoWidget(device, this); - // m_installedAppsWidget = new InstalledAppsWidget(device, this); - m_galleryWidget = new GalleryWidget(device, this); - // m_fileExplorerWidget = new FileExplorerWidget(device, this); + m_deviceInfoWidget = new DeviceInfoWidget(m_device, this); + m_installedAppsWidget = new InstalledAppsWidget(m_device, this); + m_galleryWidget = new GalleryWidget(m_device, this); + m_fileExplorerWidget = new FileExplorerWidget(m_device, this); // Set minimum heights m_galleryWidget->setMinimumHeight(300); - // m_fileExplorerWidget->setMinimumHeight(300); + m_fileExplorerWidget->setMinimumHeight(300); - stackedWidget->addWidget(m_deviceInfoWidget); // Index 0 - Info - // stackedWidget->addWidget(m_installedAppsWidget); // Index 1 - Apps - stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery - // stackedWidget->addWidget(m_fileExplorerWidget); // Index 3 - Files + stackedWidget->addWidget(m_deviceInfoWidget); // Index 0 - Info + stackedWidget->addWidget(m_installedAppsWidget); // Index 1 - Apps + stackedWidget->addWidget(m_galleryWidget); // Index 2 - Gallery + stackedWidget->addWidget(m_fileExplorerWidget); // Index 3 - Files // Set default to Info tab stackedWidget->setCurrentWidget(m_deviceInfoWidget); @@ -83,19 +90,47 @@ void DeviceMenuWidget::init() QWidget *loadingWidget = stackedWidget->widget(0); stackedWidget->removeWidget(loadingWidget); loadingWidget->deleteLater(); + + if (m_device->deviceInfo.parsedDeviceVersion.major < 13) { + Toast *toast = new Toast(this); + toast->setAttribute(Qt::WA_DeleteOnClose); + toast->setDuration(8000); // Hide after 8 seconds + toast->setTitle("Not wireless compatible"); + toast->setText("This device is not wireless compatible."); + toast->setPosition(ToastPosition::BOTTOM_MIDDLE); + toast->show(); + } else { + if (m_device->deviceInfo.isWireless) + return; + bool enabled = ServiceManager::enableWirelessConnections(m_device); + Toast *toast = new Toast(this); + toast->setAttribute(Qt::WA_DeleteOnClose); + toast->setDuration(8000); // Hide after 8 seconds + toast->setPosition(ToastPosition::BOTTOM_MIDDLE); + if (enabled) { + toast->setTitle("Wireless connections enabled"); + toast->setText( + "You can now use wireless connections with this device."); + } else { + toast->setTitle("Failed to enable wireless connections"); + toast->setText( + "Could not enable wireless connections for this device."); + } + toast->show(); + } } void DeviceMenuWidget::switchToTab(const QString &tabName) { if (tabName == "Info") { stackedWidget->setCurrentWidget(m_deviceInfoWidget); - // } else if (tabName == "Apps") { - // stackedWidget->setCurrentWidget(m_installedAppsWidget); + } else if (tabName == "Apps") { + stackedWidget->setCurrentWidget(m_installedAppsWidget); } else if (tabName == "Gallery") { qDebug() << "Switching to Gallery tab"; stackedWidget->setCurrentWidget(m_galleryWidget); } else if (tabName == "Files") { - // stackedWidget->setCurrentWidget(m_fileExplorerWidget); + stackedWidget->setCurrentWidget(m_fileExplorerWidget); } else { qDebug() << "Tab not found:" << tabName; } @@ -104,4 +139,4 @@ void DeviceMenuWidget::switchToTab(const QString &tabName) DeviceMenuWidget::~DeviceMenuWidget() { qDebug() << "DeviceMenuWidget destructor called"; -} \ No newline at end of file +} diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index 326bb8d..985c2d1 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -20,10 +20,10 @@ #ifndef DEVICEMENUWIDGET_H #define DEVICEMENUWIDGET_H #include "deviceinfowidget.h" -// #include "fileexplorerwidget.h" +#include "fileexplorerwidget.h" #include "gallerywidget.h" #include "iDescriptor.h" -// #include "installedappswidget.h" +#include "installedappswidget.h" #include #include @@ -39,11 +39,11 @@ public: private: QStackedWidget *stackedWidget; // Pointer to the stacked widget - iDescriptorDevice *device; // Pointer to the iDescriptor device + iDescriptorDevice *m_device; // Pointer to the iDescriptor device DeviceInfoWidget *m_deviceInfoWidget; - // InstalledAppsWidget *m_installedAppsWidget; + InstalledAppsWidget *m_installedAppsWidget; GalleryWidget *m_galleryWidget; - // FileExplorerWidget *m_fileExplorerWidget; + FileExplorerWidget *m_fileExplorerWidget; signals: }; diff --git a/src/devicesidebarwidget.h b/src/devicesidebarwidget.h index 1ff9a0e..fe3ade1 100644 --- a/src/devicesidebarwidget.h +++ b/src/devicesidebarwidget.h @@ -128,6 +128,18 @@ struct DeviceSelection { uint64_t ecid = 0; QString section = "Info"; + bool valid() const + { + if (type == Normal) { + return !udid.empty(); + } else if (type == Recovery) { + return ecid != 0; + } else if (type == Pending) { + return !udid.empty(); + } + return false; + } + DeviceSelection(const std::string &deviceUdid, const QString &nav = "") : type(Normal), udid(deviceUdid), section(nav) { diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index b2fc4cf..60d89a3 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -27,10 +27,6 @@ #include #include -// #include -// #include -// #include - DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0), m_systemUsage(0), m_appsUsage(0), m_mediaUsage(0), m_othersUsage(0), @@ -404,90 +400,92 @@ void DiskUsageWidget::fetchData() QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; - if (!m_device || !m_device->provider) { - result["error"] = "Invalid device."; - return result; - } + // if (!m_device || !m_device->provider) { + // result["error"] = "Invalid device."; + // return result; + // } - // Pre-populate with known info - result["totalCapacity"] = QVariant::fromValue( - m_device->deviceInfo.diskInfo.totalDiskCapacity); - result["freeSpace"] = QVariant::fromValue( - m_device->deviceInfo.diskInfo.totalDataAvailable); - result["systemUsage"] = QVariant::fromValue( - m_device->deviceInfo.diskInfo.totalSystemCapacity); + // // Pre-populate with known info + // result["totalCapacity"] = QVariant::fromValue( + // m_device->deviceInfo.diskInfo.totalDiskCapacity); + // result["freeSpace"] = QVariant::fromValue( + // m_device->deviceInfo.diskInfo.totalDataAvailable); + // result["systemUsage"] = QVariant::fromValue( + // m_device->deviceInfo.diskInfo.totalSystemCapacity); - // Create provider wrapper from existing handle - Provider provider = Provider::adopt(m_device->provider); + // // Create provider wrapper from existing handle + // TODO:remove + // Provider provider = Provider::adopt(m_device->provider); - // Apps usage - uint64_t totalAppsSpace = 0; - auto instproxy_res = IdeviceFFI::InstallationProxy::connect(provider); - if (instproxy_res.is_err()) { - result["error"] = - "Could not connect to installation proxy: " + - QString::fromStdString(instproxy_res.unwrap_err().message); - return result; - } - auto instproxy = std::move(instproxy_res.unwrap()); + // // Apps usage + // uint64_t totalAppsSpace = 0; + // auto instproxy_res = + // IdeviceFFI::InstallationProxy::connect(provider); if + // (instproxy_res.is_err()) { + // result["error"] = + // "Could not connect to installation proxy: " + + // QString::fromStdString(instproxy_res.unwrap_err().message); + // return result; + // } + // auto instproxy = std::move(instproxy_res.unwrap()); - plist_t client_opts = plist_new_dict(); - plist_dict_set_item(client_opts, "ApplicationType", - plist_new_string("User")); + // plist_t client_opts = plist_new_dict(); + // plist_dict_set_item(client_opts, "ApplicationType", + // plist_new_string("User")); - plist_t return_attrs = plist_new_array(); - plist_array_append_item(return_attrs, - plist_new_string("StaticDiskUsage")); - plist_array_append_item(return_attrs, - plist_new_string("DynamicDiskUsage")); - plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); + // plist_t return_attrs = plist_new_array(); + // plist_array_append_item(return_attrs, + // plist_new_string("StaticDiskUsage")); + // plist_array_append_item(return_attrs, + // plist_new_string("DynamicDiskUsage")); + // plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); - auto apps_result = instproxy.browse(client_opts); - if (apps_result.is_ok()) { - auto apps = std::move(apps_result.unwrap()); - for (const auto &app_info : apps) { - plist_t static_usage = - plist_dict_get_item(app_info, "StaticDiskUsage"); - if (static_usage && - plist_get_node_type(static_usage) == PLIST_UINT) { - uint64_t static_size = 0; - plist_get_uint_val(static_usage, &static_size); - totalAppsSpace += static_size; - } + // auto apps_result = instproxy.browse(client_opts); + // if (apps_result.is_ok()) { + // auto apps = std::move(apps_result.unwrap()); + // for (const auto &app_info : apps) { + // plist_t static_usage = + // plist_dict_get_item(app_info, "StaticDiskUsage"); + // if (static_usage && + // plist_get_node_type(static_usage) == PLIST_UINT) { + // uint64_t static_size = 0; + // plist_get_uint_val(static_usage, &static_size); + // totalAppsSpace += static_size; + // } - plist_t dynamic_usage = - plist_dict_get_item(app_info, "DynamicDiskUsage"); - if (dynamic_usage && - plist_get_node_type(dynamic_usage) == PLIST_UINT) { - uint64_t dynamic_size = 0; - plist_get_uint_val(dynamic_usage, &dynamic_size); - totalAppsSpace += dynamic_size; - } - } - } - result["appsUsage"] = QVariant::fromValue(totalAppsSpace); + // plist_t dynamic_usage = + // plist_dict_get_item(app_info, "DynamicDiskUsage"); + // if (dynamic_usage && + // plist_get_node_type(dynamic_usage) == PLIST_UINT) { + // uint64_t dynamic_size = 0; + // plist_get_uint_val(dynamic_usage, &dynamic_size); + // totalAppsSpace += dynamic_size; + // } + // } + // } + // result["appsUsage"] = QVariant::fromValue(totalAppsSpace); // plist_free(client_opts); // client_opts is consumed by browse, but // Media usage - uint64_t mediaSpace = 0; - IdeviceFFI::Lockdown lockdown = - IdeviceFFI::Lockdown::adopt(m_device->lockdown); - auto itunes_info_res = - lockdown.get_value("com.apple.mobile.iTunes", nullptr); - if (itunes_info_res.is_ok()) { - auto itunes_dict = std::move(itunes_info_res.unwrap()); - if (itunes_dict) { - plist_t media_node = - plist_dict_get_item(itunes_dict, "MediaLibrarySize"); - if (media_node && - plist_get_node_type(media_node) == PLIST_UINT) { - plist_get_uint_val(media_node, &mediaSpace); - } - } - } - result["mediaUsage"] = QVariant::fromValue(mediaSpace); + // uint64_t mediaSpace = 0; + // IdeviceFFI::Lockdown lockdown = + // IdeviceFFI::Lockdown::adopt(m_device->lockdown); + // auto itunes_info_res = + // lockdown.get_value("com.apple.mobile.iTunes", nullptr); + // if (itunes_info_res.is_ok()) { + // auto itunes_dict = std::move(itunes_info_res.unwrap()); + // if (itunes_dict) { + // plist_t media_node = + // plist_dict_get_item(itunes_dict, "MediaLibrarySize"); + // if (media_node && + // plist_get_node_type(media_node) == PLIST_UINT) { + // plist_get_uint_val(media_node, &mediaSpace); + // } + // } + // } + // result["mediaUsage"] = QVariant::fromValue(mediaSpace); return result; }); - watcher->setFuture(future); + // watcher->setFuture(future); } diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index c37ff01..adef72b 100644 --- a/src/exportmanager.cpp +++ b/src/exportmanager.cpp @@ -60,7 +60,7 @@ ExportManager::~ExportManager() QUuid ExportManager::startExport(iDescriptorDevice *device, const QList &items, const QString &destinationPath, - std::optional altAfc) + std::optional altAfc) { if (!device || !device->mutex) { qWarning() << "Invalid device provided to ExportManager"; @@ -193,116 +193,115 @@ void ExportManager::executeExportJob(ExportJob *job) emit exportFinished(job->jobId, summary); } - -ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, - const ExportItem &item, - const QString &destinationDir, - std::optional altAfc, - std::atomic &cancelRequested, - const QUuid &jobId) +// TODO: implement +ExportResult ExportManager::exportSingleItem( + iDescriptorDevice *device, const ExportItem &item, + const QString &destinationDir, std::optional altAfc, + std::atomic &cancelRequested, const QUuid &jobId) { ExportResult result; result.sourceFilePath = item.sourcePathOnDevice; - // Generate output path - QString outputPath = QDir(destinationDir).filePath(item.suggestedFileName); - outputPath = generateUniqueOutputPath(outputPath); - result.outputFilePath = outputPath; + // // Generate output path + // QString outputPath = + // QDir(destinationDir).filePath(item.suggestedFileName); outputPath = + // generateUniqueOutputPath(outputPath); result.outputFilePath = outputPath; - // Get file size first - char **info = nullptr; - afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( - device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); + // // Get file size first + // char **info = nullptr; + // afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( + // device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); - qint64 totalFileSize = 0; - if (infoResult == AFC_E_SUCCESS && info) { - for (int i = 0; info[i]; i += 2) { - if (strcmp(info[i], "st_size") == 0) { - totalFileSize = QString::fromUtf8(info[i + 1]).toLongLong(); - break; - } - } - afc_dictionary_free(info); - } + // qint64 totalFileSize = 0; + // if (infoResult == AFC_E_SUCCESS && info) { + // for (int i = 0; info[i]; i += 2) { + // if (strcmp(info[i], "st_size") == 0) { + // totalFileSize = QString::fromUtf8(info[i + 1]).toLongLong(); + // break; + // } + // } + // afc_dictionary_free(info); + // } - // Open file on device - uint64_t handle = 0; - afc_error_t openResult = ServiceManager::safeAfcFileOpen( - device, item.sourcePathOnDevice.toUtf8().constData(), AFC_FOPEN_RDONLY, - &handle, altAfc); + // // Open file on device + // uint64_t handle = 0; + // afc_error_t openResult = ServiceManager::safeAfcFileOpen( + // device, item.sourcePathOnDevice.toUtf8().constData(), + // AFC_FOPEN_RDONLY, &handle, altAfc); - if (openResult != AFC_E_SUCCESS) { - result.errorMessage = - QString("Failed to open file on device: %1 (AFC error: %2)") - .arg(item.sourcePathOnDevice) - .arg(static_cast(openResult)); - return result; - } + // if (openResult != AFC_E_SUCCESS) { + // result.errorMessage = + // QString("Failed to open file on device: %1 (AFC error: %2)") + // .arg(item.sourcePathOnDevice) + // .arg(static_cast(openResult)); + // return result; + // } - // Open local output file - QFile outputFile(outputPath); - if (!outputFile.open(QIODevice::WriteOnly)) { - result.errorMessage = QString("Failed to create local file: %1 (%2)") - .arg(outputPath) - .arg(outputFile.errorString()); - ServiceManager::safeAfcFileClose(device, handle, altAfc); - return result; - } + // // Open local output file + // QFile outputFile(outputPath); + // if (!outputFile.open(QIODevice::WriteOnly)) { + // result.errorMessage = QString("Failed to create local file: %1 (%2)") + // .arg(outputPath) + // .arg(outputFile.errorString()); + // ServiceManager::safeAfcFileClose(device, handle, altAfc); + // return result; + // } - char buffer[8192]; - uint32_t bytesRead = 0; - qint64 totalBytes = 0; + // char buffer[8192]; + // uint32_t bytesRead = 0; + // qint64 totalBytes = 0; - while (true) { - // Check for cancellation during file copy - if (cancelRequested.load()) { - outputFile.close(); - outputFile.remove(); // Clean up partial file - ServiceManager::safeAfcFileClose(device, handle, altAfc); - result.errorMessage = "Export cancelled by user"; - return result; - } + // while (true) { + // // Check for cancellation during file copy + // if (cancelRequested.load()) { + // outputFile.close(); + // outputFile.remove(); // Clean up partial file + // ServiceManager::safeAfcFileClose(device, handle, altAfc); + // result.errorMessage = "Export cancelled by user"; + // return result; + // } - afc_error_t readResult = ServiceManager::safeAfcFileRead( - device, handle, buffer, sizeof(buffer), &bytesRead, altAfc); + // afc_error_t readResult = ServiceManager::safeAfcFileRead( + // device, handle, buffer, sizeof(buffer), &bytesRead, altAfc); - if (readResult != AFC_E_SUCCESS || bytesRead == 0) { - break; // End of file or error - } + // if (readResult != AFC_E_SUCCESS || bytesRead == 0) { + // break; // End of file or error + // } - qint64 bytesWritten = outputFile.write(buffer, bytesRead); - if (bytesWritten != bytesRead) { - result.errorMessage = - QString("Write error: only wrote %1 of %2 bytes") - .arg(bytesWritten) - .arg(bytesRead); - outputFile.close(); - outputFile.remove(); // Clean up partial file - ServiceManager::safeAfcFileClose(device, handle, altAfc); - return result; - } + // qint64 bytesWritten = outputFile.write(buffer, bytesRead); + // if (bytesWritten != bytesRead) { + // result.errorMessage = + // QString("Write error: only wrote %1 of %2 bytes") + // .arg(bytesWritten) + // .arg(bytesRead); + // outputFile.close(); + // outputFile.remove(); // Clean up partial file + // ServiceManager::safeAfcFileClose(device, handle, altAfc); + // return result; + // } - totalBytes += bytesRead; + // totalBytes += bytesRead; - // Emit progress update every 64KB or at end of file - if (totalBytes % (64 * 1024) == 0 || totalBytes == totalFileSize) { - emit fileTransferProgress(jobId, item.suggestedFileName, totalBytes, - totalFileSize); - } - } + // // Emit progress update every 64KB or at end of file + // if (totalBytes % (64 * 1024) == 0 || totalBytes == totalFileSize) { + // emit fileTransferProgress(jobId, item.suggestedFileName, + // totalBytes, + // totalFileSize); + // } + // } - // Clean up - outputFile.close(); - ServiceManager::safeAfcFileClose(device, handle, altAfc); + // // Clean up + // outputFile.close(); + // ServiceManager::safeAfcFileClose(device, handle, altAfc); - if (totalBytes == 0) { - result.errorMessage = "No data read from device file"; - outputFile.remove(); // Clean up empty file - return result; - } + // if (totalBytes == 0) { + // result.errorMessage = "No data read from device file"; + // outputFile.remove(); // Clean up empty file + // return result; + // } result.success = true; - result.bytesTransferred = totalBytes; + // result.bytesTransferred = totalBytes; return result; } diff --git a/src/exportmanager.h b/src/exportmanager.h index 949d2e9..7594a18 100644 --- a/src/exportmanager.h +++ b/src/exportmanager.h @@ -78,7 +78,7 @@ public: QUuid startExport(iDescriptorDevice *device, const QList &items, const QString &destinationPath, - std::optional altAfc = std::nullopt); + std::optional altAfc = std::nullopt); void cancelExport(const QUuid &jobId); @@ -113,7 +113,7 @@ private: iDescriptorDevice *device = nullptr; QList items; QString destinationPath; - std::optional altAfc; + std::optional altAfc; std::atomic cancelRequested{false}; QFuture future; QFutureWatcher *watcher = nullptr; @@ -124,7 +124,7 @@ private: ExportResult exportSingleItem(iDescriptorDevice *device, const ExportItem &item, const QString &destinationDir, - std::optional altAfc, + std::optional altAfc, std::atomic &cancelRequested, const QUuid &jobId); diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 924a85e..f0759ee 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -20,7 +20,7 @@ #include "gallerywidget.h" // #include "exportmanager.h" #include "iDescriptor.h" -// #include "mediapreviewdialog.h" +#include "mediapreviewdialog.h" #include "photomodel.h" #include "servicemanager.h" #include @@ -134,7 +134,7 @@ void GalleryWidget::setupControlsLayout() static_cast(PhotoModel::ImagesOnly)); m_filterComboBox->addItem("Videos Only", static_cast(PhotoModel::VideosOnly)); - m_filterComboBox->setCurrentIndex(0); // Default to All + m_filterComboBox->setCurrentIndex(2); // Default to All m_filterComboBox->setMinimumWidth(100); // Ensure text fits m_filterComboBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -408,11 +408,11 @@ void GalleryWidget::setupPhotoGalleryView() if (filePath.isEmpty()) return; - // qDebug() << "Opening preview for" << filePath; - // auto *previewDialog = new MediaPreviewDialog( - // m_device, m_device->afcClient, filePath, this); - // previewDialog->setAttribute(Qt::WA_DeleteOnClose); - // previewDialog->show(); + qDebug() << "Opening preview for" << filePath; + auto *previewDialog = new MediaPreviewDialog( + m_device, m_device->afcClient, filePath, this); + previewDialog->setAttribute(Qt::WA_DeleteOnClose); + previewDialog->show(); }); connect(m_listView, &QListView::customContextMenuRequested, this, @@ -627,21 +627,21 @@ void GalleryWidget::onPhotoContextMenu(const QPoint &pos) exportAction->setEnabled(m_listView->selectionModel()->hasSelection()); - // connect(previewAction, &QAction::triggered, this, [this, index]() { - // // Re-use the double-click logic - // if (!index.isValid()) - // return; + connect(previewAction, &QAction::triggered, this, [this, index]() { + // Re-use the double-click logic + if (!index.isValid()) + return; - // QString filePath = m_model->data(index, Qt::UserRole).toString(); - // if (filePath.isEmpty()) - // return; + QString filePath = m_model->data(index, Qt::UserRole).toString(); + if (filePath.isEmpty()) + return; - // qDebug() << "Opening preview for" << filePath; - // auto *previewDialog = new MediaPreviewDialog( - // m_device, m_device->afcClient, filePath, this); - // previewDialog->setAttribute(Qt::WA_DeleteOnClose); - // previewDialog->show(); - // }); + qDebug() << "Opening preview for" << filePath; + auto *previewDialog = new MediaPreviewDialog( + m_device, m_device->afcClient, filePath, this); + previewDialog->setAttribute(Qt::WA_DeleteOnClose); + previewDialog->show(); + }); connect(exportAction, &QAction::triggered, this, &GalleryWidget::onExportSelected); diff --git a/src/iDescriptor.h b/src/iDescriptor.h index dd52673..9409e02 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -66,6 +66,10 @@ ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF)) #include "devicemonitor.h" +#define DeviceLockedMountErrorCode -21 +#define NotFoundErrorCode -14 +#define DISK_IMAGE_TYPE_DEVELOPER "Developer" + struct BatteryInfo { QString health; uint64_t cycleCount; @@ -104,6 +108,12 @@ struct DiskInfo { uint64_t totalDataAvailable; }; +struct DeviceVersion { + unsigned int major; + unsigned int minor; + unsigned int patch; +}; + // Carefull not all the vars are initialized in init_device.cpp struct DeviceInfo { enum class ActivationState { @@ -180,8 +190,9 @@ struct DeviceInfo { std::string marketingName; std::string regionRaw; std::string region; - unsigned int parsedDeviceVersion; + DeviceVersion parsedDeviceVersion; std::string wifiMacAddress; + bool isWireless = false; }; struct iDescriptorDevice { @@ -192,8 +203,11 @@ struct iDescriptorDevice { AfcClientHandle *afcClient; AfcClientHandle *afc2Client; LockdowndClientHandle *lockdown; - bool is_iPhone; std::recursive_mutex *mutex; + ImageMounterHandle *imageMounter; + std::shared_ptr diagRelay; + ScreenshotrClientHandle *screenshotrClient; + LocationSimulationHandle *locationSimulation; }; struct iDescriptorInitDeviceResult { @@ -204,6 +218,10 @@ struct iDescriptorInitDeviceResult { AfcClientHandle *afcClient; AfcClientHandle *afc2Client; LockdowndClientHandle *lockdown; + ImageMounterHandle *imageMounter; + std::shared_ptr diagRelay; + ScreenshotrClientHandle *screenshotrClient; + LocationSimulationHandle *locationSimulation; }; // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // struct iDescriptorRecoveryDevice { @@ -256,6 +274,25 @@ public: return PlistNavigator(next); } + PlistNavigator operator[](const std::string &key) + { + if (!current_node || plist_get_node_type(current_node) != PLIST_DICT) { + return PlistNavigator(nullptr); + } + plist_t next = plist_dict_get_item(current_node, key.c_str()); + return PlistNavigator(next); + } + + PlistNavigator operator[](const QString &key) + { + if (!current_node || plist_get_node_type(current_node) != PLIST_DICT) { + return PlistNavigator(nullptr); + } + plist_t next = + plist_dict_get_item(current_node, key.toUtf8().constData()); + return PlistNavigator(next); + } + // array index access PlistNavigator operator[](int index) { @@ -303,6 +340,47 @@ public: return result; } plist_t getNode() const { return current_node; } + + QVariant toQVariant() const + { + if (!current_node) { + return QVariant(); + } + // TODO: free + plist_type type = plist_get_node_type(current_node); + switch (type) { + case PLIST_BOOLEAN: { + uint8_t val; + plist_get_bool_val(current_node, &val); + return QVariant(static_cast(val)); + } + case PLIST_UINT: { + uint64_t val; + plist_get_uint_val(current_node, &val); + return QVariant(static_cast(val)); + } + case PLIST_STRING: { + char *val = nullptr; + plist_get_string_val(current_node, &val); + QString str_val = QString::fromUtf8(val); + if (val) + free(val); + return QVariant(str_val); + } + case PLIST_REAL: { + double val; + plist_get_real_val(current_node, &val); + return QVariant(val); + } + case PLIST_DICT: + case PLIST_ARRAY: + case PLIST_DATA: + case PLIST_DATE: + default: { + return QVariant("Unsupported Type"); + } + } + } }; // afc_error_t safe_afc_read_directory(afc_client_t afcClient, idevice_t device, @@ -351,33 +429,41 @@ init_idescriptor_device(const QString &udid, // TakeScreenshotResult take_screenshot(screenshotr_client_t shotr); -// mobile_image_mounter_error_t mount_dev_image(idevice_t device, -// unsigned int device_version, -// const char *image_dir_path); -// struct GetMountedImageResult { -// bool success; -// std::string sig; -// std::string message; -// }; +IdeviceFfiError *mount_dev_image(const iDescriptorDevice *device, + const char *image_file, + const char *signature_file); -// plist_t _get_mounted_image(const char *udid); +struct MountedImageInfo { + IdeviceFfiError *err; + uint8_t *signature; + size_t signature_len; +}; + +struct MountedImageResult { + bool success; + IdeviceFfiError *err; +}; + +MountedImageInfo _get_mounted_image(const iDescriptorDevice *device); + +void mounted_image_info_free(MountedImageInfo &info); // bool restart(std::string udid); -// enum class ImageCompatibility { -// Compatible, // Exact match or known compatible version -// MaybeCompatible, // Major version matches but minor doesn't -// NotCompatible // Not compatible -// }; +enum class ImageCompatibility { + Compatible, // Exact match or known compatible version + MaybeCompatible, // Major version matches but minor doesn't + NotCompatible // Not compatible +}; -// struct ImageInfo { -// QString version; -// QString dmgPath; -// QString sigPath; -// ImageCompatibility compatibility = ImageCompatibility::NotCompatible; -// bool isDownloaded = false; -// bool isMounted = false; -// }; +struct ImageInfo { + QString version; + QString dmgPath; + QString sigPath; + ImageCompatibility compatibility = ImageCompatibility::NotCompatible; + bool isDownloaded = false; + bool isMounted = false; +}; // /** // * @brief Compare two iPhone product types to determine which is newer @@ -428,19 +514,18 @@ bool is_product_type_newer(const std::string &productType, // std::string safeGetXML(const char *key, pugi::xml_node dict); -void get_battery_info(IdeviceProviderHandle *provider, plist_t &diagnostics); +void get_battery_info(DiagnosticsRelay *diagRelay, plist_t &diagnostics); // void parseOldDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d); // void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d); -// void fetchAppIconFromApple(QNetworkAccessManager *manager, -// const QString &bundleId, -// std::function -// callback); +void fetchAppIconFromApple(QNetworkAccessManager *manager, + const QString &bundleId, + std::function callback); // afc_error_t afc2_client_new(idevice_t device, afc_client_t *afc); -// void get_cable_info(idevice_t device, plist_t &response); +void _get_cable_info(const iDescriptorDevice *device, plist_t &response); struct NetworkDevice { QString name; // service name @@ -462,8 +547,8 @@ QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, bool isDarkMode(); -// instproxy_error_t install_IPA(idevice_t device, afc_client_t afc, -// const char *filePath); +IdeviceFfiError *_install_IPA(const iDescriptorDevice *device, + const char *filePath, const char *ipaName); // Helper struct for semantic version comparison struct AppVersion { @@ -542,4 +627,46 @@ inline QJsonObject getVersionedConfig(const QJsonObject &rootObj) } } return QJsonObject(); +} + +inline void free_directory_listing(char **entries, size_t count) +{ + if (!entries) + return; + for (size_t i = 0; i < count; i++) { + if (entries[i]) { + free(entries[i]); + } + } + free(entries); +} + +inline int read_file(const char *filename, uint8_t **data, size_t *length) +{ + FILE *file = fopen(filename, "rb"); + if (!file) { + perror("Failed to open file"); + return 0; + } + + fseek(file, 0, SEEK_END); + *length = ftell(file); + fseek(file, 0, SEEK_SET); + + *data = (uint8_t *)malloc(*length); + if (!*data) { + perror("Failed to allocate memory"); + fclose(file); + return 0; + } + + if (fread(*data, 1, *length, file) != *length) { + perror("Failed to read file"); + free(*data); + fclose(file); + return 0; + } + + fclose(file); + return 1; } \ No newline at end of file diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 3786b4e..89d8778 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -34,11 +34,6 @@ #include #include #include -#include -#include -#include -#include -#include AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, const QString &version, QWidget *parent) @@ -291,180 +286,181 @@ void InstalledAppsWidget::createContentWidget() // todo: move to services void InstalledAppsWidget::fetchInstalledApps() { - if (!m_device || !m_device->device) { + if (!m_device) { showErrorState("Invalid device"); return; } - QFuture future = QtConcurrent::run([this]() -> QVariantMap { - QVariantMap result; - QVariantList apps; + // QFuture future = QtConcurrent::run([this]() -> QVariantMap { + // QVariantMap result; + // QVariantList apps; - // result["success"] = true; - // result["apps"] = apps; - // return result; + // // result["success"] = true; + // // result["apps"] = apps; + // // return result; - instproxy_client_t instproxy = nullptr; - lockdownd_client_t lockdownClient = nullptr; - lockdownd_service_descriptor_t lockdowndService = nullptr; + // instproxy_client_t instproxy = nullptr; + // lockdownd_client_t lockdownClient = nullptr; + // lockdownd_service_descriptor_t lockdowndService = nullptr; - try { - if (lockdownd_client_new_with_handshake( - m_device->device, &lockdownClient, APP_LABEL) != - LOCKDOWN_E_SUCCESS) { - result["error"] = "Could not connect to lockdown service"; - return result; - } + // try { + // if (lockdownd_client_new_with_handshake( + // m_device->device, &lockdownClient, APP_LABEL) != + // LOCKDOWN_E_SUCCESS) { + // result["error"] = "Could not connect to lockdown service"; + // return result; + // } - if (lockdownd_start_service( - lockdownClient, "com.apple.mobile.installation_proxy", - &lockdowndService) != LOCKDOWN_E_SUCCESS) { - result["error"] = "Could not start installation proxy service"; - lockdownd_client_free(lockdownClient); - return result; - } + // if (lockdownd_start_service( + // lockdownClient, "com.apple.mobile.installation_proxy", + // &lockdowndService) != LOCKDOWN_E_SUCCESS) { + // result["error"] = "Could not start installation proxy + // service"; lockdownd_client_free(lockdownClient); return + // result; + // } - if (instproxy_client_new(m_device->device, lockdowndService, - &instproxy) != INSTPROXY_E_SUCCESS) { - result["error"] = "Could not connect to installation proxy"; - lockdownd_service_descriptor_free(lockdowndService); - lockdownd_client_free(lockdownClient); - return result; - } + // if (instproxy_client_new(m_device->device, lockdowndService, + // &instproxy) != INSTPROXY_E_SUCCESS) { + // result["error"] = "Could not connect to installation proxy"; + // lockdownd_service_descriptor_free(lockdowndService); + // lockdownd_client_free(lockdownClient); + // return result; + // } - lockdownd_service_descriptor_free(lockdowndService); - lockdowndService = nullptr; + // lockdownd_service_descriptor_free(lockdowndService); + // lockdowndService = nullptr; - // Get both User and System apps - QStringList appTypes = {"User", "System"}; + // // Get both User and System apps + // QStringList appTypes = {"User", "System"}; - for (const QString &appType : appTypes) { - plist_t client_opts = plist_new_dict(); - plist_dict_set_item( - client_opts, "ApplicationType", - plist_new_string(appType.toUtf8().constData())); + // for (const QString &appType : appTypes) { + // plist_t client_opts = plist_new_dict(); + // plist_dict_set_item( + // client_opts, "ApplicationType", + // plist_new_string(appType.toUtf8().constData())); - plist_t return_attrs = plist_new_array(); - plist_array_append_item(return_attrs, - plist_new_string("CFBundleIdentifier")); - plist_array_append_item( - return_attrs, plist_new_string("CFBundleDisplayName")); - plist_array_append_item( - return_attrs, - plist_new_string("CFBundleShortVersionString")); - plist_array_append_item(return_attrs, - plist_new_string("CFBundleVersion")); - plist_array_append_item( - return_attrs, plist_new_string("UIFileSharingEnabled")); + // plist_t return_attrs = plist_new_array(); + // plist_array_append_item(return_attrs, + // plist_new_string("CFBundleIdentifier")); + // plist_array_append_item( + // return_attrs, plist_new_string("CFBundleDisplayName")); + // plist_array_append_item( + // return_attrs, + // plist_new_string("CFBundleShortVersionString")); + // plist_array_append_item(return_attrs, + // plist_new_string("CFBundleVersion")); + // plist_array_append_item( + // return_attrs, plist_new_string("UIFileSharingEnabled")); - plist_dict_set_item(client_opts, "ReturnAttributes", - return_attrs); + // plist_dict_set_item(client_opts, "ReturnAttributes", + // return_attrs); - plist_t apps_plist = nullptr; - if (instproxy_browse(instproxy, client_opts, &apps_plist) == - INSTPROXY_E_SUCCESS && - apps_plist) { - if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { - for (uint32_t i = 0; - i < plist_array_get_size(apps_plist); i++) { - plist_t app_info = - plist_array_get_item(apps_plist, i); - if (!app_info) - continue; + // plist_t apps_plist = nullptr; + // if (instproxy_browse(instproxy, client_opts, &apps_plist) == + // INSTPROXY_E_SUCCESS && + // apps_plist) { + // if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { + // for (uint32_t i = 0; + // i < plist_array_get_size(apps_plist); i++) { + // plist_t app_info = + // plist_array_get_item(apps_plist, i); + // if (!app_info) + // continue; - QVariantMap appData; + // QVariantMap appData; - // Get bundle identifier - plist_t bundle_id = plist_dict_get_item( - app_info, "CFBundleIdentifier"); - if (bundle_id && plist_get_node_type(bundle_id) == - PLIST_STRING) { - char *bundle_id_str = nullptr; - plist_get_string_val(bundle_id, &bundle_id_str); - if (bundle_id_str) { - appData["bundleId"] = - QString(bundle_id_str); - free(bundle_id_str); - } - } + // // Get bundle identifier + // plist_t bundle_id = plist_dict_get_item( + // app_info, "CFBundleIdentifier"); + // if (bundle_id && plist_get_node_type(bundle_id) + // == + // PLIST_STRING) { + // char *bundle_id_str = nullptr; + // plist_get_string_val(bundle_id, + // &bundle_id_str); if (bundle_id_str) { + // appData["bundleId"] = + // QString(bundle_id_str); + // free(bundle_id_str); + // } + // } - // Get display name - plist_t display_name = plist_dict_get_item( - app_info, "CFBundleDisplayName"); - if (display_name && - plist_get_node_type(display_name) == - PLIST_STRING) { - char *display_name_str = nullptr; - plist_get_string_val(display_name, - &display_name_str); - if (display_name_str) { - appData["displayName"] = - QString(display_name_str); - free(display_name_str); - } - } + // // Get display name + // plist_t display_name = plist_dict_get_item( + // app_info, "CFBundleDisplayName"); + // if (display_name && + // plist_get_node_type(display_name) == + // PLIST_STRING) { + // char *display_name_str = nullptr; + // plist_get_string_val(display_name, + // &display_name_str); + // if (display_name_str) { + // appData["displayName"] = + // QString(display_name_str); + // free(display_name_str); + // } + // } - // Get version - plist_t version = plist_dict_get_item( - app_info, "CFBundleShortVersionString"); - if (version && - plist_get_node_type(version) == PLIST_STRING) { - char *version_str = nullptr; - plist_get_string_val(version, &version_str); - if (version_str) { - appData["version"] = QString(version_str); - free(version_str); - } - } + // // Get version + // plist_t version = plist_dict_get_item( + // app_info, "CFBundleShortVersionString"); + // if (version && + // plist_get_node_type(version) == PLIST_STRING) + // { char *version_str = nullptr; + // plist_get_string_val(version, &version_str); + // if (version_str) { + // appData["version"] = + // QString(version_str); free(version_str); + // } + // } - // Get file sharing enabled status - plist_t file_sharing = plist_dict_get_item( - app_info, "UIFileSharingEnabled"); - if (file_sharing && - plist_get_node_type(file_sharing) == - PLIST_BOOLEAN) { - uint8_t file_sharing_enabled = 0; - plist_get_bool_val(file_sharing, - &file_sharing_enabled); - appData["fileSharingEnabled"] = - (file_sharing_enabled != 0); - } else { - appData["fileSharingEnabled"] = false; - } + // // Get file sharing enabled status + // plist_t file_sharing = plist_dict_get_item( + // app_info, "UIFileSharingEnabled"); + // if (file_sharing && + // plist_get_node_type(file_sharing) == + // PLIST_BOOLEAN) { + // uint8_t file_sharing_enabled = 0; + // plist_get_bool_val(file_sharing, + // &file_sharing_enabled); + // appData["fileSharingEnabled"] = + // (file_sharing_enabled != 0); + // } else { + // appData["fileSharingEnabled"] = false; + // } - appData["type"] = appType; + // appData["type"] = appType; - if (!appData["bundleId"].toString().isEmpty()) { - apps.append(appData); - } - } - } - plist_free(apps_plist); - } - plist_free(client_opts); - } + // if (!appData["bundleId"].toString().isEmpty()) { + // apps.append(appData); + // } + // } + // } + // plist_free(apps_plist); + // } + // plist_free(client_opts); + // } - instproxy_client_free(instproxy); - lockdownd_client_free(lockdownClient); + // instproxy_client_free(instproxy); + // lockdownd_client_free(lockdownClient); - result["apps"] = apps; - result["success"] = true; + // result["apps"] = apps; + // result["success"] = true; - } catch (const std::exception &e) { - if (instproxy) - instproxy_client_free(instproxy); - if (lockdownClient) - lockdownd_client_free(lockdownClient); - if (lockdowndService) - lockdownd_service_descriptor_free(lockdowndService); + // } catch (const std::exception &e) { + // if (instproxy) + // instproxy_client_free(instproxy); + // if (lockdownClient) + // lockdownd_client_free(lockdownClient); + // if (lockdowndService) + // lockdownd_service_descriptor_free(lockdowndService); - result["error"] = QString("Exception: %1").arg(e.what()); - } + // result["error"] = QString("Exception: %1").arg(e.what()); + // } - return result; - }); + // return result; + // }); - m_watcher->setFuture(future); + // m_watcher->setFuture(future); } void InstalledAppsWidget::onAppsDataReady() @@ -610,7 +606,7 @@ void InstalledAppsWidget::filterApps(const QString &searchText) */ void InstalledAppsWidget::loadAppContainer(const QString &bundleId) { - if (!m_device || !m_device->device) { + if (!m_device) { return; } @@ -639,186 +635,188 @@ void InstalledAppsWidget::loadAppContainer(const QString &bundleId) m_containerLayout->addWidget(loadingWidget); - QFuture future = QtConcurrent::run([this, bundleId]() - -> QVariantMap { - QVariantMap result; + // QFuture future = QtConcurrent::run([this, bundleId]() + // -> QVariantMap { + // QVariantMap result; - afc_client_t afcClient = nullptr; - lockdownd_client_t lockdownClient = nullptr; - lockdownd_service_descriptor_t lockdowndService = nullptr; - house_arrest_client_t houseArrestClient = nullptr; - try { - if (lockdownd_client_new_with_handshake( - m_device->device, &lockdownClient, APP_LABEL) != - LOCKDOWN_E_SUCCESS) { - result["error"] = "Could not connect to lockdown service"; - return result; - } + // afc_client_t afcClient = nullptr; + // lockdownd_client_t lockdownClient = nullptr; + // lockdownd_service_descriptor_t lockdowndService = nullptr; + // house_arrest_client_t houseArrestClient = nullptr; + // try { + // if (lockdownd_client_new_with_handshake( + // m_device->device, &lockdownClient, APP_LABEL) != + // LOCKDOWN_E_SUCCESS) { + // result["error"] = "Could not connect to lockdown service"; + // return result; + // } - if (lockdownd_start_service( - lockdownClient, "com.apple.mobile.house_arrest", - &lockdowndService) != LOCKDOWN_E_SUCCESS) { - result["error"] = "Could not start house arrest service"; - lockdownd_client_free(lockdownClient); - return result; - } + // if (lockdownd_start_service( + // lockdownClient, "com.apple.mobile.house_arrest", + // &lockdowndService) != LOCKDOWN_E_SUCCESS) { + // result["error"] = "Could not start house arrest service"; + // lockdownd_client_free(lockdownClient); + // return result; + // } - if (house_arrest_client_new(m_device->device, lockdowndService, - &houseArrestClient) != - HOUSE_ARREST_E_SUCCESS) { - result["error"] = "Could not connect to house arrest"; - lockdownd_service_descriptor_free(lockdowndService); - lockdownd_client_free(lockdownClient); - return result; - } + // if (house_arrest_client_new(m_device->device, lockdowndService, + // &houseArrestClient) != + // HOUSE_ARREST_E_SUCCESS) { + // result["error"] = "Could not connect to house arrest"; + // lockdownd_service_descriptor_free(lockdowndService); + // lockdownd_client_free(lockdownClient); + // return result; + // } - lockdownd_service_descriptor_free(lockdowndService); - lockdowndService = nullptr; + // lockdownd_service_descriptor_free(lockdowndService); + // lockdowndService = nullptr; - // Send vendor container command - if (house_arrest_send_command( - houseArrestClient, "VendDocuments", - // if (house_arrest_send_command(houseArrestClient, - // "VendDocuments", - bundleId.toUtf8().constData()) != HOUSE_ARREST_E_SUCCESS) { - result["error"] = "Could not send VendDocuments command"; - house_arrest_client_free(houseArrestClient); - lockdownd_client_free(lockdownClient); - return result; - } + // // Send vendor container command + // if (house_arrest_send_command( + // houseArrestClient, "VendDocuments", + // // if (house_arrest_send_command(houseArrestClient, + // // "VendDocuments", + // bundleId.toUtf8().constData()) != HOUSE_ARREST_E_SUCCESS) + // { + // result["error"] = "Could not send VendDocuments command"; + // house_arrest_client_free(houseArrestClient); + // lockdownd_client_free(lockdownClient); + // return result; + // } - // Get result - plist_t dict = nullptr; - if (house_arrest_get_result(houseArrestClient, &dict) != - HOUSE_ARREST_E_SUCCESS || - !dict) { - result["error"] = "App container not available for this app"; - house_arrest_client_free(houseArrestClient); - lockdownd_client_free(lockdownClient); - return result; - } + // // Get result + // plist_t dict = nullptr; + // if (house_arrest_get_result(houseArrestClient, &dict) != + // HOUSE_ARREST_E_SUCCESS || + // !dict) { + // result["error"] = "App container not available for this app"; + // house_arrest_client_free(houseArrestClient); + // lockdownd_client_free(lockdownClient); + // return result; + // } - // Check for error in response - plist_t error_node = plist_dict_get_item(dict, "Error"); - if (error_node) { - char *error_str = nullptr; - plist_get_string_val(error_node, &error_str); - if (error_str) { - result["error"] = - QString("Container access denied: %1").arg(error_str); - free(error_str); - } else { - result["error"] = "Container access denied"; - } - plist_free(dict); - house_arrest_client_free(houseArrestClient); - lockdownd_client_free(lockdownClient); - return result; - } + // // Check for error in response + // plist_t error_node = plist_dict_get_item(dict, "Error"); + // if (error_node) { + // char *error_str = nullptr; + // plist_get_string_val(error_node, &error_str); + // if (error_str) { + // result["error"] = + // QString("Container access denied: + // %1").arg(error_str); + // free(error_str); + // } else { + // result["error"] = "Container access denied"; + // } + // plist_free(dict); + // house_arrest_client_free(houseArrestClient); + // lockdownd_client_free(lockdownClient); + // return result; + // } - plist_free(dict); + // plist_free(dict); - // Get AFC client for file access - if (afc_client_new_from_house_arrest_client( - houseArrestClient, &afcClient) != AFC_E_SUCCESS) { - result["error"] = - "Could not create AFC client for app container"; - house_arrest_client_free(houseArrestClient); - lockdownd_client_free(lockdownClient); - return result; - } + // // Get AFC client for file access + // if (afc_client_new_from_house_arrest_client( + // houseArrestClient, &afcClient) != AFC_E_SUCCESS) { + // result["error"] = + // "Could not create AFC client for app container"; + // house_arrest_client_free(houseArrestClient); + // lockdownd_client_free(lockdownClient); + // return result; + // } - // List root directory contents - char **list = nullptr; - if (afc_read_directory(afcClient, "/Documents", &list) != - AFC_E_SUCCESS) { - result["error"] = "Could not read app container directory"; - afc_client_free(afcClient); - house_arrest_client_free(houseArrestClient); - lockdownd_client_free(lockdownClient); - return result; - } + // // List root directory contents + // char **list = nullptr; + // if (afc_read_directory(afcClient, "/Documents", &list) != + // AFC_E_SUCCESS) { + // result["error"] = "Could not read app container directory"; + // afc_client_free(afcClient); + // house_arrest_client_free(houseArrestClient); + // lockdownd_client_free(lockdownClient); + // return result; + // } - QStringList files; - if (list) { - for (int i = 0; list[i]; i++) { - QString fileName = QString::fromUtf8(list[i]); - if (fileName != "." && fileName != "..") { - qDebug() << "Found file:" << fileName; - files.append(fileName); - } - } - afc_dictionary_free(list); - } - result["files"] = files; - result["afcClient"] = - QVariant::fromValue(reinterpret_cast(afcClient)); - result["houseArrestClient"] = QVariant::fromValue( - reinterpret_cast(houseArrestClient)); - result["success"] = true; + // QStringList files; + // if (list) { + // for (int i = 0; list[i]; i++) { + // QString fileName = QString::fromUtf8(list[i]); + // if (fileName != "." && fileName != "..") { + // qDebug() << "Found file:" << fileName; + // files.append(fileName); + // } + // } + // afc_dictionary_free(list); + // } + // result["files"] = files; + // result["afcClient"] = + // QVariant::fromValue(reinterpret_cast(afcClient)); + // result["houseArrestClient"] = QVariant::fromValue( + // reinterpret_cast(houseArrestClient)); + // result["success"] = true; - lockdownd_client_free(lockdownClient); + // lockdownd_client_free(lockdownClient); - } catch (const std::exception &e) { - if (afcClient) - afc_client_free(afcClient); - if (houseArrestClient) - house_arrest_client_free(houseArrestClient); - if (lockdownClient) - lockdownd_client_free(lockdownClient); - if (lockdowndService) - lockdownd_service_descriptor_free(lockdowndService); + // } catch (const std::exception &e) { + // if (afcClient) + // afc_client_free(afcClient); + // if (houseArrestClient) + // house_arrest_client_free(houseArrestClient); + // if (lockdownClient) + // lockdownd_client_free(lockdownClient); + // if (lockdowndService) + // lockdownd_service_descriptor_free(lockdowndService); - result["error"] = QString("Exception: %1").arg(e.what()); - } + // result["error"] = QString("Exception: %1").arg(e.what()); + // } - return result; - }); + // return result; + // }); - m_containerWatcher->setFuture(future); + // m_containerWatcher->setFuture(future); } void InstalledAppsWidget::onContainerDataReady() { QVariantMap result = m_containerWatcher->result(); - // todo - // Clear loading state - QLayoutItem *item; - while ((item = m_containerLayout->takeAt(0)) != nullptr) { - if (item->widget()) { - item->widget()->deleteLater(); - } - delete item; - } + // // todo + // // Clear loading state + // QLayoutItem *item; + // while ((item = m_containerLayout->takeAt(0)) != nullptr) { + // if (item->widget()) { + // item->widget()->deleteLater(); + // } + // delete item; + // } - if (!result.value("success", false).toBool()) { - qDebug() << "Error loading app container:" - << result.value("error").toString(); - QLabel *errorLabel = new QLabel("No data available for this app"); - errorLabel->setAlignment(Qt::AlignCenter); - m_containerLayout->addWidget(errorLabel); - return; - } + // if (!result.value("success", false).toBool()) { + // qDebug() << "Error loading app container:" + // << result.value("error").toString(); + // QLabel *errorLabel = new QLabel("No data available for this app"); + // errorLabel->setAlignment(Qt::AlignCenter); + // m_containerLayout->addWidget(errorLabel); + // return; + // } - // Get the AFC clients from the result and store them as member variables - m_houseArrestAfcClient = reinterpret_cast( - result.value("afcClient").value()); - m_houseArrestClient = reinterpret_cast( - result.value("houseArrestClient").value()); + // // Get the AFC clients from the result and store them as member variables + // m_houseArrestAfcClient = reinterpret_cast( + // result.value("afcClient").value()); + // m_houseArrestClient = reinterpret_cast( + // result.value("houseArrestClient").value()); - if (!m_houseArrestAfcClient) { - QLabel *errorLabel = - new QLabel("Failed to get AFC client for app container"); - m_containerLayout->addWidget(errorLabel); - return; - } + // if (!m_houseArrestAfcClient) { + // QLabel *errorLabel = + // new QLabel("Failed to get AFC client for app container"); + // m_containerLayout->addWidget(errorLabel); + // return; + // } - // Create AfcExplorerWidget with the house arrest AFC client - AfcExplorerWidget *explorer = new AfcExplorerWidget( - m_device, true, m_houseArrestAfcClient, "/Documents", this); - explorer->setStyleSheet("border :none;"); - m_containerLayout->addWidget(explorer); + // // Create AfcExplorerWidget with the house arrest AFC client + // AfcExplorerWidget *explorer = new AfcExplorerWidget( + // m_device, true, m_houseArrestAfcClient, "/Documents", this); + // explorer->setStyleSheet("border :none;"); + // m_containerLayout->addWidget(explorer); } void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) @@ -830,15 +828,15 @@ void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) void InstalledAppsWidget::cleanupHouseArrestClients() { - if (m_houseArrestAfcClient) { - afc_client_free(m_houseArrestAfcClient); - m_houseArrestAfcClient = nullptr; - } + // if (m_houseArrestAfcClient) { + // afc_client_free(m_houseArrestAfcClient); + // m_houseArrestAfcClient = nullptr; + // } - if (m_houseArrestClient) { - house_arrest_client_free(m_houseArrestClient); - m_houseArrestClient = nullptr; - } + // if (m_houseArrestClient) { + // house_arrest_client_free(m_houseArrestClient); + // m_houseArrestClient = nullptr; + // } } void InstalledAppsWidget::createLeftPanel() diff --git a/src/installedappswidget.h b/src/installedappswidget.h index 1f41a83..77ad366 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -41,8 +41,6 @@ #include #include #include -#include -#include // Custom App Tab Widget class AppTabWidget : public QGroupBox @@ -134,8 +132,8 @@ private: QFutureWatcher *m_watcher; QFutureWatcher *m_containerWatcher; QSplitter *m_splitter; - house_arrest_client_t m_houseArrestClient = nullptr; - afc_client_t m_houseArrestAfcClient = nullptr; + // house_arrest_client_t m_houseArrestClient = nullptr; + // afc_client_t m_houseArrestAfcClient = nullptr; // App data storage QList m_appTabs; AppTabWidget *m_selectedTab = nullptr; diff --git a/src/livescreenwidget.cpp b/src/livescreenwidget.cpp index aa3e726..16870c4 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -29,17 +29,14 @@ #include #include #include -#include -#include // todo add a retry button when failed LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) - : QWidget{parent}, m_device(device), m_timer(nullptr), - m_shotrClient(nullptr), m_fps(20) + : QWidget{parent}, m_device(device), m_timer(nullptr), m_fps(15) { setWindowTitle("Live Screen - iDescriptor"); - unsigned int device_version = idevice_get_device_version(m_device->device); - unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; + unsigned int deviceMajorVersion = + m_device->deviceInfo.parsedDeviceVersion.major; if (deviceMajorVersion > 16) { QMessageBox::warning( @@ -83,8 +80,10 @@ LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) connect(m_timer, &QTimer::timeout, this, &LiveScreenWidget::updateScreenshot); + startCapturing(); + // Defer the initialization to allow the main widget to show first - QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); + // QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); } void LiveScreenWidget::startInitialization() @@ -122,97 +121,99 @@ LiveScreenWidget::~LiveScreenWidget() m_timer->stop(); } - if (m_shotrClient) { - screenshotr_client_free(m_shotrClient); - m_shotrClient = nullptr; - } + // if (m/*_shotrClient) { + // screenshotr_client_free(m_shotrClient); + // m_shotrClient = nullptr; + // }*/ } bool LiveScreenWidget::initializeScreenshotService(bool notify) { - lockdownd_client_t lockdownClient = nullptr; - lockdownd_service_descriptor_t service = nullptr; + return true; + // lockdownd_client_t lockdownClient = nullptr; + // lockdownd_service_descriptor_t service = nullptr; - try { - m_statusLabel->setText("Connecting to screenshot service..."); + // try { + // m_statusLabel->setText("Connecting to screenshot service..."); - lockdownd_error_t ldret = lockdownd_client_new_with_handshake( - m_device->device, &lockdownClient, APP_LABEL); + // IdeviceFfiError * = + // screenshotr_take_screenshot(m_device->screenshotrClient,) - if (ldret != LOCKDOWN_E_SUCCESS) { - m_statusLabel->setText("Failed to connect to lockdown service"); - if (notify) - QMessageBox::critical(this, "Connection Failed", - "Could not connect to lockdown service.\n" - "Error code: " + - QString::number(ldret)); - return false; - } + // if (ldret != LOCKDOWN_E_SUCCESS) { + // m_statusLabel->setText("Failed to connect to lockdown service"); + // if (notify) + // QMessageBox::critical(this, "Connection Failed", + // "Could not connect to lockdown + // service.\n" "Error code: " + + // QString::number(ldret)); + // return false; + // } - lockdownd_error_t lerr = lockdownd_start_service( - lockdownClient, SCREENSHOTR_SERVICE_NAME, &service); + // lockdownd_error_t lerr = lockdownd_start_service( + // lockdownClient, SCREENSHOTR_SERVICE_NAME, &service); - lockdownd_client_free(lockdownClient); - lockdownClient = nullptr; + // lockdownd_client_free(lockdownClient); + // lockdownClient = nullptr; - if (lerr != LOCKDOWN_E_SUCCESS) { - m_statusLabel->setText("Failed to start screenshot service"); - qDebug() << lerr << "lockdownd_start_service"; - if (notify) - QMessageBox::critical( - this, "Service Failed", - "Could not start screenshot service on device.\n" - "Please ensure the developer disk image is properly " - "mounted."); - if (service) { - lockdownd_service_descriptor_free(service); - } - return false; - } + // if (lerr != LOCKDOWN_E_SUCCESS) { + // m_statusLabel->setText("Failed to start screenshot service"); + // qDebug() << lerr << "lockdownd_start_service"; + // if (notify) + // QMessageBox::critical( + // this, "Service Failed", + // "Could not start screenshot service on device.\n" + // "Please ensure the developer disk image is properly " + // "mounted."); + // if (service) { + // lockdownd_service_descriptor_free(service); + // } + // return false; + // } - screenshotr_error_t screrr = - screenshotr_client_new(m_device->device, service, &m_shotrClient); + // screenshotr_error_t screrr = + // screenshotr_client_new(m_device->device, service, + // &m_shotrClient); - lockdownd_service_descriptor_free(service); - service = nullptr; - qDebug() << screrr << "screenshotr_client_new"; - if (screrr != SCREENSHOTR_E_SUCCESS) { - m_statusLabel->setText("Failed to create screenshot client"); - if (notify) - QMessageBox::critical(this, "Client Failed", - "Could not create screenshot client.\n" - "Error code: " + - QString::number(screrr)); - return false; - } + // lockdownd_service_descriptor_free(service); + // service = nullptr; + // qDebug() << screrr << "screenshotr_client_new"; + // if (screrr != SCREENSHOTR_E_SUCCESS) { + // m_statusLabel->setText("Failed to create screenshot client"); + // if (notify) + // QMessageBox::critical(this, "Client Failed", + // "Could not create screenshot client.\n" + // "Error code: " + + // QString::number(screrr)); + // return false; + // } - // Successfully initialized, start capturing - m_statusLabel->setText("Capturing"); - startCapturing(); - return true; - } catch (const std::exception &e) { - m_statusLabel->setText("Exception occurred"); - if (notify) - QMessageBox::critical( - this, "Exception", - QString("Exception occurred: %1").arg(e.what())); + // // Successfully initialized, start capturing + // m_statusLabel->setText("Capturing"); + // startCapturing(); + // return true; + // } catch (const std::exception &e) { + // m_statusLabel->setText("Exception occurred"); + // if (notify) + // QMessageBox::critical( + // this, "Exception", + // QString("Exception occurred: %1").arg(e.what())); - if (lockdownClient) { - lockdownd_client_free(lockdownClient); - } - if (service) { - lockdownd_service_descriptor_free(service); - } - } + // if (lockdownClient) { + // lockdownd_client_free(lockdownClient); + // } + // if (service) { + // lockdownd_service_descriptor_free(service); + // } + // } } void LiveScreenWidget::startCapturing() { - if (!m_shotrClient) { - qWarning() - << "Cannot start capturing: screenshot client not initialized"; - return; - } + // if (!m_shotrClient) { + // qWarning() + // << "Cannot start capturing: screenshot client not initialized"; + // return; + // } if (m_timer) { m_timer->start(); @@ -222,22 +223,44 @@ void LiveScreenWidget::startCapturing() void LiveScreenWidget::updateScreenshot() { - if (!m_shotrClient) { - qWarning() << "Screenshot client not initialized"; - return; - } - + // if (!m_shotrClient) { + // qWarning() << "Screenshot client not initialized"; + // return; + // } + qDebug() << "Updating screenshot..."; try { - TakeScreenshotResult result = take_screenshot(m_shotrClient); + // TakeScreenshotResult result = take_screenshot(m_shotrClient); + ScreenshotData screenshot; + IdeviceFfiError *err = screenshotr_take_screenshot( + m_device->screenshotrClient, &screenshot); + if (!err && screenshot.data && screenshot.length > 0) { + qDebug() << "Screenshot captured, size:" << screenshot.length; + // QImage img(screenshot.data, // data + // static_cast(screenshot.length), // width + // 1, // height + // QImage::Format_ARGB32); // format - if (result.success && !result.img.isNull()) { - QPixmap pixmap = QPixmap::fromImage(result.img); + QByteArray byteArray( + reinterpret_cast(screenshot.data), + static_cast(screenshot.length)); + QImage image; + image.loadFromData(byteArray); + QPixmap pixmap = QPixmap::fromImage(image); m_imageLabel->setPixmap(pixmap.scaled(m_imageLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { - qWarning() << "Failed to capture screenshot"; + qDebug() << "Failed to capture screenshot"; } + + // if (result.success && !result.img.isNull()) { + // QPixmap pixmap = QPixmap::fromImage(result.img); + // m_imageLabel->setPixmap(pixmap.scaled(m_imageLabel->size(), + // Qt::KeepAspectRatio, + // Qt::SmoothTransformation)); + // } else { + // qWarning() << "Failed to capture screenshot"; + // } } catch (const std::exception &e) { qWarning() << "Exception in updateScreenshot:" << e.what(); m_statusLabel->setText("Error capturing screenshot"); diff --git a/src/livescreenwidget.h b/src/livescreenwidget.h index 361608f..bc4c1f8 100644 --- a/src/livescreenwidget.h +++ b/src/livescreenwidget.h @@ -24,8 +24,6 @@ #include #include #include -#include -#include class LiveScreenWidget : public QWidget { @@ -44,7 +42,6 @@ private: QTimer *m_timer; QLabel *m_imageLabel; QLabel *m_statusLabel; - screenshotr_client_t m_shotrClient; int m_fps; private: diff --git a/src/main.cpp b/src/main.cpp index e16ea84..9f38c87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "mainwindow.h" // #include "settingsmanager.h" +#include "iDescriptor.h" #include #include #include @@ -36,7 +37,7 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("iDescriptor"); QCoreApplication::setApplicationName("iDescriptor"); QCoreApplication::setApplicationVersion(APP_VERSION); - + // idevice_init_logger(Debug, Disabled, NULL); // if (a.arguments().contains("--reset-settings")) { // SettingsManager::sharedInstance()->clear(); // QMessageBox::information(nullptr, "Settings Reset", diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 19291ff..7c876a7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -19,10 +19,10 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" -// #include "detailwindow.h" -// #include "ifusediskunmountbutton.h" -// #include "ifusemanager.h" -// #include "settingswidget.h" +#include "detailwindow.h" +#include "ifusediskunmountbutton.h" +#include "ifusemanager.h" +#include "settingswidget.h" #include "appswidget.h" #include "devicemanagerwidget.h" @@ -42,7 +42,7 @@ #include #include "appcontext.h" -// #include "settingsmanager.h" +#include "settingsmanager.h" // #include "devicemonitor.h" #include "networkdevicemanager.h" #include "networkdeviceswidget.h" @@ -52,9 +52,9 @@ #include #include -// #ifdef WIN32 -// #include "platform/windows/check_deps.h" -// #endif +#ifdef WIN32 +#include "platform/windows/check_deps.h" +#endif using namespace IdeviceFFI; @@ -171,32 +171,26 @@ MainWindow::MainWindow(QWidget *parent) this, &MainWindow::updateNoDevicesConnected); m_ZTabWidget->addTab(m_mainStackedWidget, "iDevice"); - // auto *appsWidgetTab = - // m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); - // m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); - m_ZTabWidget->addTab(new QWidget(), "Apps"); // Placeholder for Apps tab - m_ZTabWidget->addTab(new QWidget(), - "Toolbox"); // Placeholder for Toolbox tab - m_ZTabWidget->addTab(new QWidget(), - "Jailbroken"); // Placeholder for Jailbroken tab - - // auto *jailbrokenWidget = new JailbrokenWidget(this); - // m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); + auto *appsWidgetTab = + m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); + m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); + auto *jailbrokenWidget = new JailbrokenWidget(this); + m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); m_ZTabWidget->finalizeStyles(); - // connect( - // appsWidgetTab, &ZTab::clicked, this, - // [this](int index) { AppsWidget::sharedInstance()->init(); }, - // Qt::SingleShotConnection); + connect( + appsWidgetTab, &ZTab::clicked, this, + [this](int index) { AppsWidget::sharedInstance()->init(); }, + Qt::SingleShotConnection); - // // settings button - // ZIconWidget *settingsButton = new ZIconWidget( - // QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings"); - // settingsButton->setCursor(Qt::PointingHandCursor); - // settingsButton->setFixedSize(24, 24); - // connect(settingsButton, &ZIconWidget::clicked, this, [this]() { - // SettingsManager::sharedInstance()->showSettingsDialog(); - // }); + // settings button + ZIconWidget *settingsButton = new ZIconWidget( + QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings"); + settingsButton->setCursor(Qt::PointingHandCursor); + settingsButton->setFixedSize(24, 24); + connect(settingsButton, &ZIconWidget::clicked, this, [this]() { + SettingsManager::sharedInstance()->showSettingsDialog(); + }); ZIconWidget *githubButton = new ZIconWidget( QIcon(":/resources/icons/MdiGithub.png"), "iDescriptor on GitHub"); @@ -218,30 +212,28 @@ MainWindow::MainWindow(QWidget *parent) "QLabel:hover { background-color : #13131319; }"); ui->statusbar->addPermanentWidget(appVersionLabel); ui->statusbar->addPermanentWidget(githubButton); - // ui->statusbar->addPermanentWidget(settingsButton); + ui->statusbar->addPermanentWidget(settingsButton); - // #ifdef __linux__ - // QList mounted_iFusePaths = iFuseManager::getMountPoints(); +#ifdef __linux__ + QList mounted_iFusePaths = iFuseManager::getMountPoints(); - // for (const QString &path : mounted_iFusePaths) { - // auto *p = new iFuseDiskUnmountButton(path); + for (const QString &path : mounted_iFusePaths) { + auto *p = new iFuseDiskUnmountButton(path); - // ui->statusbar->addPermanentWidget(p); - // connect(p, &iFuseDiskUnmountButton::clicked, this, [this, p, - // path]() { - // bool ok = iFuseManager::linuxUnmount(path); - // if (!ok) { - // QMessageBox::warning(nullptr, "Unmount Failed", - // "Failed to unmount iFuse at " + path - // + - // ". Please try again."); - // return; - // } - // ui->statusbar->removeWidget(p); - // p->deleteLater(); - // }); - // } - // #endif + ui->statusbar->addPermanentWidget(p); + connect(p, &iFuseDiskUnmountButton::clicked, this, [this, p, path]() { + bool ok = iFuseManager::linuxUnmount(path); + if (!ok) { + QMessageBox::warning(nullptr, "Unmount Failed", + "Failed to unmount iFuse at " + path + + ". Please try again."); + return; + } + ui->statusbar->removeWidget(p); + p->deleteLater(); + }); + } +#endif // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // irecv_error_t res_recovery = @@ -450,6 +442,7 @@ MainWindow::MainWindow(QWidget *parent) connect(NetworkDeviceManager::sharedInstance(), &NetworkDeviceManager::deviceAdded, this, [this](const NetworkDevice &device) { + // return; // FIXME: disable for now const iDescriptorDevice *idevice = AppContext::sharedInstance()->getDeviceByMacAddress( device.macAddress); diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 1a49f59..93f82d3 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -18,6 +18,8 @@ */ #include "mediapreviewdialog.h" +#include "appcontext.h" +#include "iDescriptor-ui.h" #include "mediastreamermanager.h" #include "photomodel.h" #include @@ -43,13 +45,9 @@ #include #include #include -#include "appcontext.h" -#include "iDescriptor-ui.h" - - MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device, - afc_client_t afcClient, + AfcClientHandle *afcClient, const QString &filePath, QWidget *parent) : QDialog(parent), m_device(device), m_filePath(filePath), m_isVideo(isVideoFile(filePath)), m_mainLayout(nullptr), @@ -76,11 +74,12 @@ MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device, setupUI(); loadMedia(); - connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [this](const std::string &udid) { - if (udid == m_device->udid) { - close(); - } - }); + connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, + [this](const std::string &udid) { + if (udid == m_device->udid) { + close(); + } + }); } MediaPreviewDialog::~MediaPreviewDialog() diff --git a/src/mediapreviewdialog.h b/src/mediapreviewdialog.h index 5cf897d..b80771b 100644 --- a/src/mediapreviewdialog.h +++ b/src/mediapreviewdialog.h @@ -52,7 +52,8 @@ class MediaPreviewDialog : public QDialog public: explicit MediaPreviewDialog(iDescriptorDevice *device, - afc_client_t afcClient, const QString &filePath, + AfcClientHandle *afcClient, + const QString &filePath, QWidget *parent = nullptr); ~MediaPreviewDialog(); @@ -145,7 +146,7 @@ private: bool m_isDraggingTimeline; qint64 m_videoDuration; - afc_client_t m_afcClient; + AfcClientHandle *m_afcClient; }; #endif // MEDIAPREVIEWDIALOG_H diff --git a/src/mediastreamer.cpp b/src/mediastreamer.cpp index 45cb5eb..5e07248 100644 --- a/src/mediastreamer.cpp +++ b/src/mediastreamer.cpp @@ -28,10 +28,10 @@ #include #include #include -#include #include -MediaStreamer::MediaStreamer(iDescriptorDevice *device, afc_client_t afcClient, +MediaStreamer::MediaStreamer(iDescriptorDevice *device, + AfcClientHandle *afcClient, const QString &filePath, QObject *parent) : QTcpServer(parent), m_device(device), m_afcClient(afcClient), m_filePath(filePath), m_cachedFileSize(-1), m_fileSizeCached(false) @@ -274,16 +274,16 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, context->startByte = startByte; context->endByte = endByte; context->bytesRemaining = endByte - startByte + 1; - context->afcHandle = 0; + context->afcHandle = nullptr; qDebug() << "m_filepath" << m_filePath; - // Open file on device using ServiceManager const QByteArray pathBytes = m_filePath.toUtf8(); - afc_error_t openResult = ServiceManager::safeAfcFileOpen( - m_device, pathBytes.constData(), AFC_FOPEN_RDONLY, &context->afcHandle, - m_afcClient); - if (openResult != AFC_E_SUCCESS || context->afcHandle == 0) { + IdeviceFfiError *err_open = // Use distinct variable name + ServiceManager::safeAfcFileOpen(m_device, pathBytes.constData(), + AfcRdOnly, &context->afcHandle); + + if (err_open || context->afcHandle == 0) { qWarning() << "Failed to open file on device:" << m_filePath; delete context; socket->disconnectFromHost(); @@ -292,12 +292,18 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, // Seek to start position if needed if (startByte > 0) { - afc_error_t seekResult = ServiceManager::safeAfcFileSeek( - m_device, context->afcHandle, startByte, SEEK_SET, m_afcClient); - if (seekResult != AFC_E_SUCCESS) { + // FIXME: m_afcClient must be passed as alt afc + IdeviceFfiError *seek_err = ServiceManager::safeAfcFileSeek( + m_device, context->afcHandle, startByte, SEEK_SET); + + if (seek_err) { qWarning() << "Failed to seek in file:" << m_filePath; - ServiceManager::safeAfcFileClose(m_device, context->afcHandle, - m_afcClient); + // FIXME: m_afcClient must be passed as alt afc + IdeviceFfiError *err = + ServiceManager::safeAfcFileClose(m_device, context->afcHandle); + if (err) { + idevice_error_free(err); + } delete context; socket->disconnectFromHost(); return; @@ -351,28 +357,22 @@ qint64 MediaStreamer::getFileSize() } // Get file info from device using ServiceManager - char **info = nullptr; const QByteArray pathBytes = m_filePath.toUtf8(); - afc_error_t result = ServiceManager::safeAfcGetFileInfo( - m_device, pathBytes.constData(), &info, m_afcClient); - if (result != AFC_E_SUCCESS || !info) { + AfcFileInfo info = {}; + IdeviceFfiError *info_err = ServiceManager::safeAfcGetFileInfo( + m_device, pathBytes.constData(), &info); + + if (info_err || info.size == 0) { qWarning() << "Failed to get file info for:" << m_filePath; + idevice_error_free(info_err); return -1; } - qint64 fileSize = -1; - for (int i = 0; info[i]; i += 2) { - if (strcmp(info[i], "st_size") == 0) { - bool ok; - fileSize = QString(info[i + 1]).toLongLong(&ok); - if (!ok) - fileSize = -1; - break; - } - } + size_t fileSize = info.size; - afc_dictionary_free(info); + // FIXME : safe to free ? + // afc_file_info_free(&info); if (fileSize > 0) { m_cachedFileSize = fileSize; @@ -402,12 +402,11 @@ QString MediaStreamer::getMimeType() const void MediaStreamer::streamNextChunk(StreamingContext *context) { if (!context || !context->socket) { - return; // Invalid context, don't cleanup here + return; } - // Check if context has been marked for cleanup if (context->socket->property("streamingContext").isNull()) { - return; // Already cleaned up + return; } if (context->bytesRemaining <= 0) { @@ -417,39 +416,58 @@ void MediaStreamer::streamNextChunk(StreamingContext *context) return; } - // Check if socket is still valid if (context->socket->state() != QAbstractSocket::ConnectedState) { cleanupStreamingContext(context); return; } - const int CHUNK_SIZE = 64 * 1024; // 64KB chunks + const int CHUNK_SIZE = 256 * 1024; const uint32_t bytesToRead = static_cast( qMin(static_cast(CHUNK_SIZE), context->bytesRemaining)); - auto buffer = std::make_unique(bytesToRead); - uint32_t bytesRead = 0; + uint8_t *chunkData = nullptr; + size_t bytesRead = 0; - afc_error_t readResult = ServiceManager::safeAfcFileRead( - m_device, context->afcHandle, buffer.get(), bytesToRead, &bytesRead, - m_afcClient); + IdeviceFfiError *read_err = ServiceManager::safeAfcFileRead( + m_device, context->afcHandle, &chunkData, bytesToRead, &bytesRead); - if (readResult != AFC_E_SUCCESS || bytesRead == 0) { - qWarning() << "AFC read error or EOF during streaming"; + if (read_err) { + qWarning() << "AFC read error during streaming:" << read_err->message; + idevice_error_free(read_err); cleanupStreamingContext(context); return; } - const qint64 bytesWritten = context->socket->write(buffer.get(), bytesRead); - if (bytesWritten == -1) { - qWarning() << "Socket write error"; + if (bytesRead == 0 && bytesToRead > 0) { + qWarning() << "AFC read returned 0 bytes but expected to read:" + << bytesToRead; + if (chunkData) { + afc_file_read_data_free(chunkData, 0); + } cleanupStreamingContext(context); return; } - context->bytesRemaining -= bytesWritten; + if (bytesRead > 0 && chunkData) { + const qint64 bytesWritten = context->socket->write( + reinterpret_cast(chunkData), bytesRead); + + afc_file_read_data_free(chunkData, bytesRead); + + if (bytesWritten == -1) { + qWarning() << "Socket write error"; + cleanupStreamingContext(context); + return; + } + + context->bytesRemaining -= bytesWritten; + } else { + qWarning() << "AFC read error: No data or null chunkData despite " + "bytesRead > 0."; + cleanupStreamingContext(context); + return; + } - // If we're done, clean up if (context->bytesRemaining <= 0) { qDebug() << "Streaming completed for" << QFileInfo(context->filePath).fileName(); @@ -457,9 +475,7 @@ void MediaStreamer::streamNextChunk(StreamingContext *context) return; } - // If socket buffer is getting full, let bytesWritten signal handle the next - // chunk Otherwise, continue immediately for better performance - if (context->socket->bytesToWrite() >= 32768) { + if (context->socket->bytesToWrite() >= 1024 * 1024) { // Wait for bytesWritten signal return; } else { @@ -489,8 +505,9 @@ void MediaStreamer::cleanupStreamingContext(StreamingContext *context) } if (context->afcHandle != 0) { - ServiceManager::safeAfcFileClose(context->device, context->afcHandle, - m_afcClient); + // FIXME: m_afcClient must be passed as alt afc + ServiceManager::safeAfcFileClose(context->device, context->afcHandle + /*m_afcClient*/); context->afcHandle = 0; } diff --git a/src/mediastreamer.h b/src/mediastreamer.h index 0c8eba6..80f6169 100644 --- a/src/mediastreamer.h +++ b/src/mediastreamer.h @@ -47,8 +47,9 @@ class MediaStreamer : public QTcpServer Q_OBJECT public: - explicit MediaStreamer(iDescriptorDevice *device, afc_client_t afcClient, - const QString &filePath, QObject *parent = nullptr); + explicit MediaStreamer(iDescriptorDevice *device, + AfcClientHandle *afcClient, const QString &filePath, + QObject *parent = nullptr); ~MediaStreamer(); /** @@ -87,7 +88,7 @@ private: qint64 startByte; qint64 endByte; qint64 bytesRemaining; - uint64_t afcHandle; + AfcFileHandle *afcHandle; }; HttpRequest parseHttpRequest(const QByteArray &requestData); @@ -113,7 +114,7 @@ private: QList m_activeConnections; QMutex m_connectionsMutex; - afc_client_t m_afcClient; + AfcClientHandle *m_afcClient; }; #endif // MEDIASTREAMER_H diff --git a/src/mediastreamermanager.cpp b/src/mediastreamermanager.cpp index bf371cc..b2e96c2 100644 --- a/src/mediastreamermanager.cpp +++ b/src/mediastreamermanager.cpp @@ -31,7 +31,7 @@ MediaStreamerManager *MediaStreamerManager::sharedInstance() } QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device, - afc_client_t afcClient, + AfcClientHandle *afcClient, const QString &filePath) { QMutexLocker locker(&m_streamersMutex); diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h index 0e53c90..6170bc7 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -49,7 +49,7 @@ public: * @param filePath The file path on the device * @return URL to stream the file, or empty URL if failed */ - QUrl getStreamUrl(iDescriptorDevice *device, afc_client_t afcClient, + QUrl getStreamUrl(iDescriptorDevice *device, AfcClientHandle *afcClient, const QString &filePath); /** diff --git a/src/photomodel.cpp b/src/photomodel.cpp index eb5e688..74dfed4 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -86,32 +86,38 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, AfcFileHandle *fileHandle = nullptr; - IdeviceFfiError *err = + IdeviceFfiError *err_open = // Use distinct variable name for clarity ServiceManager::safeAfcFileOpen(device, filePath.toUtf8().constData(), AfcFopenMode::AfcRdOnly, &fileHandle); - if (err || fileHandle == nullptr) { + if (err_open || fileHandle == nullptr) { qWarning() << "Failed to open video file for thumbnail:" << filePath; - if (err) { - // idevice_error_free(err); + if (err_open) { + idevice_error_free(err_open); } return {}; } // Get file size - AfcFileInfo *info = nullptr; - err = ServiceManager::safeAfcGetFileInfo( - device, filePath.toUtf8().constData(), info); + AfcFileInfo info = {}; + IdeviceFfiError + *err_info = // Use distinct variable name for the error from GetFileInfo + ServiceManager::safeAfcGetFileInfo( + device, filePath.toUtf8().constData(), &info); uint64_t fileSize = 0; - if (!err && info) { - fileSize = info->size; - // afc_file_info_free(info); - if (err) { - // idevice_error_free(err); - } + if (err_info) { + qWarning() << "Failed to get file info for thumbnail:" << filePath + << "Error:" << err_info->message; + idevice_error_free(err_info); + ServiceManager::safeAfcFileClose(device, fileHandle); + afc_file_info_free(&info); // Free internal strings of info + return {}; } + fileSize = info.size; + afc_file_info_free(&info); // Free internal strings of info after use + if (fileSize == 0) { ServiceManager::safeAfcFileClose(device, fileHandle); qWarning() << "Invalid video file size for thumbnail:" << filePath; @@ -139,31 +145,64 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, // Custom read function that reads from device on-demand auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int { - // StreamContext *ctx = static_cast(opaque); + StreamContext *ctx = static_cast(opaque); - // if (ctx->currentPos >= ctx->fileSize) { - // return AVERROR_EOF; - // } + if (ctx->currentPos >= ctx->fileSize) { + return AVERROR_EOF; + } - // uint32_t toRead = - // std::min(static_cast(bufSize), - // static_cast(ctx->fileSize - ctx->currentPos)); - // uint32_t bytesRead = 0; + uint32_t toRead = + std::min(static_cast(bufSize), + static_cast(ctx->fileSize - ctx->currentPos)); + size_t bytesRead = 0; + uint8_t *read_data_ptr = + nullptr; // Pointer to store the data allocated by safeAfcFileRead - // IdeviceFfiError *err = ServiceManager::safeAfcFileRead( - // ctx->device, ctx->fileHandle, reinterpret_cast(buf), - // toRead, &bytesRead); + // Call safeAfcFileRead to get the data into a newly allocated buffer + IdeviceFfiError *err = ServiceManager::safeAfcFileRead( + ctx->device, ctx->fileHandle, &read_data_ptr, toRead, &bytesRead); - // if (err || bytesRead == 0) { - // if (err) { - // idevice_error_free(err); - // } - // return AVERROR(EIO); - // } + if (err) { + qWarning() << "AFC read error in readPacket for file handle" + << ctx->fileHandle << ":" << err->message; + idevice_error_free(err); + return AVERROR(EIO); + } - // ctx->currentPos += bytesRead; - // return static_cast(bytesRead); - return 0; + if (bytesRead == 0) { + // If bytesRead is 0 but we expected to read more, it's an error. + // If currentPos is already at fileSize, it's EOF. + if (ctx->currentPos < ctx->fileSize) { + qWarning() << "AFC readPacket returned 0 bytes but not EOF, " + "expected toRead:" + << toRead << "currentPos:" << ctx->currentPos + << "fileSize:" << ctx->fileSize; + // Free the allocated pointer if safeAfcFileRead still allocates + // even for 0 bytes. + if (read_data_ptr) { + afc_file_read_data_free(read_data_ptr, 0); + } + return AVERROR(EIO); + } + // It's EOF + return AVERROR_EOF; + } + + // Copy the data from the newly allocated `read_data_ptr` into FFmpeg's + // `buf` + if (read_data_ptr) { + memcpy(buf, read_data_ptr, bytesRead); + afc_file_read_data_free( + read_data_ptr, + bytesRead); // Free the memory allocated by safeAfcFileRead + } else { + qWarning() << "AFC readPacket: read_data_ptr was null but " + "bytesRead > 0. This is unexpected."; + return AVERROR(EIO); + } + + ctx->currentPos += bytesRead; + return static_cast(bytesRead); }; // Custom seek function using AFC seek @@ -198,7 +237,8 @@ QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, ctx->device, ctx->fileHandle, newPos, seekWhence); if (err) { - qDebug() << "AFC seek error:" << err->message; + qDebug() << "AFC seek error:" << err->message + << "code:" << err->code; // idevice_error_free(err); return -1; } @@ -806,11 +846,9 @@ QDateTime PhotoModel::extractDateTimeFromFile(const QString &filePath) const IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo( m_device, filePath.toUtf8().constData(), info); if (!err && info) { - uint64_t birthtime_ns = info->st_birthtime; - // Convert nanoseconds since epoch to QDateTime - // The timestamp appears to be in nanoseconds since Unix epoch - uint64_t seconds = birthtime_ns / 1000000000ULL; - QDateTime dateTime = QDateTime::fromSecsSinceEpoch(seconds, Qt::UTC); + uint64_t creation_seconds = info->creation; + QDateTime dateTime = + QDateTime::fromSecsSinceEpoch(creation_seconds, Qt::UTC); // afc_file_info_free(info); if (dateTime.isValid()) { diff --git a/src/qballoontip.cpp b/src/qballoontip.cpp new file mode 100644 index 0000000..815afcb --- /dev/null +++ b/src/qballoontip.cpp @@ -0,0 +1,299 @@ +#include "qballoontip.h" +#include "iDescriptor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QBalloonTip *theSolitaryBalloonTip = nullptr; + +void QBalloonTip::showBalloon(const QIcon &icon, const QString &title, + const QString &message, QWidget *widget, + const QPoint &pos, int timeout, bool showArrow) +{ + hideBalloon(); + if (message.isEmpty() && title.isEmpty()) + return; + + theSolitaryBalloonTip = new QBalloonTip(icon, title, message, widget); + if (timeout < 0) + timeout = 10000; // 10 s default + theSolitaryBalloonTip->balloon(pos, timeout, showArrow); +} + +void QBalloonTip::hideBalloon() +{ + if (!theSolitaryBalloonTip) + return; + theSolitaryBalloonTip->hide(); + delete theSolitaryBalloonTip; + theSolitaryBalloonTip = nullptr; +} + +void QBalloonTip::updateBalloonPosition(const QPoint &pos) +{ + if (!theSolitaryBalloonTip) + return; + theSolitaryBalloonTip->hide(); + theSolitaryBalloonTip->balloon(pos, 0, theSolitaryBalloonTip->showArrow); +} + +bool QBalloonTip::isBalloonVisible() { return theSolitaryBalloonTip; } + +QBalloonTip::QBalloonTip(const QIcon &icon, const QString &title, + const QString &message, QWidget *widget) + : QWidget(nullptr, Qt::ToolTip), widget(widget), showArrow(true) +{ + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_TranslucentBackground); + if (widget) { + connect(widget, &QWidget::destroyed, this, &QBalloonTip::close); + } + + // Add drop shadow effect + QGraphicsDropShadowEffect *shadowEffect = + new QGraphicsDropShadowEffect(this); + shadowEffect->setBlurRadius(200); + shadowEffect->setColor(QColor(0, 0, 0, 80)); + shadowEffect->setOffset(0, 5); + setGraphicsEffect(shadowEffect); + + QLabel *titleLabel = new QLabel; + titleLabel->installEventFilter(this); + titleLabel->setText(title); + QFont f = titleLabel->font(); + f.setBold(true); + titleLabel->setFont(f); + titleLabel->setTextFormat(Qt::PlainText); // to maintain compat with windows + + const int iconSize = 18; + const int closeButtonSize = 15; + + QPushButton *closeButton = new QPushButton; + closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + closeButton->setIconSize(QSize(closeButtonSize, closeButtonSize)); + closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + closeButton->setFixedSize(closeButtonSize, closeButtonSize); + QObject::connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); + + QLabel *msgLabel = new QLabel; + msgLabel->installEventFilter(this); + msgLabel->setText(message); + msgLabel->setTextFormat(Qt::PlainText); + msgLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + QGridLayout *layout = new QGridLayout; + if (!icon.isNull()) { + QLabel *iconLabel = new QLabel; + iconLabel->setPixmap( + icon.pixmap(QSize(iconSize, iconSize), devicePixelRatio())); + iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + iconLabel->setMargin(2); + layout->addWidget(iconLabel, 0, 0); + layout->addWidget(titleLabel, 0, 1); + } else { + layout->addWidget(titleLabel, 0, 0, 1, 2); + } + + layout->addWidget(closeButton, 0, 2); + + layout->addWidget(msgLabel, 1, 0, 1, 3); + layout->setSizeConstraint(QLayout::SetFixedSize); + layout->setContentsMargins(3, 3, 3, 3); + setLayout(layout); +} + +QBalloonTip::~QBalloonTip() { theSolitaryBalloonTip = nullptr; } + +void QBalloonTip::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.drawPixmap(rect(), pixmap); +} + +void QBalloonTip::resizeEvent(QResizeEvent *ev) { QWidget::resizeEvent(ev); } + +void QBalloonTip::balloon(const QPoint &pos, int msecs, bool showArrow) +{ + this->showArrow = showArrow; + QScreen *screen = QGuiApplication::screenAt(pos); + if (!screen) + screen = QGuiApplication::primaryScreen(); + QRect screenRect = screen->geometry(); + QSize sh = sizeHint(); + const int border = 1; + const int ah = 18, aw = 18, rc = 7; + bool arrowAtTop = (pos.y() + sh.height() + ah < screenRect.height()); + + setContentsMargins(border + 3, border + (arrowAtTop ? ah : 0) + 2, + border + 3, border + (arrowAtTop ? 0 : ah) + 2); + updateGeometry(); + sh = sizeHint(); + + // Center the balloon relative to the pos point (button center) + int balloonX = pos.x() - sh.width() / 2; + + // Calculate arrow offset from left edge of balloon to center it + int ao = sh.width() / 2 - aw / 2; // Center the arrow on the balloon + + int ml, mr, mt, mb; + QSize sz = sizeHint(); + if (!arrowAtTop) { + ml = mt = 0; + mr = sz.width() - 1; + mb = sz.height() - ah - 1; + } else { + ml = 0; + mt = ah; + mr = sz.width() - 1; + mb = sz.height() - 1; + } + + QPainterPath path; + path.moveTo(ml + rc, mt); + if (arrowAtTop) { + if (showArrow) { + path.lineTo(ml + ao, mt); + path.lineTo(ml + ao + aw / 2, mt - ah); + path.lineTo(ml + ao + aw, mt); + } + move(qBound(screenRect.left() + 2, balloonX, + screenRect.right() - sh.width() - 2), + pos.y()); + } + path.lineTo(mr - rc, mt); + path.arcTo(QRect(mr - rc * 2, mt, rc * 2, rc * 2), 90, -90); + path.lineTo(mr, mb - rc); + path.arcTo(QRect(mr - rc * 2, mb - rc * 2, rc * 2, rc * 2), 0, -90); + if (!arrowAtTop) { + if (showArrow) { + path.lineTo(mr - ao - aw, mb); + path.lineTo(mr - ao - aw / 2, mb + ah); + path.lineTo(mr - ao, mb); + } + move(qBound(screenRect.left() + 2, balloonX, + screenRect.right() - sh.width() - 2), + pos.y() - sh.height()); + } + path.lineTo(ml + rc, mb); + path.arcTo(QRect(ml, mb - rc * 2, rc * 2, rc * 2), -90, -90); + path.lineTo(ml, mt + rc); + path.arcTo(QRect(ml, mt, rc * 2, rc * 2), 180, -90); + + // Set the mask + QBitmap bitmap = QBitmap(sizeHint()); + bitmap.fill(Qt::color0); + QPainter painter1(&bitmap); + painter1.setPen(QPen(Qt::color1, border)); + painter1.setBrush(QBrush(Qt::color1)); + painter1.drawPath(path); + setMask(bitmap); + + // Draw the border with background color + pixmap = QPixmap(sz); + pixmap.fill(Qt::transparent); + QPainter painter2(&pixmap); + painter2.setRenderHint(QPainter::Antialiasing); + bool isDark = isDarkMode(); + QColor lightColor = qApp->palette().color(QPalette::Light); + QColor darkColor = qApp->palette().color(QPalette::Dark); + QColor bgColor = isDark ? lightColor : darkColor; + painter2.setPen(QPen(bgColor.darker(160), border)); + painter2.setBrush(bgColor); + painter2.drawPath(path); + + if (msecs > 0) + timer.start(msecs, this); + + // Install event filter to detect clicks outside + qApp->installEventFilter(this); + + // Set initial scale and opacity for animation + setWindowOpacity(0.0); + + // Store the transform origin point (center of the widget) + QPoint center = rect().center(); + setProperty("transformOriginPoint", center); + + show(); + + // Create scale and opacity animations + QPropertyAnimation *scaleAnim = new QPropertyAnimation(this, "geometry"); + scaleAnim->setDuration(200); + scaleAnim->setEasingCurve(QEasingCurve::OutBack); + + // Calculate scaled geometry (start from 80% size) + QRect finalGeometry = geometry(); + QRect startGeometry = finalGeometry; + int widthDiff = finalGeometry.width() * 0.2; + int heightDiff = finalGeometry.height() * 0.2; + startGeometry.adjust(widthDiff / 2, heightDiff / 2, -widthDiff / 2, + -heightDiff / 2); + + scaleAnim->setStartValue(startGeometry); + scaleAnim->setEndValue(finalGeometry); + + QPropertyAnimation *opacityAnim = + new QPropertyAnimation(this, "windowOpacity"); + opacityAnim->setDuration(200); + opacityAnim->setStartValue(0.0); + opacityAnim->setEndValue(1.0); + opacityAnim->setEasingCurve(QEasingCurve::OutCubic); + + scaleAnim->start(QAbstractAnimation::DeleteWhenStopped); + opacityAnim->start(QAbstractAnimation::DeleteWhenStopped); +} + +void QBalloonTip::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton) { + emit messageClicked(); + } +} + +void QBalloonTip::timerEvent(QTimerEvent *e) +{ + if (e->timerId() == timer.timerId()) { + timer.stop(); + if (!underMouse()) + close(); + return; + } + QWidget::timerEvent(e); +} + +bool QBalloonTip::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouseEvent = static_cast(event); + // Check if click is outside the balloon + if (!geometry().contains(mouseEvent->globalPos())) { + close(); + return false; + } + } else if (event->type() == QEvent::WindowDeactivate) { + // Close when window loses focus + if (obj == this) { + close(); + return false; + } + } + return QWidget::eventFilter(obj, event); +} + +void QBalloonTip::hideEvent(QHideEvent *event) +{ + // Remove event filter when hiding + qApp->removeEventFilter(this); + QWidget::hideEvent(event); +} diff --git a/src/qballoontip.h b/src/qballoontip.h new file mode 100644 index 0000000..2c53374 --- /dev/null +++ b/src/qballoontip.h @@ -0,0 +1,44 @@ +#ifndef QBALLOONTIP_H +#define QBALLOONTIP_H + +#include +#include +#include +#include + +class QBalloonTip : public QWidget +{ + Q_OBJECT +public: + static void showBalloon(const QIcon &icon, const QString &title, + const QString &msg, QWidget *widget, + const QPoint &pos, int timeout, + bool showArrow = true); + static void hideBalloon(); + static bool isBalloonVisible(); + static void updateBalloonPosition(const QPoint &pos); + +private: + QBalloonTip(const QIcon &icon, const QString &title, const QString &msg, + QWidget *widget); + ~QBalloonTip(); + void balloon(const QPoint &, int, bool); + +signals: + void messageClicked(); + +protected: + void paintEvent(QPaintEvent *) override; + void resizeEvent(QResizeEvent *) override; + void mousePressEvent(QMouseEvent *e) override; + void timerEvent(QTimerEvent *e) override; + bool eventFilter(QObject *obj, QEvent *event) override; + void hideEvent(QHideEvent *event) override; + +private: + QWidget *widget; + QPixmap pixmap; + QBasicTimer timer; + bool showArrow; +}; +#endif // QBALLOONTIP_H \ No newline at end of file diff --git a/src/querymobilegestaltwidget.cpp b/src/querymobilegestaltwidget.cpp index 8b91489..6365f71 100644 --- a/src/querymobilegestaltwidget.cpp +++ b/src/querymobilegestaltwidget.cpp @@ -22,12 +22,22 @@ #include #include #include +#include #include QueryMobileGestaltWidget::QueryMobileGestaltWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) { + // todo: not tested on iOS 17,18 but it's deprecated on iOS 26 + // i am assuming it won't work + if (m_device->deviceInfo.parsedDeviceVersion.major > 16) { + QMessageBox::warning(this, "Unsupported iOS Version", + "Apple deprecated this protocol for Devices " + "running iOS 17 or later"); + close(); + return; + } setupUI(); populateKeys(); } @@ -1135,32 +1145,34 @@ QueryMobileGestaltWidget::queryMobileGestalt(const QStringList &keys) { char *xml = nullptr; uint32_t xmlLength = 0; - bool res = query_mobile_gestalt(m_device, keys, xmlLength, xml); - if (!res) { + + std::vector keys_vector; + for (const QString &key : keys) { + keys_vector.push_back(key.toUtf8().data()); + } + + auto res = m_device->diagRelay->mobilegestalt(keys_vector); + + if (!res.is_ok()) { qDebug() << "MobileGestalt query failed."; return {}; } - pugi::xml_document infoXml; - pugi::xml_parse_result result = infoXml.load_string(xml); - if (xml) - free(xml); - if (!result) { - qDebug() << "Failed to parse XML:" << result.description(); - return {}; - } - pugi::xml_node dictNode = - infoXml.child("plist").child("dict").child("key").next_sibling("dict"); - if (!dictNode) { - qDebug() << "No MobileGestalt node found in XML."; - return {}; - } + plist_t res_plist_raw = res.unwrap().unwrap(); + // TODO: safety ? + auto res_plist = PlistNavigator(res_plist_raw)["MobileGestalt"]; + plist_print(res_plist_raw); QMap results; for (const QString &key : keys) { - std::string value = safeGetXML(key.toStdString().c_str(), dictNode); - if (!value.empty()) { - results.insert(key, QString::fromStdString(value)); + // Use the new toQVariant method to get the value + QVariant value = res_plist[key.toStdString()].toQVariant(); + qDebug() << "Key:" << key + << "Value:" << value; // Print QVariant directly + if (!value.isNull()) { // Only insert if the QVariant is valid (key + // exists and value could be parsed) + results.insert(key, value); } } + plist_free(res_plist_raw); return results; } \ No newline at end of file diff --git a/src/recoverydeviceinfowidget.cpp b/src/recoverydeviceinfowidget.cpp index abbd8e3..56243cb 100644 --- a/src/recoverydeviceinfowidget.cpp +++ b/src/recoverydeviceinfowidget.cpp @@ -20,7 +20,6 @@ #include "recoverydeviceinfowidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" -#include "libirecovery.h" #include #include #include @@ -29,139 +28,143 @@ #include #include -RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget( - const iDescriptorRecoveryDevice *info, QWidget *parent) +RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(const void *info, + QWidget *parent) : QWidget{parent} { - ecid = info->ecid; // Assuming ecid is unique for each device + // ecid = info->ecid; // Assuming ecid is unique for each device - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(20, 20, 20, 20); - mainLayout->setSpacing(16); + // QVBoxLayout *mainLayout = new QVBoxLayout(this); + // mainLayout->setContentsMargins(20, 20, 20, 20); + // mainLayout->setSpacing(16); - // Device Information Group - QGroupBox *deviceInfoGroup = new QGroupBox("Device Information"); - QVBoxLayout *infoLayout = new QVBoxLayout(deviceInfoGroup); - infoLayout->setSpacing(8); - infoLayout->setContentsMargins(16, 16, 16, 16); + // // Device Information Group + // QGroupBox *deviceInfoGroup = new QGroupBox("Device Information"); + // QVBoxLayout *infoLayout = new QVBoxLayout(deviceInfoGroup); + // infoLayout->setSpacing(8); + // infoLayout->setContentsMargins(16, 16, 16, 16); - // Device name with larger font - QLabel *deviceNameLabel = - new QLabel(QString::fromStdString(info->displayName)); - QFont nameFont = deviceNameLabel->font(); - nameFont.setPointSize(nameFont.pointSize() + 2); - nameFont.setWeight(QFont::DemiBold); - deviceNameLabel->setFont(nameFont); - infoLayout->addWidget(deviceNameLabel); + // // Device name with larger font + // QLabel *deviceNameLabel = + // new QLabel(QString::fromStdString(info->displayName)); + // QFont nameFont = deviceNameLabel->font(); + // nameFont.setPointSize(nameFont.pointSize() + 2); + // nameFont.setWeight(QFont::DemiBold); + // deviceNameLabel->setFont(nameFont); + // infoLayout->addWidget(deviceNameLabel); - // Add spacing - infoLayout->addSpacing(8); + // // Add spacing + // infoLayout->addSpacing(8); - // Mode info - QString modeText = QString::fromStdString(parse_recovery_mode(info->mode)); - QLabel *modeLabel = new QLabel("Mode: " + modeText); - infoLayout->addWidget(modeLabel); + // // Mode info + // QString modeText = + // QString::fromStdString(parse_recovery_mode(info->mode)); QLabel + // *modeLabel = new QLabel("Mode: " + modeText); + // infoLayout->addWidget(modeLabel); - // ECID info - QLabel *ecidLabel = new QLabel("ECID: " + QString::number(info->ecid)); - infoLayout->addWidget(ecidLabel); + // // ECID info + // QLabel *ecidLabel = new QLabel("ECID: " + QString::number(info->ecid)); + // infoLayout->addWidget(ecidLabel); - // CPID info - QLabel *cpidLabel = new QLabel("CPID: " + QString::number(info->cpid)); - infoLayout->addWidget(cpidLabel); + // // CPID info + // QLabel *cpidLabel = new QLabel("CPID: " + QString::number(info->cpid)); + // infoLayout->addWidget(cpidLabel); - mainLayout->addWidget(deviceInfoGroup); + // mainLayout->addWidget(deviceInfoGroup); - // Actions Group - QGroupBox *actionsGroup = new QGroupBox("Actions"); - QVBoxLayout *actionsLayout = new QVBoxLayout(actionsGroup); - actionsLayout->setSpacing(12); - actionsLayout->setContentsMargins(16, 16, 16, 16); + // // Actions Group + // QGroupBox *actionsGroup = new QGroupBox("Actions"); + // QVBoxLayout *actionsLayout = new QVBoxLayout(actionsGroup); + // actionsLayout->setSpacing(12); + // actionsLayout->setContentsMargins(16, 16, 16, 16); - // Info label - QLabel *infoLabel = new QLabel( - "Exit recovery mode and restart the device into normal mode."); - infoLabel->setWordWrap(true); - QFont infoFont = infoLabel->font(); - infoFont.setPointSize(infoFont.pointSize() - 1); - infoLabel->setFont(infoFont); - QPalette infoPalette = infoLabel->palette(); - infoPalette.setColor(QPalette::WindowText, - infoPalette.color(QPalette::WindowText).lighter(120)); - infoLabel->setPalette(infoPalette); - actionsLayout->addWidget(infoLabel); + // // Info label + // QLabel *infoLabel = new QLabel( + // "Exit recovery mode and restart the device into normal mode."); + // infoLabel->setWordWrap(true); + // QFont infoFont = infoLabel->font(); + // infoFont.setPointSize(infoFont.pointSize() - 1); + // infoLabel->setFont(infoFont); + // QPalette infoPalette = infoLabel->palette(); + // infoPalette.setColor(QPalette::WindowText, + // infoPalette.color(QPalette::WindowText).lighter(120)); + // infoLabel->setPalette(infoPalette); + // actionsLayout->addWidget(infoLabel); - // Button layout - QHBoxLayout *buttonLayout = new QHBoxLayout(); - buttonLayout->addStretch(); + // // Button layout + // QHBoxLayout *buttonLayout = new QHBoxLayout(); + // buttonLayout->addStretch(); - QPushButton *exitRecoveryMode = new QPushButton("Exit Recovery Mode"); - exitRecoveryMode->setMinimumWidth(160); - exitRecoveryMode->setMinimumHeight(32); + // QPushButton *exitRecoveryMode = new QPushButton("Exit Recovery Mode"); + // exitRecoveryMode->setMinimumWidth(160); + // exitRecoveryMode->setMinimumHeight(32); - connect(exitRecoveryMode, &QPushButton::clicked, this, [this, info]() { - irecv_client_t client = NULL; - 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) { - QMessageBox::critical( - this, "Connection Error", - QString("Failed to open device with ECID %1:\n%2") - .arg(info->ecid) - .arg(irecv_strerror(ierr))); - return; - } + // connect(exitRecoveryMode, &QPushButton::clicked, this, [this, info]() { + // irecv_client_t client = NULL; + // 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) { + // QMessageBox::critical( + // this, "Connection Error", + // QString("Failed to open device with ECID %1:\n%2") + // .arg(info->ecid) + // .arg(irecv_strerror(ierr))); + // return; + // } - if (client == NULL) { - QMessageBox::critical(this, "Error", - "Client is NULL after successful open"); - return; - } + // if (client == NULL) { + // QMessageBox::critical(this, "Error", + // "Client is NULL after successful open"); + // return; + // } - error = irecv_setenv(client, "auto-boot", "true"); - if (error != IRECV_E_SUCCESS) { - QMessageBox::critical( - this, "Error", - QString("Failed to set environment variable 'auto-boot':\n%1") - .arg(irecv_strerror(error))); - irecv_close(client); - return; - } + // error = irecv_setenv(client, "auto-boot", "true"); + // if (error != IRECV_E_SUCCESS) { + // QMessageBox::critical( + // this, "Error", + // QString("Failed to set environment variable + // 'auto-boot':\n%1") + // .arg(irecv_strerror(error))); + // irecv_close(client); + // return; + // } - error = irecv_saveenv(client); - if (error != IRECV_E_SUCCESS) { - QMessageBox::critical( - this, "Error", - QString("Failed to save environment variables:\n%1") - .arg(irecv_strerror(error))); - irecv_close(client); - return; - } + // error = irecv_saveenv(client); + // if (error != IRECV_E_SUCCESS) { + // QMessageBox::critical( + // this, "Error", + // QString("Failed to save environment variables:\n%1") + // .arg(irecv_strerror(error))); + // irecv_close(client); + // return; + // } - error = irecv_reboot(client); - if (error != IRECV_E_SUCCESS) { - QMessageBox::critical(this, "Error", - QString("Failed to send reboot command:\n%1") - .arg(irecv_strerror(error))); - irecv_close(client); - return; - } + // error = irecv_reboot(client); + // if (error != IRECV_E_SUCCESS) { + // QMessageBox::critical(this, "Error", + // QString("Failed to send reboot + // command:\n%1") + // .arg(irecv_strerror(error))); + // irecv_close(client); + // return; + // } - irecv_close(client); + // irecv_close(client); - auto *msgBox = - new QMessageBox(QMessageBox::Information, "Success", - "Device is exiting recovery mode and restarting...", - QMessageBox::Ok, QApplication::activeWindow()); - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->open(); - }); + // auto *msgBox = + // new QMessageBox(QMessageBox::Information, "Success", + // "Device is exiting recovery mode and + // restarting...", QMessageBox::Ok, + // QApplication::activeWindow()); + // msgBox->setAttribute(Qt::WA_DeleteOnClose); + // msgBox->open(); + // }); - buttonLayout->addWidget(exitRecoveryMode); - buttonLayout->addStretch(); - actionsLayout->addLayout(buttonLayout); + // buttonLayout->addWidget(exitRecoveryMode); + // buttonLayout->addStretch(); + // actionsLayout->addLayout(buttonLayout); - mainLayout->addWidget(actionsGroup); - mainLayout->addStretch(); + // mainLayout->addWidget(actionsGroup); + // mainLayout->addStretch(); } diff --git a/src/recoverydeviceinfowidget.h b/src/recoverydeviceinfowidget.h index 67920ce..1d96e3d 100644 --- a/src/recoverydeviceinfowidget.h +++ b/src/recoverydeviceinfowidget.h @@ -27,7 +27,7 @@ class RecoveryDeviceInfoWidget : public QWidget Q_OBJECT public: - explicit RecoveryDeviceInfoWidget(const iDescriptorRecoveryDevice *info, + explicit RecoveryDeviceInfoWidget(const void *info, QWidget *parent = nullptr); uint64_t ecid; // Assuming ecid is unique for each device signals: diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index d006064..f85d454 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -29,22 +29,7 @@ ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device, return executeAfcClientOperation( device, [path, dirs, &count, device](AfcClientHandle *client) { - IdeviceFfiError *err = nullptr; - err = afc_list_directory(client, path, dirs, &count); - /*TODO -1 is unknown error*/ - if (err && (err->code == -1 || err->code == -11)) { - qDebug() << "Reconnecting AFC client for path:" << path; - // afc_client_free(client); - // err = afc_client_connect(device->provider, &client); - err = afc_client_connect( - device->provider, - &const_cast(device)->afcClient); - - err = afc_list_directory( - const_cast(device)->afcClient, path, - dirs, &count); - } - return err; + return afc_list_directory(client, path, dirs, &count); }, altAfc); } @@ -57,21 +42,7 @@ ServiceManager::safeAfcGetFileInfo(const iDescriptorDevice *device, return executeAfcClientOperation( device, [path, info, device](AfcClientHandle *client) { - IdeviceFfiError *err = nullptr; - err = afc_get_file_info(client, path, info); - /*TODO -1 is unknown error*/ - if (err && err->code == -1) { - // afc_client_free(client); - // err = afc_client_connect(device->provider, &client); - err = afc_client_connect( - device->provider, - &const_cast(device)->afcClient); - - err = afc_get_file_info( - const_cast(device)->afcClient, path, - info); - } - return err; + return afc_get_file_info(client, path, info); }, altAfc); } @@ -83,19 +54,7 @@ IdeviceFfiError *ServiceManager::safeAfcFileOpen( return executeAfcClientOperation( device, [path, mode, handle, device](AfcClientHandle *client) { - IdeviceFfiError *err = nullptr; - err = afc_file_open(client, path, mode, handle); - /*TODO -1 is unknown error*/ - if (err && err->code == -1) { - // afc_client_free(client); - err = afc_client_connect( - device->provider, - &const_cast(device)->afcClient); - err = afc_file_open( - const_cast(device)->afcClient, path, - mode, handle); - } - return err; + return afc_file_open(client, path, mode, handle); }, altAfc); } @@ -140,11 +99,11 @@ ServiceManager::safeAfcFileSeek(const iDescriptorDevice *device, AfcFileHandle *handle, int64_t offset, int whence) { - off_t *newPos = nullptr; + off_t newPos; return executeAfcOperation( device, - [offset, whence, newPos](AfcFileHandle *handle) { - return afc_file_seek(handle, offset, whence, newPos); + [offset, whence, &newPos](AfcFileHandle *handle) { + return afc_file_seek(handle, offset, whence, &newPos); }, handle); } @@ -178,4 +137,61 @@ AFCFileTree ServiceManager::safeGetFileTree(const iDescriptorDevice *device, device, [path, device, checkDir]() -> AFCFileTree { return get_file_tree(device, checkDir, path); }); +} + +MountedImageInfo +ServiceManager::getMountedImage(const iDescriptorDevice *device) +{ + return executeOperation( + device, + [device]() -> MountedImageInfo { return _get_mounted_image(device); }); +} + +IdeviceFfiError *ServiceManager::mountImage(const iDescriptorDevice *device, + const char *image_file, + const char *signature_file) +{ + return executeOperation( + device, [device, image_file, signature_file]() -> IdeviceFfiError * { + return mount_dev_image(device, image_file, signature_file); + }); +} + +void ServiceManager::getCableInfo(const iDescriptorDevice *device, + plist_t &response) +{ + executeOperation( + device, [device, &response]() { _get_cable_info(device, response); }); +} + +IdeviceFfiError *ServiceManager::install_IPA(const iDescriptorDevice *device, + const char *ipa_path, + const char *file_name) +{ + return executeOperation( + device, [device, ipa_path, file_name]() -> IdeviceFfiError * { + return _install_IPA(device, ipa_path, file_name); + }); +} + +bool ServiceManager::enableWirelessConnections(const iDescriptorDevice *device) +{ + return executeOperation(device, [device]() -> bool { + plist_t value = plist_new_bool(true); + bool success = false; + IdeviceFfiError *err = + lockdownd_set_value(device->lockdown, "EnableWifiConnections", + value, "com.apple.mobile.wireless_lockdown"); + + if (err != NULL) { + qDebug() << "Failed to enable wireless connections." << err->message + << "Code:" << err->code; + idevice_error_free(err); + } else { + success = true; + } + + plist_free(value); + return success; + }); } \ No newline at end of file diff --git a/src/servicemanager.h b/src/servicemanager.h index 27227ad..d8a8821 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -248,6 +248,17 @@ public: const char *path); static AFCFileTree safeGetFileTree(const iDescriptorDevice *device, const std::string &path, bool checkDir); + static MountedImageInfo getMountedImage(const iDescriptorDevice *device); + static IdeviceFfiError *mountImage(const iDescriptorDevice *device, + const char *image_file, + const char *signature_file); + static void getCableInfo(const iDescriptorDevice *device, + plist_t &response); + + static IdeviceFfiError *install_IPA(const iDescriptorDevice *device, + const char *filePath, + const char *fileName); + static bool enableWirelessConnections(const iDescriptorDevice *device); }; #endif // SERVICEMANAGER_H diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 2bb55d0..a403d13 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -44,41 +44,6 @@ struct iDescriptorToolWidget { QString iconName; }; -bool enterRecoveryMode(iDescriptorDevice *device) -{ - lockdownd_client_t client = NULL; - lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; - idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; - - if (LOCKDOWN_E_SUCCESS != - (ldret = lockdownd_client_new(device->device, &client, APP_LABEL))) { - printf("ERROR: Could not connect to lockdownd: %s (%d)\n", - lockdownd_strerror(ldret), ldret); - return false; - } - - ldret = lockdownd_enter_recovery(client); - if (ldret == LOCKDOWN_E_SESSION_INACTIVE) { - lockdownd_client_free(client); - client = NULL; - if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake( - device->device, &client, APP_LABEL))) { - printf("ERROR: Could not connect to lockdownd: %s (%d)\n", - lockdownd_strerror(ldret), ldret); - return false; - } - ldret = lockdownd_enter_recovery(client); - } - lockdownd_client_free(client); - if (ldret != LOCKDOWN_E_SUCCESS) { - printf("Failed to enter recovery mode.\n"); - return false; - } else { - printf("Device is successfully switching to recovery mode.\n"); - return true; - } -} - ToolboxWidget::ToolboxWidget(QWidget *parent) : QWidget{parent} { setupUI(); @@ -325,6 +290,7 @@ void ToolboxWidget::updateDeviceList() if (devices.isEmpty()) { m_deviceCombo->addItem("No device connected"); m_deviceCombo->setEnabled(false); + m_uuid.clear(); } else { m_deviceCombo->setEnabled(true); for (iDescriptorDevice *device : devices) { @@ -341,6 +307,14 @@ void ToolboxWidget::updateDeviceList() AppContext::sharedInstance()->getCurrentDeviceSelection()); m_deviceCombo->blockSignals(false); + + if (m_deviceCombo->count() > 0 && m_deviceCombo->currentIndex() >= 0) { + QString currentUdid = m_deviceCombo->currentData().toString(); + if (!currentUdid.isEmpty()) { + m_uuid = currentUdid.toStdString(); + qDebug() << "[toolboxwidget] Initialized m_uuid to:" << currentUdid; + } + } } void ToolboxWidget::updateToolboxStates() @@ -374,10 +348,24 @@ void ToolboxWidget::updateUI() void ToolboxWidget::onDeviceSelectionChanged() { QString selectedUdid = m_deviceCombo->currentData().toString(); + + // Clear m_uuid if no valid selection if (selectedUdid.isEmpty()) { + m_uuid.clear(); return; } + if (AppContext::sharedInstance()->getDevice(selectedUdid.toStdString()) == + nullptr) { + QMessageBox::warning(this, "Device Not Found", + "The selected device is no longer connected."); + m_uuid.clear(); // Clear stale UUID + updateDeviceList(); + return; + } + + m_uuid = selectedUdid.toStdString(); + qDebug() << "[toolboxwidget] Selected device UDID:" << selectedUdid; // Update the selected device in main menu AppContext::sharedInstance()->setCurrentDeviceSelection( DeviceSelection(selectedUdid.toStdString())); @@ -385,7 +373,7 @@ void ToolboxWidget::onDeviceSelectionChanged() void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection) { - if (selection.type == DeviceSelection::Normal) { + if (selection.valid() && selection.type == DeviceSelection::Normal) { int index = m_deviceCombo->findData(QString::fromStdString(selection.udid)); if (index != -1) { @@ -395,15 +383,23 @@ void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection) m_deviceCombo->blockSignals(false); m_uuid = selection.udid; - m_currentDevice = - AppContext::sharedInstance()->getDevice(selection.udid); } + } else { + // Clear m_uuid when selection is invalid + m_uuid.clear(); } } void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) { - + // final check to make sure device is connected if required + iDescriptorDevice *device = AppContext::sharedInstance()->getDevice(m_uuid); + if (!device && m_requiresDevice[m_toolboxes.indexOf(sender())]) { + QMessageBox::warning(this, "Device Disconnected ?", + "Please select a device to use this tool."); + return; + } + qDebug() << "idevice exists:" << (device != nullptr) << m_uuid.c_str(); switch (tool) { case iDescriptorTool::Airplayer: { if (!m_airplayWindow) { @@ -421,25 +417,25 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) } break; case iDescriptorTool::LiveScreen: { - LiveScreenWidget *liveScreen = new LiveScreenWidget(m_currentDevice); + LiveScreenWidget *liveScreen = new LiveScreenWidget(device); liveScreen->setAttribute(Qt::WA_DeleteOnClose); liveScreen->show(); } break; case iDescriptorTool::RecoveryMode: { // Handle entering recovery mode - bool success = enterRecoveryMode(m_currentDevice); - QMessageBox msgBox; - msgBox.setWindowTitle("Recovery Mode"); - if (success) { - msgBox.setText("Successfully entered recovery mode."); - } else { - msgBox.setText("Failed to enter recovery mode."); - } - msgBox.exec(); + // bool success = _enterRecoveryMode(device); + // QMessageBox msgBox; + // msgBox.setWindowTitle("Recovery Mode"); + // if (success) { + // msgBox.setText("Successfully entered recovery mode."); + // } else { + // msgBox.setText("Failed to enter recovery mode."); + // } + // msgBox.exec(); } break; case iDescriptorTool::MountDevImage: { DevDiskImageHelper *devDiskImageHelper = - new DevDiskImageHelper(m_currentDevice, this); + new DevDiskImageHelper(device, this); connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, this, [this, devDiskImageHelper](bool success) { @@ -458,22 +454,22 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) } break; case iDescriptorTool::VirtualLocation: { // Handle virtual location functionality - VirtualLocation *virtualLocation = new VirtualLocation(m_currentDevice); + VirtualLocation *virtualLocation = new VirtualLocation(device); virtualLocation->setAttribute(Qt::WA_DeleteOnClose); virtualLocation->setWindowFlag(Qt::Window); virtualLocation->resize(800, 600); virtualLocation->show(); } break; case iDescriptorTool::Restart: { - restartDevice(m_currentDevice); + restartDevice(device); } break; case iDescriptorTool::Shutdown: { - shutdownDevice(m_currentDevice); + shutdownDevice(device); } break; case iDescriptorTool::QueryMobileGestalt: { // Handle querying MobileGestalt QueryMobileGestaltWidget *queryMobileGestaltWidget = - new QueryMobileGestaltWidget(m_currentDevice); + new QueryMobileGestaltWidget(device); queryMobileGestaltWidget->setAttribute(Qt::WA_DeleteOnClose); queryMobileGestaltWidget->setWindowFlag(Qt::Window); queryMobileGestaltWidget->resize(800, 600); @@ -481,7 +477,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) } break; case iDescriptorTool::DeveloperDiskImages: { if (!m_devDiskImagesWidget) { - m_devDiskImagesWidget = new DevDiskImagesWidget(m_currentDevice); + m_devDiskImagesWidget = new DevDiskImagesWidget(device); m_devDiskImagesWidget->setAttribute(Qt::WA_DeleteOnClose); m_devDiskImagesWidget->setWindowFlag(Qt::Window); m_devDiskImagesWidget->resize(800, 600); @@ -510,9 +506,9 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) #ifndef __APPLE__ case iDescriptorTool::iFuse: { if (!m_ifuseWidget) { - m_ifuseWidget = new iFuseWidget(m_currentDevice); + m_ifuseWidget = new iFuseWidget(device); qDebug() << "Created iFuseWidget" - << m_currentDevice->deviceInfo.productType.c_str(); + << device->deviceInfo.productType.c_str(); m_ifuseWidget->setAttribute(Qt::WA_DeleteOnClose); connect(m_ifuseWidget, &QObject::destroyed, this, [this]() { m_ifuseWidget = nullptr; }); @@ -526,7 +522,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) } break; #endif case iDescriptorTool::CableInfoWidget: { - CableInfoWidget *cableInfoWidget = new CableInfoWidget(m_currentDevice); + CableInfoWidget *cableInfoWidget = new CableInfoWidget(device); cableInfoWidget->setAttribute(Qt::WA_DeleteOnClose); cableInfoWidget->setWindowFlag(Qt::Window); cableInfoWidget->resize(600, 400); @@ -566,10 +562,17 @@ void ToolboxWidget::restartDevice(iDescriptorDevice *device) return; } - if (!(restart(device->udid))) - warn("Failed to restart device"); - else { - warn("Device will restart once unplugged", "Success"); + // FIXME: move to servicemanager + auto res = device->diagRelay->restart(); + + if (res.is_err()) { + QMessageBox::warning( + nullptr, "Restart Failed", + "Failed to restart device: " + + QString::fromStdString(res.unwrap_err().message)); + } else { + QMessageBox::information(nullptr, "Restart Initiated", + "Device will restart once unplugged."); qDebug() << "Restarting device"; } } @@ -590,12 +593,17 @@ void ToolboxWidget::shutdownDevice(iDescriptorDevice *device) return; } - if (!(shutdown(device->device))) - // TODO: warn is a safe wrapper for QMessageBox but do we actually need - // it ? - warn("Failed to shutdown device"); - else { - warn("Device will shutdown once unplugged", "Success"); + // FIXME: move to servicemanager + auto res = device->diagRelay->shutdown(); + + if (res.is_err()) { + QMessageBox::warning( + nullptr, "Shutdown Failed", + "Failed to shutdown device: " + + QString::fromStdString(res.unwrap_err().message)); + } else { + QMessageBox::information(nullptr, "Shutdown Initiated", + "Device will shutdown once unplugged."); qDebug() << "Shutting down device"; } } @@ -616,13 +624,16 @@ void ToolboxWidget::_enterRecoveryMode(iDescriptorDevice *device) return; } - bool success = enterRecoveryMode(device); - QMessageBox _msgBox; - _msgBox.setWindowTitle("Recovery Mode"); - if (success) { - _msgBox.setText("Successfully entered recovery mode."); - } else { - _msgBox.setText("Failed to enter recovery mode."); - } - _msgBox.exec(); + // auto res = device->diagRelay->enterRecovery(); + // if (res.is_err()) { + // QMessageBox::warning( + // nullptr, "Enter Recovery Mode Failed", + // "Failed to enter recovery mode: " + + // QString::fromStdString(res.unwrap_err().message)); + // } else { + // QMessageBox::information(nullptr, "Enter Recovery Mode Initiated", + // "Device will enter recovery mode once + // unplugged."); + // qDebug() << "Entering recovery mode"; + // } } \ No newline at end of file diff --git a/src/toolboxwidget.h b/src/toolboxwidget.h index 1aa65b6..689dfe2 100644 --- a/src/toolboxwidget.h +++ b/src/toolboxwidget.h @@ -67,7 +67,6 @@ private: QGridLayout *m_gridLayout; QList m_toolboxes; QList m_requiresDevice; - iDescriptorDevice *m_currentDevice; std::string m_uuid; DevDiskImagesWidget *m_devDiskImagesWidget = nullptr; NetworkDevicesWidget *m_networkDevicesWidget = nullptr; diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp index 4d781b7..c94fbf8 100644 --- a/src/virtuallocationwidget.cpp +++ b/src/virtuallocationwidget.cpp @@ -45,19 +45,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent) : QWidget{parent}, m_device(device) { - unsigned int device_version = idevice_get_device_version(m_device->device); - unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF; - - if (deviceMajorVersion > 16) { - QMessageBox::warning( - this, "Unsupported iOS Version", - "Virtual Location feature requires iOS 16 or earlier.\n" - "Your device is running iOS " + - QString::number(deviceMajorVersion) + - ", which is not yet supported."); - QTimer::singleShot(0, this, &QWidget::close); - return; - } setWindowTitle("Virtual Location - iDescriptor"); // Create the main layout QHBoxLayout *mainLayout = new QHBoxLayout(this); @@ -200,6 +187,7 @@ void VirtualLocation::updateMapFromInputs() double latitude = m_latitudeEdit->text().toDouble(&latOk); double longitude = m_longitudeEdit->text().toDouble(&lonOk); + // FIXME: warn if not valid if (latOk && lonOk && latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180) { QQuickItem *rootObject = m_quickWidget->rootObject(); @@ -311,53 +299,68 @@ void VirtualLocation::onApplyClicked() m_applyButton->setEnabled(true); return; } - DevDiskImageHelper *devDiskImageHelper = - new DevDiskImageHelper(m_device, this); - connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, this, - [this, latitude, longitude, devDiskImageHelper](bool success) { - if (devDiskImageHelper) { - devDiskImageHelper->deleteLater(); - } + // DevDiskImageHelper *devDiskImageHelper = + // new DevDiskImageHelper(m_device, this); + // connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, this, + // [this, latitude, longitude, devDiskImageHelper](bool success) { + // if (devDiskImageHelper) { + // devDiskImageHelper->deleteLater(); + // } - if (!success) { - return; - } + // if (!success) { + // return; + // } - // Apply location - emit locationChanged(latitude, longitude); - updateMapFromInputs(); + // // Apply location + // emit locationChanged(latitude, longitude); + // updateMapFromInputs(); - // Visual feedback - m_applyButton->setText("Applied!"); + // // Visual feedback + // m_applyButton->setText("Applied!"); - bool locationSuccess = set_location( - m_device->device, - const_cast( - m_latitudeEdit->text().toStdString().c_str()), - const_cast( - m_longitudeEdit->text().toStdString().c_str())); + // bool locationSuccess = set_location( + // m_device->device, + // const_cast( + // m_latitudeEdit->text().toStdString().c_str()), + // const_cast( + // m_longitudeEdit->text().toStdString().c_str())); - if (!locationSuccess) { - QMessageBox::warning(this, "Error", - "Failed to set location on device"); - } else { - SettingsManager::sharedInstance()->saveRecentLocation( - latitude, longitude); + // if (!locationSuccess) { + // QMessageBox::warning(this, "Error", + // "Failed to set location on device"); + // } else { + // SettingsManager::sharedInstance()->saveRecentLocation( + // latitude, longitude); - QMessageBox::information(this, "Success", - "Location applied successfully!"); - } - }); - connect( - devDiskImageHelper, &DevDiskImageHelper::destroyed, this, - [this]() { - QTimer::singleShot(1000, this, [this]() { - m_applyButton->setText("Apply Location"); - m_applyButton->setEnabled(true); - }); - }, - Qt::SingleShotConnection); - devDiskImageHelper->start(); + // QMessageBox::information(this, "Success", + // "Location applied + // successfully!"); + // } + // }); + // connect( + // devDiskImageHelper, &DevDiskImageHelper::destroyed, this, + // [this]() { + // QTimer::singleShot(1000, this, [this]() { + // m_applyButton->setText("Apply Location"); + // m_applyButton->setEnabled(true); + // }); + // }, + // Qt::SingleShotConnection); + // devDiskImageHelper->start(); + // FIXME: create issue for c bindings + IdeviceFfiError *err = location_simulation_set(m_device->locationSimulation, + latitude, longitude); + if (err != nullptr) { + QMessageBox::warning(this, "Error", + "Failed to set location on device:\n" + + QString::fromStdString(err->message)); + // idevice_ffi_error_free(err); + } else { + // SettingsManager::sharedInstance()->saveRecentLocation( + // latitude, longitude); + QMessageBox::information(this, "Success", + "Location applied successfully!"); + } } void VirtualLocation::loadRecentLocations(QVBoxLayout *layout) From 2a71f011d952ace795cc00cad9ca82a3ee7f030a Mon Sep 17 00:00:00 2001 From: uncor3 Date: Thu, 8 Jan 2026 21:09:47 +0000 Subject: [PATCH 04/15] implement statusballoon, refactor export logic and add new icons --- resources.qrc | 2 + .../icons/QlementineIconsWireless116.png | Bin 0 -> 14728 bytes resources/icons/UimProcess.png | Bin 0 -> 19003 bytes src/afcexplorerwidget.cpp | 11 +- src/appcontext.cpp | 79 ++- src/appcontext.h | 11 +- src/core/services/init_device.cpp | 60 +- src/devicemanagerwidget.cpp | 5 +- src/devicesidebarwidget.cpp | 23 +- src/devicesidebarwidget.h | 5 +- src/exportmanager.cpp | 274 ++------ src/exportmanager.h | 53 +- src/exportmanagerthread.cpp | 166 +++++ src/exportmanagerthread.h | 40 ++ src/gallerywidget.cpp | 109 +-- src/gallerywidget.h | 5 +- src/heartbeat.h | 64 +- src/iDescriptor.h | 60 +- src/livescreenwidget.cpp | 1 + src/mainwindow.cpp | 72 +- src/photomodel.cpp | 35 +- src/photomodel.h | 2 +- src/qballoontip.cpp | 86 +-- src/qballoontip.h | 21 +- src/servicemanager.cpp | 127 +++- src/servicemanager.h | 12 + src/statusballoon.cpp | 643 ++++++++++++++++++ src/statusballoon.h | 109 +++ src/toolboxwidget.cpp | 3 +- 29 files changed, 1532 insertions(+), 546 deletions(-) create mode 100644 resources/icons/QlementineIconsWireless116.png create mode 100644 resources/icons/UimProcess.png create mode 100644 src/exportmanagerthread.cpp create mode 100644 src/exportmanagerthread.h create mode 100644 src/statusballoon.cpp create mode 100644 src/statusballoon.h diff --git a/resources.qrc b/resources.qrc index 7bc83ba..0024a9c 100644 --- a/resources.qrc +++ b/resources.qrc @@ -35,6 +35,8 @@ resources/icons/ClarityEyeLine.png resources/icons/MaterialSymbolsLightImageOutlineSharp.png resources/icons/MaterialSymbolsFolder.png + resources/icons/QlementineIconsWireless116.png + resources/icons/UimProcess.png qml/MapView.qml resources/iphone.png resources/ios-wallpapers/iphone-ios4.png diff --git a/resources/icons/QlementineIconsWireless116.png b/resources/icons/QlementineIconsWireless116.png new file mode 100644 index 0000000000000000000000000000000000000000..0fa302d329e02c10d104f6a2a1f3fefd66b41769 GIT binary patch literal 14728 zcmeIZXH-*L)HWJogdj>4kRCumkshUY5kwGFdPgCEg7hja5d=JRfdJAEh$2mcG!Y0Q zf(Zl^DUmKElt^zOkbK*7-tmob-~V@vJMK8YC~K{`_nd3iXU_fXwyEKnQ~akuAkZ13 z8~S%aATaPN7zAYme(Z$(I0S)UAR~QUi!i6(lPnVr<2LJ?%CBm^SKR)~=3Q37!dbMm z2i{5lK*fW|`Ik_@n??)IZvj%L7T$U%S^VAD^>@}nuIu~P)oDZdiAn)JLlV?O`~9x^ zsfMXgtpwHeh>87!)B)0AA4MaO5aBSo&L5ac*Ie^Y+kC`y7Q(IrVt}wOrDB_cdEX&9 z{`pI12E|bdjJz#{Xd<9mFtr@B7o_f9W!mocWJ| zKUekN4g9A<{~7UrUg$qJ`v3hvmMel{$;R_uYSkxp$CG%4KQ&`F_V-?I6>a-cd&1)f zd}0P9SJLEwKVGd+c$zkbfcTqt{;zyT_8W##JA!Zh00Io8)FP*XrOZ)*Z=`aaQ#AFs0s8OGqV z7OJE1iu!eeMOf?Am;2d>ELfVy>9p|nV@^ms6!z8(48dwK9ER;f6wenEqFWs+X#7N>AW(SJ6P3G`1QL-yue2)hnbkH?u;R=Lt_kbJ|{#YY@KDfuVY>lwZU+Kq$3enL;B#>xM4RfQ=X2Elt!fq_6HQZ#1b< zH9B@owaHQS=hMg8o8okYU5kqm=L-y6ih-R`;GIn(NAs3wVYK_oKaY2g=Z$y2`u`y{@h5w4P7D%0zol&cejD9#uDq%U@yuyWBmJn!bR_XA`3N?;eQ% z^Z&O-VCTx%j)1r3tKZ@c^2>jZd5P+Ps!x{1dZ%L%38vZ}N6QBd#512lmKxZ5YvMRg zaDZDJN??QM8MG^IRlJONMYtNYx4h#@G&0k5@qx6atk`62;G;g&|KJw{H(yfA_gbK7^m1+!TG>> z?-y_9JR&A*Qt6NVU`Xsu@GD;~@VX<#$5+QC4Y&o(C)@9`A(-E5Ry{G<+3S0Z5O$R; zEZ=w>R()32WfIsC%oY4mdMHAVScX?@b=C!;jyVqQM;+NB(&VV0X{-xF(8wep3%^>O zgxw!St!daOE!_dEyA49CWZnPN>A-IzH!hoI01C?6YhXNf99N^cjy`r{x&}f8ilx7d zZ;Hbjit4%40Z(dszTNV^um(<>n+4MFZ_FRtA4Yb<7z6TjbX}~0b;sGQMHIFsfj~D+ z1bCT;(Dq?qnrDESiu`3joYmj2w$v;C3>l#pW=3=Y$@!SrM#G~rhPwEZs6uuaC`9jT z1i=|9e0MJifhG?NvwP;~?D2;g5MA*T3WGjHFE)}vo?>TisrzSctyLn3m1Yl;u$=)U z^Vo$REv`@B8Roq=2EX15U*E&*PSwyIONt!k)A#4sk76QP^pE{JXWIPHAkf~wE@N(KZ zF~ciar5Ab!(ur1S4%5{BAPrVb!!<(!T4&nuQ;I$f5A#m|+RkdrKiYG2W4fLkhY>l~ zwBqxS>TvyV785(}O`R@wzW9uO*Z=soEjocPSGu_M-JO%Z9y~@my;s|BG5b0Y(3w63 zul#LF&*~uGT2uPK;klR=*6jMX7_fq;hhX~wQ@(iwwuxftySIH(%9p3CI@te0H_bLeBkVwF9iV4l1W zpJ0$iZTDd##5%x+ivfgoe?4L2>jmI8(COXB-RY_Ha?CYJ- zf|?iYm3OL+#0B>(SeeDBEdIRZqtN}2aK&u(lfu^MXJt44E#9=^3T8)0XvAwvA|LAL zc10Jx3S*u9a>%+ul3jsa)Nig-lhkO5@PGsuNo7KYYO@JI{c<%Vs^jlsX7mumX zh%Y{Rx~t{s-(cK>sJ`9 zH+`p^=yFjHR_21!17aV`Ei${6+HnEJIgog7P31$V@9K_O70G#QQ?kwfD+HtSZKPL6 zxUOm5C6r@4fsrHq8&X=LTsj%>ac|>TXWcfl46`)fRPR5v2yid1&()Tp)+=Tfpoxp4 ze3om;EndV)oJp3Z-^@a{s3aFpot?*JuNfPN&Iu!%dX}b|xrJ(ag_qV^0c(!eL7lXE zaM)3AK>^7N4#6~a`XgHMVS7UTVM^`d>(x?$Ml-}Y>zXgOIA9>9>me`Z7LzYHe|H+N z`@Tt%*4DmT>u5T}m}BRKzR+%F+wQ8lZoBdRFM19xxm>2?B^QY0#{H-1_h=KNbSxsS ze)S^8cnXdWkgL}m3{4qe3ekmy{%}j^OGJ?izqx+BI~sZsFQ$h}1|$SGotk;P7D&s) zOj+&ViP40up-s}anuu**d|*-u=?*FxG|?ixeIhFl007)yGHN3>&yy4yMLnVTV)^f7^&9Mg&v7(w3erp2?2H| zDc>Mi#`^;yP=+c22*5#UawN*0&8CB%PYTc=;OY1M~&_s z`(b{op|opy<)G$!)4aEwdSeX-DiO7A7xJ2k@cKZJGd)}tcN%yS{Kog0`)YLUi}r^y z(Y}@EM4Zv%l(P`D9;}tq{a!8f$;+G`3uGRz%YC1@JP8oL+3l85yiBxhKD`w2s-c)u zSnRC_m;H2@FYS`n)Q+z1hquju*wi6XX*vv(mjvhh zlz`M~NcM!zc=^nxKzpAr+!P0F(125+**{X#9ntZ!e<$WvcNTN>x(|!8FT3Y+>~&n# z>3q6L=G5}*S|{}3m^A;AmijFu@Q*FSfieRz^&pNnY()t z=mmeHg;1$nC@M|v(reW(dLWbH29a}WG$xaE+)oV^8$%rWr;8OSCX(OlIpK&Fz@;6NJSb-C0;L9|R&({|$&FC%Y_*8ceD4Q_h9 zQDhggqxY$gpTSmwXve^HJ`0o!dTg0Tp#Q+QR#Zm+ZdkOVALCeSk9%1YLEr?=UMRLM zOS9aYxs0LswTjOTNb>X`GjcOeu5x>TcV8D_wJFdcRLOzDb3Xwbcfw!`ub9h|)106= zJ?FcmVjwX)9yt_lI#YQD^&5h*zOg2ZGq@J6Mbd*L7ifZAG-gHx+RK%N>ToGPcD|^s z4B;_xb$}!vvG?+eam+V)cNJM%0vvGpdJsOHb7wuxs9!^LeX}T^Q z)wehOIl|FA4+D!qWXI56ty}TXG49&7jy%wqdTU|y*5Nf$aevgJ9VW<^aOg@`9fcjF z=tx2^Ew}Y`q9YdV5|JE=h9GaAEwj^w^b{rgeuFfc;kBOPNh*->=Q5$B`Ok9rO=AKF zx)$ogjUe7C{t)aUXxe4zC@=yqXl%8C=cBJ@BgENGGC&p$XFQlV{2C}Nv@>bXkY^-P zL1U*US_bw8yeA@Z+9Kg$g4_&1O6+GboPE8gR_%V_YBg?>VYR&Fw+#e)*jcen$75Qi z@~P-8&C7uc#;<_fND=DRS(3a|C_m(vh&*#X6=Wm#NuyG{ukcvM&)Tj+jn zt(Z-0M#N(&!d#XML^U(?dH3}#y=yP#^|!QiIPEl*-nDeKik$&cA+d2B3+=st+BW^& zC%YO$gMqag@dM#hB_PM6G+G1A1oW}I z9*d#yHBJ!K_&R#8zp*{=XWU%Xf-es=e1xXA{{X1S*il{<(sGwu$NKI+zDddFIE`Qf zOj}b&Kr0~avBo>;F3-SAzWkBV-423_l3-}E0qk;)yGQ4oL6tw98}VSvSvKN`#x^|7 z0pzR;8v9>gZPPQ_q9UK0AmI6DK;%tiKs{d3YD*4wIr3}k|D zS!}xO>nqLF=w55IvdpMoX-viS)fIq7LIt1cgm6cO5eNFALxzkTF8N!I=TksqC#xT? z3R+VlTq%Xr=o)fUt3!k`xQ$Dt5~Sf~z^=%*p@skqdLZ+|O%Pd-zCU6gh@Wr_rDc4b z&g+g+wA=jpRdW`g-j{*BmhcV9GVqz=@T;#JTh0Hv8m0pxe|%Oh-Ve!B&7uP?=in$? zRi)1m!tROIQDVejK($Orgx#bZx=3nOjdL-)eQ{?hV|+1SulUSWJGo+hHTppn3;=9a zVSZG#8>YzG_SD!Ezhr3w`l?-)IFRO9DW`eA*xbXYPKe}39KnyWPb&U_@?8ocvery^ zyQ9DZ8Iy@nIlLz_;2dzj8Tkf2&%Orl2=ten6p|K+X(P|`9pJ75HYJI&&l*pY%I^vx zd1Mv0oZY~M|2_3_6oQ@Q^N1}zT~_>-a)sQ=SV(W@Py?7Hr(+BBv0hRY$$x!-&j>y@ z1_`!z(+%2odWE7zD9Uim9nBx4yjQRypRATWwi%(1CNKOTQOI{gZ_*4B}&rwmgV#z9+aYJ&k!~ zJi@&60bsp?9)}?*O>qia(+~q*FXh0?Z_{M)#No8l1KXi$RLeq&O*w|e9`cEak?Nw^ z?mIpW`DBylSyt4?9A;~aFRP@;^!q*>eF{>6ALpG7n2Wd98#&**Vvh@8o+oSMl-Aw} z$;Je2y(=INw3Kg8b24(AR9$^+ocGjrv0!+(UAtuJc?R?zn4MO<~zxIfI)TejQWRJpAyL^39EF7i@P(Y+tJVc!L*AmMai^_bnHVVXZe7 z$y!Z0!sbA-uS%9yRoaZToCVMkYo0qr948P{5WDtzsn(BLu2W(ABuxgt87!P-c3_Qd z&Oqz9C-_GJh4lE1q8vC!IyA8A4%x2Dwdz`_Oy)5)V66th@hQB|69ugD0;`v1Nc=(S z;%7H&#`QpgDjO>}T0g%RLmgmjwF$5I@n5Av6)(w+o`vVkOCSO}LOP_T-Z>9AJb|K; zPtU98lW-c)`PPh7rSa`Xiy|k&`->(9uKJ*YmX6(bWgO0qcjBpa&U%N~R}qiFbooe+t+r=T0grRS{8%9vgYI#>qS&Bb_#M>@OUqUn2o`n1 z_YK*(3B|LqzHrUzHD=K4#8;KiPfwGkYsjgHyw9)R8^c(*5P4*6sldFFHjQJ8maJ!} zJV|&kr6yoz)?8fyar}o$%w6W@<1C|dD_Spw?OP2-KhL@v0rbl-jui)CIgU}32X?l{N(=T5}mC={t|?u!-15NP!;UMR|-3TAzS53P$w zxEt!8VYS)BU`ZVS4)y3(PjGGjsK9TtSq+;>GiW)4 z{Lr6%dKyk@)JE`wmp1~1x!XPx$ZTo`1{-V$-A=pF8><(+*GI_efEvg&J!DxZ97*}|cDbWm15SK?dh zQw%ky0rPZivCOK=iTTJjNmFaF6IoS8MC5fU=dYHrIzJt*zBeii?eqgUpW+(qj4)9&H>DtTelc+4&5 z=WxD868p2_pN?382eH%0i;4QskMIwWR_MGc#CJ? zbiqVV%-y_kic-&5#=ysrqcIFwe0o=NUcmb_QRQM%J%T`pHr@*M?Pwz_gPOG^_T0^!u za3-qY)z~_m+%dcN0`x)EJ|7=M0$$i@y_ExO>n{y|1}_`S`}B~|8r5@W7sZ7OP&Z={ zNFtBKtQTveBJ1GQf_T|boIBL*{$3Prt9eEFz@?2ek0E6*v;;ta$VA9DBwPt}TnRxK zvNawLb$hK1uc`OO zJi$&t1*H?<4|gLFi;R6T5kd#XprG`5zW7`t^zwo0wAM9xOENYDJRqX4vv&F}yJtKA z-VkWyy8;IYZak(p*)NlACIkQGTguEcVhf#j+O1}N_@pFW;eRk}IdDtO`b$Lq z6l2iWxAm;o(dx$*#66P8_#VCHV7tiDquB6KZ1#~GNA>z;yf7a{J?-6*Gf@CF52MDq zy}W%AWdtNAnJ(7LIx`Zx2MKD=g&Goq&e<+Yd!)RB$M+ohS|+rYo!Z-|aCk$stDP-+vYP=}9GyVy>}qq>4@?DS|cpTv~U z@)F4cqus@gue~xhvK`oeDKR6m-w{k4TyYd1e#H-I|I_VY|00oI|K(_=Ca`1uovTe~ z6klVECD9&_NzhlLT`1ykcZ1cE7s zc-$VQjr1Jm-ZJJbxVut1w;p^V6N+@j`)FzGMH68;0Rb%JOU4h$_aEc{H{|1kUbH0~ zDH7{OMJOii__#W4AvJns?Re8ft=MT_XS9%V{i=Q4LMeCH9_MZLv5NqfS{5c03EP-? z4CLJVAqMli-0Kg<$*YCk9mRfu*1hi#%IAb5>;=TA&ILAmdB-|Cwvbey&>qxMx&_28z z{4b2}SKKoolC72&k`WlMP|pCmZ9i-}h6(x?$j{E5c4!cLC60NfbZ# zCD*GVZ#jnCJe+E0??Zfi-`3qLzfW1}FtA#h<>oQ&)3}2VciV%!h1gfVzDb+NKhS1A zbiN0dWSBkqf!TLtRo`b4h$QZtq&pANbY$6%ez@@Z475c>Wc`k-f9Nuj3~(aL3W>g-&wkLkH#wP|ppUKf3*XKFF861T8q{q}d$cpn zwx^R{?TZ&Krw3g)l5XZeR7Fi_ZY*IYbl;Y5Jv+4+kC0`OzEs8a8Gu7TQTHsJs(t|l zqYUp^YY9iqIqh5H!n;N8D6kT2Z=KB@d^pUIW1c(E16xVy@g7H9<> z(W`_5y^1k{;Aw?KBosxy{x(hzA}yN4^#}cc#w=ROJsQ)-@3=FMwEX^{yzPGF1NA{u z%k^DR1WBZAZ`XP$0DG7h5~vR@$+xJxIw(?GcI-~%jJJSy%5@#L1m9Uszy@FbQEi^V zfohS1AI1MQn}f%6W<9~C0_dW;!9vOjl=osLfnEfcd|nd@|4~e!8^hC{QX?e&#&sd{ z{)7wLR$|})K4V>%e_`IO3Y1Q5nY-ob>q1Gl#NOK8D^KI%G93jkU1^ z9Hx_lfvKm~CTjZ+s1Nrw4=)Tpu75Y+fRDg$lAC;92i5XTR8RB1>C$c?$Msv)e=8f; z@hl6=3HAfK*it6kuCP_bdMwc=@~hph^$?P2(|n(edG_wGt8k*oE)*mNKX7)43%Bkp zRi89t!li;9hhS~WNseaG>_e28{nK5&HFuYf;2LjWZ_UBV{QB5OA&00hQ~Om5W<0ug z*;S;08LT;*>#(ZIwkq#b_{(X>=;_kFny4xac^7E?j6;CzXcaw@_c{XeL(ESFn9OSQ zKM%HDA{ilC5D!fkh8D;p>2#09c@^jU^BdEDAQFi-^^fKvwK>8L=&Q14Ap`@!7%#FK z&QMpMnk0Uh>tjdsYIAWrtH3*2#+6Xyz>ExJOO%L{b|NygF(oI|94JUDO0?3{VyY(k zaAAQ`uK@IBd~yDYv&XfWzZV`zFd0Dm9%%Kk;HH_fqXLx5Rn<-b3Vyb)zUsWTAK#(& zx{1a>ddwe0NQtYsMb#N>t1Ca9;UJc8{Pj@CR$NDSCLb7MN}0V4HXPQTatg`FE1}${ zgs)?G`#(GT$j0heeU6MK-f}-{%AY30goP5z=1&iJFb6ZdXp=)D}@@ zGNd%y0v_L;LReT*6%R8drVU_cJ;1=_e;jNVbGEa?KPn|y?kU2k}7Ic1gAVtBmA+k*{ zf;>oxd1)z0d5RTr#i4Jm-6_Ir4$Jt@8Mi{Vgp#1WpLA=ohHx1IjlWK7e*wB@ z$par7_@^yd9UDYnnMyeekEClm=(;#UqDxt`9F^>CC1ImM~1-$>?s zhz=+}WVP;ZSUI^0GAW3=R3!9bt1TFpTv~!GzCda9LF*Ar$Lnc@6{P@IEc(M4X*s#AD}(Q%uj4n@G--&0qER*ieUZ< zyJwz`6}0arD&HX+UiOGD7{8SN1PeGe@i9|a4k*BWutEY7{Vp}CKm?Y;iV8fR{uN2o z^fMR3()jtOZ2wsI=TZpfo)JMGgZipwOwjVN-`ffn{_&38<{au)A~JFxvpkT*`{kwz z74YI@t{FY;q6{RNv$+MFkO0;P+a?2RqdaMyN5vYC=KP zh(E2=jGkNi86rhTparG2V;Bu< zb`=4Dg)*D#Gn~8e1u66Gvy)KN>0NqAUOmk+t}PXt!R%1xL@<5QlT-7-a({BSh?HG; zcTrYAW;GG;Z)eqYv zja@V^&4GOx7V5bN_HygjMN&XEL61)+{4@HBRRuEV_ox4zzx{8D_Wy5Vz%AijOsRN#ll4$Z-hI>gk%`R2bqBiFH@hT{xaD5wP&5SjT+r} zP2q50m^5Xx7vDMcv1jh!92j~9n5OoWzvQ3?jIE^B4NR7ic@Vr~a|)?D@0xxj#W?L< z5kDbNvYHM}27=Q$zUCEn8#pR(baU-)$S5Yijxv^@KGC}Rl=t-BX2y!e89+z#04(0U zgf#)12nqO7oJuzpA8BcOGwQot;Q(&{Y=9nG;oV+?T4 zp8U|h1neX*zao|Lp_hvI67kL>%t>-PZ%=uAs;kL~@6*MEb2L41TJG zN3gsV%v0b{+7FBz$=5}0hO|Ir<%4KVPCkj#iR9#aXJ>?1(#*r4SIhuy(Yh{3j>9T& zlLXYB?b>pCgs=e(DHlE9h2l0WQ?N>4$%_+((&eCpUimMP)7gBJ2Hs$IpUHXFmVcF-qLXqi%2&lAI5qCP2QNd%Eo} zjsr#%DQmLcoX3{uAmD!;f+fwmvCIhLJuWY;U#6xB$MV9#=76KFARrGTB-3yC*G?(K z8C#L*6aGhfhAxVLINg>klmAI&A;>x~I1^AtiO$-)kai>N1yBwsQiwe_4!AC6pewd< z3oOC#@H7-A4_LhmCt2z`kTDBe4+u|PN+Tn~VohCnmB5+6(CoZoF(B3-hR}u(rJhAqX0V;y} zcb39h4m>Tk@&57Qb|FJ}V0XkZ=6~IgD>$@z%=w4yQCeMC#3ZD$-h!pc5=acH@@B2g zAhJ%Ki|A(K(6c+6++Kf5932-ifP4KJ%OoH(HLdMf|Hhm>beV9P9@Cx~_Vk%fCZN9( zL!T;Hndg%a5QU$QfBm)1TKHMY1o8r~=PUXAslf&_VD5DmoKM?E^T6waH7oS3^gkVB z;1dyf(MU^iKPXE5jM$6=i^O&n_jnnvVDTV(?iqkRrOIEPk`U8MmGuvUHq(j)$1#4T zo6IaITVQy`JzJI#qBDc$o@2qwHe+o>ZYv=APXL`}en4riq(fw309n$=w4`)0&hT;( z9vu~a&!3qIMf@}0lCPF3my5NOYRts>d`Jfr_18f+#+;WkO`hEb0z8=G!P(DX=85Y2 zICoX+7vbK;0%~-R0S(qQ@wY$;hy8U|s^lcc!LlyIA?S-j-mdoCynhrCkOP8b9keaE z@%EJH>f*pVjk1F18^oN&C5>VZ8#RFJG9S-0Csd>1Ni)3<9PUhs=)#!w_4%%e4FcQ( zbvH}U7YM<+-GkGSP|@MR=&#Wh*?07m?N5m!HYfvZ8UMi&84p=gO+@0ASE#)4sq|;N zo?{)~&ik{VWWYdeiNt<{3MwZe1+FgcQ2`ITFR-1Y4opk}ZAWAU3VQ@~sCFRS*KP9r zybH`jEPbxx-%Jnyy!#1&D^^%I3pjW<&-mZrMjt{j-(9roB%cpUIYUV=rD4Z!I@I$|4qlN06nte*i!rV)2IIq{wJLOH0S>X>)4<(7zP)a42^W(1-`HZ8C^Hk KuhnyT@_zu4e|jze literal 0 HcmV?d00001 diff --git a/resources/icons/UimProcess.png b/resources/icons/UimProcess.png new file mode 100644 index 0000000000000000000000000000000000000000..0ff6cff2c4578be283b0e25d3006432e69319c63 GIT binary patch literal 19003 zcmeFZ_dnHt{6G9UFNcnOjEpi4vO<)Gm2>P-RuYm?X30wSI)`^g5*5i_WrxTp>y$)@ z2HDObdmj7X;QKoId~VnG`Wvq6r%pZBV?7_w$GE?WF*ej;W;nqB006U|uI4oW0HL2C zfS`l^*zoV&1ppMFr>S<`-)1R;am1tVRa?)G*~izl-#jqZ%N2X_Z&ygv&*eOx#Pa;Akb~Uwo&9@<8HWKqdrgPKO{LCnS9XIl zdkz~H8~>~a_XiU8hh59}OPkX)Z~zh1u)F~))zPv^cw(?SUevbh@k#}#*I-h zI0lzt*nadk00sx_|NoKy@5lh-ZEweG)_B8gKV!F{{zHBtFDSxcVc=YPzQwY3erEg0 zEfI0ZW^xcHV1Dk*p~$Go*8M@T%09<~W5c%6fP3;NO4-&ov+K}B63O!t+m@o`*dx%l zz4;&{iCkCo*JHUNZiBy{qb#iSYgt$y$FWAj{@sohmF^HG`yeJ_I*7YSj{?N%bd_Tnjp&f^_=}DNwx*oa%~I?mFC$K}s}n<{dYc;Z zRVD!1)jfM>>$7?oHO`VnE^NLoOtc6YJL~0k-S`r$2ks_5tp6wN>$a6(qA$Ha|0nen zXL`zq!qMrw%-}mnm~`7&E4%Rm!4@JqeC0P;6IS(ai1!}7&O}*isz7k9Kv}x5~yNjG};psab4&RQ}STxWsP^(sL>~W3gO-x(+W$vaXI^) z65>{Y{+Z>ftX>mI9}J2cL|{{bewj8jk@qa!lc&t@jZ`58SANBSL!lFiu?LZ~Uv6EC zMwk7E7ws!(h0%gqka)UA_HFyhr$x^xrQdSAoWenGGA~3TH8F;I1ctQ+7U#eGLQkn= zhcyo3JVcjem({Y_pi}1FEf)!9^&(bRr8eyWG)wjqBchs;kJaGCz8Q|M<&Wj%g@_KC z`|QI@&lj{G? zHkzGCTf=wC!A1dTQP=QXuLy)R}!! znfdm!o9HWBXHy7r*%*`*MB$tIb@lyO%b2Y-nKf6&6z^iwmtqx_p}qCLx6dEu;o!Iv zh>;SdCRBa)?tAJQ73ier*0W6Rw#hPCo;`tr;0iCs@~YiJ6JzsU0*K2;ke6_oJmi)9 z6FD}B_i6yF;?z{g#@iPwuWuP9dXM2MHr8Ix=Mt=bY~z37kYOe82LB)G+2VBWuGKy# zA~4L5La(T+m=Tt*(e#Uzkfd;|FOpLEfB5Z%<{zpL4jF{!yCazN@OvB^U+|YARiau@ zKNOE>B58yyY%iJ0F0_Bj%3pT)esH;Y<@1X3J}2U}HnBED9UqS0p_4%BzJv_btG_f` zHtbB0Ze?Tjz`KI^D;~+IL0)$<$3~mbDbqcTZR;^Bk5$iNa&v-rFO=vaf!O-rQeVS3 zU;AZ=qk9-97V6x0k2lqJPRCinX4FtH&_>F$a#4aeMTKJ)dCTG|#PzF3mqI>Bw-X1f zLw83fM;;3{lLwntwz4vIybCmdR2s{#s|Dk84q9_hgCBMAoS|z!fr>A#Nwi(hFo8VaESemZz%-h3C-(pI;x7 z50$?KG{QNyx2@70CX69YL-hvv(9s z98G#`N;ZKE2mrIcKWi#G;hjl4Y(ISeRShaZ0hd-X2%rT#ZJ@1A9Bp-8wY^yRVC)e= zdEEpB;O6KRxMQ@if~>b_P9e_g0Wd7!@*w!ba&l+zDj{bsvQQA@Wg{&|G`5BldL+)M z>qSG61?)$!XRwpc_;xG7YW*6t%TM}SZ%kE%_Uab5^Rl{mk*`vyvy8V1ZC{H?Nqwzh zaUZ1$R1uu|#NSCslJ68JqF0@IRG}lP==p_`*rpY7mWBD7UK$vJ5{LCh_>1A?!CE{m z_<|h+1utU!8mz@$GINN?x(dxt_ zigsXcJR#%)KsInOL3?sQyE1Wd4yn zi+$lx!mN})&;UR^HU-nMMWOXb4Nio#7M0vrb3J!l_doyMH=Mcx1ypy~$*Cg;6^o^> zmjC*^fNmKATlClU&HU$;*uy{r7S;Jk0De=*Ge{JahKK&vk3G*Gexxe2ac$|D1LY{5 zMg@9EEb91}2x+j4XX@0`@5$5&cA!){#in^>_forG9S1M`G4$LVMmZP?Yf7e5$B7xc z6xQ24pAWeZg5VouoeoYwQu+FHx#S~AH;FR94Rf*~s?sv&FS@qE!wge{-lCljBOO?V zeYipx$d{5@h`PwAQ4r|6s$f~R`you{TLfN|!M@<`R~><$Y^@l`g|$Xc&t37TWXn-F z+@|MHnvZRgJvf*#)XT!IH}0u774B|ydp0sQg*myFTs?klp?Lg5W!twvTlun~`M7UG zt95H7;WJlnFgxpUfd3d_RPcR)rEK|` zEkcbD1DFH6!GGn~)_-xPCocg|RyA$rrVy2P>~Hz%jfa-j8B)sp7VHUTK4N z`L*_@l5MSKKR;W6N8f})vkeIrTdyUvN(^tvIJ2tZg^3b_KPwMUPWZFX>jSWaW`GzF zrW}@@+1;$`o6}WN63|#2x|}IW`g*-5X$fT?_IQ_fyWhXEDS*TNgJrw*pQk{(IiRAJ zvr~3hZOu|~b$F|_@sNe!w~PLM{?GN62WzLD%SLj0u5DhZv}K%Za0bib`QS}P&>c!B zpq?ckw%YU~OLi}}%a4-y5OH7F+(vrXb!t zSgFQ5JKhtc)rYPb8$Brh`v}`cgYdsWR6HjuJbCt$f6LU2(sx%-?7OCt%|g1F6tplH zMnXOE^mN>qaS0P>%3GyvzVtopuSCa*Kw4dzS2;2!nY@$cI)}hYRV;*TqYxYn1k|{;h0-6B3hNO(=1xVmQ*93+;Q0>aKa!#*DuxAwt8uF_g=*%kqWorxMS`s4|kRa z0j_qsMNR4|lEjDQ`mhc=Ff`=b;cKpTry)8&PZ>TF2HaS#TJyE`@S#tQ%OTP=FIQ8E zb~p6+aX_>rP?j)!nAoeU-;;vw6eY}UFOvtSq#ITBNAsN9)jXcsTgm%4D{u8E7d7el zdbNfApdcW%ou$PitZ$9vG%wbZl<%@qoiJ%y^RiII*Vjpo!A;!1Z^FqYCq=0l8;HWo z@prTe&PfoKhU*x48F-Jy>EiE31*zi)FG^nFAYT&&(=WVuu5dJWRY)l@$Z*Iz`^lpa zjq!{P?Z`B8@OrfXQ|P4W%b} zamn8fa+bbwc=Sj{>?;tJWA-fH2SFYg^97A%EM{!BRm;U>yMk7m^sp^{i;3n>zwZd75_c1R+EMX3cis5%`v^$W5G2aNnvkVFWzMU-PkNm9FoQ8A=TPlek4s7F1g)JM3S&WE6{ZNx}zLPxHc@xumPpOYz&utG<{h z5hxJiQ3bQpaI>+^E04K+f2+Jz&*lK7iu$%Yx0YXTo<9*%KH_hFqAYQamP&MAd$Sqy z_VVt^YzO_&BTO57+FY3UjeqZ_)_T((8Z)i+Jdp)T>5u?FQ@!HM4el`Cg@tfcw|s5l zToPhH@j}~8Qd^q;jtPF|rc10?{*lCnD0y{_A4!L4svfRYw}-+p6Yv{vHL-&9r7!Es zed_&EPLCLUz4+PZ1;4Q%m4-hF>#bp|yepfvTIs&R>;u|{S*NAk>Tj=b6Sba6Gu-#; z6{gx4Q>{_qv%il0H9vXWFen9_0Te39K9wmVIbk{*;~*P2uQ(Z6-hno@rUW#W>;3A@ zIEgzh#YPefQ9V~nbc&fV-d|1#pIAD7of>)-!DbDDg8NsJ4mhFsL`<*-nD*K9q#J)JCtjUX_Qt^)PgivxpH>$h&{EFR?!9hO}v zP7H*iF;PUr8+Mp07k(;TAMPI^r%!VfEht4M9b!VpXFgc-pD&^Tt2hR>pKTw5hQ)2^|*TOwfm26 z!<}3P*XU=|0nnb!vl11HHTbHe+^x6WZdP!m>D<%KvnpDU!GGm1Dljjm?0QKNRUpya z0T)W>&@7~_&x*;il699l+`ARqYwR_sGkLuQ`}K6%8=f7b1(CBavD+*a2v61h^w@sh zMwuC4_$f0r)br^I$jQoTdzaEvIH@22erNN?h=P>V>~*?Hi-YWJ6~+ow8}VZ_J$0_* zNPvi-IQe$VQE!i$}5JyB3>5oh|BC^L8@-|sJhXFr6Q3ODwZaB7ftOj8r zLhmu}_t_QJAcj2$qq;o1A0$P!V~fhOs8CiI;*<_*rVZA#7goF&^v#5I5(%kU?ziXW zR%7Oxj^nZ2h_7al->67#LSL=Xk90^sJ+S%?70}g96{6D#rKW}LQ8Xl%XanN$2LwhM zZ|Szv5(NzTPoNwPiWZgbADE;43l<~npNJbm@#3#jq#GZmF2JPT%#97$?CU!Rsu2s? z%5sPtY}C-Pg`gK|_uNx=IzK}qO@aVJG(l*zIi^pPLcPH+b z4(1o~gL+5>3md-7|HL;H$9Wq!k?SoqR3StP%IpXRyigx8n@)ieng?PP>EX)SFNfdn z1aZC)^fHb_9qIAx)yGO-{Mg#Q`O{Vwy33v$(2<S?6--1?fGXGy?uno6>>qIM%wKO$gi&t~aG>iXMtpQ_%w|&n zrG~Nxd){i||H{Yw40_p>oRO)g688uP+0glmb?1u6m{u+ZcyLUnJ^ch*6CB~p^ee? zF(0F^89jyzNE9>(ppr+?%(Q=j#Dp(TBz^MJ;CcKH^}y_)m4kXuKpE~Mr@RsKEdWXA7uvNP* zcY1&h$5;s5Hz6a9KK7F2Q&wm8D{i-`F20&MU;Xsjx+dBj%4*@D;I#zB*Nj`E&D?8a zmg)t}Cq=0Czi2UDw@05YG0ez=l=u%Tn<}ubD9!NDw`ZSWt~TehCRObxB=|NifuW+L z+vh52&PnJCk#hCnD#HGF!-akJ!TyLgle9f5S#A+U*^mF2l%i!MY}6lN{RWj!(J+O+ zUY%x|C=`WLz4S}mbqmok%v8tcJ7Oc1^a^OK-@vf60$tBYKo5<_zICt~bl&4b5gr=+ z`or!3`}8(RWb{?Lvh?N~HP@*N!J1VQS$I24soi z>f~#K$47G)n|z=oU)93gc5EOdnWzSU_s_9szwms=26h(TXg*FHr4iv4S6sV6(Bh`j zHH*fcjR?!>R(9_4R%8Z$(wF7~6bZXcJdR@0guZ!WgU%MplSIg$cGqSqkJGT;yX6su z|J~O$wRB!f3aR@X>kO!H5P5NhQy~v1*@fNx?2Q~Z@P#FpombIFzfR1C8J9Z;TemHNFH50_hEbdx0;uSjK{Q7zQGXKLFHejH@RS}U9X$z?H_{o zPn=Vd@0LizdOK>4O3#X{g{)lr$O@CID~SIk)Vgxw0*~oSo%H;|trz>kowEoA@H>)J zTEq=6>%F=e_h{=I!5W&gdE_Sm z&!a!j+saBj_R~8RUnlc7pXgqEh5fxJAY?W0PVhV{j*p(aY1wRW{H=ez#|rza%^Cs% z`z`9t3)}lb>wD#f!s9EFbL`}tTlE&cdYj+As?Kd4&ukS(25{N2j-y3Gxx4saVD{M9 z=Jn)blv{&==g?;?Or|f;*yzqOnP$4z9Dm_fZ1Y8=e@i@LJc$xp++($wQe7;Vf)vMO zTY@Ieuru6T<+SZJD4=rbCKv(0Q3-I|+sRhT%D(3$Cx7wKat$aM{wmiQ4zyQV(KA4O zFU&Q#;qyyDVpBgoyZu9qdoS_QX-5M*(a|(V{W3dsu&2}smOEv#<9mV8v+ciC@c<=LFIx@jd62{ARWO7NvS6F7P!K%{;Z0W}rc2rVN84-;G$jg(Bw|;$EnH~P_7>1a> zBo8S1@VtY_K_U$CgPSUhqy#%Jh0#6p@SP_>dr?a?6un?Eba2%?o`z>PUr$&vdU&N9 zjI&`bTI->xCw@lyzg3B=`T5jYCjEp$8YH)TDS9|{5f3E-^J91{6ylO=*Q6yb!PE@1M9@?KF>fpXf3pTg7i~RZVmxL8nGRhIy*~`&~RQB(1_&*tG)~wOa zmSagMZ-cM--85$deg+{Ag{Y^AlPn*Ro?~v`I%C^dpf(#0AVxzJptur)Vg&aj9{;7$ zdQcS{@_aFjW~U*s1c7aDj9Q^?yM`iNe>C0(urhFVJFRV|;V4>w=CX8f6xCwS;>u6g z1mGruGJ-cxGTmmB2X5E1v{BD<&>}WIp3cUwh3GalDJC>`{a=e<9@Tq~Fsm|&Jk}fm-b=8iQZF_>mmwDeL7Bn@YV@cDr4^C^ zopD*J#={W}X72>JCC0u_EnEV3g0e~=NiLkFJfeypjWgh!FvEQwF>lI5w%bCNulutt zm5WD6PvLA=#}atVw%pE6CaUS+yMYssyUB9^rCUge2z)>1^h-7M&D2NzbgK{xZ}Ujp zR2FW-Rdf9bJg25*JA0gI|6e0JliCLW6yH#>0PwMFePT@)HLB+Pqkw+{lrR$dnFy}n#Z^lm6r#I(o*9_q~}|dkjE*s#u7aiK%A@e=^1k^t17urbAsdAD5s~J%K1Kj1Ciytq{k00}9+&Z}2|} zQAVs|uSVncm6hIpzo^->>Hk6MFy_LQ9AEybrtW(YFCov>dJ4R$mr)n z)si$FAklY_xpLic=9@e7x!Hf5m%)XnKvF0$#*Nba7D9*T-fwg;l>9KlbNRuC?w$9G z4ugVi3R$NAT>Lq3j1|8JYD$c;{ZOWflu%^AMm?q&CiPV(P1c$bcq`={(TqQUsQ(OPu`|C5< zS7{}4q1sr_P#!2}FW+}GXpS8Bfwg@zbjb;G0jW})gR8vQt41MJMbUor6RsEKfju+s z!!Q?mitTAO&r;PDmm-5yDu}~a7^fpOD@aQ}65E!={iR%Lwx7y=`FdGJmyE z74>|56v>-*PN$)W8!cud>mwd?pcMuu-aWK;a2wq*>MuVwAEl!`=SiW;7@2^50aN^D zI6rklkw?^7ZI&01Wm;`*o^w8kpc^TFh;YxM1VH=13%G2Br+xCfrH6=uNdJf5Gc{P>*G+ozYgJ80>9*ZTN1 zR>*o_h!Gv4@SuD|g?}PtBlXeb6-^j%qsduZ-xo(=cmWqSiL9fAbY*uM*RycU#HM`x z=A)A$4{vM2Jah1YEsr=mWggsP(AV(I8%?`KM50R-92`>dxl%K7SdTQu63mtD+p73uq_Ux;UvU$-Q!GiXS0)q`gXo}HU| zYZcl$_QLH3ZiMbuw@zL;1w-D|k!UWPu+I5Mx||}ew(zlpRRvn7$wn@q{d+%B54bPJ zCzM&z-ZSbb=-RaB0;8&n4Q^8)fj)8S7k9hSjEruRCOhD*^?PMdM*+|MdBd{{EZOT!uCVQWx$Q-)IOhSrh%m zgnc3PANDkQkVj%CCq4o?4F#TT?_Y=@*XL60dFWVLr(52!0R zKv|XjIpdmt&VzPtpPIc<@36?7l>z&<|EB)83t|V!40Ddu@8di7Y z_2fgXSLt6|$A36lAt!@t+58>BVgL90oH?+$B= zLdx|>_A#EKsQ^i!vxf<@q6)x-zQOdV;Ln`(?ycJ9MH>EHIg{VersAAS zlo(<6mC2|g$#To=e*)Ntr*jQ_!)=u{&*$9RVEO{n;7^%VgJyr5An$5g8u~2g?cz+$ zp$<0n&EW+@rMfOfmb>Kd^15C&Y97?{_3K>vGj!`bjHIfy()W_BgXO^*1@Ux3gsLt) z{6@k`D2s>p=Q^=iza8bWO0nU)Z@)Xnoi?zj``s*x*86 zisl7qHlx;(w_d`@#V23IiqXciyzEWBMtzEgA3${)Fimsgs^mo{C%y;-Fc=+HB7$Z5&jsYaqrfl zK|qAF5~e*XN~K6&mI|wBNTBuY& zt|84aUhnZlSGWJn=@q8`Yb+PAkO;S7MCLSD7;0O-=fR1qQbPkQ#sDtN0F;*z-sF?h z%A5o*kUr&~%n3c>42v&>2C=$>K*rgQZ?XTZ6~MhlXmSGGUmo7M#_8-kEx+5>89{!W z_Ro}&xMZP(=)CHNuI){7dOP_HS{1W@Sv+o!fW+rPFRQ<+7(%6$~rVX~1R z8!i6y{K*;6;;gQqn>bpQX+p~VA9+Gl^8Dg1IS-(81q0Z!xoQ4c3jlsYMxO?{kALES zo}*ANGBevh_9YsG>SDMJfU(85xgKy-pk7QFS7VS`l^ zpDJ1IDfI?J=KEd)RSd{wlsKflkGMN=qaC4eR{iwR7*2^^lM8TW&`dmE-~Y|LoTjd} zyF2g9|46tFF3gB+;oPYI%G?mKp8koH$};pu?|%lbQ3uUWr~U>vi}o*HPG~*~$^np< zBU&2B%njYSk=+!~930||3iF~yX8`=17}64`vszAg%k?^}0rwLP#iswH42AMPY4No4 zf*v&-Z^)aiR(T4sSw~7skj2e{L|vcn!!$lU0&AOzn1hMaM@%{Bqdo!ydi(5mZ;ygI z^;Y@zF%?I56@{cOS-rFLAEuM;c4!_YII_!?`+p6j8==V!q0tNm(LUOq8 zsVO1=Uf^g~1A?z#wK11Z7AsM{k+ec^py_4)r#2o<)G-)|jy7N-8^f`?|7Ok6d(f6+ zjo$p9e~<$e5!kRBSs@}8&2BsP%m0i-8~y+ZNEMUP{RwmKc$P`ovVUVv z)UjoDkJ`Oon2_~=o!#*Nhdpf+H>{#|cS_^aZe%I;lM<63^`V&5g#KC1C}XY9j(`2; z&fcn>>fbZ}+;6LsW|Y1l0>s|&`>!6r@ag6+1q{*7Iw zj*W=Uboe6vTnc6Kye*gIMNKgPcsNJ90l@eAz~7@nRng|4>B6g?ItX$D$@dfeBoxmS z9n?u!WZg2qOh?MaFdUJN$vzH#pa%+Qx0JBr)#~L<+6`&r&Hd*57ogfGbx$YpgfzhO zeK%sZ3;V2N>cQ6BGrq6pN4!NFpmboQb3We+B78bmrpmc#DmCmXp0-$1M@{G_B>;i4 zZqJ)Uari=_$mGhWPr+Z0hL8w-Xd$sIn#@D&xW`3IR%>H-r>1MvVBiA-mb-}cK;9U= zacfk|Hu!Wlq3$0{`4eLDG%&(k+dfUVzi$c3&jS6EK$%Vh*P&qdiFsUCMoyy9i*E$eBWn8YoF%5?)7LjQ=zCIsfN z8urdff2ofI*84KD5Cd0)rF1^1K{*s?~ z=K&(`Cw(FcxH@%eFf;G(9EHgVrnCa`wLifJK6WnXxY#aTbFF3PDTucAja;m`CiH~E zPZA{3hI@A_VA&OB{C7xy;1oP9LQ|Zmyx-HIGG+8F9xIr9b0om_cn1=$&jx6($_$!D z+C3i<>H=T?5g~KLVGnzj#D{ICN7W^F*gYA@_KxDyC+~zjOF9Km5cqgd_@(LOh5#FX zn=|6!RU%v8qeD0TJ+<{EHgZI&QdwpFkn2U8E28v3bGLU?S8Q1B@g7jc#8=~d+bTE6 z&+d;MQq}${@}5OGrzJn$c=(v#KaUCxaXVj~!~DL;q>W8tGqu;&Nqd zT*;TBzDA^I?C2PY~0GfFo?XHo{40aYiTW~Dh@`&)=bsIUuEhr;mj!$el zZ2%}x_@&qq4k|LnS~NEPYUC-!6Oa(pX8c}+bF&PVrS}VNEvZcnf!#xpmr?iFktG%d z2pi7Xcvf?vA6zhx!=?Y^;@wKrhUnGsmNZbg9Q|LdjH2ZP6ZU7IS%ob}%*}x_3io=( z{eHuPXI6Lt26#TZf*N{&TRN6L@iZVH0`F#;HD3_s>H)iwLzHsY$xOINL}_tg^O@m_ zoZX^}7jaUP2QMqptt@$}D_4#KV*aldtv7S-H|EP0YRMuRsy1KVMS^$Pqnnz<4T=K- zs^dQI=-UXz2^xF@@%1!;$@u6PT;S#9eyjAY=g?a_a#)M`iq`&oAKrl7MIuLyvK?SV zGWyi|%WRX*>IWlLfVmbk&@5yuf-Bq$>5aw~(;-QbYGZ8A!-2Y$%eR}^h})5QFKHi) z=ov{MB~q}?8L5e77l)(qf;tW??-$_%V)gHgsms%Rrb>^FAzGnFLk7!eg)#L z(UYrfVgnf9bZi!;$H=Q5tF$oBu$qe5D!>LYI|4`}qz{ZfS^!p(YmZvBjfi>3f$PWC z2d*QLz&2}Ie0wlA{|En0Vxz+t5CP(TAsTMMUaT~}w-J;bTk;v=Zd1NcUIk6vE?5zu z^w@6_N9*G$hjt_$PeJf6go}8h@wUs`rm!O8SUx!%uuy?N?N4sPYM=4z3@A`<6ncOp zv*6AmNC-P7mq(f5K%tTVtb#0y_dBd@7oMT@Ja68Sd8WSL2ipDfnHNAT(7Cwnm8%U^ zhw}L^3H~thZa;2N8S8Z*<7wysHqh~W*dE&Che2&--!E?YSxU_ZN=r}MhOE}>%WQhd zS=IelA0c&i)CIRMB<8UffYwXo&nh`Kc@;2uFi-i2~AK)h&vFQc!~r zUur*)w#dgHlGu5O?xaZI6+x(ZngI;MX2x1JzeGU|*e<2#6m6{Og1c3Z|H6ANGU6%HH9^CJz2!cfrC*knjt%bguid=aHm(C?(ne9$``}O}kRQ<(!SFkHz?d~?~Sf7TSrP<#}Oi;fmf3<@;0BMcqt^~Ri!qo}PmA+>^ z(7(XpX*T3O!4DS`CVp>J6$)zAtoD3h2=`}3!sOQc$I2k|NL;YfM{)n0L~h!#mu0d3 zbhsG$((DuIW5Zj-7}vb;S@%$X4zLCTYj(v4d!B2by-Qs{PeR+l!7+|c$|9)Q(%9ica40B4gtD0I5ZLE4(i+_F9Ec1d}ig?e-d zDIgx}>L}H3<9*_IrJP(`D4r3~rES*~I%l4kQJr`W`o00;_*rvYQ&enM41;i6kMP!N!Cws! zlUMO~zlE$4F3q4^pHP84gp?WKXAbhtQo#e-C(qjR?K@@I zJ1)0@ipjB@>4L||FQCWcy$1dyPor6lz4CX*z9TqD2e$iy8R@#eAaqy6T9_(Y z@vInaH7ye-nRf)+))u^ApFVC*)7a=F$aDFsg8yGGj_;7`=S7+vW)frtssBg;g&9%&#nhUgzlfz}nQ!rw#=^R1_cT zJdCiq*Nxupj~QM*MOEw4SwGt#8&3cK7R@r=k6Gu57u_w;Z7NgdcHsqYBT8kALi&xI znw>r}L2S7=IWU`)^ih%>ZMcoEebD=~>lR&dg(1~KNKP1nwD}f4nb{W!KRNCRZEW}7 z__f0vBa@_+7j9NzeR}==X$|XFu}gk>L65zWF3H&1<&ccU&C0gw;fG#tsUW!w*4?*L zI7m?!KK<$tKVHF&2rYS!kKqP?BRy}RZ7F^@L#ZQ7+YP#*Z0$_*7_2ypl3WWR%=B!4 z7?w7i*Y}JNvzFZs1dgviXk+!EJJzR0^@D}>e0|s2ri-UQ%NVJU3~^$+KKxyYolHxN zemIg;6)KKM6Z}F*AE6q~^AkNm*SSAMiBFjYbqAXngrpB*x3IsjIl~2d{rXqZ4_&yd$_PZGkj42~Aa#-Je3%6i-H{dI3fX$uu9Lgl%}mi5|v z#i`4#Jw+=elCeM!g7UG^=`KB#LXgB%n@B-9gs*Yc>a8tD_a~BH>aF0+O!(gWv3r5c zHfNG4E|WoXA0l;-JgHc}CFNT+dq12#?S3E0BEM~5U^B=o!Im}n@4$sg?}-eCR(V(^ zTb)o=Mf7U`Oq0-V{^J~^30u`>V1tCA8`z-aSkX_jnEF0>Nzv~v^x**AvRk?3&2W4> z{7>nDd&s!+GeSH0rXq~0aK~(eU%Ew-5zh9$PebS2B0VzT(lhGWLZHp$jE?`CH#_bK zXZt=XvNDdjOiAj@1xM0-rm%e5(EM}d91vE_73>~fQ4aMOy`q-|K$e+sAn{fuy`-lT z@^qS(SE0`m3}5n#*7M6DXR%o4xv?|DMmOdc>iO-m>h!2aG@;h(M0>LEowwsc&7ey{ z&GPds@pe^-^?pvoc>_D%3v#y8efx;@{JZ_OXlQ%m!@_%YUki#3XdGo->M{!Z2-0o) zF2t(Jl3Y3*L63u!$*VgF18Dt>;t?sJ-r*mWl{gg}6`f8<+LTdV2eV zVo0sCqzETOIO?-P#!nq|1SR2q&kvLLe}g^Z@NK<(<*YR=DuGgl6YE?l=39Fnap#m_ z&2irOU9Mij|GTVDXXW(t1z6}QV!31$2e~nH$t}6rIAm=a5XsNNu8{VF30r(Wm<))T z{BvDzdY&uVJO39By$zZ?1sXfEAv8~)c@>=FEZbvQyNcIwV01v>#y?E?zqTc;2T_iF z3!X9J&BR+OJ-jqHTDb!OKR&&AFiChbfe5{-iC=(0c%n^M;y67;J8H`bPTA=kH=1h4 z!-H#^-5iH!e;3tKG4UlVo7TgCAv%9A zA8T;&%vsD_Et%+oBNVK%vKE?ePS=NYIKanBeiJSj9EZRjb-oWSg4rC4aTj;5_AR&K z4qji?CWZ2uma}FNgp1GG$oPpOd`+Lj* z&1WnRX8Ue@H;)EfGQE;z$j8OYyF#>*4LN^o^*vriLp?>xg*Q+cIEGSQtAZ9*t@(a@}~``EbrO@>oo2)S%nshHA$_j!W)eWMDw2* z;^QlKS4bfR&6L?b&#%$&vi{Ykg<60ILaIcHCjIb2VEdFG*K-hnc+u>HYl-@xQA0&dv%u4!Fx z<}4Rz2mevT_ZZN9BR;g=(qm%uJRpxl{{&p+3egg$)i?9qv}wiB>PpM-&jylzn$-Gw zpwZ0U^4QMlS1~8z?0@TQ7i{tu$iVRmy-KRbU_ZQ$JshBn-P`l$4MIE|px3y#)L$Wu zyl>iy^HW|d_J3AnTU)gwywanyx!*AJd;n*LvV6p3?v3>domAa0`5uc&Bql1h`3;Y3FT}WaN>!!}^z&3K0GLf*%-kOXxbS!q=WQhk zmrs(O;Lj43;QHdEN6-`6BgP13>wRSIivwH`_r?0$OBGZ&NFT3G2r(fN)$xA2jrE~D zzi9SXp)&^U_M^6-M$S_3{89sz~14hNeG z5yQ(#GHGFTnh2aHJXA8i@kh^}ExPLntY?d3Hy<1W0Xzq$tfkm09HEa>RN$zJSNJe9 z?t>mbI4$f48(A+^3%IIYKH}ZKlE&L7(>#3e}Q$+Kfbr%(urx)-Sdkq(YiH zeOn_9bghpB(AUl^uGGW$p@C9K_OVT*6g#yi|}h=Bu2#<FD1^Q?^p|oGpZ-{wIe)L57G|vcYg$SZINwwEg-f6` zJE#hlmW05*5;Eq1*^-Qs1BTr_*3<8)0k4PTtH_T4#K2wLEt_&9L>Keqr80HsIZfWS zCa)|Gr3ek_08B8+Y7Hb%w5gkUP+C$kCL5%eInH(*eous0AM8w0R_7Z3Okm`L84n{} z%7x*Wpwhc{yVIQ+KX*FFCciUIV1waW;pyds-xTMLGG+uFNU2#x9c#T2%fyom1zYa4FoDv|)&RG}$eQ$m} zBLY9SJn~tizThKK@J)}Dyu-N9Wt(PX&V5<&W}Z(!^Ftu18NAwU6Cz1xytj9BePRlm z!Jybc!gIl~qMofr&ER{VdYHo~u5{1au{x;!{@~Lg!J5ItpSVo34fn^b+;GgQ28t7CKBr(?lYBqV zIP>p)DAm0>$hh~w;wReORxwO%J}kMmqpiA)S*~^+34GXeQCw9h`r(@tu=@vsjODF54tfo}=W4?N-R5|b=R@clX%fSrl;gUC z1212OkaJgxcQT4Rx@FYSpe*PphqHg!9vZ>AFd1#yJ1>y&KHUYn44TSxwf96SQtz$x zPL?>P_1};>K6TYDCw%YKqBIotm_~||uJg}xw#D-wB;I(saUw3mk>K&B8Wo2E!XI+F z|53-Az6iZ4SH8QEC`t?3G#)Ugm~QNPv+0Tnd)9TU`sbdQNAF1=EiA3#L25|B2F5^a)$br2V;A@Qor^ZqjGNTA}Z4z+_fc zB^PKAE_lTl2|R&n{Q7n~szP>W?f#mmb*Xt*jVxykHJ@?fx!o9Apn}fTSjkxO2pc@6 zv>`N?p90hU802{d29|Ulo8k7H$O$+XW~BE5%e@#-aG5Y3_IB6dUUPlZ149<^V?*^K z+$uC)7#+7L7cLkwH_h{$5E$Atrvnddy1U*VuiM`I{MLt?pF(w}Ge++pe0)vWW7{1n zpA1!>?td^WJyY~9!vTS}EEEBT_@2K{5`py~HsCn4+iS71=7JDELRij9$bSEBTJG0W zsJydF;Tzi`rY>aO%<$V;&t$sT0YKSyBmzD!N_?#`hHRbYh5pNef_DFhawnV%MwSNs zHmg^*_AiT`f%Sm?7x7h~0ViRpSUZ2+H1B=j{74?Aa+8kAdf>&Vsiq^o@= zluNCzLZu;`mor?YCcSvjY10^J74|p zS9$-N;(O7ze(~)Muetbce}Vq2r{yaR56r*8qLcUg?8PrfQr;VvwQyGUGkB~pE&x_P zpjlbqa65}b^pZ?=2zP=2kn1$JQ5!7D(6|lQAGxLEYYpYL0J%c1cw&G95}*mCEx-YQ z3u2kKpj;IfU}q{I{Q+#EO&7SOdYi{(KCoH`1w|mY#flj+iOB%m76?2}z;#v`bmoc^ z$W^%_xdT|&0Y`)cGP;4|0J@j905{@;xE{dW{t64v9S{Zc8XTg59nIW{%VeQkCm@%z ylx-bIDbQE9lz`pDZj-lAt`Lwr>g7Qa%RlN}V_sG=S)EwG00f?{elF{r5}E)UXRICo literal 0 HcmV?d00001 diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 386d796..9d49691 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -291,11 +291,14 @@ void AfcExplorerWidget::onFileListContextMenu(const QPoint &pos) if (!currPath.endsWith("/")) currPath += "/"; + // FIXME: index + int index = 0; for (QListWidgetItem *selItem : filesToExport) { QString fileName = selItem->text(); QString devicePath = currPath == "/" ? "/" + fileName : currPath + fileName; - exportItems.append(ExportItem(devicePath, fileName)); + exportItems.append(ExportItem(devicePath, fileName, index)); + index++; } // Start export with singleton - manager will show its own dialog @@ -346,11 +349,13 @@ void AfcExplorerWidget::onExportClicked() if (!currPath.endsWith("/")) currPath += "/"; + int index = 0; for (QListWidgetItem *item : filesToExport) { QString fileName = item->text(); QString devicePath = currPath == "/" ? "/" + fileName : currPath + fileName; - exportItems.append(ExportItem(devicePath, fileName)); + exportItems.append(ExportItem(devicePath, fileName, index)); + index++; } // Start export with singleton - manager will show its own dialog @@ -869,4 +874,4 @@ void AfcExplorerWidget::goUp() // Add the new path to history and load it m_history.push(parentPath); loadPath(parentPath); -} \ No newline at end of file +} diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 13b41eb..970f8eb 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -66,24 +66,20 @@ AppContext::AppContext(QObject *parent) : QObject{parent} const std::string wifiMacAddress = PlistNavigator(fileData)["WiFiMACAddress"].getString(); // plist_free(fileData); - qDebug() << "Found pairing file for MAC" - << QString::fromStdString(wifiMacAddress); bool isCompatible = !wifiMacAddress.empty(); // TODO: !important invalidate old pairing files - // libimobiledevice does not append WIFIMACAddress to the pairing file + // sometimes there is no WiFiMACAddress if (!isCompatible) { continue; } + qDebug() << "Found pairing file for MAC" + << QString::fromStdString(wifiMacAddress); - IdevicePairingFile *pairing_file = nullptr; - idevice_pairing_file_read( - lockdowndir.filePath(fileName).toUtf8().constData(), &pairing_file); - if (pairing_file) { - qDebug() << "Caching pairing file for MAC" - << QString::fromStdString(wifiMacAddress); - m_pairingFileCache[QString::fromStdString(wifiMacAddress)] = - pairing_file; - } + qDebug() << "Caching pairing file for MAC" + << QString::fromStdString(wifiMacAddress) << "Local Path" + << lockdowndir.filePath(fileName); + m_pairingFileCache[QString::fromStdString(wifiMacAddress)] = + lockdowndir.filePath(fileName); } } @@ -99,14 +95,24 @@ void AppContext::addDevice(QString udid, addType, wifiMacAddress, initResult]() { if (addType == AddType::UpgradeToWireless) { - const IdevicePairingFile *pairingFile = - getCachedPairingFile(udid); - if (!pairingFile) { + // udid is mac address here + const QString _pairingFilePath = getCachedPairingFile(udid); + + if (_pairingFilePath.isEmpty()) { + qDebug() << "Cannot upgrade to wireless, no cached pairing " + "file for" + << udid; + return; + } + + QFile pairingFilePath(_pairingFilePath); + if (!pairingFilePath.exists()) { qDebug() << "Cannot upgrade to wireless, no pairing file for" << udid; return; } + pairingFilePath.close(); QList networkDevices = NetworkDeviceManager::sharedInstance() @@ -121,15 +127,8 @@ void AppContext::addDevice(QString udid, if (it != networkDevices.constEnd()) { - IdevicePairingFile *pairing_file = nullptr; - idevice_pairing_file_read( - QString("/var/lib/lockdown/%1.plist") - .arg(udid) - .toUtf8() - .constData(), - &pairing_file); *initResult = init_idescriptor_device( - udid, {it->address, pairing_file}); + udid, {it->address, pairingFilePath.fileName()}); } else { qDebug() << "No network device found with MAC address:" << wifiMacAddress; @@ -137,19 +136,29 @@ void AppContext::addDevice(QString udid, } } else if (addType == AddType::Wireless) { // FIXME: its not udid here its macAddress - const IdevicePairingFile *pairingFile = - getCachedPairingFile(udid); - if (!pairingFile) { - qDebug() << "Cannot initialize wireless device, no pairing " + const QString _pairingFilePath = getCachedPairingFile(udid); + + if (_pairingFilePath.isEmpty()) { + qDebug() << "Cannot upgrade to wireless, no cached pairing " "file for" << udid; return; } + QFile pairingFilePath(_pairingFilePath); + if (!pairingFilePath.exists()) { + qDebug() + << "Cannot upgrade to wireless, no pairing file for" + << udid; + return; + } + pairingFilePath.close(); + QList networkDevices = NetworkDeviceManager::sharedInstance() ->m_networkProvider->getNetworkDevices(); + // todo : retry logic if not found auto it = std::find_if( networkDevices.constBegin(), networkDevices.constEnd(), [wifiMacAddress](const NetworkDevice &device) { @@ -159,7 +168,7 @@ void AppContext::addDevice(QString udid, if (it != networkDevices.constEnd()) { *initResult = init_idescriptor_device( - udid, {it->address, pairingFile}); + udid, {it->address, pairingFilePath.fileName()}); } else { qDebug() << "No network device found with MAC address:" << wifiMacAddress; @@ -473,14 +482,13 @@ AppContext::getDeviceByMacAddress(const QString &macAddress) const } void AppContext::cachePairingFile(const QString &udid, - IdevicePairingFile *pairingFile) + const QString &pairingFilePath) { - m_pairingFileCache.insert(udid, pairingFile); + m_pairingFileCache.insert(udid, pairingFilePath); } -const IdevicePairingFile * -AppContext::getCachedPairingFile(const QString &udid) const +const QString AppContext::getCachedPairingFile(const QString &udid) const { - const IdevicePairingFile *pairingFile = nullptr; + QString pairingFile; // Retrieve the pairing file from the cache if (m_pairingFileCache.contains(udid)) { @@ -489,3 +497,8 @@ AppContext::getCachedPairingFile(const QString &udid) const return pairingFile; } + +void AppContext::heartbeatFailed(const QString &macAddress, int tries) +{ + emit deviceHeartbeatFailed(macAddress, tries); +} \ No newline at end of file diff --git a/src/appcontext.h b/src/appcontext.h index c4d5200..49d2584 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -34,8 +34,9 @@ public: QList getAllDevices(); explicit AppContext(QObject *parent = nullptr); bool noDevicesConnected() const; - void cachePairingFile(const QString &udid, IdevicePairingFile *pairingFile); - const IdevicePairingFile *getCachedPairingFile(const QString &udid) const; + // QMap + void cachePairingFile(const QString &udid, const QString &pairingFilePath); + const QString getCachedPairingFile(const QString &udid) const; // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // QList getAllRecoveryDevices(); @@ -55,8 +56,7 @@ private: // #endif QStringList m_pendingDevices; DeviceSelection m_currentSelection = DeviceSelection(""); - // FIXME: QString can be macAddress or udid - both works fine for now - QMap m_pairingFileCache; + QMap m_pairingFileCache; signals: void deviceAdded(iDescriptorDevice *device); void deviceRemoved(const std::string &udid, const std::string &macAddress); @@ -79,11 +79,14 @@ signals: */ void deviceChange(); void currentDeviceSelectionChanged(const DeviceSelection &selection); + void deviceHeartbeatFailed(const QString &macAddress, int tries); public slots: void removeDevice(QString udid); void addDevice(QString udid, DeviceMonitorThread::IdeviceConnectionType connType, AddType addType, QString wifiMacAddress = QString()); + void heartbeatFailed(const QString &macAddress, int tries); + // void heartbeatThreadExited(const QString &macAddress); #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void addRecoveryDevice(uint64_t ecid); void removeRecoveryDevice(uint64_t ecid); diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index bfc94d7..b502eee 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -24,16 +24,15 @@ #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT #include "libirecovery.h" #endif -#include -#include - #include "../../heartbeat.h" +#include #include #include -#include - #include +#include +#include #include + std::string safeGetXML(const char *key, pugi::xml_node dict) { for (pugi::xml_node child = dict.first_child(); child; @@ -365,15 +364,12 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, } } -// FIXME:spawn on a new thread? -// wireless connections sometimes take more than 10sec to connect -// and ofc it freezes the ui -// TODO:idevice_start_session ? iDescriptorInitDeviceResult -init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) +init_idescriptor_device(const QString &udid, + const WirelessInitArgs &wirelessArgs) { const bool isWireless = - !wirelessArgs.ip.isEmpty() && wirelessArgs.pairing_file; + !wirelessArgs.ip.isEmpty() && !wirelessArgs.pairing_file.isEmpty(); qDebug() << "Initializing iDescriptor device with UDID: " << udid << (isWireless ? "over wireless" : "over USB"); @@ -391,16 +387,13 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) IdevicePairingFile *pairing_file = nullptr; IdeviceHandle *deviceHandle = nullptr; HeartbeatClientHandle *heartbeat = nullptr; - HeartBeatThread *heartbeatThread = nullptr; + HeartbeatThread *heartbeatThread = nullptr; ImageMounterHandle *image_mounter = nullptr; DiagnosticsRelayClientHandle *diagnostics_relay = nullptr; ScreenshotrClientHandle *screenshotr_client = nullptr; LocationSimulationHandle *location_simulation = nullptr; - // FIXME: remove debug - std::stringstream ss; plist_t val = nullptr; - // 1. Connect to usbmuxd IdeviceFfiError *err = idevice_usbmuxd_new_default_connection(0, &usbmuxd_conn); if (err) { @@ -410,7 +403,6 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) } } - // 2. Create default address handle err = idevice_usbmuxd_default_addr_new(&addr_handle); if (err) { qDebug() << "Failed to create address handle"; @@ -422,16 +414,20 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) struct sockaddr_in addr_in; memset(&addr_in, 0, sizeof(addr_in)); addr_in.sin_family = AF_INET; - addr_in.sin_port = htons(0); // Port doesn't matter for provider + addr_in.sin_port = htons(0); inet_pton(AF_INET, wirelessArgs.ip.toUtf8().constData(), &addr_in.sin_addr); - // IdevicePairingFile *pairing_file = nullptr; - // idevice_pairing_file_read( - // wirelessArgs.pairing_file.toUtf8().constData(), &pairing_file); + err = idevice_pairing_file_read( + wirelessArgs.pairing_file.toUtf8().constData(), &pairing_file); + if (err) { + qDebug() << "Failed to read pairing file"; + goto cleanup; + } + err = idevice_tcp_provider_new( (const idevice_sockaddr *)&addr_in, - const_cast(wirelessArgs.pairing_file), - APP_LABEL, &provider); + const_cast(pairing_file), APP_LABEL, + &provider); if (err) { qDebug() << "Failed to create wireless provider"; goto cleanup; @@ -441,7 +437,8 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) qDebug() << "Failed to start Heartbeat service"; goto cleanup; } - heartbeatThread = new HeartBeatThread(heartbeat); + // udid is mac address here for wireless + heartbeatThread = new HeartbeatThread(heartbeat, udid); heartbeatThread->start(); while (!heartbeatThread->initialCompleted()) { @@ -539,16 +536,7 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) // goto cleanup; // } get_device_info_xml(udid.toUtf8().constData(), lockdown, infoXml); - // infoXml.print(ss, " "); // " " for indentation - // qDebug().noquote() << "--- Full Device Info XML ---" - // << QString::fromStdString(ss.str()); - // Received plist: { - // Domain: "com.apple.mobile.wireless_lockdown", - // Key: "EnableWifiConnections", - // Request: "GetValue", - // Value: true - // } lockdownd_get_value(lockdown, "EnableWifiConnections", "com.apple.mobile.wireless_lockdown", &val); if (val) @@ -564,10 +552,14 @@ init_idescriptor_device(const QString &udid, WirelessInitArgs wirelessArgs) result.diagRelay = std::make_shared( DiagnosticsRelay::adopt(diagnostics_relay)); result.locationSimulation = location_simulation; - AppContext::sharedInstance()->cachePairingFile(udid, pairing_file); + // TODO cache pairing file path result.deviceInfo.isWireless = isWireless; fullDeviceInfo(infoXml, afc_client, result.diagRelay.get(), result); - + ::QObject::connect(heartbeatThread, &HeartbeatThread::heartbeatFailed, + AppContext::sharedInstance(), + &AppContext::heartbeatFailed); + ::QObject::connect(heartbeatThread, &HeartbeatThread::heartbeatThreadExited, + AppContext::sharedInstance(), &AppContext::removeDevice); cleanup: // Cleanup on error // FIXME: implement proper cleanup diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp index 9a9accd..aedfe34 100644 --- a/src/devicemanagerwidget.cpp +++ b/src/devicemanagerwidget.cpp @@ -145,8 +145,9 @@ 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->addDevice(tabTitle, device->udid)}; + m_deviceWidgets[device->udid] = std::pair{ + deviceWidget, m_sidebar->addDevice(tabTitle, device->udid, + device->deviceInfo.isWireless)}; } // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT diff --git a/src/devicesidebarwidget.cpp b/src/devicesidebarwidget.cpp index d3afda0..028bd79 100644 --- a/src/devicesidebarwidget.cpp +++ b/src/devicesidebarwidget.cpp @@ -26,9 +26,10 @@ // DeviceSidebarItem Implementation DeviceSidebarItem::DeviceSidebarItem(const QString &deviceName, - const std::string &uuid, QWidget *parent) + const std::string &uuid, bool isWireless, + QWidget *parent) : QFrame(parent), m_deviceName(deviceName), m_uuid(uuid), m_selected(false), - m_collapsed(false) + m_wireless(isWireless), m_collapsed(false) { setupUI(); setFrameStyle(QFrame::StyledPanel); @@ -51,10 +52,20 @@ void DeviceSidebarItem::setupUI() [this]() { emit deviceSelected(m_uuid); }); // Device name label + QHBoxLayout *nameLayout = new QHBoxLayout(); + nameLayout->setContentsMargins(0, 0, 0, 0); m_deviceLabel = new QLabel(m_deviceName); m_deviceLabel->setStyleSheet("QLabel { font-weight: bold; }"); m_deviceLabel->setWordWrap(true); - headerLayout->addWidget(m_deviceLabel); + nameLayout->addWidget(m_deviceLabel); + if (m_wireless) { + auto wirelessIcon = new ZIconLabel( + QIcon(":/resources/icons/QlementineIconsWireless116.png"), + "Wireless", this); + nameLayout->setSpacing(5); + nameLayout->addWidget(wirelessIcon); + } + headerLayout->addLayout(nameLayout); // Toggle button m_toggleButton = new QPushButton(); @@ -292,9 +303,11 @@ DeviceSidebarWidget::DeviceSidebarWidget(QWidget *parent) } DeviceSidebarItem *DeviceSidebarWidget::addDevice(const QString &deviceName, - const std::string &uuid) + const std::string &uuid, + bool isWireless) { - DeviceSidebarItem *item = new DeviceSidebarItem(deviceName, uuid, this); + DeviceSidebarItem *item = + new DeviceSidebarItem(deviceName, uuid, isWireless, this); // Connect to unified handler connect(item, &DeviceSidebarItem::deviceSelected, this, diff --git a/src/devicesidebarwidget.h b/src/devicesidebarwidget.h index fe3ade1..846faa3 100644 --- a/src/devicesidebarwidget.h +++ b/src/devicesidebarwidget.h @@ -36,7 +36,7 @@ class DeviceSidebarItem : public QFrame public: explicit DeviceSidebarItem(const QString &deviceName, - const std::string &uuid, + const std::string &uuid, bool isWireless, QWidget *parent = nullptr); const std::string &getDeviceUuid() const; @@ -67,6 +67,7 @@ private: QWidget *m_optionsWidget; QPushButton *m_toggleButton; QLabel *m_deviceLabel; + bool m_wireless; // Navigation buttons QPushButton *m_infoButton; @@ -164,7 +165,7 @@ public: // Unified interface DeviceSidebarItem *addDevice(const QString &deviceName, - const std::string &uuid); + const std::string &uuid, bool isWireless); DevicePendingSidebarItem *addPendingDevice(const QString &uuid); RecoveryDeviceSidebarItem *addRecoveryDevice(uint64_t ecid); diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index adef72b..9422558 100644 --- a/src/exportmanager.cpp +++ b/src/exportmanager.cpp @@ -18,8 +18,9 @@ */ #include "exportmanager.h" -#include "exportprogressdialog.h" #include "servicemanager.h" +#include "statusballoon.h" +#include #include #include #include @@ -32,29 +33,23 @@ ExportManager *ExportManager::sharedInstance() static ExportManager self; return &self; } +// TODO: unfinished -ExportManager::ExportManager(QObject *parent) : QObject(parent) -{ - // The singleton now creates and owns the dialog. - // No parent is passed, so it's a top-level window. - m_exportProgressDialog = new ExportProgressDialog(this, nullptr); -} +ExportManager::ExportManager(QObject *parent) : QObject(parent) {} ExportManager::~ExportManager() { // Cancel all active jobs QMutexLocker locker(&m_jobsMutex); - for (auto jobPtr : m_activeJobs) { - jobPtr->cancelRequested = true; - if (jobPtr->watcher) { - jobPtr->watcher->cancel(); - jobPtr->watcher->waitForFinished(); - } - delete jobPtr; - } + // for (auto jobPtr : m_activeJobs) { + // jobPtr->cancelRequested = true; + // if (jobPtr->watcher) { + // jobPtr->watcher->cancel(); + // jobPtr->watcher->waitForFinished(); + // } + // // delete jobPtr; + // } m_activeJobs.clear(); - - // The dialog will be deleted automatically due to parent-child relationship } QUuid ExportManager::startExport(iDescriptorDevice *device, @@ -62,6 +57,8 @@ QUuid ExportManager::startExport(iDescriptorDevice *device, const QString &destinationPath, std::optional altAfc) { + qDebug() << "startExport() entry - items:" << items.size() + << "dest:" << destinationPath; if (!device || !device->mutex) { qWarning() << "Invalid device provided to ExportManager"; return QUuid(); @@ -89,32 +86,30 @@ QUuid ExportManager::startExport(iDescriptorDevice *device, job->items = items; job->destinationPath = destinationPath; job->altAfc = altAfc; - job->watcher = new QFutureWatcher(this); - const QUuid jobId = job->jobId; + // fixme : pass ExportJob + job->statusBalloonProcessId = + StatusBalloon::sharedInstance()->startExportProcess( + QString("Exporting %1 items").arg(items.size()), items.size(), + destinationPath); - connect(job->watcher, &QFutureWatcher::finished, this, - [this, jobId]() { cleanupJob(jobId); }); + // Use ExportManager's own jobId for its internal tracking and signals + const QUuid managerJobId = job->jobId; + + // todo:cleanupJob ? + // connect(job->watcher, &QFutureWatcher::finished, this, + // [this, managerJobId]() { cleanupJob(managerJobId); }); // Store job before starting { QMutexLocker locker(&m_jobsMutex); - m_activeJobs[jobId] = job; + m_activeJobs[managerJobId] = job; } - emit exportStarted(jobId, items.size(), destinationPath); - - // The manager now shows its own dialog - m_exportProgressDialog->showForJob(jobId); - - ExportJob *jobPtr = m_activeJobs[jobId]; - jobPtr->future = - QtConcurrent::run([this, jobPtr]() { executeExportJob(jobPtr); }); - jobPtr->watcher->setFuture(jobPtr->future); - - qDebug() << "Started export job" << jobId << "for" << items.size() + m_exportThread->executeExportJob(job); + qDebug() << "Started export job" << managerJobId << "for" << items.size() << "items"; - return jobId; + return managerJobId; } void ExportManager::cancelExport(const QUuid &jobId) @@ -139,198 +134,7 @@ bool ExportManager::isJobRunning(const QUuid &jobId) const return m_activeJobs.contains(jobId); } -void ExportManager::executeExportJob(ExportJob *job) -{ - ExportJobSummary summary; - summary.jobId = job->jobId; - summary.totalItems = job->items.size(); - summary.destinationPath = job->destinationPath; - - qDebug() << "Executing export job" << job->jobId << "with" - << job->items.size() << "items"; - - for (int i = 0; i < job->items.size(); ++i) { - // Check for cancellation - if (job->cancelRequested.load()) { - summary.wasCancelled = true; - qDebug() << "Export job" << job->jobId << "was cancelled"; - emit exportCancelled(job->jobId); - return; - } - - const ExportItem &item = job->items.at(i); - - emit exportProgress(job->jobId, i + 1, job->items.size(), - item.suggestedFileName); - - ExportResult result = - exportSingleItem(job->device, item, job->destinationPath, - job->altAfc, job->cancelRequested, job->jobId); - - if (result.success) { - summary.successfulItems++; - summary.totalBytesTransferred += result.bytesTransferred; - } else { - summary.failedItems++; - } - - emit itemExported(job->jobId, result); - - // Check for cancellation again after potentially long file operation - if (job->cancelRequested.load()) { - summary.wasCancelled = true; - qDebug() << "Export job" << job->jobId - << "was cancelled during execution"; - emit exportCancelled(job->jobId); - return; - } - } - - qDebug() << "Export job" << job->jobId - << "completed - Success:" << summary.successfulItems - << "Failed:" << summary.failedItems - << "Bytes:" << summary.totalBytesTransferred; - - emit exportFinished(job->jobId, summary); -} -// TODO: implement -ExportResult ExportManager::exportSingleItem( - iDescriptorDevice *device, const ExportItem &item, - const QString &destinationDir, std::optional altAfc, - std::atomic &cancelRequested, const QUuid &jobId) -{ - ExportResult result; - result.sourceFilePath = item.sourcePathOnDevice; - - // // Generate output path - // QString outputPath = - // QDir(destinationDir).filePath(item.suggestedFileName); outputPath = - // generateUniqueOutputPath(outputPath); result.outputFilePath = outputPath; - - // // Get file size first - // char **info = nullptr; - // afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( - // device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); - - // qint64 totalFileSize = 0; - // if (infoResult == AFC_E_SUCCESS && info) { - // for (int i = 0; info[i]; i += 2) { - // if (strcmp(info[i], "st_size") == 0) { - // totalFileSize = QString::fromUtf8(info[i + 1]).toLongLong(); - // break; - // } - // } - // afc_dictionary_free(info); - // } - - // // Open file on device - // uint64_t handle = 0; - // afc_error_t openResult = ServiceManager::safeAfcFileOpen( - // device, item.sourcePathOnDevice.toUtf8().constData(), - // AFC_FOPEN_RDONLY, &handle, altAfc); - - // if (openResult != AFC_E_SUCCESS) { - // result.errorMessage = - // QString("Failed to open file on device: %1 (AFC error: %2)") - // .arg(item.sourcePathOnDevice) - // .arg(static_cast(openResult)); - // return result; - // } - - // // Open local output file - // QFile outputFile(outputPath); - // if (!outputFile.open(QIODevice::WriteOnly)) { - // result.errorMessage = QString("Failed to create local file: %1 (%2)") - // .arg(outputPath) - // .arg(outputFile.errorString()); - // ServiceManager::safeAfcFileClose(device, handle, altAfc); - // return result; - // } - - // char buffer[8192]; - // uint32_t bytesRead = 0; - // qint64 totalBytes = 0; - - // while (true) { - // // Check for cancellation during file copy - // if (cancelRequested.load()) { - // outputFile.close(); - // outputFile.remove(); // Clean up partial file - // ServiceManager::safeAfcFileClose(device, handle, altAfc); - // result.errorMessage = "Export cancelled by user"; - // return result; - // } - - // afc_error_t readResult = ServiceManager::safeAfcFileRead( - // device, handle, buffer, sizeof(buffer), &bytesRead, altAfc); - - // if (readResult != AFC_E_SUCCESS || bytesRead == 0) { - // break; // End of file or error - // } - - // qint64 bytesWritten = outputFile.write(buffer, bytesRead); - // if (bytesWritten != bytesRead) { - // result.errorMessage = - // QString("Write error: only wrote %1 of %2 bytes") - // .arg(bytesWritten) - // .arg(bytesRead); - // outputFile.close(); - // outputFile.remove(); // Clean up partial file - // ServiceManager::safeAfcFileClose(device, handle, altAfc); - // return result; - // } - - // totalBytes += bytesRead; - - // // Emit progress update every 64KB or at end of file - // if (totalBytes % (64 * 1024) == 0 || totalBytes == totalFileSize) { - // emit fileTransferProgress(jobId, item.suggestedFileName, - // totalBytes, - // totalFileSize); - // } - // } - - // // Clean up - // outputFile.close(); - // ServiceManager::safeAfcFileClose(device, handle, altAfc); - - // if (totalBytes == 0) { - // result.errorMessage = "No data read from device file"; - // outputFile.remove(); // Clean up empty file - // return result; - // } - - result.success = true; - // result.bytesTransferred = totalBytes; - return result; -} - -QString ExportManager::generateUniqueOutputPath(const QString &basePath) const -{ - if (!QFile::exists(basePath)) { - return basePath; - } - - QFileInfo fileInfo(basePath); - QString baseName = fileInfo.completeBaseName(); - QString suffix = fileInfo.suffix(); - QString directory = fileInfo.absolutePath(); - - int counter = 1; - QString uniquePath; - - do { - QString newName = QString("%1_%2").arg(baseName).arg(counter); - if (!suffix.isEmpty()) { - newName += "." + suffix; - } - uniquePath = QDir(directory).filePath(newName); - counter++; - } while (QFile::exists(uniquePath) && counter < 10000); - - return uniquePath; -} - +// TODO: is not being used ? QString ExportManager::extractFileName(const QString &devicePath) const { int lastSlash = devicePath.lastIndexOf('/'); @@ -344,13 +148,13 @@ void ExportManager::cleanupJob(const QUuid &jobId) { QMutexLocker locker(&m_jobsMutex); auto it = m_activeJobs.find(jobId); - if (it != m_activeJobs.end()) { - if (it.value()->watcher) { - it.value()->watcher->deleteLater(); - } + // if (it != m_activeJobs.end()) { + // if (it.value()->watcher) { + // it.value()->watcher->deleteLater(); + // } - delete it.value(); - m_activeJobs.erase(it); - qDebug() << "Cleaned up export job" << jobId; - } -} + // // delete it.value(); + // m_activeJobs.erase(it); + // qDebug() << "Cleaned up export job" << jobId; + // } +} \ No newline at end of file diff --git a/src/exportmanager.h b/src/exportmanager.h index 7594a18..4926435 100644 --- a/src/exportmanager.h +++ b/src/exportmanager.h @@ -20,6 +20,7 @@ #ifndef EXPORTMANAGER_H #define EXPORTMANAGER_H +#include "exportmanagerthread.h" #include "iDescriptor.h" #include #include @@ -35,35 +36,6 @@ // Forward declaration class ExportProgressDialog; -struct ExportItem { - QString sourcePathOnDevice; - QString suggestedFileName; - - ExportItem() = default; - ExportItem(const QString &sourcePath, const QString &fileName) - : sourcePathOnDevice(sourcePath), suggestedFileName(fileName) - { - } -}; - -struct ExportResult { - QString sourceFilePath; - QString outputFilePath; - bool success = false; - QString errorMessage; - qint64 bytesTransferred = 0; -}; - -struct ExportJobSummary { - QUuid jobId; - int totalItems = 0; - int successfulItems = 0; - int failedItems = 0; - qint64 totalBytesTransferred = 0; - QString destinationPath; - bool wasCancelled = false; -}; - class ExportManager : public QObject { Q_OBJECT @@ -85,7 +57,10 @@ public: bool isExporting() const; bool isJobRunning(const QUuid &jobId) const; + static QString generateUniqueOutputPath(const QString &basePath); + // todo: should we delete this in ~ExportManager? + ExportManagerThread *m_exportThread = new ExportManagerThread(this); signals: void exportStarted(const QUuid &jobId, int totalItems, @@ -108,28 +83,8 @@ private: explicit ExportManager(QObject *parent = nullptr); ~ExportManager(); - struct ExportJob { - QUuid jobId; - iDescriptorDevice *device = nullptr; - QList items; - QString destinationPath; - std::optional altAfc; - std::atomic cancelRequested{false}; - QFuture future; - QFutureWatcher *watcher = nullptr; - }; - void executeExportJob(ExportJob *job); - ExportResult exportSingleItem(iDescriptorDevice *device, - const ExportItem &item, - const QString &destinationDir, - std::optional altAfc, - std::atomic &cancelRequested, - const QUuid &jobId); - - QString generateUniqueOutputPath(const QString &basePath) const; - QString extractFileName(const QString &devicePath) const; void cleanupJob(const QUuid &jobId); diff --git a/src/exportmanagerthread.cpp b/src/exportmanagerthread.cpp new file mode 100644 index 0000000..278f709 --- /dev/null +++ b/src/exportmanagerthread.cpp @@ -0,0 +1,166 @@ + +#include "exportmanagerthread.h" +#include "iDescriptor.h" +#include "servicemanager.h" +#include +#include +#include +#include + +// TODO: unfinished +void ExportManagerThread::executeExportJob(ExportJob *job) +{ + // FIXME: limit to 1 at a time + QtConcurrent::run([this, job]() { executeExportJobInternal(job); }); +} + +void ExportManagerThread::executeExportJobInternal(ExportJob *job) +{ + qDebug() << "Worker thread started for export job" << job->jobId; + ExportJobSummary summary; + summary.jobId = job->jobId; + summary.totalItems = job->items.size(); + summary.destinationPath = job->destinationPath; + + qDebug() << "Executing export job" << job->jobId << "with" + << job->items.size() << "items"; + + for (int i = 0; i < job->items.size(); ++i) { + // todo:Check for cancellation + // if (job->cancelRequested.load() || + // balloon->isCancelRequested( + // job->statusBalloonProcessId)) { // Use + // // statusBalloonProcessId + // summary.wasCancelled = true; + // qDebug() << "Export job" << job->jobId << "was cancelled"; + + // emit exportCancelled(job->jobId); + // return; + // } + + const ExportItem &item = job->items.at(i); + + // emit exportProgress(job->jobId, i + 1, job->items.size(), + // item.suggestedFileName); + + ExportResult result = exportSingleItem( + job->device, item, job->destinationPath, job->altAfc, + job->cancelRequested, job->statusBalloonProcessId); + if (result.success) { + summary.successfulItems++; + summary.totalBytesTransferred += result.bytesTransferred; + } else { + summary.failedItems++; + } + + emit itemExported(job->jobId, result); + + // // Check for cancellation again after potentially long file + // // operation + // if (job->cancelRequested.load() || + // balloon->isCancelRequested( + // job->statusBalloonProcessId)) { // Use + // // statusBalloonProcessId + // summary.wasCancelled = true; + // qDebug() << "Export job" << job->jobId + // << "was cancelled during execution"; + + // QMetaObject::invokeMethod( + // QCoreApplication::instance(), + // [balloon,2 + // id = + // job->statusBalloonProcessId]() { // Use + // // + // statusBalloonProcessId + // balloon->markProcessCancelled(id); + // }, + // Qt::QueuedConnection); + + // emit exportCancelled(job->jobId); + // return; + // } + } + + qDebug() << "Export job" << job->jobId + << "completed - Success:" << summary.successfulItems + << "Failed:" << summary.failedItems + << "Bytes:" << summary.totalBytesTransferred; + + emit exportFinished(job->jobId, summary); +} + +ExportResult ExportManagerThread::exportSingleItem( + iDescriptorDevice *device, const ExportItem &item, + const QString &destinationDir, std::optional altAfc, + std::atomic &cancelRequested, + const QUuid &statusBalloonProcessId) // Change parameter name and type +{ + ExportResult result; + result.sourceFilePath = item.sourcePathOnDevice; + + // Generate output path + QString outputPath = QDir(destinationDir).filePath(item.suggestedFileName); + // todo problem + outputPath = generateUniqueOutputPath(outputPath); + result.outputFilePath = outputPath; + + // Progress callback + const QString ¤tFile = item.suggestedFileName; + int fileIndex = item.itemIndex; + auto progressCallback = + [this, statusBalloonProcessId, fileIndex, + currentFile](qint64 transferred, // Use statusBalloonProcessId + qint64 total) { + qDebug() << "Export progress callback for" << fileIndex + << "- transferred:" << transferred << "total:" << total; + emit fileTransferProgress(statusBalloonProcessId, fileIndex, + currentFile, transferred, total); + }; + + qDebug() << "About to export file from device:" << item.sourcePathOnDevice + << "to" << outputPath; + // Export file using ServiceManager + IdeviceFfiError *err = ServiceManager::exportFileToPath( + device, item.sourcePathOnDevice.toUtf8().constData(), + outputPath.toUtf8().constData(), progressCallback, &cancelRequested); + + if (err != nullptr) { + result.errorMessage = + QString("Failed to export file: %1").arg(err->message); + qDebug() << result.errorMessage; + idevice_error_free(err); + return result; + } + + // Get file size for statistics + QFileInfo fileInfo(outputPath); + result.bytesTransferred = fileInfo.size(); + result.success = true; + + return result; +} +QString ExportManagerThread::generateUniqueOutputPath(const QString &basePath) +{ + if (!QFile::exists(basePath)) { + return basePath; + } + + QFileInfo fileInfo(basePath); + QString baseName = fileInfo.completeBaseName(); + QString suffix = fileInfo.suffix(); + QString directory = fileInfo.absolutePath(); + + int counter = 1; + QString uniquePath; + + do { + QString newName = QString("%1_%2").arg(baseName).arg(counter); + if (!suffix.isEmpty()) { + newName += "." + suffix; + } + uniquePath = QDir(directory).filePath(newName); + counter++; + } while (QFile::exists(uniquePath) && counter < 10000); + + return uniquePath; +} \ No newline at end of file diff --git a/src/exportmanagerthread.h b/src/exportmanagerthread.h new file mode 100644 index 0000000..4e3c5b4 --- /dev/null +++ b/src/exportmanagerthread.h @@ -0,0 +1,40 @@ +#ifndef EXPORTMANAGERTHREAD_H +#define EXPORTMANAGERTHREAD_H +#include "iDescriptor.h" +#include "servicemanager.h" +#include +#include +#include + +class ExportManager; + +using namespace IdeviceFFI; + +class ExportManagerThread : public QObject +{ + Q_OBJECT +public: + ExportManagerThread(QObject *parent = nullptr) : QObject(parent) {} + + void executeExportJob(ExportJob *job); + ExportResult exportSingleItem(iDescriptorDevice *device, + const ExportItem &item, + const QString &destinationDir, + std::optional altAfc, + std::atomic &cancelRequested, + const QUuid &statusBalloonProcessId); + +private: + void executeExportJobInternal(ExportJob *job); + QString generateUniqueOutputPath(const QString &basePath); +signals: + void exportProgress(const QUuid &jobId, int currentItem, int totalItems, + const QString ¤tFileName); + void fileTransferProgress(const QUuid &jobId, int fileIndex, + const QString ¤tFile, + qint64 bytesTransferred, qint64 totalFileSize); + void itemExported(const QUuid &jobId, const ExportResult &result); + void exportFinished(const QUuid &jobId, const ExportJobSummary &summary); + void exportCancelled(const QUuid &jobId); +}; +#endif // EXPORTMANAGERTHREAD_H diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index f0759ee..c855088 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -18,7 +18,7 @@ */ #include "gallerywidget.h" -// #include "exportmanager.h" +#include "exportmanager.h" #include "iDescriptor.h" #include "mediapreviewdialog.h" #include "photomodel.h" @@ -42,6 +42,7 @@ #include #include +// todo: dont load paths on main thread, handle /* FIXME: this needs to be refactored once we figure out how to query Photos.sqlite @@ -85,7 +86,7 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) m_retryButton = new QPushButton("Retry", this); connect(m_retryButton, &QPushButton::clicked, this, [this]() { m_stackedWidget->setCurrentWidget(m_loadingWidget); - QTimer::singleShot(100, this, &GalleryWidget::loadAlbumList); + QTimer::singleShot(100, this, &GalleryWidget::reload); }); errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); m_errorWidget = new QWidget(); @@ -95,6 +96,13 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) m_stackedWidget->setCurrentWidget(m_loadingWidget); setControlsEnabled(false); // Disable controls until album is selected } + +void GalleryWidget::reload() +{ + m_loaded = false; + load(); +} + /*Load is called when the tab is active*/ void GalleryWidget::load() { @@ -102,8 +110,16 @@ void GalleryWidget::load() return; m_loaded = true; + qDebug() << "Before reading DCIM directory"; - loadAlbumList(); + auto *watcher = new QFutureWatcher(this); + auto future = ServiceManager::getFileTreeAsync(m_device, "/DCIM", true); + watcher->setFuture(future); + + connect(watcher, &QFutureWatcher::finished, [this, watcher]() { + watcher->deleteLater(); + loadAlbumList(watcher->result()); + }); } void GalleryWidget::setupControlsLayout() @@ -112,6 +128,8 @@ void GalleryWidget::setupControlsLayout() m_controlsLayout->setSpacing(5); m_controlsLayout->setContentsMargins(7, 7, 7, 7); + m_importButton = new QPushButton("Import"); + // Sort order combo box QLabel *sortLabel = new QLabel("Sort:"); sortLabel->setStyleSheet("font-weight: bold;"); @@ -128,13 +146,13 @@ void GalleryWidget::setupControlsLayout() QLabel *filterLabel = new QLabel("Filter:"); filterLabel->setStyleSheet("font-weight: bold;"); m_filterComboBox = new QComboBox(); - m_filterComboBox->addItem("All Media", - static_cast(PhotoModel::ImagesOnly)); + m_filterComboBox->addItem("All Media", static_cast(PhotoModel::All)); m_filterComboBox->addItem("Images Only", static_cast(PhotoModel::ImagesOnly)); m_filterComboBox->addItem("Videos Only", static_cast(PhotoModel::VideosOnly)); - m_filterComboBox->setCurrentIndex(2); // Default to All + m_filterComboBox->setCurrentIndex( + static_cast(PhotoModel::All)); // Default to All m_filterComboBox->setMinimumWidth(100); // Ensure text fits m_filterComboBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -164,6 +182,7 @@ void GalleryWidget::setupControlsLayout() // Add widgets to layout m_controlsLayout->addWidget(m_backButton); + m_controlsLayout->addWidget(m_importButton); m_controlsLayout->addWidget(sortLabel); m_controlsLayout->addWidget(m_sortComboBox); m_controlsLayout->addWidget(filterLabel); @@ -222,39 +241,42 @@ void GalleryWidget::onExportSelected() return; } - // if (ExportManager::sharedInstance()->isExporting()) { - // QMessageBox::information(this, "Export in Progress", - // "An export is already in progress."); - // return; - // } + if (ExportManager::sharedInstance()->isExporting()) { + QMessageBox::information(this, "Export in Progress", + "An export is already in progress."); + return; + } - // QModelIndexList selectedIndexes = - // m_listView->selectionModel()->selectedIndexes(); - // QStringList filePaths = m_model->getSelectedFilePaths(selectedIndexes); + QModelIndexList selectedIndexes = + m_listView->selectionModel()->selectedIndexes(); + QStringList filePaths = m_model->getSelectedFilePaths(selectedIndexes); - // if (filePaths.isEmpty()) { - // QMessageBox::information(this, "No Items", - // "No valid items selected for export."); - // return; - // } + if (filePaths.isEmpty()) { + QMessageBox::information(this, "No Items", + "No valid items selected for export."); + return; + } - // QString exportDir = selectExportDirectory(); - // if (exportDir.isEmpty()) { - // return; - // } + QString exportDir = selectExportDirectory(); + if (exportDir.isEmpty()) { + return; + } - // // Convert QStringList to QList - // QList exportItems; - // for (const QString &filePath : filePaths) { - // QString fileName = filePath.split('/').last(); - // exportItems.append(ExportItem(filePath, fileName)); - // } + // Convert QStringList to QList + QList exportItems; + // FIXME: index + int index = 0; + for (const QString &filePath : filePaths) { + QString fileName = filePath.split('/').last(); + exportItems.append(ExportItem(filePath, fileName, index)); + ++index; + } - // qDebug() << "Starting export of selected files:" << exportItems.size() - // << "items to" << exportDir; + qDebug() << "Starting export of selected files:" << exportItems.size() + << "items to" << exportDir; - // ExportManager::sharedInstance()->startExport(m_device, exportItems, - // exportDir); + ExportManager::sharedInstance()->startExport(m_device, exportItems, + exportDir); } void GalleryWidget::onExportAll() @@ -419,12 +441,8 @@ void GalleryWidget::setupPhotoGalleryView() &GalleryWidget::onPhotoContextMenu); } -void GalleryWidget::loadAlbumList() +void GalleryWidget::loadAlbumList(const AFCFileTree &dcimTree) { - qDebug() << "Before reading DCIM directory"; - AFCFileTree dcimTree = - ServiceManager::safeGetFileTree(m_device, "/DCIM", true); - if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; m_stackedWidget->setCurrentWidget(m_errorWidget); @@ -433,8 +451,6 @@ void GalleryWidget::loadAlbumList() return; } - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); - qDebug() << "DCIM directory read successfully, found" << dcimTree.entries.size() << "entries"; @@ -464,6 +480,7 @@ void GalleryWidget::loadAlbumList() } m_albumListView->setModel(albumModel); + m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); } void GalleryWidget::onAlbumSelected(const QString &albumPath) @@ -484,6 +501,8 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) }); } + connect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, + &PhotoModel::requestThumbnail, Qt::QueuedConnection); // Set album path and load photos m_model->setAlbumPath(albumPath); @@ -497,9 +516,16 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) void GalleryWidget::onBackToAlbums() { + if (m_model) { + disconnect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, + &PhotoModel::requestThumbnail); + } + // Switch back to album selection view m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); - m_model->clear(); + if (m_model) { + m_model->clear(); + } // Disable controls and hide back button setControlsEnabled(false); @@ -566,6 +592,7 @@ QIcon GalleryWidget::loadAlbumThumbnail(const QString &albumPath) if (firstImagePath.endsWith(".HEIC", Qt::CaseInsensitive)) { qDebug() << "Loading HEIC thumbnail from:" << firstImagePath; + // FIXME: move to servicemanager thumbnail = load_heic(imageData); } else { // Load regular image formats diff --git a/src/gallerywidget.h b/src/gallerywidget.h index d8073e0..d1976ee 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -58,10 +58,11 @@ private slots: void onBackToAlbums(); private: + void reload(); void setupControlsLayout(); void setupAlbumSelectionView(); void setupPhotoGalleryView(); - void loadAlbumList(); + void loadAlbumList(const AFCFileTree &dcimTree); void setControlsEnabled(bool enabled); QString selectExportDirectory(); QIcon loadAlbumThumbnail(const QString &albumPath); @@ -80,7 +81,7 @@ private: ZLoadingWidget *m_loadingWidget; QWidget *m_errorWidget; QPushButton *m_retryButton; - + QPushButton *m_importButton; // Album selection view QWidget *m_albumSelectionWidget; QListView *m_albumListView; diff --git a/src/heartbeat.h b/src/heartbeat.h index 7026d4b..5874a76 100644 --- a/src/heartbeat.h +++ b/src/heartbeat.h @@ -1,26 +1,19 @@ #ifndef HEARTBEATTHREAD_H #define HEARTBEATTHREAD_H +#include "iDescriptor.h" #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include using namespace IdeviceFFI; -class HeartBeatThread : public QThread +class HeartbeatThread : public QThread { Q_OBJECT public: - HeartBeatThread(HeartbeatClientHandle *heartbeat, QObject *parent = nullptr) - : QThread(parent), m_hb(Heartbeat::adopt(heartbeat)) + HeartbeatThread(HeartbeatClientHandle *heartbeat, QString macAddress, + QObject *parent = nullptr) + : QThread(parent), m_hb(Heartbeat::adopt(heartbeat)), + m_macAddress(macAddress) { } @@ -28,7 +21,6 @@ public: { qDebug() << "Heartbeat thread started"; try { - // Start with initial interval (15 seconds as per the tool example) u_int64_t interval = 15; while (!isInterruptionRequested()) { @@ -38,7 +30,19 @@ public: qDebug() << "Failed to get marco:" << QString::fromStdString(result.unwrap_err().message); - break; + m_tries++; + emit heartbeatFailed(m_macAddress, m_tries); + if (m_tries >= HEARTBEAT_RETRY_LIMIT) { + qDebug() + << "Maximum heartbeat retries reached, exiting for " + "device" + << m_macAddress; + emit heartbeatThreadExited(m_macAddress); + break; + } + // If get_marco failed, skip the rest of this iteration + // and try again with the current interval. + continue; } // 2. Get the new interval from device @@ -51,15 +55,31 @@ public: qDebug() << "Failed to send polo:" << QString::fromStdString( polo_result.unwrap_err().message); - break; + m_tries++; + emit heartbeatFailed(m_macAddress, m_tries); + if (m_tries >= HEARTBEAT_RETRY_LIMIT) { + qDebug() << "Maximum heartbeat retries reached, " + "exiting for " + "device" + << m_macAddress; + emit heartbeatThreadExited(m_macAddress); + break; + } + // If send_polo failed, skip the rest of this iteration + // and try again with the current interval. + continue; } - qDebug() << "Sent polo successfully"; - interval += 5; - m_initialCompleted = true; + // If both marco and polo succeeded: + qDebug() << "Sent polo successfully"; + interval += 5; // Increment interval for the next cycle + m_initialCompleted = true; // Mark as initially completed after + // first successful full cycle } } catch (const std::exception &e) { qDebug() << "Heartbeat error:" << e.what(); + + emit heartbeatThreadExited(m_macAddress); } } @@ -68,5 +88,11 @@ public: private: Heartbeat m_hb; bool m_initialCompleted = false; + QString m_macAddress; + unsigned int m_tries = 0; + +signals: + void heartbeatFailed(const QString &macAddress, unsigned int tries = 0); + void heartbeatThreadExited(const QString &macAddress); }; #endif // HEARTBEATTHREAD_H \ No newline at end of file diff --git a/src/iDescriptor.h b/src/iDescriptor.h index 9409e02..be62981 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,16 @@ #define NotFoundErrorCode -14 #define DISK_IMAGE_TYPE_DEVELOPER "Developer" +#define HEARTBEAT_RETRY_LIMIT 2 + +#ifdef __linux__ +#define LOCKDOWN_PATH "/var/lib/lockdown" +#elif __APPLE__ +#define LOCKDOWN_PATH "/var/db/lockdown" +#else +#define LOCKDOWN_PATH "" +#endif + struct BatteryInfo { QString health; uint64_t cycleCount; @@ -412,12 +423,12 @@ void get_device_info_xml(const char *udid, LockdowndClientHandle *client, pugi::xml_document &infoXml); struct WirelessInitArgs { - QString ip; - const IdevicePairingFile *pairing_file; + const QString ip; + const QString pairing_file; }; iDescriptorInitDeviceResult init_idescriptor_device(const QString &udid, - WirelessInitArgs wirelessArgs = {nullptr, nullptr}); + const WirelessInitArgs &wirelessArgs = {"", ""}); // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // iDescriptorInitDeviceResultRecovery @@ -669,4 +680,45 @@ inline int read_file(const char *filename, uint8_t **data, size_t *length) fclose(file); return 1; -} \ No newline at end of file +} + +struct ExportItem { + QString sourcePathOnDevice; + QString suggestedFileName; + int itemIndex = -1; + + ExportItem() = default; + ExportItem(const QString &sourcePath, const QString &fileName, int index) + : sourcePathOnDevice(sourcePath), suggestedFileName(fileName), + itemIndex(index) + { + } +}; + +struct ExportResult { + QString sourceFilePath; + QString outputFilePath; + bool success = false; + QString errorMessage; + qint64 bytesTransferred = 0; +}; + +struct ExportJobSummary { + QUuid jobId; + int totalItems = 0; + int successfulItems = 0; + int failedItems = 0; + qint64 totalBytesTransferred = 0; + QString destinationPath; + bool wasCancelled = false; +}; + +struct ExportJob { + QUuid jobId; + iDescriptorDevice *device = nullptr; + QList items; + QString destinationPath; + std::optional altAfc; + std::atomic cancelRequested{false}; + QUuid statusBalloonProcessId; +}; \ No newline at end of file diff --git a/src/livescreenwidget.cpp b/src/livescreenwidget.cpp index 16870c4..30f6aee 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -228,6 +228,7 @@ void LiveScreenWidget::updateScreenshot() // return; // } qDebug() << "Updating screenshot..."; + // FIXME: move to services try { // TakeScreenshotResult result = take_screenshot(m_shotrClient); ScreenshotData screenshot; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7c876a7..f2608a0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -44,8 +44,10 @@ #include "appcontext.h" #include "settingsmanager.h" // #include "devicemonitor.h" +#include "Toast.h" #include "networkdevicemanager.h" #include "networkdeviceswidget.h" +#include "statusballoon.h" #include #include #include @@ -205,6 +207,12 @@ MainWindow::MainWindow(QWidget *parent) "QLabel:hover { background-color : #13131319; }"); ui->statusbar->addWidget(m_connectedDeviceCountLabel); + // TODO: implement downloads/uploads progress stuff + + StatusBalloon *statusBalloon = StatusBalloon::sharedInstance(); + + ui->statusbar->addWidget(statusBalloon->getButton()); + ui->statusbar->setContentsMargins(0, 0, 0, 0); QLabel *appVersionLabel = new QLabel(QString("v%1").arg(APP_VERSION)); appVersionLabel->setContentsMargins(5, 0, 5, 0); @@ -410,33 +418,16 @@ MainWindow::MainWindow(QWidget *parent) connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [](const std::string &udid, const std::string &wifiMacAddress) { - const IdevicePairingFile *pairingFile = - AppContext::sharedInstance()->getCachedPairingFile( - QString::fromStdString(udid)); - - if (pairingFile) { - // qDebug() << "Device removed, pairing file for UDID" - // << QString::fromStdString(udid) << "MAC" - // << QString::fromStdString(wifiMacAddress) - // << "exists in cache."; - // try to upgrade device to wireless if possible - qDebug() - << "Upgrading device to wireless connection for UDID" - << QString::fromStdString(udid); - QMetaObject::invokeMethod( - AppContext::sharedInstance(), "addDevice", - Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(udid)), - Q_ARG(DeviceMonitorThread::IdeviceConnectionType, - DeviceMonitorThread::CONNECTION_NETWORK), - Q_ARG(AddType, AddType::UpgradeToWireless), - Q_ARG(QString, QString::fromStdString(wifiMacAddress))); - - } else { - qDebug() - << "Device removed, no cached pairing file for UDID" - << QString::fromStdString(udid); - } + qDebug() << "Upgrading device to wireless connection for UDID" + << QString::fromStdString(udid); + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", + Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(udid)), + Q_ARG(DeviceMonitorThread::IdeviceConnectionType, + DeviceMonitorThread::CONNECTION_NETWORK), + Q_ARG(AddType, AddType::UpgradeToWireless), + Q_ARG(QString, QString::fromStdString(wifiMacAddress))); }); connect(NetworkDeviceManager::sharedInstance(), @@ -462,18 +453,6 @@ MainWindow::MainWindow(QWidget *parent) << device.macAddress; return; } - - const IdevicePairingFile *pairingFile = - AppContext::sharedInstance()->getCachedPairingFile( - device.macAddress); - - if (!pairingFile) { - qDebug() << "No cached pairing file for network device MAC:" - << device.macAddress - << "Cannot add as wireless device."; - return; - } - qDebug() << "Trying to add network device with MAC:" << device.macAddress; @@ -488,6 +467,21 @@ MainWindow::MainWindow(QWidget *parent) // Handle network device addition if needed }); + connect(AppContext::sharedInstance(), &AppContext::deviceHeartbeatFailed, + this, [this](const QString &macAddress, int tries) { + Toast *toast = new Toast(this); + toast->setAttribute(Qt::WA_DeleteOnClose); + toast->setDuration(8000); // Hide after 8 seconds + toast->setTitle("Heartbeat failed"); + toast->setText( + QString("Heartbeat failed for device with MAC %1. " + "Number of failed attempts: %2") + .arg(macAddress) + .arg(tries)); + toast->setPosition(ToastPosition::BOTTOM_MIDDLE); + toast->show(); + }); + // NetworkDevicesWidget *m_networkDevicesWidget = new // NetworkDevicesWidget(); // m_networkDevicesWidget->setAttribute(Qt::WA_DeleteOnClose); diff --git a/src/photomodel.cpp b/src/photomodel.cpp index 74dfed4..f5304f9 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -40,9 +40,8 @@ extern "C" { #include #include } +// todo implement std::priority_queue with thread pool -// Limit concurrent video thumbnail generation to 2 to prevent resource -// exhaustion QSemaphore PhotoModel::m_videoThumbnailSemaphore(4); PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType, @@ -59,17 +58,27 @@ PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType, void PhotoModel::clear() { + blockSignals(true); + // Clean up any active watchers for (auto *watcher : m_activeLoaders.values()) { if (watcher) { + watcher->disconnect(); watcher->cancel(); - watcher->waitForFinished(); + // watcher->waitForFinished(); watcher->deleteLater(); } } m_activeLoaders.clear(); m_loadingPaths.clear(); m_thumbnailCache.clear(); + + beginResetModel(); + m_photos.clear(); + m_allPhotos.clear(); + endResetModel(); + + blockSignals(false); } PhotoModel::~PhotoModel() @@ -448,18 +457,15 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const return info.filePath; case Qt::DecorationRole: { - qDebug() << "DecorationRole requested for index:" << index.row(); // Check memory cache first if (QPixmap *cached = m_thumbnailCache.object(info.filePath)) { - qDebug() << "Cache HIT for:" << info.fileName; return QIcon(*cached); } // Prevent duplicate requests if (m_loadingPaths.contains(info.filePath) || m_activeLoaders.contains(info.filePath)) { - qDebug() << "Already loading:" << info.fileName; // Return appropriate placeholder based on file type if (info.fileName.endsWith(".MOV", Qt::CaseInsensitive) || info.fileName.endsWith(".MP4", Qt::CaseInsensitive) || @@ -473,7 +479,6 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const // Start async loading for both images and videos if (!m_loadingPaths.contains(info.filePath)) { - qDebug() << "Starting load for:" << info.fileName; emit const_cast(this)->thumbnailNeedsToBeLoaded( index.row()); } @@ -516,7 +521,6 @@ void PhotoModel::requestThumbnail(int index) connect(watcher, &QFutureWatcher::finished, this, [this, watcher, filePath = info.filePath]() { - qDebug() << "Thumbnail load finished for:" << filePath; QPixmap thumbnail = watcher->result(); m_loadingPaths.remove(filePath); @@ -549,16 +553,12 @@ void PhotoModel::requestThumbnail(int index) if (isVideo) { future = QtConcurrent::run([this, info]() { // Acquire semaphore FIRST to limit concurrent video processing - qDebug() << "Waiting for semaphore for:" << info.fileName; m_videoThumbnailSemaphore.acquire(); - qDebug() << "Acquired semaphore for:" << info.fileName; // Generate video thumbnail using FFmpeg directly (no QMediaPlayer) QPixmap thumbnail = generateVideoThumbnailFFmpeg( m_device, info.filePath, m_thumbnailSize); - // Release semaphore - qDebug() << "Releasing semaphore for:" << info.fileName; m_videoThumbnailSemaphore.release(); return thumbnail; }); @@ -587,7 +587,6 @@ QPixmap PhotoModel::loadThumbnailFromDevice(iDescriptorDevice *device, } if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { - qDebug() << "Loading HEIC image from data for:" << filePath; QPixmap img = load_heic(imageData); return img.isNull() ? QPixmap() : img.scaled(size, Qt::KeepAspectRatio, @@ -872,13 +871,11 @@ PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const void PhotoModel::setAlbumPath(const QString &albumPath) { - if (m_albumPath != albumPath) { - qDebug() << "Setting new album path:" << albumPath; - clear(); + qDebug() << "Setting new album path:" << albumPath; + clear(); - m_albumPath = albumPath; - populatePhotoPaths(); - } + m_albumPath = albumPath; + populatePhotoPaths(); } void PhotoModel::refreshPhotos() { populatePhotoPaths(); } \ No newline at end of file diff --git a/src/photomodel.h b/src/photomodel.h index bc9866f..015501e 100644 --- a/src/photomodel.h +++ b/src/photomodel.h @@ -90,7 +90,7 @@ signals: void thumbnailNeedsToBeLoaded(int index); void exportRequested(const QStringList &filePaths); -private slots: +public slots: void requestThumbnail(int index); private: diff --git a/src/qballoontip.cpp b/src/qballoontip.cpp index 815afcb..1ec676a 100644 --- a/src/qballoontip.cpp +++ b/src/qballoontip.cpp @@ -36,7 +36,7 @@ void QBalloonTip::hideBalloon() if (!theSolitaryBalloonTip) return; theSolitaryBalloonTip->hide(); - delete theSolitaryBalloonTip; + // delete theSolitaryBalloonTip; theSolitaryBalloonTip = nullptr; } @@ -52,12 +52,17 @@ bool QBalloonTip::isBalloonVisible() { return theSolitaryBalloonTip; } QBalloonTip::QBalloonTip(const QIcon &icon, const QString &title, const QString &message, QWidget *widget) - : QWidget(nullptr, Qt::ToolTip), widget(widget), showArrow(true) + : QWidget(widget ? widget->window() : QApplication::activeWindow(), + Qt::ToolTip), + widget(widget), showArrow(true) { - setAttribute(Qt::WA_DeleteOnClose); + // setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_TranslucentBackground); if (widget) { connect(widget, &QWidget::destroyed, this, &QBalloonTip::close); + } else if (QApplication::activeWindow()) { + connect(QApplication::activeWindow(), &QWidget::destroyed, this, + &QBalloonTip::close); } // Add drop shadow effect @@ -68,49 +73,50 @@ QBalloonTip::QBalloonTip(const QIcon &icon, const QString &title, shadowEffect->setOffset(0, 5); setGraphicsEffect(shadowEffect); - QLabel *titleLabel = new QLabel; - titleLabel->installEventFilter(this); - titleLabel->setText(title); - QFont f = titleLabel->font(); - f.setBold(true); - titleLabel->setFont(f); - titleLabel->setTextFormat(Qt::PlainText); // to maintain compat with windows + // QLabel *titleLabel = new QLabel; + // titleLabel->installEventFilter(this); + // titleLabel->setText(title); + // QFont f = titleLabel->font(); + // f.setBold(true); + // titleLabel->setFont(f); + // titleLabel->setTextFormat(Qt::PlainText); // to maintain compat with + // windows - const int iconSize = 18; - const int closeButtonSize = 15; + // const int iconSize = 18; + // const int closeButtonSize = 15; - QPushButton *closeButton = new QPushButton; - closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); - closeButton->setIconSize(QSize(closeButtonSize, closeButtonSize)); - closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - closeButton->setFixedSize(closeButtonSize, closeButtonSize); - QObject::connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); + // QPushButton *closeButton = new QPushButton; + // closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + // closeButton->setIconSize(QSize(closeButtonSize, closeButtonSize)); + // closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + // closeButton->setFixedSize(closeButtonSize, closeButtonSize); + // QObject::connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); - QLabel *msgLabel = new QLabel; - msgLabel->installEventFilter(this); - msgLabel->setText(message); - msgLabel->setTextFormat(Qt::PlainText); - msgLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + // QLabel *msgLabel = new QLabel; + // msgLabel->installEventFilter(this); + // msgLabel->setText(message); + // msgLabel->setTextFormat(Qt::PlainText); + // msgLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); - QGridLayout *layout = new QGridLayout; - if (!icon.isNull()) { - QLabel *iconLabel = new QLabel; - iconLabel->setPixmap( - icon.pixmap(QSize(iconSize, iconSize), devicePixelRatio())); - iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - iconLabel->setMargin(2); - layout->addWidget(iconLabel, 0, 0); - layout->addWidget(titleLabel, 0, 1); - } else { - layout->addWidget(titleLabel, 0, 0, 1, 2); - } + // QGridLayout *layout = new QGridLayout; + // if (!icon.isNull()) { + // QLabel *iconLabel = new QLabel; + // iconLabel->setPixmap( + // icon.pixmap(QSize(iconSize, iconSize), devicePixelRatio())); + // iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + // iconLabel->setMargin(2); + // layout->addWidget(iconLabel, 0, 0); + // layout->addWidget(titleLabel, 0, 1); + // } else { + // layout->addWidget(titleLabel, 0, 0, 1, 2); + // } - layout->addWidget(closeButton, 0, 2); + // layout->addWidget(closeButton, 0, 2); - layout->addWidget(msgLabel, 1, 0, 1, 3); - layout->setSizeConstraint(QLayout::SetFixedSize); - layout->setContentsMargins(3, 3, 3, 3); - setLayout(layout); + // layout->addWidget(msgLabel, 1, 0, 1, 3); + // layout->setSizeConstraint(QLayout::SetFixedSize); + // layout->setContentsMargins(3, 3, 3, 3); + // setLayout(layout); } QBalloonTip::~QBalloonTip() { theSolitaryBalloonTip = nullptr; } diff --git a/src/qballoontip.h b/src/qballoontip.h index 2c53374..ea932e7 100644 --- a/src/qballoontip.h +++ b/src/qballoontip.h @@ -10,24 +10,21 @@ class QBalloonTip : public QWidget { Q_OBJECT public: - static void showBalloon(const QIcon &icon, const QString &title, - const QString &msg, QWidget *widget, - const QPoint &pos, int timeout, - bool showArrow = true); - static void hideBalloon(); - static bool isBalloonVisible(); - static void updateBalloonPosition(const QPoint &pos); - -private: - QBalloonTip(const QIcon &icon, const QString &title, const QString &msg, - QWidget *widget); - ~QBalloonTip(); + explicit QBalloonTip(const QIcon &icon, const QString &title, + const QString &msg, QWidget *widget); + void hideBalloon(); + bool isBalloonVisible(); + void updateBalloonPosition(const QPoint &pos); + void showBalloon(const QIcon &icon, const QString &title, + const QString &msg, QWidget *widget, const QPoint &pos, + int timeout, bool showArrow = true); void balloon(const QPoint &, int, bool); signals: void messageClicked(); protected: + ~QBalloonTip(); void paintEvent(QPaintEvent *) override; void resizeEvent(QResizeEvent *) override; void mousePressEvent(QMouseEvent *e) override; diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index f85d454..f814586 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -19,6 +19,7 @@ #include "servicemanager.h" #include "iDescriptor.h" +#include IdeviceFfiError * ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device, @@ -139,6 +140,15 @@ AFCFileTree ServiceManager::safeGetFileTree(const iDescriptorDevice *device, }); } +QFuture +ServiceManager::getFileTreeAsync(const iDescriptorDevice *device, + const std::string &path, bool checkDir) +{ + return QtConcurrent::run([device, path, checkDir]() { + return get_file_tree(device, checkDir, path); + }); +} + MountedImageInfo ServiceManager::getMountedImage(const iDescriptorDevice *device) { @@ -194,4 +204,119 @@ bool ServiceManager::enableWirelessConnections(const iDescriptorDevice *device) plist_free(value); return success; }); -} \ No newline at end of file +} + +IdeviceFfiError *ServiceManager::exportFileToPath( + const iDescriptorDevice *device, const char *device_path, + const char *local_path, + std::function progressCallback, + std::atomic *cancelRequested) +{ + qDebug() + << "[serviceManager::exportFileToPath] Exporting file from device path:" + << device_path << "to local path:" << local_path; + return executeOperation( + device, + [device, device_path, local_path, progressCallback, + cancelRequested]() -> IdeviceFfiError * { + AfcFileHandle *afcHandle = nullptr; + qDebug() << "Opening file on device:" << device_path; + IdeviceFfiError *err_open = safeAfcFileOpen( + device, device_path, AfcFopenMode::AfcRdOnly, &afcHandle); + + if (err_open != nullptr) { + qDebug() << "Failed to open file on device:" << device_path + << "Error Code:" << err_open->code + << "Message:" << err_open->message; + return err_open; + } + qDebug() << "File opened on device successfully"; + + FILE *out = fopen(local_path, "wb"); + if (!out) { + qDebug() << "Failed to open local file:" << local_path; + IdeviceFfiError *err_close = + safeAfcFileClose(device, afcHandle); + if (err_close != nullptr) { + // idevice_error_free(err_close); + } + return new IdeviceFfiError{1, "FAILED_TO_OPEN_LOCAL_FILE"}; + } + qDebug() << "Local file opened successfully"; + + const size_t CHUNK_SIZE = 256 * 1024; // 256KB chunks + uint8_t *chunkData = nullptr; + size_t bytesRead = 0; + qint64 totalBytesRead = 0; + + // Get file size for progress + AfcFileInfo fileInfo; + IdeviceFfiError *info_err = + safeAfcGetFileInfo(device, device_path, &fileInfo); + qint64 totalFileSize = 0; + if (info_err == nullptr) { + totalFileSize = fileInfo.size; + // afc_file_info_free(&fileInfo); + } else { + // idevice_error_free(info_err); + } + + IdeviceFfiError *read_err = nullptr; + // Read file in chunks + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + // Check for cancellation + if (cancelRequested && cancelRequested->load()) { + fclose(out); + safeAfcFileClose(device, afcHandle); + return new IdeviceFfiError{1, "OPERATION_CANCELLED"}; + } + + read_err = safeAfcFileRead(device, afcHandle, &chunkData, + CHUNK_SIZE, &bytesRead); + + if (read_err != nullptr) { + qDebug() << "Error reading file:" << read_err->message; + fclose(out); + safeAfcFileClose(device, afcHandle); + return read_err; + } + + if (bytesRead == 0) { + // End of file reached + break; + } + + // Write chunk to local file + size_t written = fwrite(chunkData, 1, bytesRead, out); + + // Free the memory allocated by afc_file_read + afc_file_read_data_free(chunkData, bytesRead); + chunkData = nullptr; + + if (written != bytesRead) { + qDebug() << "Failed to write all bytes to local file"; + fclose(out); + safeAfcFileClose(device, afcHandle); + return new IdeviceFfiError{1, "WRITE_ERROR"}; + } + + totalBytesRead += bytesRead; + + // Report progress + if (progressCallback) { + progressCallback(totalBytesRead, totalFileSize); + } + } + + fclose(out); + + IdeviceFfiError *err_close = safeAfcFileClose(device, afcHandle); + if (err_close != nullptr) { + qDebug() << "Failed to close AFC file:" << err_close->message; + return err_close; + } + + return nullptr; // Success + }); +} diff --git a/src/servicemanager.h b/src/servicemanager.h index d8a8821..695684e 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -22,6 +22,7 @@ #include "iDescriptor.h" #include +#include #include #include #include @@ -199,6 +200,7 @@ public: // altAfc was explicitly provided but is null, which is an // invalid state. qDebug() << "[executeAfcClientOperation] altAfc is null"; + // c string is not safe in IdeviceFfiError ? return new IdeviceFfiError{1, "ALT_AFC_CLIENT_IS_NULL"}; } @@ -248,6 +250,9 @@ public: const char *path); static AFCFileTree safeGetFileTree(const iDescriptorDevice *device, const std::string &path, bool checkDir); + static QFuture + getFileTreeAsync(const iDescriptorDevice *device, const std::string &path, + bool checkDir); static MountedImageInfo getMountedImage(const iDescriptorDevice *device); static IdeviceFfiError *mountImage(const iDescriptorDevice *device, const char *image_file, @@ -259,6 +264,13 @@ public: const char *filePath, const char *fileName); static bool enableWirelessConnections(const iDescriptorDevice *device); + + // File export operations + static IdeviceFfiError *exportFileToPath( + const iDescriptorDevice *device, const char *device_path, + const char *local_path, + std::function progressCallback = nullptr, + std::atomic *cancelRequested = nullptr); }; #endif // SERVICEMANAGER_H diff --git a/src/statusballoon.cpp b/src/statusballoon.cpp new file mode 100644 index 0000000..2245a6c --- /dev/null +++ b/src/statusballoon.cpp @@ -0,0 +1,643 @@ +#include "statusballoon.h" +#include "exportmanager.h" +#include "exportmanagerthread.h" +#include "iDescriptor.h" +#include "qballoontip.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Process::Process(QWidget *parent) : QWidget(parent) {} + +StatusBalloon *StatusBalloon::sharedInstance() +{ + static StatusBalloon instance; + return &instance; +} + +StatusBalloon::StatusBalloon(QWidget *parent) + : QBalloonTip(QIcon(), "", "", parent) +{ + setMinimumHeight(300); + setMinimumWidth(300); + // Create main layout + m_mainLayout = new QVBoxLayout(); + m_mainLayout->setSpacing(8); + m_mainLayout->setContentsMargins(12, 12, 12, 12); + + // Header label + m_headerLabel = new QLabel("Processes"); + QFont headerFont = m_headerLabel->font(); + headerFont.setPointSize(headerFont.pointSize() + 2); + headerFont.setBold(true); + m_headerLabel->setFont(headerFont); + m_mainLayout->addWidget(m_headerLabel); + + // Container for processes + m_processesContainer = new QWidget(); + m_processesLayout = new QVBoxLayout(m_processesContainer); + m_processesLayout->setSpacing(12); + m_processesLayout->setContentsMargins(0, 0, 0, 0); + m_mainLayout->addWidget(m_processesContainer); + + setLayout(m_mainLayout); + connect(m_button, &ZIconWidget::clicked, this, &StatusBalloon::showBalloon); + connectExportThreadSignals(); +} + +void StatusBalloon::connectExportThreadSignals() +{ + ExportManager *exportManager = ExportManager::sharedInstance(); + + connect(exportManager->m_exportThread, &ExportManagerThread::exportFinished, + this, &StatusBalloon::onExportFinished); + + connect(exportManager->m_exportThread, &ExportManagerThread::itemExported, + this, &StatusBalloon::onItemExported); + + connect(exportManager->m_exportThread, + &ExportManagerThread::fileTransferProgress, this, + &StatusBalloon::onFileTransferProgress); + QTimer::singleShot(0, this, [this]() { + // test + startExportProcess("Test Export Process", 10, "/path/to/destination"); + }); +} + +void StatusBalloon::onFileTransferProgress(const QUuid &processId, + int currentItem, + const QString ¤tFile, + qint64 bytesTransferred, + qint64 totalBytes) +{ + qDebug() << "StatusBalloon::updateProcessProgress entry:" << processId + << currentItem << currentFile << bytesTransferred << totalBytes; + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + qDebug() << "StatusBalloon::updateProcessProgress: unknown processId" + << processId; + return; + } + + ProcessItem *item = m_processes[processId]; + item->completedItems = currentItem; + item->currentFile = currentFile; + item->transferredBytes = bytesTransferred; + item->totalBytes = totalBytes; + + if (!item->processWidget) + qDebug() + << "StatusBalloon::updateProcessProgress: no widget for processId" + << processId; + + // Update status label + QString statusText; + if (item->status == ProcessStatus::Running) { + if (!item->currentFile.isEmpty()) { + statusText = item->currentFile; + } else { + statusText = "Processing..."; + } + } else if (item->status == ProcessStatus::Completed) { + statusText = "Completed successfully"; + } else if (item->status == ProcessStatus::Failed) { + statusText = "Failed"; + } else if (item->status == ProcessStatus::Cancelled) { + statusText = "Cancelled"; + } + item->statusLabel->setText(statusText); + + // Update progress bar + // progess should be based on exported bytes vs total bytes of the current + // file + if (item->totalItems > 0) { + int progress = (item->transferredBytes * 100) / item->totalBytes; + item->progressBar->setValue(progress); + } + + // Update stats + QString statsText = QString("%1 of %2 items") + .arg(item->completedItems) + .arg(item->totalItems); + if (item->failedItems > 0) { + statsText += QString(" • %1 failed").arg(item->failedItems); + } + + if (item->status == ProcessStatus::Running && item->transferredBytes > 0) { + // Calculate transfer rate + QDateTime now = QDateTime::currentDateTime(); + qint64 elapsed = m_lastUpdateTime[item->processId].msecsTo(now); + if (elapsed > 0) { + qint64 bytesDiff = item->transferredBytes - + m_lastBytesTransferred[item->processId]; + qint64 bytesPerSecond = (bytesDiff * 1000) / elapsed; + if (bytesPerSecond > 0) { + statsText += " • " + formatTransferRate(bytesPerSecond); + } + m_lastBytesTransferred[item->processId] = item->transferredBytes; + m_lastUpdateTime[item->processId] = now; + } + } + + item->statsLabel->setText(statsText); + + // Update buttons + if (item->status == ProcessStatus::Running) { + item->cancelButton->setVisible(true); + item->actionButton->setVisible(false); + } else { + item->cancelButton->setVisible(false); + if (item->type == ProcessType::Export && + item->status == ProcessStatus::Completed) { + item->actionButton->setVisible(true); + } + } +} + +// todo fix these +// StatusBalloon::onItemExported entry: +// QUuid("{9bd97848-cb52-4ef8-93c1-a1d1c285e6a3}") Success: true +// StatusBalloon::onItemExported: unknown processId +// QUuid("{9bd97848-cb52-4ef8-93c1-a1d1c285e6a3}") +// StatusBalloon::onExportFinished entry: +// QUuid("{9bd97848-cb52-4ef8-93c1-a1d1c285e6a3}") WasCancelled: false +// StatusBalloon::onExportFinished: unknown processId +// QUuid("{9bd97848-cb52-4ef8-93c1-a1d1c285e6a3}") + +void StatusBalloon::onExportFinished(const QUuid &processId, + const ExportJobSummary &summary) +{ + qDebug() << "StatusBalloon::onExportFinished entry:" << processId + << "WasCancelled:" << summary.wasCancelled; + QMutexLocker locker(&m_processesMutex); + if (!m_processes.contains(processId)) { + qDebug() << "StatusBalloon::onExportFinished: unknown processId" + << processId; + return; + } + + // todo: handle failed ? + ProcessItem *item = m_processes[processId]; + if (summary.wasCancelled) { + item->status = ProcessStatus::Cancelled; + } else { + item->status = ProcessStatus::Completed; + } + item->endTime = QDateTime::currentDateTime(); + + updateUI(); +} + +void StatusBalloon::onItemExported(const QUuid &processId, + const ExportResult &result) +{ + qDebug() << "StatusBalloon::onItemExported entry:" << processId + << "Success:" << result.success; + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + qDebug() << "StatusBalloon::onItemExported: unknown processId" + << processId; + return; + } + + ProcessItem *item = m_processes[processId]; + if (result.success) { + item->completedItems += 1; + } else { + item->failedItems += 1; + } + + updateUI(); +} + +QUuid StatusBalloon::startExportProcess(const QString &title, int totalItems, + const QString &destinationPath) +{ + qDebug() << "StatusBalloon::startExportProcess entry:" << title + << totalItems << destinationPath; + + // allocate item first so it can be used after unlocking + auto *item = new ProcessItem(); + item->processId = QUuid::createUuid(); + item->type = ProcessType::Export; + item->status = ProcessStatus::Running; + item->title = title; + item->totalItems = totalItems; + item->completedItems = 0; + item->failedItems = 0; + item->totalBytes = 0; + item->transferredBytes = 0; + item->startTime = QDateTime::currentDateTime(); + item->destinationPath = destinationPath; + + { // scope the lock only for shared-state mutation + QMutexLocker locker(&m_processesMutex); + m_processes[item->processId] = item; + m_currentProcessId = item->processId; + m_lastBytesTransferred[item->processId] = 0; + m_lastUpdateTime[item->processId] = QDateTime::currentDateTime(); + } // mutex released here + + // UI work must run without holding m_processesMutex to avoid re-locking + // deadlock + createProcessWidget(item); + updateUI(); + + return item->processId; +} + +QUuid StatusBalloon::startUploadProcess(const QString &title, int totalItems) +{ + // allocate item first + auto *item = new ProcessItem(); + item->processId = QUuid::createUuid(); + item->type = ProcessType::Upload; + item->status = ProcessStatus::Running; + item->title = title; + item->totalItems = totalItems; + item->completedItems = 0; + item->failedItems = 0; + item->totalBytes = 0; + item->transferredBytes = 0; + item->startTime = QDateTime::currentDateTime(); + + { // scope the lock only for shared-state mutation + QMutexLocker locker(&m_processesMutex); + m_processes[item->processId] = item; + m_currentProcessId = item->processId; + m_lastBytesTransferred[item->processId] = 0; + m_lastUpdateTime[item->processId] = QDateTime::currentDateTime(); + } // mutex released here + + createProcessWidget(item); + updateUI(); + + return item->processId; +} + +void StatusBalloon::createProcessWidget(ProcessItem *item) +{ + item->processWidget = new QWidget(); + auto *layout = new QVBoxLayout(item->processWidget); + layout->setSpacing(6); + layout->setContentsMargins(0, 0, 0, 0); + + // Title + item->titleLabel = new QLabel(item->title); + QFont titleFont = item->titleLabel->font(); + titleFont.setBold(true); + item->titleLabel->setFont(titleFont); + layout->addWidget(item->titleLabel); + + // Status + item->statusLabel = new QLabel("Starting..."); + layout->addWidget(item->statusLabel); + + // Progress bar + item->progressBar = new QProgressBar(); + item->progressBar->setRange(0, 100); + item->progressBar->setValue(0); + item->progressBar->setTextVisible(true); // show text for debugging + item->progressBar->setFixedHeight(12); // make it visible + layout->addWidget(item->progressBar); + + // Stats + item->statsLabel = new QLabel(); + QFont statsFont = item->statsLabel->font(); + statsFont.setPointSize(statsFont.pointSize() - 1); + item->statsLabel->setFont(statsFont); + layout->addWidget(item->statsLabel); + + // Buttons layout + auto *buttonsLayout = new QHBoxLayout(); + buttonsLayout->setSpacing(6); + + // Action button (Open Folder for export, hidden initially) + item->actionButton = new QPushButton(); + item->actionButton->setVisible(false); + if (item->type == ProcessType::Export) { + item->actionButton->setText("Open Folder"); + connect(item->actionButton, &QPushButton::clicked, this, + &StatusBalloon::onOpenFolderClicked); + } + buttonsLayout->addWidget(item->actionButton); + + buttonsLayout->addStretch(); + + // Cancel button + item->cancelButton = new QPushButton("Cancel"); + connect(item->cancelButton, &QPushButton::clicked, this, + &StatusBalloon::onCancelClicked); + buttonsLayout->addWidget(item->cancelButton); + + layout->addLayout(buttonsLayout); + + m_processesLayout->addWidget(item->processWidget); +} + +void StatusBalloon::markProcessCompleted(const QUuid &processId) +{ + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + return; + } + + ProcessItem *item = m_processes[processId]; + item->status = ProcessStatus::Completed; + item->endTime = QDateTime::currentDateTime(); + + updateUI(); + + // Check if all processes are done + bool allDone = true; + for (auto *proc : m_processes) { + if (proc->status == ProcessStatus::Running) { + allDone = false; + break; + } + } +} + +void StatusBalloon::markProcessFailed(const QUuid &processId, + const QString &error) +{ + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + return; + } + + ProcessItem *item = m_processes[processId]; + item->status = ProcessStatus::Failed; + item->endTime = QDateTime::currentDateTime(); + + updateUI(); +} + +void StatusBalloon::markProcessCancelled(const QUuid &processId) +{ + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + return; + } + + ProcessItem *item = m_processes[processId]; + item->status = ProcessStatus::Cancelled; + item->endTime = QDateTime::currentDateTime(); + + updateUI(); +} + +void StatusBalloon::incrementFailedItems(const QUuid &processId) +{ + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + return; + } + + m_processes[processId]->failedItems++; + updateUI(); +} + +void StatusBalloon::updateUI() +{ + QMutexLocker locker(&m_processesMutex); + + // Update header + int running = 0, completed = 0, failed = 0; + for (auto *item : m_processes) { + if (item->status == ProcessStatus::Running) + running++; + else if (item->status == ProcessStatus::Completed) + completed++; + else if (item->status == ProcessStatus::Failed) + failed++; + } + + QString headerText = QString("Processes: %1 running").arg(running); + if (completed > 0 || failed > 0) { + headerText += QString(" • %1 completed").arg(completed); + if (failed > 0) { + headerText += QString(" • %1 failed").arg(failed); + } + } + m_headerLabel->setText(headerText); + + // Update each process widget + for (auto *item : m_processes) { + if (!item->processWidget) + continue; + + // Update status label + QString statusText; + if (item->status == ProcessStatus::Running) { + if (!item->currentFile.isEmpty()) { + statusText = item->currentFile; + } else { + statusText = "Processing..."; + } + } else if (item->status == ProcessStatus::Completed) { + statusText = "Completed successfully"; + } else if (item->status == ProcessStatus::Failed) { + statusText = "Failed"; + } else if (item->status == ProcessStatus::Cancelled) { + statusText = "Cancelled"; + } + item->statusLabel->setText(statusText); + + // Update progress bar + if (item->totalItems > 0) { + int progress = (item->completedItems * 100) / item->totalItems; + item->progressBar->setValue(progress); + } + + // Update stats + QString statsText = QString("%1 of %2 items") + .arg(item->completedItems) + .arg(item->totalItems); + if (item->failedItems > 0) { + statsText += QString(" • %1 failed").arg(item->failedItems); + } + + if (item->status == ProcessStatus::Running && + item->transferredBytes > 0) { + // Calculate transfer rate + QDateTime now = QDateTime::currentDateTime(); + qint64 elapsed = m_lastUpdateTime[item->processId].msecsTo(now); + if (elapsed > 0) { + qint64 bytesDiff = item->transferredBytes - + m_lastBytesTransferred[item->processId]; + qint64 bytesPerSecond = (bytesDiff * 1000) / elapsed; + if (bytesPerSecond > 0) { + statsText += " • " + formatTransferRate(bytesPerSecond); + } + m_lastBytesTransferred[item->processId] = + item->transferredBytes; + m_lastUpdateTime[item->processId] = now; + } + } + + item->statsLabel->setText(statsText); + + // Update buttons + if (item->status == ProcessStatus::Running) { + item->cancelButton->setVisible(true); + item->actionButton->setVisible(false); + } else { + item->cancelButton->setVisible(false); + if (item->type == ProcessType::Export && + item->status == ProcessStatus::Completed) { + item->actionButton->setVisible(true); + } + } + } + + showBalloon(); +} + +void StatusBalloon::showBalloon() +{ + qDebug() << "StatusBalloon::showBalloon" << sender(); + QPoint pos = m_button->mapToGlobal( + QPoint(m_button->width() / 2, m_button->height())); + + balloon(pos, -1, true); +} + +bool StatusBalloon::isProcessRunning(const QUuid &processId) const +{ + QMutexLocker locker(&m_processesMutex); + if (!m_processes.contains(processId)) { + return false; + } + return m_processes[processId]->status == ProcessStatus::Running; +} + +bool StatusBalloon::hasActiveProcesses() const +{ + QMutexLocker locker(&m_processesMutex); + for (auto *item : m_processes) { + if (item->status == ProcessStatus::Running) { + return true; + } + } + return false; +} + +bool StatusBalloon::isCancelRequested(const QUuid &processId) const +{ + QMutexLocker locker(&m_processesMutex); + if (!m_processes.contains(processId)) { + return false; + } + return m_processes[processId]->cancelRequested.load(); +} + +void StatusBalloon::onCancelClicked() +{ + QPushButton *button = qobject_cast(sender()); + if (!button) + return; + + QMutexLocker locker(&m_processesMutex); + + // Find which process this button belongs to + for (auto *item : m_processes) { + if (item->cancelButton == button) { + item->cancelRequested.store(true); + button->setEnabled(false); + button->setText("Cancelling..."); + break; + } + } +} + +void StatusBalloon::onOpenFolderClicked() +{ + QPushButton *button = qobject_cast(sender()); + if (!button) + return; + + QMutexLocker locker(&m_processesMutex); + + for (auto *item : m_processes) { + if (item->actionButton == button && item->type == ProcessType::Export) { + QDesktopServices::openUrl( + QUrl::fromLocalFile(item->destinationPath)); + break; + } + } +} + +QString StatusBalloon::formatFileSize(qint64 bytes) const +{ + const qint64 KB = 1024; + const qint64 MB = KB * 1024; + const qint64 GB = MB * 1024; + + if (bytes >= GB) { + return QString("%1 GB").arg( + QString::number(bytes / double(GB), 'f', 2)); + } else if (bytes >= MB) { + return QString("%1 MB").arg( + QString::number(bytes / double(MB), 'f', 1)); + } else if (bytes >= KB) { + return QString("%1 KB").arg( + QString::number(bytes / double(KB), 'f', 0)); + } else { + return QString("%1 B").arg(bytes); + } +} + +QString StatusBalloon::formatTransferRate(qint64 bytesPerSecond) const +{ + return formatFileSize(bytesPerSecond) + "/s"; +} + +void StatusBalloon::removeProcessWidget(const QUuid &processId) +{ + QMutexLocker locker(&m_processesMutex); + + if (!m_processes.contains(processId)) { + return; + } + + ProcessItem *item = m_processes[processId]; + if (item->processWidget) { + m_processesLayout->removeWidget(item->processWidget); + item->processWidget->deleteLater(); + } + + // delete item; + m_processes.remove(processId); + + if (m_processes.isEmpty()) { + hide(); + } +} + +ZIconWidget *StatusBalloon::getButton() { return m_button; } diff --git a/src/statusballoon.h b/src/statusballoon.h new file mode 100644 index 0000000..784b995 --- /dev/null +++ b/src/statusballoon.h @@ -0,0 +1,109 @@ +#ifndef STATUSBALLOON_H +#define STATUSBALLOON_H + +#include "iDescriptor-ui.h" +#include "iDescriptor.h" +#include "qballoontip.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class ProcessType { Export, Upload }; + +enum class ProcessStatus { Queued, Running, Completed, Failed, Cancelled }; + +struct ProcessItem { + QUuid processId; + ProcessType type; + ProcessStatus status; + QString title; + QString currentFile; + int totalItems; + int completedItems; + int failedItems; + qint64 totalBytes; + qint64 transferredBytes; + QDateTime startTime; + QDateTime endTime; + QString destinationPath; // For export + QWidget *processWidget; + QLabel *titleLabel; + QLabel *statusLabel; + QLabel *statsLabel; + QProgressBar *progressBar; + QPushButton *actionButton; + QPushButton *cancelButton; + std::atomic cancelRequested{false}; +}; + +class Process : public QWidget +{ + Q_OBJECT +public: + explicit Process(QWidget *parent = nullptr); +}; + +class StatusBalloon : public QBalloonTip +{ + Q_OBJECT +public: + explicit StatusBalloon(QWidget *parent = nullptr); + static StatusBalloon *sharedInstance(); + + // Process management + QUuid startExportProcess(const QString &title, int totalItems, + const QString &destinationPath); + QUuid startUploadProcess(const QString &title, int totalItems); + + void onFileTransferProgress(const QUuid &processId, int currentItem, + const QString ¤tFile, + qint64 bytesTransferred, qint64 totalBytes); + void markProcessCompleted(const QUuid &processId); + void markProcessFailed(const QUuid &processId, const QString &error); + void markProcessCancelled(const QUuid &processId); + void incrementFailedItems(const QUuid &processId); + + bool isProcessRunning(const QUuid &processId) const; + bool hasActiveProcesses() const; + bool isCancelRequested(const QUuid &processId) const; + ZIconWidget *getButton(); +private slots: + void onCancelClicked(); + void onOpenFolderClicked(); + +private: + void updateUI(); + void showBalloon(); + void createProcessWidget(ProcessItem *item); + QString formatFileSize(qint64 bytes) const; + QString formatTransferRate(qint64 bytesPerSecond) const; + void removeProcessWidget(const QUuid &processId); + void connectExportThreadSignals(); + void onExportFinished(const QUuid &processId, + const ExportJobSummary &summary); + void onItemExported(const QUuid &processId, const ExportResult &result); + + QVBoxLayout *m_mainLayout; + QLabel *m_headerLabel; + QWidget *m_processesContainer; + QVBoxLayout *m_processesLayout; + + QMap m_processes; + QUuid m_currentProcessId; + mutable QMutex m_processesMutex; + + QMap m_lastBytesTransferred; + QMap m_lastUpdateTime; + ZIconWidget *m_button = + new ZIconWidget(QIcon(":/resources/icons/UimProcess.png"), "Processes"); +}; +#endif // STATUSBALLOON_H diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index a403d13..a8d56f1 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -298,7 +298,8 @@ void ToolboxWidget::updateDeviceList() QString::fromStdString(device->udid).left(8) + "..."; m_deviceCombo->addItem( QString::fromStdString(device->deviceInfo.productType) + " / " + - shortUdid, + shortUdid + + (device->deviceInfo.isWireless ? " (Wi-Fi)" : ""), QString::fromStdString(device->udid)); } } From ac81ee087cb654f13c3744785de52f82ead7bede Mon Sep 17 00:00:00 2001 From: uncor3 Date: Thu, 15 Jan 2026 02:06:11 +0000 Subject: [PATCH 05/15] Refactor LiveScreenWidget, implement HouseArrest , fix bugs - Introduced ScreenshotrThread to manage screenshot capturing in a separate thread. - Updated LiveScreenWidget to utilize the new thread for capturing screenshots. - Removed unused timer and related code for periodic screenshot updates. - Enhanced error handling and initialization logic for screenshot service. - Updated ServiceManager to include methods for taking screenshots and enabling developer mode. - Refactored various methods in ServiceManager to accept optional parameters for AFC client handling. - Improved error handling in VirtualLocation for setting device location. - Enhanced ZLoadingWidget to support multiple content states using QStackedWidget. - Cleaned up code and comments across multiple files for better readability and maintainability. --- src/afcexplorerwidget.cpp | 4 +- src/appcontext.cpp | 1 - src/cableinfowidget.cpp | 51 ++- src/cableinfowidget.h | 3 + src/core/services/get_file_tree.cpp | 21 +- src/core/services/init_device.cpp | 11 +- src/devdiskimagehelper.cpp | 104 ++--- src/devdiskimagehelper.h | 2 - src/devdiskimageswidget.cpp | 18 +- src/diskusagewidget.cpp | 140 +++---- src/gallerywidget.cpp | 40 +- src/gallerywidget.h | 2 - src/iDescriptor.h | 11 +- src/installedappswidget.cpp | 628 ++++++++++++---------------- src/installedappswidget.h | 10 +- src/livescreenwidget.cpp | 195 ++------- src/livescreenwidget.h | 53 ++- src/photomodel.cpp | 12 +- src/querymobilegestaltwidget.cpp | 5 +- src/servicemanager.cpp | 100 ++++- src/servicemanager.h | 24 +- src/toolboxwidget.cpp | 5 +- src/virtuallocationwidget.cpp | 148 ++++++- src/zloadingwidget.cpp | 90 +++- src/zloadingwidget.h | 14 +- 25 files changed, 901 insertions(+), 791 deletions(-) diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 9d49691..e4f43b3 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -204,8 +204,8 @@ void AfcExplorerWidget::loadPath(const QString &path) updateAddressBar(path); updateNavigationButtons(); - AFCFileTree tree = - ServiceManager::safeGetFileTree(m_device, path.toStdString(), m_afc); + AFCFileTree tree = ServiceManager::safeGetFileTree( + m_device, path.toStdString(), true, m_afc); if (!tree.success) { showErrorState(); return; diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 970f8eb..42b7953 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -266,7 +266,6 @@ void AppContext::addDevice(QString udid, .mutex = new std::recursive_mutex(), .imageMounter = initResult->imageMounter, .diagRelay = initResult->diagRelay, - .screenshotrClient = initResult->screenshotrClient, .locationSimulation = initResult->locationSimulation}; m_devices[device->udid] = device; if (addType == AddType::Regular) { diff --git a/src/cableinfowidget.cpp b/src/cableinfowidget.cpp index 0c4a336..3d62c8a 100644 --- a/src/cableinfowidget.cpp +++ b/src/cableinfowidget.cpp @@ -30,7 +30,6 @@ CableInfoWidget::CableInfoWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device), m_response(nullptr) { setupUI(); - initCableInfo(); connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [this](const std::string &udid) { if (m_device->udid == udid) { @@ -38,12 +37,13 @@ CableInfoWidget::CableInfoWidget(iDescriptorDevice *device, QWidget *parent) this->deleteLater(); } }); + QTimer::singleShot(200, this, &CableInfoWidget::initCableInfo); } void CableInfoWidget::setupUI() { setWindowTitle("Cable Information - iDescriptor"); - m_mainLayout = new QVBoxLayout(this); + m_mainLayout = new QVBoxLayout(); m_mainLayout->setSpacing(20); m_mainLayout->setContentsMargins(20, 20, 20, 20); @@ -58,7 +58,14 @@ void CableInfoWidget::setupUI() new QLabel("Please wait while we analyze the connected cable."); m_descriptionLabel->setStyleSheet("font-size: 9px;"); + QPushButton *redoButton = new QPushButton("Re-analyze"); + connect(redoButton, &QPushButton::clicked, this, [this]() { + m_loadingWidget->showLoading(); + QTimer::singleShot(200, this, &CableInfoWidget::initCableInfo); + }); headerLayout->addWidget(m_statusLabel); + headerLayout->addStretch(); + headerLayout->addWidget(redoButton); m_mainLayout->addLayout(headerLayout); @@ -71,14 +78,38 @@ void CableInfoWidget::setupUI() m_mainLayout->addWidget(m_descriptionLabel); m_mainLayout->addWidget(m_infoWidget); m_mainLayout->addStretch(); + m_loadingWidget = new ZLoadingWidget(true, this); + m_loadingWidget->setupContentWidget(m_mainLayout); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_loadingWidget); + + QVBoxLayout *errorLayout = new QVBoxLayout(); + m_errorLabel = new QLabel(); + m_errorLabel->setAlignment(Qt::AlignCenter); + errorLayout->addStretch(); + errorLayout->addWidget(m_errorLabel); + + QPushButton *retryButton = new QPushButton("Retry"); + retryButton->setMaximumWidth(retryButton->sizeHint().width()); + connect(retryButton, &QPushButton::clicked, this, [this]() { + m_loadingWidget->showLoading(); + QTimer::singleShot(200, this, &CableInfoWidget::initCableInfo); + }); + errorLayout->addWidget(retryButton, 0, Qt::AlignHCenter); + errorLayout->addStretch(); + + m_loadingWidget->setupErrorWidget(errorLayout); } void CableInfoWidget::initCableInfo() { if (!m_device) { - m_statusLabel->setText("Something went wrong (no device ?)"); - m_statusLabel->setStyleSheet( + m_errorLabel->setText("Something went wrong (no device ?)"); + m_errorLabel->setStyleSheet( "QLabel { color: #dc3545; font-size: 18px; font-weight: bold; }"); + m_loadingWidget->showError(); return; } @@ -104,8 +135,7 @@ void CableInfoWidget::analyzeCableInfo() if (!ioreg.valid()) { return; } - - m_cableInfo.isConnected = true; + m_cableInfo.isConnected = ioreg["ConnectionActive"].getBool(); // Check if genuine (Apple manufacturer and valid model info) m_cableInfo.manufacturer = QString::fromStdString( @@ -197,6 +227,13 @@ void CableInfoWidget::updateUI() delete item; } + if (!m_cableInfo.isConnected) { + m_errorLabel->setText( + "Device does not seem to be connected to any cable."); + m_loadingWidget->showError(); + return; + } + // Update status and icon based on cable type QString statusText; QString statusStyle; @@ -204,6 +241,7 @@ void CableInfoWidget::updateUI() m_descriptionLabel->setText("Please note that this check may not be " "absolute guarantee of authenticity."); if (m_cableInfo.isGenuine) { + // todo: type-c to type-c statusText = QString("✅ Genuine %1") .arg(m_cableInfo.isTypeC ? "USB-C to Lightning Cable" : "Lightning Cable"); @@ -284,6 +322,7 @@ void CableInfoWidget::updateUI() createInfoRow(m_infoLayout, row++, "Supported Transports:", m_cableInfo.supportedTransports.join(", ")); } + m_loadingWidget->stop(true); } void CableInfoWidget::createInfoRow(QGridLayout *layout, int row, diff --git a/src/cableinfowidget.h b/src/cableinfowidget.h index f9aee59..be6de40 100644 --- a/src/cableinfowidget.h +++ b/src/cableinfowidget.h @@ -21,6 +21,7 @@ #define CABLEINFOWIDGET_H #include "iDescriptor.h" +#include "zloadingwidget.h" #include #include #include @@ -73,6 +74,8 @@ private: QLabel *m_descriptionLabel; QGroupBox *m_infoWidget; QGridLayout *m_infoLayout; + ZLoadingWidget *m_loadingWidget; + QLabel *m_errorLabel; // Data iDescriptorDevice *m_device; diff --git a/src/core/services/get_file_tree.cpp b/src/core/services/get_file_tree.cpp index 1bf3021..28afbb8 100644 --- a/src/core/services/get_file_tree.cpp +++ b/src/core/services/get_file_tree.cpp @@ -24,7 +24,8 @@ #include AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, - const std::string &path) + const std::string &path, + std::optional altAfc) { qDebug() << "Getting file tree for path:" << QString::fromStdString(path); AFCFileTree result; @@ -35,8 +36,8 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, size_t count = 0; // Use safe wrapper to read directory - IdeviceFfiError *err = - ServiceManager::safeAfcReadDirectory(device, path.c_str(), &dirs); + IdeviceFfiError *err = ServiceManager::safeAfcReadDirectory( + device, path.c_str(), &dirs, count, altAfc); if (err) { qDebug() << "Failed to read directory:" << path.c_str() @@ -52,7 +53,7 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, // Iterate through directory entries for (int i = 0; dirs[i]; i++) { - qDebug() << "Found entry:" << dirs[i]; + // qDebug() << "Found entry:" << dirs[i]; std::string entryName = dirs[i]; if (entryName == "." || entryName == "..") continue; @@ -69,8 +70,8 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, // Get file info using safe wrapper AfcFileInfo info = {}; - IdeviceFfiError *info_err = - ServiceManager::safeAfcGetFileInfo(device, fullPath.c_str(), &info); + IdeviceFfiError *info_err = ServiceManager::safeAfcGetFileInfo( + device, fullPath.c_str(), &info, altAfc); if (info_err) { qDebug() << "Failed to get file info for:" << fullPath.c_str() @@ -80,18 +81,20 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, bool isDir = false; if (!info_err) { - qDebug() << "Entry:" << entryName.c_str() << "Type:" << info.st_ifmt - << "Size:" << info.size; + // qDebug() << "Entry:" << entryName.c_str() << "Type:" << + // info.st_ifmt + // << "Size:" << info.size; if (strcmp(info.st_ifmt, "S_IFDIR") == 0) { isDir = true; } else if (strcmp(info.st_ifmt, "S_IFLNK") == 0) { // Check if symlink points to a directory char **dir_contents = nullptr; + size_t count = 0; // FIXME: recursively call safeAfcGetFileInfo to figure out if // it's a dir IdeviceFfiError *link_err = ServiceManager::safeAfcReadDirectory( - device, fullPath.c_str(), &dir_contents); + device, fullPath.c_str(), &dir_contents, count, altAfc); if (!link_err) { isDir = true; diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index b502eee..7242920 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -390,7 +390,6 @@ init_idescriptor_device(const QString &udid, HeartbeatThread *heartbeatThread = nullptr; ImageMounterHandle *image_mounter = nullptr; DiagnosticsRelayClientHandle *diagnostics_relay = nullptr; - ScreenshotrClientHandle *screenshotr_client = nullptr; LocationSimulationHandle *location_simulation = nullptr; plist_t val = nullptr; @@ -515,13 +514,6 @@ init_idescriptor_device(const QString &udid, goto cleanup; } - // err = screenshotr_connect(provider, &screenshotr_client); - - // if (err) { - // qDebug() << "Failed to create Screenshotr client"; - // goto cleanup; - // } - err = afc2_client_connect(provider, &afc2_client); if (err) { qDebug() << "Failed to create AFC2 client"; @@ -547,8 +539,9 @@ init_idescriptor_device(const QString &udid, result.afcClient = afc_client; result.afc2Client = afc2_client; result.lockdown = lockdown; + // TODO:remove, not really required to get some stuff going so it can be + // optional result.imageMounter = image_mounter; - result.screenshotrClient = screenshotr_client; result.diagRelay = std::make_shared( DiagnosticsRelay::adopt(diagnostics_relay)); result.locationSimulation = location_simulation; diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index 62e165b..95cab5b 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -18,8 +18,10 @@ */ #include "devdiskimagehelper.h" +#include "appcontext.h" #include "devdiskmanager.h" #include "qprocessindicator.h" +#include "servicemanager.h" #include "settingsmanager.h" #include #include @@ -29,8 +31,7 @@ DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device, QWidget *parent) - : QDialog(parent), m_device(device), m_isDownloading(false), - m_isMounting(false) + : QDialog(parent), m_device(device) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle("Developer Disk Image - iDescriptor"); @@ -113,7 +114,9 @@ void DevDiskImageHelper::start() }); qDebug() << "isMountAvailable:" << isMountAvailable; if (!isMountAvailable) { - finishWithError("Failed to download compatible image."); + finishWithError( + "There is no compatible developer disk image available for " + + QString::number(deviceMajorVersion) + "."); } } else { finishWithSuccess(); @@ -123,22 +126,20 @@ void DevDiskImageHelper::start() void DevDiskImageHelper::checkAndMount() { - // GetMountedImageResult result = - // DevDiskManager::sharedInstance()->getMountedImage(m_device); - // qDebug() << "checkAndMount result:" << result.success - // << result.message.c_str() << QString::fromStdString(result.sig); - // if (!result.success) { - // showRetryUI(QString::fromStdString(result.message)); - // return; - // } + MountedImageInfo info = ServiceManager::getMountedImage( + AppContext::sharedInstance()->getDevice(m_device->udid)); + if (info.err && info.err->code != NotFoundErrorCode) { + onMountButtonClicked(); + return; + } - // // If image is already mounted - // if (!result.sig.empty()) { - // finishWithSuccess(); - // return; - // } + // If image is already mounted + if (info.signature && info.signature_len) { + finishWithSuccess(); + return; + } - // onMountButtonClicked(); + onMountButtonClicked(); } void DevDiskImageHelper::onMountButtonClicked() @@ -146,7 +147,6 @@ void DevDiskImageHelper::onMountButtonClicked() QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); m_mountButton->setVisible(false); m_loadingIndicator->start(); - m_isMounting = true; // Check if we need to download first unsigned int deviceMajorVersion = @@ -174,32 +174,13 @@ void DevDiskImageHelper::onMountButtonClicked() if (hasDownloadedImage) { // // Mount directly - // showStatus("Mounting developer disk image..."); - - // mobile_image_mounter_error_t err = - // DevDiskManager::sharedInstance()->mountImage(versionToMount, - // m_device); - - // m_isMounting = false; - // if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - // showStatus("Developer disk image mounted successfully"); - // finishWithSuccess(); - // } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - // showRetryUI( - // "Device is locked. Please unlock your device and try - // again."); - // } else { - // showRetryUI("Failed to mount developer disk image.\n" - // "Please ensure:\n" - // "• Device is unlocked\n" - // "• Using a genuine cable\n" - // "• Developer mode is enabled (iOS 16+)"); - // } + m_downloadingVersion = versionToMount; + showStatus("Mounting developer disk image..."); + onImageDownloadFinished(versionToMount, true, ""); } else { // Need to download first showStatus( "Downloading developer disk image...\nThis may take a moment."); - m_isDownloading = true; // Connect to download signals connect(DevDiskManager::sharedInstance(), @@ -222,36 +203,43 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version, bool success, const QString &errorMessage) { - if (!m_isDownloading || version != m_downloadingVersion) { + if (version != m_downloadingVersion) { + qDebug() << "Ignoring download finished for version" << version + << "expected" << m_downloadingVersion; return; } - m_isDownloading = false; - if (!success) { showRetryUI("Failed to download developer disk image:\n" + errorMessage); return; } - // Download successful, now mount showStatus("Download complete. Mounting..."); - // mobile_image_mounter_error_t err = - // DevDiskManager::sharedInstance()->mountImage(version, m_device); + auto paths = DevDiskManager::sharedInstance()->getPathsForVersion(version); - // if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - // showStatus("Developer disk image mounted successfully"); - // finishWithSuccess(); - // } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - // showRetryUI( - // "Device is locked. Please unlock your device and try again."); - // } else { - // showRetryUI( - // "Failed to mount developer disk image.\n" - // "Please ensure the device is unlocked and using a genuine - // cable."); - // } + IdeviceFfiError *err = + ServiceManager::mountImage(m_device, paths.first.toStdString().c_str(), + paths.second.toStdString().c_str()); + + if (err == nullptr) { + return finishWithSuccess(); + } + + qDebug() << "onImageDownloadFinished:" << err->code + << QString::fromStdString(err->message); + + if (err->code == DeviceLockedMountErrorCode) { + showRetryUI( + "Device is locked. Please unlock your device and try again."); + + } else { + showRetryUI( + "Failed to mount developer disk image.\n" + "Please ensure the device is unlocked and using a genuine cable."); + } + idevice_error_free(err); } void DevDiskImageHelper::showRetryUI(const QString &errorMessage) diff --git a/src/devdiskimagehelper.h b/src/devdiskimagehelper.h index 9bfbedf..a589448 100644 --- a/src/devdiskimagehelper.h +++ b/src/devdiskimagehelper.h @@ -66,8 +66,6 @@ private: QPushButton *m_retryButton; QPushButton *m_cancelButton; - bool m_isDownloading; - bool m_isMounting; QString m_downloadingVersion; }; diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index 5404f58..93a72bb 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -606,17 +606,11 @@ void DevDiskImagesWidget::mountImage(const QString &version) .arg(m_deviceComboBox->currentText())); return updateUI(); } else if (info.err->code == DeviceLockedMountErrorCode) { - QMessageBox::critical(this, "Device Locked", - "The device is locked. Please unlock it and try" - " again."); - mounted_image_info_free(info); - return updateUI(); + /* Never returns DeviceLockedMountErrorCode when doing + image_mounter_lookup_image but maybe used in future */ } else if (info.err->code == NotFoundErrorCode) { - QMessageBox::critical( - this, "No Mounted Image", - "No developer disk image is mounted on the device."); - mounted_image_info_free(info); - return updateUI(); + // OK, no image mounted + qDebug() << "Mount image: no mounted image found"; } else { QMessageBox::critical( this, "Mount Check Failed", @@ -697,10 +691,6 @@ void DevDiskImagesWidget::closeEvent(QCloseEvent *event) event->accept(); } -// Toolbox clicked: "Developer Disk Images" -// terminate called after throwing an instance of 'std::logic_error' -// what(): basic_string: construction from null is not valid - void DevDiskImagesWidget::checkMountedImage() { iDescriptorDevice *currentDevice = diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 60d89a3..2dd3596 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -400,92 +400,80 @@ void DiskUsageWidget::fetchData() QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; - // if (!m_device || !m_device->provider) { - // result["error"] = "Invalid device."; - // return result; - // } + if (!m_device || !m_device->provider) { + result["error"] = "Invalid device."; + return result; + } - // // Pre-populate with known info - // result["totalCapacity"] = QVariant::fromValue( - // m_device->deviceInfo.diskInfo.totalDiskCapacity); - // result["freeSpace"] = QVariant::fromValue( - // m_device->deviceInfo.diskInfo.totalDataAvailable); - // result["systemUsage"] = QVariant::fromValue( - // m_device->deviceInfo.diskInfo.totalSystemCapacity); + // Pre-populate with known info + result["totalCapacity"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalDiskCapacity); + result["freeSpace"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalDataAvailable); + result["systemUsage"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalSystemCapacity); - // // Create provider wrapper from existing handle - // TODO:remove - // Provider provider = Provider::adopt(m_device->provider); + // Apps usage + uint64_t totalAppsSpace = 0; - // // Apps usage - // uint64_t totalAppsSpace = 0; - // auto instproxy_res = - // IdeviceFFI::InstallationProxy::connect(provider); if - // (instproxy_res.is_err()) { - // result["error"] = - // "Could not connect to installation proxy: " + - // QString::fromStdString(instproxy_res.unwrap_err().message); - // return result; - // } - // auto instproxy = std::move(instproxy_res.unwrap()); + InstallationProxyClientHandle *installationProxyClientHandle = nullptr; + installation_proxy_connect(m_device->provider, + &installationProxyClientHandle); + auto instproxy = + IdeviceFFI::InstallationProxy::adopt(installationProxyClientHandle); - // plist_t client_opts = plist_new_dict(); - // plist_dict_set_item(client_opts, "ApplicationType", - // plist_new_string("User")); + plist_t client_opts = plist_new_dict(); + plist_dict_set_item(client_opts, "ApplicationType", + plist_new_string("User")); - // plist_t return_attrs = plist_new_array(); - // plist_array_append_item(return_attrs, - // plist_new_string("StaticDiskUsage")); - // plist_array_append_item(return_attrs, - // plist_new_string("DynamicDiskUsage")); - // plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); + plist_t return_attrs = plist_new_array(); + plist_array_append_item(return_attrs, + plist_new_string("StaticDiskUsage")); + plist_array_append_item(return_attrs, + plist_new_string("DynamicDiskUsage")); + plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); - // auto apps_result = instproxy.browse(client_opts); - // if (apps_result.is_ok()) { - // auto apps = std::move(apps_result.unwrap()); - // for (const auto &app_info : apps) { - // plist_t static_usage = - // plist_dict_get_item(app_info, "StaticDiskUsage"); - // if (static_usage && - // plist_get_node_type(static_usage) == PLIST_UINT) { - // uint64_t static_size = 0; - // plist_get_uint_val(static_usage, &static_size); - // totalAppsSpace += static_size; - // } + auto apps_result = instproxy.browse(client_opts); + if (apps_result.is_ok()) { + auto apps = std::move(apps_result.unwrap()); + for (const auto &app_info : apps) { + plist_t static_usage = + plist_dict_get_item(app_info, "StaticDiskUsage"); + if (static_usage && + plist_get_node_type(static_usage) == PLIST_UINT) { + uint64_t static_size = 0; + plist_get_uint_val(static_usage, &static_size); + totalAppsSpace += static_size; + } - // plist_t dynamic_usage = - // plist_dict_get_item(app_info, "DynamicDiskUsage"); - // if (dynamic_usage && - // plist_get_node_type(dynamic_usage) == PLIST_UINT) { - // uint64_t dynamic_size = 0; - // plist_get_uint_val(dynamic_usage, &dynamic_size); - // totalAppsSpace += dynamic_size; - // } - // } - // } - // result["appsUsage"] = QVariant::fromValue(totalAppsSpace); - // plist_free(client_opts); // client_opts is consumed by browse, but + plist_t dynamic_usage = + plist_dict_get_item(app_info, "DynamicDiskUsage"); + if (dynamic_usage && + plist_get_node_type(dynamic_usage) == PLIST_UINT) { + uint64_t dynamic_size = 0; + plist_get_uint_val(dynamic_usage, &dynamic_size); + totalAppsSpace += dynamic_size; + } + } + } + result["appsUsage"] = QVariant::fromValue(totalAppsSpace); + plist_free(client_opts); // client_opts is consumed by browse, but // Media usage - // uint64_t mediaSpace = 0; - // IdeviceFFI::Lockdown lockdown = - // IdeviceFFI::Lockdown::adopt(m_device->lockdown); - // auto itunes_info_res = - // lockdown.get_value("com.apple.mobile.iTunes", nullptr); - // if (itunes_info_res.is_ok()) { - // auto itunes_dict = std::move(itunes_info_res.unwrap()); - // if (itunes_dict) { - // plist_t media_node = - // plist_dict_get_item(itunes_dict, "MediaLibrarySize"); - // if (media_node && - // plist_get_node_type(media_node) == PLIST_UINT) { - // plist_get_uint_val(media_node, &mediaSpace); - // } - // } - // } - // result["mediaUsage"] = QVariant::fromValue(mediaSpace); + uint64_t mediaSpace = 0; + plist_t out = nullptr; + + IdeviceFfiError *err = lockdownd_get_value( + m_device->lockdown, "com.apple.mobile.iTunes", nullptr, &out); + if (!err && out) { + plist_t media_node = plist_dict_get_item(out, "MediaLibrarySize"); + if (media_node && plist_get_node_type(media_node) == PLIST_UINT) { + plist_get_uint_val(media_node, &mediaSpace); + } + } + result["mediaUsage"] = QVariant::fromValue(mediaSpace); return result; }); - // watcher->setFuture(future); + watcher->setFuture(future); } diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index c855088..3c5b1d8 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -52,18 +52,14 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) : QWidget{parent}, m_device(device), m_model(nullptr), - m_stackedWidget(nullptr), m_albumSelectionWidget(nullptr), - m_albumListView(nullptr), m_photoGalleryWidget(nullptr), - m_listView(nullptr), m_backButton(nullptr) + m_albumSelectionWidget(nullptr), m_albumListView(nullptr), + m_photoGalleryWidget(nullptr), m_listView(nullptr), m_backButton(nullptr) { m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(0, 0, 0, 0); - - // Setup controls at the top (outside of stacked widget) + m_loadingWidget = new ZLoadingWidget(true, this); setupControlsLayout(); - - // Create stacked widget for different views - m_stackedWidget = new QStackedWidget(this); + m_mainLayout->addWidget(m_loadingWidget); // Setup album selection view setupAlbumSelectionView(); @@ -72,11 +68,7 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) setupPhotoGalleryView(); // Add stacked widget to main layout - m_mainLayout->addWidget(m_stackedWidget); setLayout(m_mainLayout); - m_loadingWidget = new ZLoadingWidget(true, this); - m_stackedWidget->addWidget(m_loadingWidget); - m_stackedWidget->setCurrentWidget(m_loadingWidget); QVBoxLayout *errorLayout = new QVBoxLayout(); errorLayout->setAlignment(Qt::AlignCenter); @@ -84,16 +76,13 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) errorLabel->setStyleSheet("font-weight: bold; color: red;"); errorLayout->addWidget(errorLabel); m_retryButton = new QPushButton("Retry", this); + errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); + m_loadingWidget->setupErrorWidget(errorLayout); connect(m_retryButton, &QPushButton::clicked, this, [this]() { - m_stackedWidget->setCurrentWidget(m_loadingWidget); + m_loadingWidget->showLoading(); QTimer::singleShot(100, this, &GalleryWidget::reload); }); - errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); - m_errorWidget = new QWidget(); - m_errorWidget->setLayout(errorLayout); - m_stackedWidget->addWidget(m_errorWidget); - m_stackedWidget->setCurrentWidget(m_loadingWidget); setControlsEnabled(false); // Disable controls until album is selected } @@ -374,7 +363,7 @@ void GalleryWidget::setupAlbumSelectionView() layout->addWidget(m_albumListView); - m_stackedWidget->addWidget(m_albumSelectionWidget); + m_loadingWidget->setupContentWidget(m_albumSelectionWidget); connect(m_albumListView, &QListView::doubleClicked, this, [this](const QModelIndex &index) { @@ -417,7 +406,7 @@ void GalleryWidget::setupPhotoGalleryView() layout->addWidget(m_listView); // Add the photo gallery widget to stacked widget - m_stackedWidget->addWidget(m_photoGalleryWidget); + m_loadingWidget->setupAditionalWidget(m_photoGalleryWidget); // Connect double-click to open preview dialog connect(m_listView, &QListView::doubleClicked, this, @@ -445,7 +434,7 @@ void GalleryWidget::loadAlbumList(const AFCFileTree &dcimTree) { if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; - m_stackedWidget->setCurrentWidget(m_errorWidget); + m_loadingWidget->showError(); QMessageBox::warning(this, "Error", "Could not access DCIM directory on device."); return; @@ -480,7 +469,8 @@ void GalleryWidget::loadAlbumList(const AFCFileTree &dcimTree) } m_albumListView->setModel(albumModel); - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + m_loadingWidget->stop(); + m_loadingWidget->switchToWidget(m_albumSelectionWidget); } void GalleryWidget::onAlbumSelected(const QString &albumPath) @@ -507,8 +497,7 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) m_model->setAlbumPath(albumPath); // Switch to photo gallery view - m_stackedWidget->setCurrentWidget(m_photoGalleryWidget); - + m_loadingWidget->switchToWidget(m_photoGalleryWidget); // Enable controls and show back button setControlsEnabled(true); m_backButton->show(); @@ -522,7 +511,8 @@ void GalleryWidget::onBackToAlbums() } // Switch back to album selection view - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + m_loadingWidget->switchToWidget(m_albumSelectionWidget); + if (m_model) { m_model->clear(); } diff --git a/src/gallerywidget.h b/src/gallerywidget.h index d1976ee..2e152bb 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -77,9 +77,7 @@ private: // UI components QVBoxLayout *m_mainLayout; QHBoxLayout *m_controlsLayout; - QStackedWidget *m_stackedWidget; ZLoadingWidget *m_loadingWidget; - QWidget *m_errorWidget; QPushButton *m_retryButton; QPushButton *m_importButton; // Album selection view diff --git a/src/iDescriptor.h b/src/iDescriptor.h index be62981..1768d78 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -69,6 +69,7 @@ #define DeviceLockedMountErrorCode -21 #define NotFoundErrorCode -14 +#define ServiceNotFoundErrorCode -15 #define DISK_IMAGE_TYPE_DEVELOPER "Developer" #define HEARTBEAT_RETRY_LIMIT 2 @@ -217,7 +218,6 @@ struct iDescriptorDevice { std::recursive_mutex *mutex; ImageMounterHandle *imageMounter; std::shared_ptr diagRelay; - ScreenshotrClientHandle *screenshotrClient; LocationSimulationHandle *locationSimulation; }; @@ -231,7 +231,6 @@ struct iDescriptorInitDeviceResult { LockdowndClientHandle *lockdown; ImageMounterHandle *imageMounter; std::shared_ptr diagRelay; - ScreenshotrClientHandle *screenshotrClient; LocationSimulationHandle *locationSimulation; }; // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT @@ -414,8 +413,10 @@ struct AFCFileTree { std::string currentPath; }; -AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, - const std::string &path = "/"); +AFCFileTree +get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path = "/", + std::optional altAfc = std::nullopt); bool detect_jailbroken(AfcClientHandle *afc); @@ -438,8 +439,6 @@ init_idescriptor_device(const QString &udid, // bool shutdown(idevice_t device); -// TakeScreenshotResult take_screenshot(screenshotr_client_t shotr); - IdeviceFfiError *mount_dev_image(const iDescriptorDevice *device, const char *image_file, const char *signature_file); diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 89d8778..460d79a 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -22,6 +22,7 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "qprocessindicator.h" +#include "servicemanager.h" #include "zlineedit.h" #include #include @@ -36,7 +37,8 @@ #include AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, - const QString &version, QWidget *parent) + const QString &version, const QPixmap &icon, + QWidget *parent) : QGroupBox(parent), m_appName(appName), m_bundleId(bundleId), m_version(version), m_selected(false) { @@ -45,31 +47,7 @@ AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, setCursor(Qt::PointingHandCursor); setupUI(); - fetchAppIcon(); -} - -void AppTabWidget::fetchAppIcon() -{ - ::fetchAppIconFromApple( - m_networkManager, m_bundleId, [this](const QPixmap &pixmap) { - if (!pixmap.isNull()) { - QPixmap scaled = - pixmap.scaled(32, 32, Qt::KeepAspectRatioByExpanding, - Qt::SmoothTransformation); - QPixmap rounded(32, 32); - rounded.fill(Qt::transparent); - - QPainter painter(&rounded); - painter.setRenderHint(QPainter::Antialiasing); - QPainterPath path; - path.addRoundedRect(QRectF(0, 0, 32, 32), 8, 8); - painter.setClipPath(path); - painter.drawPixmap(0, 0, scaled); - painter.end(); - - m_iconLabel->setPixmap(rounded); - } - }); + m_iconLabel->setPixmap(icon); } void AppTabWidget::setSelected(bool selected) @@ -290,177 +268,132 @@ void InstalledAppsWidget::fetchInstalledApps() showErrorState("Invalid device"); return; } + // todo maybe clear m_watcher ? + QFuture future = QtConcurrent::run([this]() -> QVariantMap { + QVariantMap result; + QVariantList apps; - // QFuture future = QtConcurrent::run([this]() -> QVariantMap { - // QVariantMap result; - // QVariantList apps; + try { + InstallationProxyClientHandle *installationProxyClientHandle = + nullptr; + installation_proxy_connect(m_device->provider, + &installationProxyClientHandle); + auto instproxy = IdeviceFFI::InstallationProxy::adopt( + installationProxyClientHandle); - // // result["success"] = true; - // // result["apps"] = apps; - // // return result; + // Get both User and System apps + QStringList appTypes = {"User", "System"}; - // instproxy_client_t instproxy = nullptr; - // lockdownd_client_t lockdownClient = nullptr; - // lockdownd_service_descriptor_t lockdowndService = nullptr; + for (const QString &appType : appTypes) { + plist_t client_opts = plist_new_dict(); + plist_dict_set_item( + client_opts, "ApplicationType", + plist_new_string(appType.toUtf8().constData())); - // try { - // if (lockdownd_client_new_with_handshake( - // m_device->device, &lockdownClient, APP_LABEL) != - // LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not connect to lockdown service"; - // return result; - // } + plist_t return_attrs = plist_new_array(); + plist_array_append_item(return_attrs, + plist_new_string("CFBundleIdentifier")); + plist_array_append_item( + return_attrs, plist_new_string("CFBundleDisplayName")); + plist_array_append_item( + return_attrs, + plist_new_string("CFBundleShortVersionString")); + plist_array_append_item(return_attrs, + plist_new_string("CFBundleVersion")); + plist_array_append_item( + return_attrs, plist_new_string("UIFileSharingEnabled")); - // if (lockdownd_start_service( - // lockdownClient, "com.apple.mobile.installation_proxy", - // &lockdowndService) != LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not start installation proxy - // service"; lockdownd_client_free(lockdownClient); return - // result; - // } + plist_dict_set_item(client_opts, "ReturnAttributes", + return_attrs); - // if (instproxy_client_new(m_device->device, lockdowndService, - // &instproxy) != INSTPROXY_E_SUCCESS) { - // result["error"] = "Could not connect to installation proxy"; - // lockdownd_service_descriptor_free(lockdowndService); - // lockdownd_client_free(lockdownClient); - // return result; - // } + auto installedApps = instproxy.browse(client_opts); + if (installedApps.is_ok()) { + plist_t apps_plist; + installedApps.unwrap(); - // lockdownd_service_descriptor_free(lockdowndService); - // lockdowndService = nullptr; + // if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { + for (const auto &app_info : installedApps.unwrap()) { + if (!app_info) + continue; - // // Get both User and System apps - // QStringList appTypes = {"User", "System"}; + QVariantMap appData; - // for (const QString &appType : appTypes) { - // plist_t client_opts = plist_new_dict(); - // plist_dict_set_item( - // client_opts, "ApplicationType", - // plist_new_string(appType.toUtf8().constData())); + // Get bundle identifier + plist_t bundle_id = + plist_dict_get_item(app_info, "CFBundleIdentifier"); + if (bundle_id && + plist_get_node_type(bundle_id) == PLIST_STRING) { + char *bundle_id_str = nullptr; + plist_get_string_val(bundle_id, &bundle_id_str); + if (bundle_id_str) { + appData["bundleId"] = QString(bundle_id_str); + free(bundle_id_str); + } + } - // plist_t return_attrs = plist_new_array(); - // plist_array_append_item(return_attrs, - // plist_new_string("CFBundleIdentifier")); - // plist_array_append_item( - // return_attrs, plist_new_string("CFBundleDisplayName")); - // plist_array_append_item( - // return_attrs, - // plist_new_string("CFBundleShortVersionString")); - // plist_array_append_item(return_attrs, - // plist_new_string("CFBundleVersion")); - // plist_array_append_item( - // return_attrs, plist_new_string("UIFileSharingEnabled")); + // Get display name + plist_t display_name = plist_dict_get_item( + app_info, "CFBundleDisplayName"); + if (display_name && + plist_get_node_type(display_name) == PLIST_STRING) { + char *display_name_str = nullptr; + plist_get_string_val(display_name, + &display_name_str); + if (display_name_str) { + appData["displayName"] = + QString(display_name_str); + free(display_name_str); + } + } - // plist_dict_set_item(client_opts, "ReturnAttributes", - // return_attrs); + // Get version + plist_t version = plist_dict_get_item( + app_info, "CFBundleShortVersionString"); + if (version && + plist_get_node_type(version) == PLIST_STRING) { + char *version_str = nullptr; + plist_get_string_val(version, &version_str); + if (version_str) { + appData["version"] = QString(version_str); + free(version_str); + } + } - // plist_t apps_plist = nullptr; - // if (instproxy_browse(instproxy, client_opts, &apps_plist) == - // INSTPROXY_E_SUCCESS && - // apps_plist) { - // if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { - // for (uint32_t i = 0; - // i < plist_array_get_size(apps_plist); i++) { - // plist_t app_info = - // plist_array_get_item(apps_plist, i); - // if (!app_info) - // continue; + // Get file sharing enabled status + plist_t file_sharing = plist_dict_get_item( + app_info, "UIFileSharingEnabled"); + if (file_sharing && plist_get_node_type(file_sharing) == + PLIST_BOOLEAN) { + uint8_t file_sharing_enabled = 0; + plist_get_bool_val(file_sharing, + &file_sharing_enabled); + appData["fileSharingEnabled"] = + (file_sharing_enabled != 0); + } else { + appData["fileSharingEnabled"] = false; + } - // QVariantMap appData; + appData["type"] = appType; - // // Get bundle identifier - // plist_t bundle_id = plist_dict_get_item( - // app_info, "CFBundleIdentifier"); - // if (bundle_id && plist_get_node_type(bundle_id) - // == - // PLIST_STRING) { - // char *bundle_id_str = nullptr; - // plist_get_string_val(bundle_id, - // &bundle_id_str); if (bundle_id_str) { - // appData["bundleId"] = - // QString(bundle_id_str); - // free(bundle_id_str); - // } - // } + if (!appData["bundleId"].toString().isEmpty()) { + apps.append(appData); + } + } - // // Get display name - // plist_t display_name = plist_dict_get_item( - // app_info, "CFBundleDisplayName"); - // if (display_name && - // plist_get_node_type(display_name) == - // PLIST_STRING) { - // char *display_name_str = nullptr; - // plist_get_string_val(display_name, - // &display_name_str); - // if (display_name_str) { - // appData["displayName"] = - // QString(display_name_str); - // free(display_name_str); - // } - // } + plist_free(apps_plist); + } + plist_free(client_opts); + } + result["apps"] = apps; + result["success"] = true; + } catch (const std::exception &e) { + result["error"] = QString("Exception: %1").arg(e.what()); + } - // // Get version - // plist_t version = plist_dict_get_item( - // app_info, "CFBundleShortVersionString"); - // if (version && - // plist_get_node_type(version) == PLIST_STRING) - // { char *version_str = nullptr; - // plist_get_string_val(version, &version_str); - // if (version_str) { - // appData["version"] = - // QString(version_str); free(version_str); - // } - // } + return result; + }); - // // Get file sharing enabled status - // plist_t file_sharing = plist_dict_get_item( - // app_info, "UIFileSharingEnabled"); - // if (file_sharing && - // plist_get_node_type(file_sharing) == - // PLIST_BOOLEAN) { - // uint8_t file_sharing_enabled = 0; - // plist_get_bool_val(file_sharing, - // &file_sharing_enabled); - // appData["fileSharingEnabled"] = - // (file_sharing_enabled != 0); - // } else { - // appData["fileSharingEnabled"] = false; - // } - - // appData["type"] = appType; - - // if (!appData["bundleId"].toString().isEmpty()) { - // apps.append(appData); - // } - // } - // } - // plist_free(apps_plist); - // } - // plist_free(client_opts); - // } - - // instproxy_client_free(instproxy); - // lockdownd_client_free(lockdownClient); - - // result["apps"] = apps; - // result["success"] = true; - - // } catch (const std::exception &e) { - // if (instproxy) - // instproxy_client_free(instproxy); - // if (lockdownClient) - // lockdownd_client_free(lockdownClient); - // if (lockdowndService) - // lockdownd_service_descriptor_free(lockdowndService); - - // result["error"] = QString("Exception: %1").arg(e.what()); - // } - - // return result; - // }); - - // m_watcher->setFuture(future); + m_watcher->setFuture(future); } void InstalledAppsWidget::onAppsDataReady() @@ -498,6 +431,18 @@ void InstalledAppsWidget::onAppsDataReady() m_appTabs.clear(); m_selectedTab = nullptr; + // fetch icon from springboard service + SpringBoardServicesClientHandle *springboardClient = nullptr; + IdeviceFfiError *err = nullptr; + err = springboard_services_connect(m_device->provider, &springboardClient); + + if (err != nullptr) { + qDebug() << "Error connecting to SpringBoard services:" + << QString::fromUtf8(err->message); + } else { + qDebug() << "Successfully connected to SpringBoard services."; + } + // Create tabs for each app for (const QVariant &appVariant : apps) { QVariantMap appData = appVariant.toMap(); @@ -523,24 +468,41 @@ void InstalledAppsWidget::onAppsDataReady() tabName += " (System)"; } - createAppTab(tabName, bundleId, version); - } + if (springboardClient) { + void *out_result; + size_t out_result_len; - // m_contentLabel->setText( - // QString("Found %1 installed apps").arg(apps.count())); + err = springboard_services_get_icon(springboardClient, + bundleId.toUtf8().constData(), + &out_result, &out_result_len); + if (err != nullptr) { + qWarning() << "Error getting icon for" << bundleId << ":" + << QString::fromUtf8(err->message); + createAppTab(tabName, bundleId, version); + } else { + QByteArray byteArray(reinterpret_cast(out_result), + static_cast(out_result_len)); + QImage image; + image.loadFromData(byteArray); + QPixmap pixmap = QPixmap::fromImage(image); + createAppTab(tabName, bundleId, version, pixmap); + } + } - // Select first tab if available - if (!m_appTabs.isEmpty()) { - selectAppTab(m_appTabs.first()); + // Select first tab if available + if (!m_appTabs.isEmpty()) { + selectAppTab(m_appTabs.first()); + } } } void InstalledAppsWidget::createAppTab(const QString &appName, const QString &bundleId, - const QString &version) + const QString &version, + const QPixmap &icon) { AppTabWidget *tabWidget = - new AppTabWidget(appName, bundleId, version, this); + new AppTabWidget(appName, bundleId, version, icon, this); connect(tabWidget, &AppTabWidget::clicked, this, &InstalledAppsWidget::onAppTabClicked); @@ -575,6 +537,7 @@ void InstalledAppsWidget::selectAppTab(AppTabWidget *tab) QString bundleId = tab->getBundleId(); // Load app container data + // FIXME: handle quickly repeated selections loadAppContainer(bundleId); } @@ -635,188 +598,138 @@ void InstalledAppsWidget::loadAppContainer(const QString &bundleId) m_containerLayout->addWidget(loadingWidget); - // QFuture future = QtConcurrent::run([this, bundleId]() - // -> QVariantMap { - // QVariantMap result; + QFuture future = + QtConcurrent::run([this, bundleId]() -> QVariantMap { + QVariantMap result; + IdeviceFfiError *err = nullptr; + HouseArrestClientHandle *houseArrestClient = nullptr; + AfcClientHandle *afcClient = nullptr; - // afc_client_t afcClient = nullptr; - // lockdownd_client_t lockdownClient = nullptr; - // lockdownd_service_descriptor_t lockdowndService = nullptr; - // house_arrest_client_t houseArrestClient = nullptr; - // try { - // if (lockdownd_client_new_with_handshake( - // m_device->device, &lockdownClient, APP_LABEL) != - // LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not connect to lockdown service"; - // return result; - // } + try { + err = house_arrest_client_connect(m_device->provider, + &houseArrestClient); - // if (lockdownd_start_service( - // lockdownClient, "com.apple.mobile.house_arrest", - // &lockdowndService) != LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not start house arrest service"; - // lockdownd_client_free(lockdownClient); - // return result; - // } + if (err != nullptr) { + qDebug() + << "Error connecting to House Arrest for" << bundleId + << ":" << QString::fromUtf8(err->message); + result["error"] = + QString("Error connecting to House Arrest: %1") + .arg(QString::fromUtf8(err->message)); + return result; + } - // if (house_arrest_client_new(m_device->device, lockdowndService, - // &houseArrestClient) != - // HOUSE_ARREST_E_SUCCESS) { - // result["error"] = "Could not connect to house arrest"; - // lockdownd_service_descriptor_free(lockdowndService); - // lockdownd_client_free(lockdownClient); - // return result; - // } + err = house_arrest_vend_documents(houseArrestClient, + bundleId.toUtf8().constData(), + &afcClient); + if (err != nullptr) { + qDebug() << "Error vending documents for" << bundleId << ":" + << QString::fromUtf8(err->message); + result["error"] = QString("Error vending documents: %1") + .arg(QString::fromUtf8(err->message)); + house_arrest_client_free(houseArrestClient); + return result; + } - // lockdownd_service_descriptor_free(lockdowndService); - // lockdowndService = nullptr; + char **dirs = nullptr; + size_t count = 0; - // // Send vendor container command - // if (house_arrest_send_command( - // houseArrestClient, "VendDocuments", - // // if (house_arrest_send_command(houseArrestClient, - // // "VendDocuments", - // bundleId.toUtf8().constData()) != HOUSE_ARREST_E_SUCCESS) - // { - // result["error"] = "Could not send VendDocuments command"; - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + // // Use safe wrapper to read directory + // IdeviceFfiError *err = ServiceManager::safeAfcReadDirectory( + // m_device, "/Documents", &dirs, count, afcClient); - // // Get result - // plist_t dict = nullptr; - // if (house_arrest_get_result(houseArrestClient, &dict) != - // HOUSE_ARREST_E_SUCCESS || - // !dict) { - // result["error"] = "App container not available for this app"; - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + // if (err != nullptr) { + // qDebug() << "Error reading Documents dir:" + // << QString::fromUtf8(err->message); + // result["error"] = QString("Error reading Documents dir: + // %1") + // .arg(QString::fromUtf8(err->message)); + // if (afcClient) + // afc_client_free(afcClient); + // if (houseArrestClient) + // house_arrest_client_free(houseArrestClient); + // return result; + // } - // // Check for error in response - // plist_t error_node = plist_dict_get_item(dict, "Error"); - // if (error_node) { - // char *error_str = nullptr; - // plist_get_string_val(error_node, &error_str); - // if (error_str) { - // result["error"] = - // QString("Container access denied: - // %1").arg(error_str); - // free(error_str); - // } else { - // result["error"] = "Container access denied"; - // } - // plist_free(dict); - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + // QStringList files; + // if (dirs) { + // for (int i = 0; dirs[i]; i++) { + // QString fileName = QString::fromUtf8(dirs[i]); + // if (fileName != "." && fileName != "..") { + // qDebug() << "Found file:" << fileName; + // files.append(fileName); + // } + // } + // } - // plist_free(dict); + // free_directory_listing(dirs, count); + // qDebug() << "Total files found:" << files.size(); + // result["files"] = files; + result["afcClient"] = + QVariant::fromValue(reinterpret_cast(afcClient)); + result["houseArrestClient"] = QVariant::fromValue( + reinterpret_cast(houseArrestClient)); + result["success"] = true; - // // Get AFC client for file access - // if (afc_client_new_from_house_arrest_client( - // houseArrestClient, &afcClient) != AFC_E_SUCCESS) { - // result["error"] = - // "Could not create AFC client for app container"; - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + } catch (const std::exception &e) { + qDebug() << "Exception while loading app container:" + << e.what(); + if (afcClient) + afc_client_free(afcClient); + if (houseArrestClient) + house_arrest_client_free(houseArrestClient); - // // List root directory contents - // char **list = nullptr; - // if (afc_read_directory(afcClient, "/Documents", &list) != - // AFC_E_SUCCESS) { - // result["error"] = "Could not read app container directory"; - // afc_client_free(afcClient); - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + result["error"] = QString("Exception: %1").arg(e.what()); + } - // QStringList files; - // if (list) { - // for (int i = 0; list[i]; i++) { - // QString fileName = QString::fromUtf8(list[i]); - // if (fileName != "." && fileName != "..") { - // qDebug() << "Found file:" << fileName; - // files.append(fileName); - // } - // } - // afc_dictionary_free(list); - // } - // result["files"] = files; - // result["afcClient"] = - // QVariant::fromValue(reinterpret_cast(afcClient)); - // result["houseArrestClient"] = QVariant::fromValue( - // reinterpret_cast(houseArrestClient)); - // result["success"] = true; + return result; + }); - // lockdownd_client_free(lockdownClient); - - // } catch (const std::exception &e) { - // if (afcClient) - // afc_client_free(afcClient); - // if (houseArrestClient) - // house_arrest_client_free(houseArrestClient); - // if (lockdownClient) - // lockdownd_client_free(lockdownClient); - // if (lockdowndService) - // lockdownd_service_descriptor_free(lockdowndService); - - // result["error"] = QString("Exception: %1").arg(e.what()); - // } - - // return result; - // }); - - // m_containerWatcher->setFuture(future); + m_containerWatcher->setFuture(future); } void InstalledAppsWidget::onContainerDataReady() { QVariantMap result = m_containerWatcher->result(); - // // todo - // // Clear loading state - // QLayoutItem *item; - // while ((item = m_containerLayout->takeAt(0)) != nullptr) { - // if (item->widget()) { - // item->widget()->deleteLater(); - // } - // delete item; - // } + // todo + // Clear loading state + QLayoutItem *item; + while ((item = m_containerLayout->takeAt(0)) != nullptr) { + if (item->widget()) { + item->widget()->deleteLater(); + } + delete item; + } - // if (!result.value("success", false).toBool()) { - // qDebug() << "Error loading app container:" - // << result.value("error").toString(); - // QLabel *errorLabel = new QLabel("No data available for this app"); - // errorLabel->setAlignment(Qt::AlignCenter); - // m_containerLayout->addWidget(errorLabel); - // return; - // } + if (!result.value("success", false).toBool()) { + qDebug() << "Error loading app container:" + << result.value("error").toString(); + QLabel *errorLabel = new QLabel("No data available for this app"); + errorLabel->setAlignment(Qt::AlignCenter); + m_containerLayout->addWidget(errorLabel); + return; + } - // // Get the AFC clients from the result and store them as member variables - // m_houseArrestAfcClient = reinterpret_cast( - // result.value("afcClient").value()); - // m_houseArrestClient = reinterpret_cast( - // result.value("houseArrestClient").value()); + // Get the AFC clients from the result and store them as member + // variables + m_houseArrestAfcClient = reinterpret_cast( + result.value("afcClient").value()); + m_houseArrestClient = reinterpret_cast( + result.value("houseArrestClient").value()); - // if (!m_houseArrestAfcClient) { - // QLabel *errorLabel = - // new QLabel("Failed to get AFC client for app container"); - // m_containerLayout->addWidget(errorLabel); - // return; - // } + if (!m_houseArrestAfcClient) { + QLabel *errorLabel = + new QLabel("Failed to get AFC client for app container"); + m_containerLayout->addWidget(errorLabel); + return; + } - // // Create AfcExplorerWidget with the house arrest AFC client - // AfcExplorerWidget *explorer = new AfcExplorerWidget( - // m_device, true, m_houseArrestAfcClient, "/Documents", this); - // explorer->setStyleSheet("border :none;"); - // m_containerLayout->addWidget(explorer); + // Create AfcExplorerWidget with the house arrest AFC client + AfcExplorerWidget *explorer = new AfcExplorerWidget( + m_device, true, m_houseArrestAfcClient, "/Documents", this); + explorer->setStyleSheet("border :none;"); + m_containerLayout->addWidget(explorer); } void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) @@ -828,15 +741,16 @@ void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) void InstalledAppsWidget::cleanupHouseArrestClients() { - // if (m_houseArrestAfcClient) { - // afc_client_free(m_houseArrestAfcClient); - // m_houseArrestAfcClient = nullptr; - // } + if (m_houseArrestAfcClient) { + // FIXME: create an issue afc_client_free crashes + // afc_client_free(m_houseArrestAfcClient); + m_houseArrestAfcClient = nullptr; + } - // if (m_houseArrestClient) { - // house_arrest_client_free(m_houseArrestClient); - // m_houseArrestClient = nullptr; - // } + if (m_houseArrestClient) { + house_arrest_client_free(m_houseArrestClient); + m_houseArrestClient = nullptr; + } } void InstalledAppsWidget::createLeftPanel() diff --git a/src/installedappswidget.h b/src/installedappswidget.h index 77ad366..596b399 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -49,7 +49,8 @@ class AppTabWidget : public QGroupBox public: AppTabWidget(const QString &appName, const QString &bundleId, - const QString &version, QWidget *parent = nullptr); + const QString &version, const QPixmap &icon = QPixmap(), + QWidget *parent = nullptr); void setSelected(bool selected); bool isSelected() const { return m_selected; } @@ -66,7 +67,6 @@ protected: void mousePressEvent(QMouseEvent *event) override; private: - void fetchAppIcon(); void setupUI(); QString m_appName; @@ -105,7 +105,7 @@ private: void createRightPanel(); void fetchInstalledApps(); void createAppTab(const QString &appName, const QString &bundleId, - const QString &version); + const QString &version, const QPixmap &icon = QPixmap()); void showLoadingState(); void showErrorState(const QString &error); void selectAppTab(AppTabWidget *tab); @@ -132,8 +132,8 @@ private: QFutureWatcher *m_watcher; QFutureWatcher *m_containerWatcher; QSplitter *m_splitter; - // house_arrest_client_t m_houseArrestClient = nullptr; - // afc_client_t m_houseArrestAfcClient = nullptr; + HouseArrestClientHandle *m_houseArrestClient = nullptr; + AfcClientHandle *m_houseArrestAfcClient = nullptr; // App data storage QList m_appTabs; AppTabWidget *m_selectedTab = nullptr; diff --git a/src/livescreenwidget.cpp b/src/livescreenwidget.cpp index 30f6aee..b0338b2 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -29,9 +29,10 @@ #include #include #include + // todo add a retry button when failed LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) - : QWidget{parent}, m_device(device), m_timer(nullptr), m_fps(15) + : QWidget{parent}, m_device(device) { setWindowTitle("Live Screen - iDescriptor"); @@ -71,19 +72,9 @@ LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) m_imageLabel = new QLabel(); m_imageLabel->setMinimumSize(300, 600); m_imageLabel->setAlignment(Qt::AlignCenter); - m_imageLabel->setFrameStyle(QFrame::Box | QFrame::Plain); mainLayout->addWidget(m_imageLabel, 1); - // Timer for periodic screenshots - m_timer = new QTimer(this); - m_timer->setInterval(1000 / m_fps); - connect(m_timer, &QTimer::timeout, this, - &LiveScreenWidget::updateScreenshot); - - startCapturing(); - - // Defer the initialization to allow the main widget to show first - // QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); + QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); } void LiveScreenWidget::startInitialization() @@ -117,153 +108,55 @@ void LiveScreenWidget::startInitialization() LiveScreenWidget::~LiveScreenWidget() { - if (m_timer) { - m_timer->stop(); + if (m_thread) { + m_thread->requestInterruption(); + m_thread->wait(); + m_thread->deleteLater(); + m_thread = nullptr; + } + if (m_screenshotrClient) { + screenshotr_client_free(m_screenshotrClient); + m_screenshotrClient = nullptr; } - - // if (m/*_shotrClient) { - // screenshotr_client_free(m_shotrClient); - // m_shotrClient = nullptr; - // }*/ } bool LiveScreenWidget::initializeScreenshotService(bool notify) { - return true; - // lockdownd_client_t lockdownClient = nullptr; - // lockdownd_service_descriptor_t service = nullptr; - - // try { - // m_statusLabel->setText("Connecting to screenshot service..."); - - // IdeviceFfiError * = - // screenshotr_take_screenshot(m_device->screenshotrClient,) - - // if (ldret != LOCKDOWN_E_SUCCESS) { - // m_statusLabel->setText("Failed to connect to lockdown service"); - // if (notify) - // QMessageBox::critical(this, "Connection Failed", - // "Could not connect to lockdown - // service.\n" "Error code: " + - // QString::number(ldret)); - // return false; - // } - - // lockdownd_error_t lerr = lockdownd_start_service( - // lockdownClient, SCREENSHOTR_SERVICE_NAME, &service); - - // lockdownd_client_free(lockdownClient); - // lockdownClient = nullptr; - - // if (lerr != LOCKDOWN_E_SUCCESS) { - // m_statusLabel->setText("Failed to start screenshot service"); - // qDebug() << lerr << "lockdownd_start_service"; - // if (notify) - // QMessageBox::critical( - // this, "Service Failed", - // "Could not start screenshot service on device.\n" - // "Please ensure the developer disk image is properly " - // "mounted."); - // if (service) { - // lockdownd_service_descriptor_free(service); - // } - // return false; - // } - - // screenshotr_error_t screrr = - // screenshotr_client_new(m_device->device, service, - // &m_shotrClient); - - // lockdownd_service_descriptor_free(service); - // service = nullptr; - // qDebug() << screrr << "screenshotr_client_new"; - // if (screrr != SCREENSHOTR_E_SUCCESS) { - // m_statusLabel->setText("Failed to create screenshot client"); - // if (notify) - // QMessageBox::critical(this, "Client Failed", - // "Could not create screenshot client.\n" - // "Error code: " + - // QString::number(screrr)); - // return false; - // } - - // // Successfully initialized, start capturing - // m_statusLabel->setText("Capturing"); - // startCapturing(); - // return true; - // } catch (const std::exception &e) { - // m_statusLabel->setText("Exception occurred"); - // if (notify) - // QMessageBox::critical( - // this, "Exception", - // QString("Exception occurred: %1").arg(e.what())); - - // if (lockdownClient) { - // lockdownd_client_free(lockdownClient); - // } - // if (service) { - // lockdownd_service_descriptor_free(service); - // } - // } + try { + m_statusLabel->setText("Connecting to screenshot service..."); + IdeviceFfiError *err = + screenshotr_connect(m_device->provider, &m_screenshotrClient); + if (err) { + qDebug() << "Failed to create Screenshotr client"; + return false; // proceed to mount image + } + // Successfully initialized, start capturing + m_statusLabel->setText("Capturing"); + startCapturing(); + return true; + } catch (const std::exception &e) { + m_statusLabel->setText("Exception occurred"); + if (notify) + QMessageBox::critical( + this, "Exception", + QString("Exception occurred: %1").arg(e.what())); + } } void LiveScreenWidget::startCapturing() { - // if (!m_shotrClient) { - // qWarning() - // << "Cannot start capturing: screenshot client not initialized"; - // return; - // } - - if (m_timer) { - m_timer->start(); - qDebug() << "Started capturing"; + if (!m_screenshotrClient) { + qWarning() + << "Cannot start capturing: screenshot client not initialized"; + return; } -} -void LiveScreenWidget::updateScreenshot() -{ - // if (!m_shotrClient) { - // qWarning() << "Screenshot client not initialized"; - // return; - // } - qDebug() << "Updating screenshot..."; - // FIXME: move to services - try { - // TakeScreenshotResult result = take_screenshot(m_shotrClient); - ScreenshotData screenshot; - IdeviceFfiError *err = screenshotr_take_screenshot( - m_device->screenshotrClient, &screenshot); - if (!err && screenshot.data && screenshot.length > 0) { - qDebug() << "Screenshot captured, size:" << screenshot.length; - // QImage img(screenshot.data, // data - // static_cast(screenshot.length), // width - // 1, // height - // QImage::Format_ARGB32); // format - - QByteArray byteArray( - reinterpret_cast(screenshot.data), - static_cast(screenshot.length)); - QImage image; - image.loadFromData(byteArray); - QPixmap pixmap = QPixmap::fromImage(image); - m_imageLabel->setPixmap(pixmap.scaled(m_imageLabel->size(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - } else { - qDebug() << "Failed to capture screenshot"; - } - - // if (result.success && !result.img.isNull()) { - // QPixmap pixmap = QPixmap::fromImage(result.img); - // m_imageLabel->setPixmap(pixmap.scaled(m_imageLabel->size(), - // Qt::KeepAspectRatio, - // Qt::SmoothTransformation)); - // } else { - // qWarning() << "Failed to capture screenshot"; - // } - } catch (const std::exception &e) { - qWarning() << "Exception in updateScreenshot:" << e.what(); - m_statusLabel->setText("Error capturing screenshot"); - } -} + m_thread = new ScreenshotrThread(m_screenshotrClient, m_device, this); + connect(m_thread, &ScreenshotrThread::screenshotCaptured, this, + [this](const QPixmap &pixmap) { + m_imageLabel->setPixmap( + pixmap.scaled(m_imageLabel->size(), Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + }); + m_thread->start(); +} \ No newline at end of file diff --git a/src/livescreenwidget.h b/src/livescreenwidget.h index bc4c1f8..f3714ac 100644 --- a/src/livescreenwidget.h +++ b/src/livescreenwidget.h @@ -21,10 +21,57 @@ #define LIVESCREEN_H #include "iDescriptor.h" +#include "servicemanager.h" #include +#include #include #include +class ScreenshotrThread : public QThread +{ + Q_OBJECT +public: + explicit ScreenshotrThread(ScreenshotrClientHandle *client, + iDescriptorDevice *device, + QObject *parent = nullptr) + : QThread(parent), m_device(device), m_client(client), m_fps(15) + { + } + +protected: + void run() override + { + qDebug() << "Started capturing"; + + // Thread loop to continuously fetch screenshots + while (!isInterruptionRequested()) { + ScreenshotData screenshotData; + IdeviceFfiError *err = ServiceManager::takeScreenshot( + m_device, m_client, &screenshotData); + if (!err && screenshotData.data && screenshotData.length > 0) { + QByteArray byteArray( + reinterpret_cast(screenshotData.data), + static_cast(screenshotData.length)); + QImage image; + image.loadFromData(byteArray); + QPixmap pixmap = QPixmap::fromImage(image); + emit screenshotCaptured(pixmap); + screenshotr_screenshot_free(screenshotData); + } else { + qDebug() << "Failed to capture screenshot"; + } + msleep(1000 / m_fps); // Capture at ~m_fps FPS + } + } +signals: + void screenshotCaptured(const QPixmap &pixmap); + +private: + ScreenshotrClientHandle *m_client; + int m_fps; + iDescriptorDevice *m_device; +}; + class LiveScreenWidget : public QWidget { Q_OBJECT @@ -39,13 +86,13 @@ private: void startCapturing(); iDescriptorDevice *m_device; - QTimer *m_timer; QLabel *m_imageLabel; QLabel *m_statusLabel; - int m_fps; + ScreenshotrClientHandle *m_screenshotrClient = nullptr; + ScreenshotrThread *m_thread = nullptr; private: - void startInitialization(); // Add this line + void startInitialization(); }; #endif // LIVESCREEN_H diff --git a/src/photomodel.cpp b/src/photomodel.cpp index f5304f9..c961fda 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -683,7 +683,9 @@ void PhotoModel::populatePhotoPaths() qDebug() << "Photo directory C string:" << photoDir; char **files = nullptr; - err = ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files); + size_t count = 0; + err = + ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files, count); if (err) { qDebug() << "Failed to read photo directory:" << photoDir << "Error:" << err->message; @@ -711,8 +713,7 @@ void PhotoModel::populatePhotoPaths() m_allPhotos.append(info); } } - // free(files); - // afc_dictionary_free(files); + free_directory_listing(files, count); } // Apply initial filtering and sorting, which will also reset the model @@ -743,16 +744,11 @@ void PhotoModel::applyFilterAndSort() { beginResetModel(); - // int i = 0; // Filter photos m_photos.clear(); for (const PhotoInfo &info : m_allPhotos) { if (matchesFilter(info)) { m_photos.append(info); - // if (i == 3) { - // break; - // } - // i++; } } diff --git a/src/querymobilegestaltwidget.cpp b/src/querymobilegestaltwidget.cpp index 6365f71..ba01781 100644 --- a/src/querymobilegestaltwidget.cpp +++ b/src/querymobilegestaltwidget.cpp @@ -23,19 +23,20 @@ #include #include #include +#include #include QueryMobileGestaltWidget::QueryMobileGestaltWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) { - // todo: not tested on iOS 17,18 but it's deprecated on iOS 26 + // FIXME: not tested on iOS 17,18 but it's deprecated on iOS 26 // i am assuming it won't work if (m_device->deviceInfo.parsedDeviceVersion.major > 16) { QMessageBox::warning(this, "Unsupported iOS Version", "Apple deprecated this protocol for Devices " "running iOS 17 or later"); - close(); + QTimer::singleShot(0, this, &QWidget::close); return; } setupUI(); diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index f814586..4c07e13 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -21,12 +21,10 @@ #include "iDescriptor.h" #include -IdeviceFfiError * -ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device, - const char *path, char ***dirs, - std::optional altAfc) +IdeviceFfiError *ServiceManager::safeAfcReadDirectory( + const iDescriptorDevice *device, const char *path, char ***dirs, + size_t count, std::optional altAfc) { - size_t count = 0; return executeAfcClientOperation( device, [path, dirs, &count, device](AfcClientHandle *client) { @@ -121,32 +119,44 @@ ServiceManager::safeAfcFileTell(const iDescriptorDevice *device, handle); } -QByteArray -ServiceManager::safeReadAfcFileToByteArray(const iDescriptorDevice *device, - const char *path) +QByteArray ServiceManager::safeReadAfcFileToByteArray( + const iDescriptorDevice *device, const char *path, + std::optional altAfc) { - return executeOperation(device, [path, device]() -> QByteArray { - return read_afc_file_to_byte_array(device, path); - }); + return executeOperation( + device, + [path, device]() -> QByteArray { + return read_afc_file_to_byte_array(device, path); + }, + altAfc); } -AFCFileTree ServiceManager::safeGetFileTree(const iDescriptorDevice *device, - const std::string &path, - bool checkDir) +AFCFileTree +ServiceManager::safeGetFileTree(const iDescriptorDevice *device, + const std::string &path, bool checkDir, + std::optional altAfc) { return executeOperation( - device, [path, device, checkDir]() -> AFCFileTree { - return get_file_tree(device, checkDir, path); - }); + device, + [path, device, checkDir](AfcClientHandle *afc) -> AFCFileTree { + return get_file_tree(device, checkDir, path, afc); + }, + altAfc); } QFuture ServiceManager::getFileTreeAsync(const iDescriptorDevice *device, - const std::string &path, bool checkDir) + const std::string &path, bool checkDir, + std::optional altAfc) { - return QtConcurrent::run([device, path, checkDir]() { - return get_file_tree(device, checkDir, path); - }); + return executeOperation>( + device, + [device, path, checkDir]() -> QFuture { + return QtConcurrent::run([device, path, checkDir]() { + return get_file_tree(device, checkDir, path); + }); + }, + altAfc); } MountedImageInfo @@ -320,3 +330,51 @@ IdeviceFfiError *ServiceManager::exportFileToPath( return nullptr; // Success }); } + +IdeviceFfiError * +ServiceManager::takeScreenshot(const iDescriptorDevice *device, + ScreenshotrClientHandle *screenshotrClient, + ScreenshotData *screenshot) +{ + return executeOperation( + device, [device, screenshotrClient, screenshot]() -> IdeviceFfiError * { + return screenshotr_take_screenshot(screenshotrClient, screenshot); + }); +} + +// requires iOS 17+ +IdeviceFfiError *ServiceManager::enableDevMode(const iDescriptorDevice *device) +{ + return executeOperation( + device, [device]() -> IdeviceFfiError * { + IdeviceFfiError *err = nullptr; + AmfiClientHandle *amfi = nullptr; + err = amfi_connect(device->provider, &amfi); + if (err == NULL) { + // Show developer mode option in settings + err = amfi_reveal_developer_mode_option_in_ui(amfi); + if (err != NULL) { + return err; + } + qDebug() << "Developer mode option revealed in UI."; + // // Enable developer mode (triggers reboot) + err = amfi_accept_developer_mode(amfi); + if (err != NULL) { + qDebug() << "Failed to accept developer mode." + << err->message << "Code:" << err->code; + return err; + } + + err = amfi_enable_developer_mode(amfi); + + if (err != NULL) { + qDebug() << "Failed to enable developer mode." + << err->message << "Code:" << err->code; + return err; + } + qDebug() << "Developer mode enabled, device will reboot."; + // // After reboot, accept developer mode + } + return err; + }); +} \ No newline at end of file diff --git a/src/servicemanager.h b/src/servicemanager.h index 695684e..1c892f5 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -216,7 +216,7 @@ public: // Specific AFC operation wrappers static IdeviceFfiError *safeAfcReadDirectory( const iDescriptorDevice *device, const char *path, char ***dirs, - std::optional altAfc = std::nullopt); + size_t count, std::optional altAfc = std::nullopt); static IdeviceFfiError * safeAfcGetFileInfo(const iDescriptorDevice *device, const char *path, @@ -245,14 +245,17 @@ public: AfcFileHandle *handle, off_t *position); // Utility functions - static QByteArray - safeReadAfcFileToByteArray(const iDescriptorDevice *device, - const char *path); - static AFCFileTree safeGetFileTree(const iDescriptorDevice *device, - const std::string &path, bool checkDir); + static QByteArray safeReadAfcFileToByteArray( + const iDescriptorDevice *device, const char *path, + std::optional altAfc = std::nullopt); + static AFCFileTree + safeGetFileTree(const iDescriptorDevice *device, const std::string &path, + bool checkDir, + std::optional altAfc = std::nullopt); static QFuture getFileTreeAsync(const iDescriptorDevice *device, const std::string &path, - bool checkDir); + bool checkDir, + std::optional altAfc = std::nullopt); static MountedImageInfo getMountedImage(const iDescriptorDevice *device); static IdeviceFfiError *mountImage(const iDescriptorDevice *device, const char *image_file, @@ -271,6 +274,13 @@ public: const char *local_path, std::function progressCallback = nullptr, std::atomic *cancelRequested = nullptr); + + static IdeviceFfiError * + takeScreenshot(const iDescriptorDevice *device, + ScreenshotrClientHandle *screenshotrClient, + ScreenshotData *screenshot); + + static IdeviceFfiError *enableDevMode(const iDescriptorDevice *device); }; #endif // SERVICEMANAGER_H diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index a8d56f1..8c9d47a 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -79,9 +79,8 @@ void ToolboxWidget::setupUI() m_scrollArea = new QScrollArea(); m_scrollArea->setWidgetResizable(true); m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setStyleSheet( - "QScrollArea { background: transparent; border: none; }"); - m_scrollArea->viewport()->setStyleSheet("background: transparent;"); + m_scrollArea->setFrameStyle(QFrame::NoFrame); + m_scrollArea->viewport()->setAutoFillBackground(false); m_contentWidget = new QWidget(); QVBoxLayout *contentLayout = new QVBoxLayout(m_contentWidget); diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp index c94fbf8..198b161 100644 --- a/src/virtuallocationwidget.cpp +++ b/src/virtuallocationwidget.cpp @@ -22,6 +22,7 @@ #include "devdiskimagehelper.h" #include "devdiskmanager.h" #include "iDescriptor.h" +#include "servicemanager.h" #include "settingsmanager.h" #include #include @@ -140,9 +141,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent) // Register this object with QML context so QML can call our slots m_quickWidget->rootContext()->setContextProperty("cppHandler", this); - qDebug() << "QuickWidget status:" << m_quickWidget->status(); - qDebug() << "QuickWidget errors:" << m_quickWidget->errors(); - connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [this](const std::string &udid) { if (m_device->udid == udid) { @@ -347,20 +345,138 @@ void VirtualLocation::onApplyClicked() // }, // Qt::SingleShotConnection); // devDiskImageHelper->start(); - // FIXME: create issue for c bindings - IdeviceFfiError *err = location_simulation_set(m_device->locationSimulation, - latitude, longitude); - if (err != nullptr) { - QMessageBox::warning(this, "Error", - "Failed to set location on device:\n" + - QString::fromStdString(err->message)); - // idevice_ffi_error_free(err); - } else { - // SettingsManager::sharedInstance()->saveRecentLocation( - // latitude, longitude); - QMessageBox::information(this, "Success", - "Location applied successfully!"); + + int major = m_device->deviceInfo.parsedDeviceVersion.major; + + if (major < 17) { + QMessageBox::warning(this, "TODO", "TODO"); + m_applyButton->setEnabled(true); + return; } + + IdeviceFfiError *err = nullptr; + // Connect to CoreDeviceProxy + CoreDeviceProxyHandle *core_device = NULL; + err = core_device_proxy_connect(m_device->provider, &core_device); + if (err != NULL) { + fprintf(stderr, "Failed to connect to CoreDeviceProxy: [%d] %s", + err->code, err->message); + idevice_error_free(err); + } + + // Get server RSD port + uint16_t rsd_port; + err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port); + if (err != NULL) { + fprintf(stderr, "Failed to get server RSD port: [%d] %s", err->code, + err->message); + idevice_error_free(err); + core_device_proxy_free(core_device); + } + + // Create TCP adapter and connect to RSD port + AdapterHandle *adapter = NULL; + err = core_device_proxy_create_tcp_adapter(core_device, &adapter); + if (err != NULL) { + fprintf(stderr, "Failed to create TCP adapter: [%d] %s", err->code, + err->message); + idevice_error_free(err); + } + + // Connect to RSD port + ReadWriteOpaque *stream = NULL; + err = adapter_connect(adapter, rsd_port, &stream); + if (err != NULL) { + fprintf(stderr, "Failed to connect to RSD port: [%d] %s", err->code, + err->message); + idevice_error_free(err); + adapter_free(adapter); + } + + RsdHandshakeHandle *handshake = NULL; + err = rsd_handshake_new(stream, &handshake); + if (err != NULL) { + fprintf(stderr, "Failed to perform RSD handshake: [%d] %s", err->code, + err->message); + idevice_error_free(err); + // adapter_close(stream); + idevice_stream_free(stream); + adapter_free(adapter); + } + + // Create RemoteServerClient + RemoteServerHandle *remote_server = NULL; + err = remote_server_connect_rsd(adapter, handshake, &remote_server); + if (err != NULL) { + // needs dev mode + fprintf(stderr, "Failed to create remote server: [%d] %s", err->code, + err->message); + if (err->code == ServiceNotFoundErrorCode) { + auto res = QMessageBox::question( + this, "Enable Developer Mode?", + "Location Simulation service not found. Enable Developer " + "Mode on the device?", + QMessageBox::Yes | QMessageBox::No); + if (res == QMessageBox::Yes) { + IdeviceFfiError *devmodeErr = + ServiceManager::enableDevMode(m_device); + if (devmodeErr != NULL) { + QMessageBox::warning( + this, "Error", + QString("Failed to enable Developer Mode:\n%1") + .arg(devmodeErr->message)); + idevice_error_free(devmodeErr); + } else { + QMessageBox::information( + this, "Success", + "Developer Mode enabled successfully. Please try " + "applying the location again."); + } + } + + idevice_error_free(err); + adapter_free(adapter); + rsd_handshake_free(handshake); + } + + // Create LocationSimulationClient + LocationSimulationHandle *location_sim = NULL; + err = location_simulation_new(remote_server, &location_sim); + if (err != NULL) { + fprintf(stderr, + "Failed to create location simulation client: [%d] %s", + err->code, err->message); + idevice_error_free(err); + remote_server_free(remote_server); + } + + // Set location + err = location_simulation_set(location_sim, latitude, longitude); + if (err != NULL) { + fprintf(stderr, "Failed to set location: [%d] %s", err->code, + err->message); + idevice_error_free(err); + } else { + printf("Successfully set location to %.6f, %.6f\n", latitude, + longitude); + } + } + + // // FIXME: create issue for c bindings + // IdeviceFfiError *err = + // location_simulation_set(m_device->locationSimulation, + // latitude, longitude); + // if (err != nullptr) { + // QMessageBox::warning(this, "Error", + // "Failed to set location on device:\n" + + // QString::fromStdString(err->message)); + // // idevice_ffi_error_free(err); + // } else { + // // SettingsManager::sharedInstance()->saveRecentLocation( + // // latitude, longitude); + // QMessageBox::information(this, "Success", + // "Location applied successfully!"); + // } } void VirtualLocation::loadRecentLocations(QVBoxLayout *layout) diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp index 65debd0..fdbfd04 100644 --- a/src/zloadingwidget.cpp +++ b/src/zloadingwidget.cpp @@ -3,28 +3,106 @@ #include "qprocessindicator.h" #include #include +#include -ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) : QWidget{parent} +ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) + : QStackedWidget{parent}, m_loadingIndicator(new QProcessIndicator()) { - m_loadingIndicator = new QProcessIndicator(); m_loadingIndicator->setType(QProcessIndicator::line_rotate); m_loadingIndicator->setFixedSize(64, 32); if (start) { m_loadingIndicator->start(); } - QHBoxLayout *loadingLayout = new QHBoxLayout(); + // Create a proper container widget for the loading indicator + QWidget *loadingWidget = new QWidget(this); + QHBoxLayout *loadingLayout = new QHBoxLayout(loadingWidget); loadingLayout->setSpacing(1); - + loadingLayout->addStretch(); loadingLayout->addWidget(m_loadingIndicator); - setLayout(loadingLayout); + loadingLayout->addStretch(); + + addWidget(loadingWidget); // Loading widget at index 0 } -void ZLoadingWidget::stop() +void ZLoadingWidget::setupContentWidget(QWidget *contentWidget) +{ + addWidget(contentWidget); // Content widget at index 1 +} + +void ZLoadingWidget::setupContentWidget(QLayout *contentLayout) +{ + QWidget *contentWidget = new QWidget(); + contentWidget->setLayout(contentLayout); + + addWidget(contentWidget); // Content widget at index 1 +} + +void ZLoadingWidget::setupErrorWidget(QWidget *errorWidget) +{ + addWidget(errorWidget); // Error widget at index 2 +} + +void ZLoadingWidget::setupErrorWidget(QLayout *errorLayout) +{ + QWidget *errorWidget = new QWidget(); + errorWidget->setLayout(errorLayout); + + addWidget(errorWidget); // Error widget at index 2 +} + +void ZLoadingWidget::setupErrorWidget(const QString &errorMessage) +{ + QWidget *errorWidget = new QWidget(); + QVBoxLayout *errorLayout = new QVBoxLayout(errorWidget); + errorLayout->setAlignment(Qt::AlignCenter); + + QLabel *errorLabel = new QLabel(errorMessage); + errorLabel->setAlignment(Qt::AlignCenter); + errorLabel->setStyleSheet("QLabel { color: red; }"); + errorLayout->addWidget(errorLabel); + + addWidget(errorWidget); // Error widget at index 2 +} + +void ZLoadingWidget::setupAditionalWidget(QWidget *customWidget) +{ + addWidget(customWidget); +} + +void ZLoadingWidget::switchToWidget(QWidget *widget) +{ + int index = indexOf(widget); + if (index != -1) { + setCurrentIndex(index); + } +} + +void ZLoadingWidget::stop(bool showContent) { if (m_loadingIndicator) { m_loadingIndicator->stop(); } + if (showContent) { + // FIXME: dont use hardcoded index + setCurrentIndex(1); + } +} + +void ZLoadingWidget::showError() +{ + m_loadingIndicator->stop(); + // FIXME: dont use hardcoded index + setCurrentIndex(2); +} + +void ZLoadingWidget::showLoading() +{ + if (m_loadingIndicator) { + m_loadingIndicator->start(); + } + // FIXME: dont use hardcoded index + setCurrentIndex(0); } ZLoadingWidget::~ZLoadingWidget() diff --git a/src/zloadingwidget.h b/src/zloadingwidget.h index 1c8050e..9ddabf4 100644 --- a/src/zloadingwidget.h +++ b/src/zloadingwidget.h @@ -1,15 +1,25 @@ #ifndef ZLOADINGWIDGET_H #define ZLOADINGWIDGET_H +#include #include -class ZLoadingWidget : public QWidget +class ZLoadingWidget : public QStackedWidget { Q_OBJECT public: explicit ZLoadingWidget(bool start = true, QWidget *parent = nullptr); ~ZLoadingWidget(); - void stop(); + void stop(bool showContent = true); + void showLoading(); + void setupContentWidget(QWidget *contentWidget); + void setupContentWidget(QLayout *contentLayout); + void setupErrorWidget(QWidget *errorWidget); + void setupErrorWidget(const QString &errorMessage); + void setupErrorWidget(QLayout *errorLayout); + void setupAditionalWidget(QWidget *customWidget); + void switchToWidget(QWidget *widget); + void showError(); private: class QProcessIndicator *m_loadingIndicator = nullptr; From f00bf62144a97669bd25dd8967474d9136b16921 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:13:23 +0000 Subject: [PATCH 06/15] add utils, enhance ui, remove debug stuff --- src/cableinfowidget.cpp | 3 +- src/devicedatabase.cpp | 2 +- src/gallerywidget.cpp | 12 ++-- src/iDescriptor-utils.h | 120 ++++++++++++++++++++++++++++++++++++++++ src/iDescriptor.h | 50 +---------------- src/servicemanager.cpp | 1 - 6 files changed, 131 insertions(+), 57 deletions(-) create mode 100644 src/iDescriptor-utils.h diff --git a/src/cableinfowidget.cpp b/src/cableinfowidget.cpp index 3d62c8a..b85b9b2 100644 --- a/src/cableinfowidget.cpp +++ b/src/cableinfowidget.cpp @@ -229,7 +229,8 @@ void CableInfoWidget::updateUI() if (!m_cableInfo.isConnected) { m_errorLabel->setText( - "Device does not seem to be connected to any cable."); + QString("%1 does not seem to be connected to any cable.") + .arg(m_device->deviceInfo.productType)); m_loadingWidget->showError(); return; } diff --git a/src/devicedatabase.cpp b/src/devicedatabase.cpp index 77dff79..5b5767f 100644 --- a/src/devicedatabase.cpp +++ b/src/devicedatabase.cpp @@ -570,5 +570,5 @@ std::string DeviceDatabase::parseRegionInfo(const std::string &code) if (code == "C/A") return "Canada (English, French)"; - return "Unknown Region (" + code + ")"; + return code; } \ No newline at end of file diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 3c5b1d8..7a8929b 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -153,7 +153,9 @@ void GalleryWidget::setupControlsLayout() m_exportAllButton = new QPushButton("Export All"); // Back button - m_backButton = new QPushButton("← Back to Albums"); + m_backButton = new QPushButton("←"); + m_backButton->setToolTip("Back to Albums"); + m_backButton->setMaximumWidth(30); m_backButton->hide(); // Hidden initially // Connect signals @@ -491,8 +493,8 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) }); } - connect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, - &PhotoModel::requestThumbnail, Qt::QueuedConnection); + // connect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, + // &PhotoModel::requestThumbnail, Qt::QueuedConnection); // Set album path and load photos m_model->setAlbumPath(albumPath); @@ -506,8 +508,8 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) void GalleryWidget::onBackToAlbums() { if (m_model) { - disconnect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, - &PhotoModel::requestThumbnail); + // disconnect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, + // &PhotoModel::requestThumbnail); } // Switch back to album selection view diff --git a/src/iDescriptor-utils.h b/src/iDescriptor-utils.h new file mode 100644 index 0000000..b949c62 --- /dev/null +++ b/src/iDescriptor-utils.h @@ -0,0 +1,120 @@ +#include +#include +#include + +struct ProductTypeVersion { + int major; + int minor; + + ProductTypeVersion(int maj = 0, int min = 0) : major(maj), minor(min) {} + + // Compare two product type versions + // Returns: -1 if this < other, 0 if equal, 1 if this > other + int compareTo(const ProductTypeVersion &other) const + { + if (major != other.major) { + return major < other.major ? -1 : 1; + } + if (minor != other.minor) { + return minor < other.minor ? -1 : 1; + } + return 0; // Equal + } + + bool operator<(const ProductTypeVersion &other) const + { + return compareTo(other) < 0; + } + + bool operator==(const ProductTypeVersion &other) const + { + return compareTo(other) == 0; + } + + bool operator>(const ProductTypeVersion &other) const + { + return compareTo(other) > 0; + } +}; + +namespace iDescriptor +{ +class Utils +{ +private: + // Extract version numbers from iPhone product type string + // Example: "iPhone8,1" -> ProductTypeVersion{8, 1} + static ProductTypeVersion + extractProductTypeVersion(const std::string &productType) + { + // Regex to match iPhone followed by major,minor numbers + std::regex pattern(R"(iPhone(\d+),(\d+))"); + std::smatch matches; + + if (std::regex_search(productType, matches, pattern)) { + if (matches.size() >= 3) { + try { + int major = std::stoi(matches[1].str()); + int minor = std::stoi(matches[2].str()); + return ProductTypeVersion(major, minor); + } catch (const std::invalid_argument &e) { + throw std::invalid_argument( + "Invalid numeric values in product type: " + + productType); + } + } + } + + throw std::invalid_argument("Invalid iPhone product type format: " + + productType); + } + + static bool compare_product_type(const std::string &productType, + const std::string &otherProductType) + { + try { + ProductTypeVersion version1 = + extractProductTypeVersion(productType); + ProductTypeVersion version2 = + extractProductTypeVersion(otherProductType); + + // Return true if productType is newer/higher than otherProductType + return version1 > version2; + } catch (const std::exception &e) { + // Handle invalid product types - you might want to log this + return false; + } + } + +public: + static bool isProductTypeNewer(const std::string &productType, + const std::string &otherProductType) + { + return compare_product_type(productType, otherProductType); + } + + static QString formatSize(uint64_t bytes) + { + const char *units[] = {"B", "KB", "MB", "GB", "TB"}; + int unitIndex = 0; + double size = bytes; + + while (size >= 1024 && unitIndex < 4) { + size /= 1024; + unitIndex++; + } + + return QString("%1 %2") + .arg(QString::number(size, 'f', 1)) + .arg(units[unitIndex]); + }; + + static bool isVideoFile(const QString &fileName) + { + /* known iPhone video file extensions */ + return fileName.endsWith(".MOV", Qt::CaseInsensitive) || + fileName.endsWith(".MP4", Qt::CaseInsensitive) || + fileName.endsWith(".M4V", Qt::CaseInsensitive); + } +}; +} // namespace iDescriptor \ No newline at end of file diff --git a/src/iDescriptor.h b/src/iDescriptor.h index 1768d78..f7ed588 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -66,6 +66,7 @@ #define IDEVICE_DEVICE_VERSION(maj, min, patch) \ ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF)) #include "devicemonitor.h" +#include "iDescriptor-utils.h" #define DeviceLockedMountErrorCode -21 #define NotFoundErrorCode -14 @@ -475,53 +476,6 @@ struct ImageInfo { bool isMounted = false; }; -// /** -// * @brief Compare two iPhone product types to determine which is newer -// * @param productType First iPhone product type (e.g., "iPhone8,1") -// * @param otherProductType Second iPhone product type (e.g., "iPhone7,2") -// * @return true if productType is newer than otherProductType, false -// otherwise -// * -// * Examples: -// * - compare_product_type("iPhone8,1", "iPhone7,2") returns true -// * - compare_product_type("iPhone6,1", "iPhone8,1") returns false -// * - compare_product_type("iPhone8,2", "iPhone8,1") returns true -// */ -// bool compare_product_type(std::string productType, -// std::string otherProductType); - -// /** -// * @brief Check if two iPhone product types are exactly equal -// * @param productType First iPhone product type -// * @param otherProductType Second iPhone product type -// * @return true if both product types are identical -// */ -// bool are_product_types_equal(const std::string &productType, -// const std::string &otherProductType); - -// /** -// * @brief Check if first product type is newer than second -// * @param productType First iPhone product type -// * @param otherProductType Second iPhone product type -// * @return true if productType is newer than otherProductType -// */ -bool is_product_type_newer(const std::string &productType, - const std::string &otherProductType); - -// /** -// * @brief Check if first product type is older than second -// * @param productType First iPhone product type -// * @param otherProductType Second iPhone product type -// * @return true if productType is older than otherProductType -// */ -// bool is_product_type_older(const std::string &productType, -// const std::string &otherProductType); - -// bool query_mobile_gestalt(iDescriptorDevice *id_device, const QStringList -// &keys, -// uint32_t &xml_size, char *&xml_data); -// ; - // std::string safeGetXML(const char *key, pugi::xml_node dict); void get_battery_info(DiagnosticsRelay *diagRelay, plist_t &diagnostics); @@ -533,8 +487,6 @@ void fetchAppIconFromApple(QNetworkAccessManager *manager, const QString &bundleId, std::function callback); -// afc_error_t afc2_client_new(idevice_t device, afc_client_t *afc); - void _get_cable_info(const iDescriptorDevice *device, plist_t &response); struct NetworkDevice { diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index 4c07e13..dc4a3d7 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -274,7 +274,6 @@ IdeviceFfiError *ServiceManager::exportFileToPath( IdeviceFfiError *read_err = nullptr; // Read file in chunks while (true) { - std::this_thread::sleep_for(std::chrono::seconds(2)); // Check for cancellation if (cancelRequested && cancelRequested->load()) { fclose(out); From 652b1fa4c89a31d71ba218505bf0261679cdd850 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:16:31 +0000 Subject: [PATCH 07/15] refactor(gallery): use QThreadPool for much more efficient img loading --- src/imageloader.cpp | 501 ++++++++++++++++++++++++++++++++ src/imageloader.h | 52 ++++ src/imagetask.h | 52 ++++ src/mediapreviewdialog.cpp | 3 +- src/photomodel.cpp | 567 ++----------------------------------- src/photomodel.h | 22 +- 6 files changed, 639 insertions(+), 558 deletions(-) create mode 100644 src/imageloader.cpp create mode 100644 src/imageloader.h create mode 100644 src/imagetask.h diff --git a/src/imageloader.cpp b/src/imageloader.cpp new file mode 100644 index 0000000..c8fd5da --- /dev/null +++ b/src/imageloader.cpp @@ -0,0 +1,501 @@ +#include "imageloader.h" +#include "iDescriptor.h" +#include "imagetask.h" +#include "servicemanager.h" +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +ImageLoader::ImageLoader(QObject *parent) : QObject(parent) +{ + m_pool.setMaxThreadCount(10); + // 350 MB cache for thumbnails + m_cache.setMaxCost(350 * 1024 * 1024); +} + +bool ImageLoader::isLoading(const QString &path) +{ + return m_pending.contains(path); +} + +void ImageLoader::requestThumbnail(const iDescriptorDevice *device, + const QString &path, int priority, + unsigned int row) +{ + if (auto *cached = m_cache.object(path)) { + emit thumbnailReady(path, *cached, row); + return; + } + + if (m_pending.contains(path)) + return; + + m_pending.insert(path); + + // FIXME: qsize + auto *task = new ImageTask(device, path, QSize(128, 128), row); + + connect(task, &ImageTask::finished, this, &ImageLoader::onTaskFinished, + Qt::QueuedConnection); + + m_pool.start(task, priority); +} + +void ImageLoader::cancelThumbnail(const QString &path) +{ + m_pending.remove(path); +} + +void ImageLoader::clear() +{ + m_pending.clear(); + m_cache.clear(); +} + +void ImageLoader::onTaskFinished(const QString &path, const QPixmap &pixmap, + unsigned int row) +{ + // Stale? + if (!m_pending.contains(path)) + return; + + m_pending.remove(path); + + // Cache + m_cache.insert(path, new QPixmap(pixmap)); + + emit thumbnailReady(path, pixmap, row); +} + +// Static function that runs in worker thread +QPixmap ImageLoader::loadThumbnailFromDevice(const iDescriptorDevice *device, + const QString &filePath, + const QSize &size) +{ + // Load from device using ServiceManager + QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray( + device, filePath.toUtf8().constData()); + + if (imageData.isEmpty()) { + qDebug() << "Could not read from device:" << filePath; + return {}; // Return empty pixmap on error + } + + if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { + QPixmap img = load_heic(imageData); + return img.isNull() ? QPixmap() + : img.scaled(size, Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } + + // Use QImageReader for efficient, low-memory scaled loading + QBuffer buffer(&imageData); + buffer.open(QIODevice::ReadOnly); + + QImageReader reader(&buffer); + if (reader.canRead()) { + // This is the key optimization: it decodes a smaller image directly, + // saving a massive amount of memory. + reader.setScaledSize(size); + QImage image = reader.read(); + if (!image.isNull()) { + return QPixmap::fromImage(image); + } + qDebug() << "QImageReader failed to decode" << filePath + << "Error:" << reader.errorString(); + } + + // Fallback for formats QImageReader might struggle with + QPixmap original; + if (original.loadFromData(imageData)) { + return original.scaled(size, Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } + + qDebug() << "Could not decode image data for:" << filePath; + return {}; +} + +QPixmap +ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, + const QString &filePath, + const QSize &requestedSize) +{ + QPixmap thumbnail; + + AfcFileHandle *fileHandle = nullptr; + + IdeviceFfiError *err_open = // Use distinct variable name for clarity + ServiceManager::safeAfcFileOpen(device, filePath.toUtf8().constData(), + AfcFopenMode::AfcRdOnly, &fileHandle); + + if (err_open || fileHandle == nullptr) { + qWarning() << "Failed to open video file for thumbnail:" << filePath; + if (err_open) { + idevice_error_free(err_open); + } + return {}; + } + + // Get file size + AfcFileInfo info = {}; + IdeviceFfiError + *err_info = // Use distinct variable name for the error from GetFileInfo + ServiceManager::safeAfcGetFileInfo( + device, filePath.toUtf8().constData(), &info); + + uint64_t fileSize = 0; + if (err_info) { + qWarning() << "Failed to get file info for thumbnail:" << filePath + << "Error:" << err_info->message; + idevice_error_free(err_info); + ServiceManager::safeAfcFileClose(device, fileHandle); + afc_file_info_free(&info); // Free internal strings of info + return {}; + } + + fileSize = info.size; + afc_file_info_free(&info); // Free internal strings of info after use + + if (fileSize == 0) { + ServiceManager::safeAfcFileClose(device, fileHandle); + qWarning() << "Invalid video file size for thumbnail:" << filePath; + return {}; + } + + // Create custom AVIOContext for reading from device on-demand + AVFormatContext *formatCtx = avformat_alloc_context(); + if (!formatCtx) { + ServiceManager::safeAfcFileClose(device, fileHandle); + qWarning() << "Failed to allocate format context"; + return {}; + } + + // Context for streaming read from device + struct StreamContext { + const iDescriptorDevice *device; + AfcFileHandle *fileHandle; + uint64_t fileSize; + uint64_t currentPos; + }; + + StreamContext *streamCtx = + new StreamContext{device, fileHandle, fileSize, 0}; + + // Custom read function that reads from device on-demand + auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int { + StreamContext *ctx = static_cast(opaque); + + if (ctx->currentPos >= ctx->fileSize) { + return AVERROR_EOF; + } + + uint32_t toRead = + std::min(static_cast(bufSize), + static_cast(ctx->fileSize - ctx->currentPos)); + size_t bytesRead = 0; + uint8_t *read_data_ptr = + nullptr; // Pointer to store the data allocated by safeAfcFileRead + + // Call safeAfcFileRead to get the data into a newly allocated buffer + IdeviceFfiError *err = ServiceManager::safeAfcFileRead( + ctx->device, ctx->fileHandle, &read_data_ptr, toRead, &bytesRead); + + if (err) { + qWarning() << "AFC read error in readPacket for file handle" + << ctx->fileHandle << ":" << err->message; + idevice_error_free(err); + return AVERROR(EIO); + } + + if (bytesRead == 0) { + // If bytesRead is 0 but we expected to read more, it's an error. + // If currentPos is already at fileSize, it's EOF. + if (ctx->currentPos < ctx->fileSize) { + qWarning() << "AFC readPacket returned 0 bytes but not EOF, " + "expected toRead:" + << toRead << "currentPos:" << ctx->currentPos + << "fileSize:" << ctx->fileSize; + // Free the allocated pointer if safeAfcFileRead still allocates + // even for 0 bytes. + if (read_data_ptr) { + afc_file_read_data_free(read_data_ptr, 0); + } + return AVERROR(EIO); + } + // It's EOF + return AVERROR_EOF; + } + + // Copy the data from the newly allocated `read_data_ptr` into FFmpeg's + // `buf` + if (read_data_ptr) { + memcpy(buf, read_data_ptr, bytesRead); + afc_file_read_data_free( + read_data_ptr, + bytesRead); // Free the memory allocated by safeAfcFileRead + } else { + qWarning() << "AFC readPacket: read_data_ptr was null but " + "bytesRead > 0. This is unexpected."; + return AVERROR(EIO); + } + + ctx->currentPos += bytesRead; + return static_cast(bytesRead); + }; + + // Custom seek function using AFC seek + auto seekPacket = [](void *opaque, int64_t offset, int whence) -> int64_t { + StreamContext *ctx = static_cast(opaque); + + if (whence == AVSEEK_SIZE) { + return static_cast(ctx->fileSize); + } + + int64_t newPos = 0; + int seekWhence = SEEK_SET; + + if (whence == SEEK_SET) { + newPos = offset; + seekWhence = SEEK_SET; + } else if (whence == SEEK_CUR) { + newPos = static_cast(ctx->currentPos) + offset; + seekWhence = SEEK_SET; + } else if (whence == SEEK_END) { + newPos = static_cast(ctx->fileSize) + offset; + seekWhence = SEEK_SET; + } else { + return -1; + } + + if (newPos < 0 || newPos > static_cast(ctx->fileSize)) { + return -1; + } + + IdeviceFfiError *err = ServiceManager::safeAfcFileSeek( + ctx->device, ctx->fileHandle, newPos, seekWhence); + + if (err) { + qDebug() << "AFC seek error:" << err->message + << "code:" << err->code; + // idevice_error_free(err); + return -1; + } + + ctx->currentPos = static_cast(newPos); + return newPos; + }; + + const int avioBufferSize = 32768; // 32KB buffer for streaming + unsigned char *avioBuffer = + static_cast(av_malloc(avioBufferSize)); + if (!avioBuffer) { + delete streamCtx; + ServiceManager::safeAfcFileClose(device, fileHandle); + avformat_free_context(formatCtx); + return {}; + } + + AVIOContext *avioCtx = + avio_alloc_context(avioBuffer, avioBufferSize, 0, streamCtx, readPacket, + nullptr, seekPacket); + + if (!avioCtx) { + av_free(avioBuffer); + delete streamCtx; + ServiceManager::safeAfcFileClose(device, fileHandle); + avformat_free_context(formatCtx); + return {}; + } + + formatCtx->pb = avioCtx; + formatCtx->flags |= AVFMT_FLAG_CUSTOM_IO; + + // Open input + if (avformat_open_input(&formatCtx, nullptr, nullptr, nullptr) < 0) { + qWarning() << "Failed to open video format"; + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + avformat_free_context(formatCtx); + return {}; + } + + // Find stream info + if (avformat_find_stream_info(formatCtx, nullptr) < 0) { + qWarning() << "Failed to find stream info"; + avformat_close_input(&formatCtx); + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + return {}; + } + + // Find video stream + int videoStreamIndex = -1; + const AVCodec *codec = nullptr; + AVCodecParameters *codecParams = nullptr; + + for (unsigned int i = 0; i < formatCtx->nb_streams; i++) { + if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + videoStreamIndex = i; + codecParams = formatCtx->streams[i]->codecpar; + codec = avcodec_find_decoder(codecParams->codec_id); + break; + } + } + + if (videoStreamIndex == -1 || !codec) { + qWarning() << "No video stream found"; + avformat_close_input(&formatCtx); + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + return {}; + } + + // Allocate codec context + AVCodecContext *codecCtx = avcodec_alloc_context3(codec); + if (!codecCtx) { + avformat_close_input(&formatCtx); + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + return {}; + } + + if (avcodec_parameters_to_context(codecCtx, codecParams) < 0) { + avcodec_free_context(&codecCtx); + avformat_close_input(&formatCtx); + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + return {}; + } + + // Open codec + if (avcodec_open2(codecCtx, codec, nullptr) < 0) { + avcodec_free_context(&codecCtx); + avformat_close_input(&formatCtx); + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + return {}; + } + + // Allocate frame + AVFrame *frame = av_frame_alloc(); + AVPacket *packet = av_packet_alloc(); + + if (!frame || !packet) { + if (frame) + av_frame_free(&frame); + if (packet) + av_packet_free(&packet); + avcodec_free_context(&codecCtx); + avformat_close_input(&formatCtx); + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + return {}; + } + + // Read frames until we get a valid one + bool frameDecoded = false; + while (av_read_frame(formatCtx, packet) >= 0) { + if (packet->stream_index == videoStreamIndex) { + if (avcodec_send_packet(codecCtx, packet) >= 0) { + if (avcodec_receive_frame(codecCtx, frame) >= 0) { + frameDecoded = true; + av_packet_unref(packet); + break; + } + } + } + av_packet_unref(packet); + } + + if (frameDecoded) { + // Convert frame to RGB24 + SwsContext *swsCtx = + sws_getContext(frame->width, frame->height, + static_cast(frame->format), + frame->width, frame->height, AV_PIX_FMT_RGB24, + SWS_BILINEAR, nullptr, nullptr, nullptr); + + if (swsCtx) { + AVFrame *rgbFrame = av_frame_alloc(); + if (rgbFrame) { + rgbFrame->format = AV_PIX_FMT_RGB24; + rgbFrame->width = frame->width; + rgbFrame->height = frame->height; + + if (av_frame_get_buffer(rgbFrame, 0) >= 0) { + sws_scale(swsCtx, frame->data, frame->linesize, 0, + frame->height, rgbFrame->data, + rgbFrame->linesize); + + // Convert to QImage + QImage img(rgbFrame->data[0], rgbFrame->width, + rgbFrame->height, rgbFrame->linesize[0], + QImage::Format_RGB888); + + // Create a deep copy since AVFrame will be freed + QImage imgCopy = img.copy(); + + // Scale to requested size + thumbnail = QPixmap::fromImage( + imgCopy.scaled(requestedSize, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + } + + av_frame_free(&rgbFrame); + } + + sws_freeContext(swsCtx); + } + } + + // Cleanup + av_frame_free(&frame); + av_packet_free(&packet); + avcodec_free_context(&codecCtx); + avformat_close_input(&formatCtx); + + // Close the AFC file handle + ServiceManager::safeAfcFileClose(device, fileHandle); + + // Free AVIO context and stream context + av_free(avioCtx->buffer); + avio_context_free(&avioCtx); + delete streamCtx; + + return thumbnail; +} + +// todo make sure this is also a task used in mediapreviewdialog.cpp +QPixmap ImageLoader::loadImage(const iDescriptorDevice *device, + const QString &filePath) +{ + QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray( + device, filePath.toUtf8().constData()); + + if (imageData.isEmpty()) { + qDebug() << "Could not read from device:" << filePath; + 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; + } + + QPixmap original; + if (!original.loadFromData(imageData)) { + qDebug() << "Could not decode image data for:" << filePath; + return QPixmap(); + } + + return original; +} \ No newline at end of file diff --git a/src/imageloader.h b/src/imageloader.h new file mode 100644 index 0000000..55ada91 --- /dev/null +++ b/src/imageloader.h @@ -0,0 +1,52 @@ +#ifndef IMAGELOADER_H +#define IMAGELOADER_H + +#include "iDescriptor.h" +#include +#include +#include +#include +#include +#include + +class ImageLoader : public QObject +{ + Q_OBJECT +public: + enum LoadPriority { Low = 0, Medium = 50, High = 100 }; + Q_ENUM(LoadPriority) + + explicit ImageLoader(QObject *parent = nullptr); + static ImageLoader &sharedInstance() + { + static ImageLoader instance; + return instance; + } + void requestThumbnail(const iDescriptorDevice *device, const QString &path, + int priority, unsigned int row = 0); + void cancelThumbnail(const QString &path); + bool isLoading(const QString &path); + void clear(); + QCache m_cache; + static QPixmap loadThumbnailFromDevice(const iDescriptorDevice *device, + const QString &filePath, + const QSize &size); + static QPixmap generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, + const QString &filePath, + const QSize &size); + static QPixmap loadImage(const iDescriptorDevice *device, + const QString &filePath); +signals: + void thumbnailReady(const QString &path, const QPixmap &image, + unsigned int row); + +private slots: + void onTaskFinished(const QString &path, const QPixmap &image, + unsigned int row); + +private: + QThreadPool m_pool; + QSet m_pending; +}; + +#endif // IMAGELOADER_H \ No newline at end of file diff --git a/src/imagetask.h b/src/imagetask.h new file mode 100644 index 0000000..8f723ea --- /dev/null +++ b/src/imagetask.h @@ -0,0 +1,52 @@ +#ifndef IMAGETASK_H +#define IMAGETASK_H + +#include "iDescriptor.h" +#include +#include +#include +#include +#include + +#include "imageloader.h" + +class ImageTask : public QObject, public QRunnable +{ + Q_OBJECT +public: + ImageTask(const iDescriptorDevice *device, const QString &path, + const QSize &thumbnailSize, unsigned int row) + : m_device(device), m_path(path), m_thumbnailSize(thumbnailSize), + m_row(row) + { + setAutoDelete(true); + } + +signals: + void finished(const QString &path, const QPixmap &image, unsigned int row); + +protected: + void run() override + { + bool isVideo = iDescriptor::Utils::isVideoFile(m_path); + + if (isVideo) { + QPixmap thumbnail = ImageLoader::generateVideoThumbnailFFmpeg( + m_device, m_path, m_thumbnailSize); + + emit finished(m_path, thumbnail, m_row); + } else { + QPixmap image = ImageLoader::loadThumbnailFromDevice( + m_device, m_path, m_thumbnailSize); + emit finished(m_path, image, m_row); + } + } + +private: + const iDescriptorDevice *m_device; + QString m_path; + QSize m_thumbnailSize; + unsigned int m_row; +}; + +#endif // IMAGETASK_H diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 93f82d3..edb0ce9 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -20,6 +20,7 @@ #include "mediapreviewdialog.h" #include "appcontext.h" #include "iDescriptor-ui.h" +#include "imageloader.h" #include "mediastreamermanager.h" #include "photomodel.h" #include @@ -211,7 +212,7 @@ void MediaPreviewDialog::loadMedia() void MediaPreviewDialog::loadImage() { auto future = QtConcurrent::run( - [this]() { return PhotoModel::loadImage(m_device, m_filePath); }); + [this]() { return ImageLoader::loadImage(m_device, m_filePath); }); auto *watcher = new QFutureWatcher(this); connect(watcher, &QFutureWatcher::finished, this, diff --git a/src/photomodel.cpp b/src/photomodel.cpp index c961fda..05eca32 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -20,6 +20,7 @@ #include "photomodel.h" #include "iDescriptor.h" // #include "mediastreamermanager.h" +#include "imageloader.h" #include "servicemanager.h" #include #include @@ -34,44 +35,32 @@ #include #include #include -extern "C" { -#include -#include -#include -#include -} -// todo implement std::priority_queue with thread pool - -QSemaphore PhotoModel::m_videoThumbnailSemaphore(4); PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType, QObject *parent) : QAbstractListModel(parent), m_device(device), m_thumbnailSize(120, 120), m_sortOrder(NewestFirst), m_filterType(filterType) { - // 350 MB cache for thumbnails - m_thumbnailCache.setMaxCost(350 * 1024 * 1024); - - connect(this, &PhotoModel::thumbnailNeedsToBeLoaded, this, - &PhotoModel::requestThumbnail, Qt::QueuedConnection); + connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this, + &PhotoModel::onThumbnailReady); } void PhotoModel::clear() { blockSignals(true); - // Clean up any active watchers - for (auto *watcher : m_activeLoaders.values()) { - if (watcher) { - watcher->disconnect(); - watcher->cancel(); - // watcher->waitForFinished(); - watcher->deleteLater(); - } - } - m_activeLoaders.clear(); - m_loadingPaths.clear(); - m_thumbnailCache.clear(); + // // Clean up any active watchers + // for (auto *watcher : m_activeLoaders.values()) { + // if (watcher) { + // watcher->disconnect(); + // watcher->cancel(); + // // watcher->waitForFinished(); + // watcher->deleteLater(); + // } + // } + // m_activeLoaders.clear(); + // m_loadingPaths.clear(); + // m_thumbnailCache.clear(); beginResetModel(); m_photos.clear(); @@ -87,355 +76,6 @@ PhotoModel::~PhotoModel() clear(); } -QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, - const QString &filePath, - const QSize &requestedSize) -{ - QPixmap thumbnail; - - AfcFileHandle *fileHandle = nullptr; - - IdeviceFfiError *err_open = // Use distinct variable name for clarity - ServiceManager::safeAfcFileOpen(device, filePath.toUtf8().constData(), - AfcFopenMode::AfcRdOnly, &fileHandle); - - if (err_open || fileHandle == nullptr) { - qWarning() << "Failed to open video file for thumbnail:" << filePath; - if (err_open) { - idevice_error_free(err_open); - } - return {}; - } - - // Get file size - AfcFileInfo info = {}; - IdeviceFfiError - *err_info = // Use distinct variable name for the error from GetFileInfo - ServiceManager::safeAfcGetFileInfo( - device, filePath.toUtf8().constData(), &info); - - uint64_t fileSize = 0; - if (err_info) { - qWarning() << "Failed to get file info for thumbnail:" << filePath - << "Error:" << err_info->message; - idevice_error_free(err_info); - ServiceManager::safeAfcFileClose(device, fileHandle); - afc_file_info_free(&info); // Free internal strings of info - return {}; - } - - fileSize = info.size; - afc_file_info_free(&info); // Free internal strings of info after use - - if (fileSize == 0) { - ServiceManager::safeAfcFileClose(device, fileHandle); - qWarning() << "Invalid video file size for thumbnail:" << filePath; - return {}; - } - - // Create custom AVIOContext for reading from device on-demand - AVFormatContext *formatCtx = avformat_alloc_context(); - if (!formatCtx) { - ServiceManager::safeAfcFileClose(device, fileHandle); - qWarning() << "Failed to allocate format context"; - return {}; - } - - // Context for streaming read from device - struct StreamContext { - iDescriptorDevice *device; - AfcFileHandle *fileHandle; - uint64_t fileSize; - uint64_t currentPos; - }; - - StreamContext *streamCtx = - new StreamContext{device, fileHandle, fileSize, 0}; - - // Custom read function that reads from device on-demand - auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int { - StreamContext *ctx = static_cast(opaque); - - if (ctx->currentPos >= ctx->fileSize) { - return AVERROR_EOF; - } - - uint32_t toRead = - std::min(static_cast(bufSize), - static_cast(ctx->fileSize - ctx->currentPos)); - size_t bytesRead = 0; - uint8_t *read_data_ptr = - nullptr; // Pointer to store the data allocated by safeAfcFileRead - - // Call safeAfcFileRead to get the data into a newly allocated buffer - IdeviceFfiError *err = ServiceManager::safeAfcFileRead( - ctx->device, ctx->fileHandle, &read_data_ptr, toRead, &bytesRead); - - if (err) { - qWarning() << "AFC read error in readPacket for file handle" - << ctx->fileHandle << ":" << err->message; - idevice_error_free(err); - return AVERROR(EIO); - } - - if (bytesRead == 0) { - // If bytesRead is 0 but we expected to read more, it's an error. - // If currentPos is already at fileSize, it's EOF. - if (ctx->currentPos < ctx->fileSize) { - qWarning() << "AFC readPacket returned 0 bytes but not EOF, " - "expected toRead:" - << toRead << "currentPos:" << ctx->currentPos - << "fileSize:" << ctx->fileSize; - // Free the allocated pointer if safeAfcFileRead still allocates - // even for 0 bytes. - if (read_data_ptr) { - afc_file_read_data_free(read_data_ptr, 0); - } - return AVERROR(EIO); - } - // It's EOF - return AVERROR_EOF; - } - - // Copy the data from the newly allocated `read_data_ptr` into FFmpeg's - // `buf` - if (read_data_ptr) { - memcpy(buf, read_data_ptr, bytesRead); - afc_file_read_data_free( - read_data_ptr, - bytesRead); // Free the memory allocated by safeAfcFileRead - } else { - qWarning() << "AFC readPacket: read_data_ptr was null but " - "bytesRead > 0. This is unexpected."; - return AVERROR(EIO); - } - - ctx->currentPos += bytesRead; - return static_cast(bytesRead); - }; - - // Custom seek function using AFC seek - auto seekPacket = [](void *opaque, int64_t offset, int whence) -> int64_t { - StreamContext *ctx = static_cast(opaque); - - if (whence == AVSEEK_SIZE) { - return static_cast(ctx->fileSize); - } - - int64_t newPos = 0; - int seekWhence = SEEK_SET; - - if (whence == SEEK_SET) { - newPos = offset; - seekWhence = SEEK_SET; - } else if (whence == SEEK_CUR) { - newPos = static_cast(ctx->currentPos) + offset; - seekWhence = SEEK_SET; - } else if (whence == SEEK_END) { - newPos = static_cast(ctx->fileSize) + offset; - seekWhence = SEEK_SET; - } else { - return -1; - } - - if (newPos < 0 || newPos > static_cast(ctx->fileSize)) { - return -1; - } - - IdeviceFfiError *err = ServiceManager::safeAfcFileSeek( - ctx->device, ctx->fileHandle, newPos, seekWhence); - - if (err) { - qDebug() << "AFC seek error:" << err->message - << "code:" << err->code; - // idevice_error_free(err); - return -1; - } - - ctx->currentPos = static_cast(newPos); - return newPos; - }; - - const int avioBufferSize = 32768; // 32KB buffer for streaming - unsigned char *avioBuffer = - static_cast(av_malloc(avioBufferSize)); - if (!avioBuffer) { - delete streamCtx; - ServiceManager::safeAfcFileClose(device, fileHandle); - avformat_free_context(formatCtx); - return {}; - } - - AVIOContext *avioCtx = - avio_alloc_context(avioBuffer, avioBufferSize, 0, streamCtx, readPacket, - nullptr, seekPacket); - - if (!avioCtx) { - av_free(avioBuffer); - delete streamCtx; - ServiceManager::safeAfcFileClose(device, fileHandle); - avformat_free_context(formatCtx); - return {}; - } - - formatCtx->pb = avioCtx; - formatCtx->flags |= AVFMT_FLAG_CUSTOM_IO; - - // Open input - if (avformat_open_input(&formatCtx, nullptr, nullptr, nullptr) < 0) { - qWarning() << "Failed to open video format"; - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - avformat_free_context(formatCtx); - return {}; - } - - // Find stream info - if (avformat_find_stream_info(formatCtx, nullptr) < 0) { - qWarning() << "Failed to find stream info"; - avformat_close_input(&formatCtx); - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - return {}; - } - - // Find video stream - int videoStreamIndex = -1; - const AVCodec *codec = nullptr; - AVCodecParameters *codecParams = nullptr; - - for (unsigned int i = 0; i < formatCtx->nb_streams; i++) { - if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - videoStreamIndex = i; - codecParams = formatCtx->streams[i]->codecpar; - codec = avcodec_find_decoder(codecParams->codec_id); - break; - } - } - - if (videoStreamIndex == -1 || !codec) { - qWarning() << "No video stream found"; - avformat_close_input(&formatCtx); - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - return {}; - } - - // Allocate codec context - AVCodecContext *codecCtx = avcodec_alloc_context3(codec); - if (!codecCtx) { - avformat_close_input(&formatCtx); - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - return {}; - } - - if (avcodec_parameters_to_context(codecCtx, codecParams) < 0) { - avcodec_free_context(&codecCtx); - avformat_close_input(&formatCtx); - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - return {}; - } - - // Open codec - if (avcodec_open2(codecCtx, codec, nullptr) < 0) { - avcodec_free_context(&codecCtx); - avformat_close_input(&formatCtx); - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - return {}; - } - - // Allocate frame - AVFrame *frame = av_frame_alloc(); - AVPacket *packet = av_packet_alloc(); - - if (!frame || !packet) { - if (frame) - av_frame_free(&frame); - if (packet) - av_packet_free(&packet); - avcodec_free_context(&codecCtx); - avformat_close_input(&formatCtx); - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - return {}; - } - - // Read frames until we get a valid one - bool frameDecoded = false; - while (av_read_frame(formatCtx, packet) >= 0) { - if (packet->stream_index == videoStreamIndex) { - if (avcodec_send_packet(codecCtx, packet) >= 0) { - if (avcodec_receive_frame(codecCtx, frame) >= 0) { - frameDecoded = true; - av_packet_unref(packet); - break; - } - } - } - av_packet_unref(packet); - } - - if (frameDecoded) { - // Convert frame to RGB24 - SwsContext *swsCtx = - sws_getContext(frame->width, frame->height, - static_cast(frame->format), - frame->width, frame->height, AV_PIX_FMT_RGB24, - SWS_BILINEAR, nullptr, nullptr, nullptr); - - if (swsCtx) { - AVFrame *rgbFrame = av_frame_alloc(); - if (rgbFrame) { - rgbFrame->format = AV_PIX_FMT_RGB24; - rgbFrame->width = frame->width; - rgbFrame->height = frame->height; - - if (av_frame_get_buffer(rgbFrame, 0) >= 0) { - sws_scale(swsCtx, frame->data, frame->linesize, 0, - frame->height, rgbFrame->data, - rgbFrame->linesize); - - // Convert to QImage - QImage img(rgbFrame->data[0], rgbFrame->width, - rgbFrame->height, rgbFrame->linesize[0], - QImage::Format_RGB888); - - // Create a deep copy since AVFrame will be freed - QImage imgCopy = img.copy(); - - // Scale to requested size - thumbnail = QPixmap::fromImage( - imgCopy.scaled(requestedSize, Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - } - - av_frame_free(&rgbFrame); - } - - sws_freeContext(swsCtx); - } - } - - // Cleanup - av_frame_free(&frame); - av_packet_free(&packet); - avcodec_free_context(&codecCtx); - avformat_close_input(&formatCtx); - - // Close the AFC file handle - ServiceManager::safeAfcFileClose(device, fileHandle); - - // Free AVIO context and stream context - av_free(avioCtx->buffer); - avio_context_free(&avioCtx); - delete streamCtx; - - return thumbnail; -} - int PhotoModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) @@ -457,19 +97,14 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const return info.filePath; case Qt::DecorationRole: { - + ImageLoader &imgloader = ImageLoader::sharedInstance(); // Check memory cache first - if (QPixmap *cached = m_thumbnailCache.object(info.filePath)) { + if (QPixmap *cached = imgloader.m_cache.object(info.filePath)) { return QIcon(*cached); } - // Prevent duplicate requests - if (m_loadingPaths.contains(info.filePath) || - m_activeLoaders.contains(info.filePath)) { - // Return appropriate placeholder based on file type - if (info.fileName.endsWith(".MOV", Qt::CaseInsensitive) || - info.fileName.endsWith(".MP4", Qt::CaseInsensitive) || - info.fileName.endsWith(".M4V", Qt::CaseInsensitive)) { + if (imgloader.isLoading(info.filePath)) { + if (iDescriptor::Utils::isVideoFile(info.fileName)) { return QIcon(":/resources/icons/video-x-generic.png"); } else { return QIcon(":/resources/icons/" @@ -477,17 +112,10 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const } } - // Start async loading for both images and videos - if (!m_loadingPaths.contains(info.filePath)) { - emit const_cast(this)->thumbnailNeedsToBeLoaded( - index.row()); - } + imgloader.requestThumbnail(m_device, info.filePath, index.row(), + index.row()); - // Return placeholder while loading - if (info.fileName.endsWith(".MOV", Qt::CaseInsensitive) || - info.fileName.endsWith(".MP4", Qt::CaseInsensitive) || - info.fileName.endsWith(".M4V", Qt::CaseInsensitive)) { - // return QIcon::fromTheme("video-x-generic"); + if (iDescriptor::Utils::isVideoFile(info.fileName)) { return QIcon(":/resources/icons/video-x-generic.png"); } else { return QIcon( @@ -503,148 +131,13 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const } } -void PhotoModel::requestThumbnail(int index) +void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap, + unsigned int row) { - if (index < 0 || index >= m_photos.size()) - return; - - PhotoInfo &info = m_photos[index]; - info.thumbnailRequested = true; - - if (m_loadingPaths.contains(info.filePath)) - return; - - m_loadingPaths.insert(info.filePath); - - auto *watcher = new QFutureWatcher(); - m_activeLoaders[info.filePath] = watcher; - - connect(watcher, &QFutureWatcher::finished, this, - [this, watcher, filePath = info.filePath]() { - QPixmap thumbnail = watcher->result(); - - m_loadingPaths.remove(filePath); - m_activeLoaders.remove(filePath); - if (!thumbnail.isNull()) { - int cost = thumbnail.width() * thumbnail.height() * 4; - m_thumbnailCache.insert(filePath, new QPixmap(thumbnail), - cost); - - for (int i = 0; i < m_photos.size(); ++i) { - if (m_photos[i].filePath == filePath) { - QModelIndex idx = createIndex(i, 0); - emit dataChanged(idx, idx, {Qt::DecorationRole}); - break; - } - } - } else { - qDebug() << "Failed to load thumbnail for:" - << QFileInfo(filePath).fileName(); - } - - watcher->deleteLater(); - }); - - bool isVideo = info.fileName.endsWith(".MOV", Qt::CaseInsensitive) || - info.fileName.endsWith(".MP4", Qt::CaseInsensitive) || - info.fileName.endsWith(".M4V", Qt::CaseInsensitive); - - QFuture future; - if (isVideo) { - future = QtConcurrent::run([this, info]() { - // Acquire semaphore FIRST to limit concurrent video processing - m_videoThumbnailSemaphore.acquire(); - - // Generate video thumbnail using FFmpeg directly (no QMediaPlayer) - QPixmap thumbnail = generateVideoThumbnailFFmpeg( - m_device, info.filePath, m_thumbnailSize); - - m_videoThumbnailSemaphore.release(); - return thumbnail; - }); - } else { - future = QtConcurrent::run([info, this]() { - return loadThumbnailFromDevice(m_device, info.filePath, - m_thumbnailSize); - }); + if (m_photos[row].filePath == path) { + QModelIndex idx = createIndex(row, 0); + emit dataChanged(idx, idx, {Qt::DecorationRole}); } - - watcher->setFuture(future); -} - -// Static function that runs in worker thread -QPixmap PhotoModel::loadThumbnailFromDevice(iDescriptorDevice *device, - const QString &filePath, - const QSize &size) -{ - // Load from device using ServiceManager - QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray( - device, filePath.toUtf8().constData()); - - if (imageData.isEmpty()) { - qDebug() << "Could not read from device:" << filePath; - return {}; // Return empty pixmap on error - } - - if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { - QPixmap img = load_heic(imageData); - return img.isNull() ? QPixmap() - : img.scaled(size, Qt::KeepAspectRatio, - Qt::SmoothTransformation); - } - - // Use QImageReader for efficient, low-memory scaled loading - QBuffer buffer(&imageData); - buffer.open(QIODevice::ReadOnly); - - QImageReader reader(&buffer); - if (reader.canRead()) { - // This is the key optimization: it decodes a smaller image directly, - // saving a massive amount of memory. - reader.setScaledSize(size); - QImage image = reader.read(); - if (!image.isNull()) { - return QPixmap::fromImage(image); - } - qDebug() << "QImageReader failed to decode" << filePath - << "Error:" << reader.errorString(); - } - - // Fallback for formats QImageReader might struggle with - QPixmap original; - if (original.loadFromData(imageData)) { - return original.scaled(size, Qt::KeepAspectRatio, - Qt::SmoothTransformation); - } - - qDebug() << "Could not decode image data for:" << filePath; - return {}; -} - -QPixmap PhotoModel::loadImage(iDescriptorDevice *device, - const QString &filePath) -{ - QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray( - device, filePath.toUtf8().constData()); - - if (imageData.isEmpty()) { - qDebug() << "Could not read from device:" << filePath; - 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; - } - - QPixmap original; - if (!original.loadFromData(imageData)) { - qDebug() << "Could not decode image data for:" << filePath; - return QPixmap(); - } - - return original; } void PhotoModel::populatePhotoPaths() @@ -856,9 +349,7 @@ QDateTime PhotoModel::extractDateTimeFromFile(const QString &filePath) const PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const { - if (fileName.endsWith(".MOV", Qt::CaseInsensitive) || - fileName.endsWith(".MP4", Qt::CaseInsensitive) || - fileName.endsWith(".M4V", Qt::CaseInsensitive)) { + if (iDescriptor::Utils::isVideoFile(fileName)) { return PhotoInfo::Video; } else { return PhotoInfo::Image; @@ -874,4 +365,4 @@ void PhotoModel::setAlbumPath(const QString &albumPath) populatePhotoPaths(); } -void PhotoModel::refreshPhotos() { populatePhotoPaths(); } \ No newline at end of file +void PhotoModel::refreshPhotos() { populatePhotoPaths(); } diff --git a/src/photomodel.h b/src/photomodel.h index 015501e..c0aaaaf 100644 --- a/src/photomodel.h +++ b/src/photomodel.h @@ -79,19 +79,7 @@ public: QStringList getAllFilePaths() const; QStringList getFilteredFilePaths() const; - static QPixmap loadImage(iDescriptorDevice *device, - const QString &filePath); - // Static helper methods - static QPixmap loadThumbnailFromDevice(iDescriptorDevice *device, - const QString &filePath, - const QSize &size); void clear(); -signals: - void thumbnailNeedsToBeLoaded(int index); - void exportRequested(const QStringList &filePaths); - -public slots: - void requestThumbnail(int index); private: // Data members @@ -102,9 +90,6 @@ private: // Thumbnail management QSize m_thumbnailSize; - mutable QCache m_thumbnailCache; - mutable QHash *> m_activeLoaders; - mutable QSet m_loadingPaths; // Sorting and filtering SortOrder m_sortOrder; @@ -119,10 +104,9 @@ private: QDateTime extractDateTimeFromFile(const QString &filePath) const; PhotoInfo::FileType determineFileType(const QString &fileName) const; - static QPixmap generateVideoThumbnailFFmpeg(iDescriptorDevice *device, - const QString &filePath, - const QSize &requestedSize); - static QSemaphore m_videoThumbnailSemaphore; +private slots: + void onThumbnailReady(const QString &path, const QPixmap &pixmap, + unsigned int row); }; #endif // PHOTOMODEL_H \ No newline at end of file From 278e8cb868090075756da9f99cf7637e2c17e542 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:18:05 +0000 Subject: [PATCH 08/15] calculate Gallery size using Photos.sqlite --- src/diskusagewidget.cpp | 217 ++++++++++++++++++++++++++++++++-------- src/diskusagewidget.h | 19 ++-- 2 files changed, 186 insertions(+), 50 deletions(-) diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 2dd3596..a0b631d 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -20,6 +20,10 @@ #include "diskusagewidget.h" #include "diskusagebar.h" #include "iDescriptor.h" +#include "servicemanager.h" +extern "C" { +#include +} #include #include @@ -27,6 +31,8 @@ #include #include +using namespace iDescriptor; + DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0), m_systemUsage(0), m_appsUsage(0), m_mediaUsage(0), m_othersUsage(0), @@ -92,7 +98,7 @@ void DiskUsageWidget::setupUI() m_diskBarLayout->setSpacing(0); /* - FIXME: There is bug with qt, related to NSPopover on macOS + FIXME: There is a bug with qt, related to NSPopover on macOS need to revisit this when we find a fix */ // #ifdef Q_OS_MAC @@ -122,14 +128,18 @@ void DiskUsageWidget::setupUI() m_systemBar = new QWidget(); m_appsBar = new QWidget(); m_mediaBar = new QWidget(); + m_galleryBar = new QWidget(); m_othersBar = new QWidget(); m_freeBar = new QWidget(); + // required for tooltips to have default styling m_systemBar->setObjectName("systemBar"); m_appsBar->setObjectName("appsBar"); m_mediaBar->setObjectName("mediaBar"); + m_galleryBar->setObjectName("galleryBar"); m_othersBar->setObjectName("othersBar"); m_freeBar->setObjectName("freeBar"); + // Set colors m_systemBar->setStyleSheet( "QWidget#systemBar { background-color: #a1384d; border: 1px solid" @@ -140,6 +150,9 @@ void DiskUsageWidget::setupUI() "#63b4da; padding: 0; margin: 0; }"); m_mediaBar->setStyleSheet("QWidget#mediaBar { background-color: #2ECC71; " "border: none; padding: 0; margin: 0; }"); + m_galleryBar->setStyleSheet( + "QWidget#galleryBar { background-color: #9b59b6; border: 1px solid " + "#8e44ad; padding: 0; margin: 0; }"); m_othersBar->setStyleSheet( "QWidget#othersBar { background-color: #a28729; border: 1px solid " "#c4a32d; padding: 0; margin: 0; }"); @@ -152,12 +165,14 @@ void DiskUsageWidget::setupUI() m_systemBar->setContentsMargins(0, 0, 0, 0); m_appsBar->setContentsMargins(0, 0, 0, 0); m_mediaBar->setContentsMargins(0, 0, 0, 0); + m_galleryBar->setContentsMargins(0, 0, 0, 0); m_othersBar->setContentsMargins(0, 0, 0, 0); m_freeBar->setContentsMargins(0, 0, 0, 0); m_diskBarLayout->addWidget(m_systemBar); m_diskBarLayout->addWidget(m_appsBar); m_diskBarLayout->addWidget(m_mediaBar); + m_diskBarLayout->addWidget(m_galleryBar); m_diskBarLayout->addWidget(m_othersBar); m_diskBarLayout->addWidget(m_freeBar); @@ -174,6 +189,7 @@ void DiskUsageWidget::setupUI() m_systemLabel = new QLabel("System", m_legendWidget); m_appsLabel = new QLabel("Apps", m_legendWidget); m_mediaLabel = new QLabel("Media", m_legendWidget); + m_galleryLabel = new QLabel("Gallery", m_legendWidget); m_othersLabel = new QLabel("Others", m_legendWidget); m_freeLabel = new QLabel("Free", m_legendWidget); @@ -182,15 +198,23 @@ void DiskUsageWidget::setupUI() m_systemLabel->setStyleSheet(labelStyle); m_appsLabel->setStyleSheet(labelStyle); m_mediaLabel->setStyleSheet(labelStyle); + m_galleryLabel->setStyleSheet(labelStyle); m_othersLabel->setStyleSheet(labelStyle); m_freeLabel->setStyleSheet(labelStyle); + // FIXME:switch to zloadingwidget and remove unnecessary stretches when + // m_galleryLabel is invisible m_legendLayout->addWidget(m_systemLabel); - m_legendLayout->addWidget(m_appsLabel); - m_legendLayout->addWidget(m_mediaLabel); - m_legendLayout->addWidget(m_othersLabel); - m_legendLayout->addWidget(m_freeLabel); m_legendLayout->addStretch(); + m_legendLayout->addWidget(m_appsLabel); + m_legendLayout->addStretch(); + m_legendLayout->addWidget(m_mediaLabel); + m_legendLayout->addStretch(); + m_legendLayout->addWidget(m_galleryLabel); + m_legendLayout->addStretch(); + m_legendLayout->addWidget(m_othersLabel); + m_legendLayout->addStretch(); + m_legendLayout->addWidget(m_freeLabel); // Add the legend widget (not the layout) to the data layout m_dataLayout->addWidget(m_legendWidget); @@ -235,6 +259,8 @@ void DiskUsageWidget::updateUI() (int)((double)m_systemUsage / m_totalCapacity * totalWidth); int appsWidth = (int)((double)m_appsUsage / m_totalCapacity * totalWidth); int mediaWidth = (int)((double)m_mediaUsage / m_totalCapacity * totalWidth); + int galleryWidth = + (int)((double)m_galleryUsage / m_totalCapacity * totalWidth); int othersWidth = (int)((double)m_othersUsage / m_totalCapacity * totalWidth); int freeWidth = (int)((double)m_freeSpace / m_totalCapacity * totalWidth); @@ -250,10 +276,13 @@ void DiskUsageWidget::updateUI() othersWidth = 1; if (m_freeSpace > 0 && freeWidth == 0) freeWidth = 1; + if (m_galleryUsage > 0 && galleryWidth == 0) + galleryWidth = 1; m_diskBarLayout->setStretchFactor(m_systemBar, systemWidth); m_diskBarLayout->setStretchFactor(m_appsBar, appsWidth); m_diskBarLayout->setStretchFactor(m_mediaBar, mediaWidth); + m_diskBarLayout->setStretchFactor(m_galleryBar, galleryWidth); m_diskBarLayout->setStretchFactor(m_othersBar, othersWidth); m_diskBarLayout->setStretchFactor(m_freeBar, freeWidth); @@ -267,53 +296,47 @@ void DiskUsageWidget::updateUI() m_mediaBar->setVisible(m_mediaUsage > 0); m_mediaLabel->setVisible(m_mediaUsage > 0); + m_galleryBar->setVisible(m_galleryUsage > 0); + m_galleryLabel->setVisible(m_galleryUsage > 0); + m_othersBar->setVisible(m_othersUsage > 0); m_othersLabel->setVisible(m_othersUsage > 0); m_freeBar->setVisible(m_freeSpace > 0); m_freeLabel->setVisible(m_freeSpace > 0); - // Format sizes for display - auto formatSize = [](uint64_t bytes) -> QString { - const char *units[] = {"B", "KB", "MB", "GB", "TB"}; - int unitIndex = 0; - double size = bytes; - - while (size >= 1024 && unitIndex < 4) { - size /= 1024; - unitIndex++; - } - - return QString("%1 %2") - .arg(QString::number(size, 'f', 1)) - .arg(units[unitIndex]); - }; - // Update legend labels with sizes m_systemLabel->setText( - QString("System (%1)").arg(formatSize(m_systemUsage))); - m_appsLabel->setText(QString("Apps (%1)").arg(formatSize(m_appsUsage))); - m_mediaLabel->setText(QString("Media (%1)").arg(formatSize(m_mediaUsage))); + QString("System (%1)").arg(Utils::formatSize(m_systemUsage))); + m_appsLabel->setText( + QString("Apps (%1)").arg(Utils::formatSize(m_appsUsage))); + m_mediaLabel->setText( + QString("Media (%1)").arg(Utils::formatSize(m_mediaUsage))); m_othersLabel->setText( - QString("Others (%1)").arg(formatSize(m_othersUsage))); - m_freeLabel->setText(QString("Free (%1)").arg(formatSize(m_freeSpace))); + QString("Others (%1)").arg(Utils::formatSize(m_othersUsage))); + m_freeLabel->setText( + QString("Free (%1)").arg(Utils::formatSize(m_freeSpace))); + m_galleryLabel->setText( + QString("Gallery (%1)").arg(Utils::formatSize(m_galleryUsage))); qDebug() << "Disk Usage Updated:" << "System:" << m_systemUsage << "Apps:" << m_appsUsage << "Media:" << m_mediaUsage << "Others:" << m_othersUsage - << "Free:" << m_freeSpace; + << "Gallery:" << m_galleryUsage << "Free:" << m_freeSpace; // Set stretch factors and ensure minimum visibility int systemStretch = std::max( 1, (int)(m_systemUsage / 1000000)); // Convert to MB for stretch int appsStretch = std::max(1, (int)(m_appsUsage / 1000000)); int mediaStretch = std::max(1, (int)(m_mediaUsage / 1000000)); + int galleryStretch = std::max(1, (int)(m_galleryUsage / 1000000)); int othersStretch = std::max(1, (int)(m_othersUsage / 1000000)); int freeStretch = std::max(1, (int)(m_freeSpace / 1000000)); m_diskBarLayout->setStretchFactor(m_systemBar, systemStretch); m_diskBarLayout->setStretchFactor(m_appsBar, appsStretch); m_diskBarLayout->setStretchFactor(m_mediaBar, mediaStretch); + m_diskBarLayout->setStretchFactor(m_galleryBar, galleryStretch); m_diskBarLayout->setStretchFactor(m_othersBar, othersStretch); m_diskBarLayout->setStretchFactor(m_freeBar, freeStretch); @@ -335,36 +358,34 @@ void DiskUsageWidget::updateUI() // #else m_systemBar->setToolTip( QString("System: %1 (%2%)") - .arg(formatSize(m_systemUsage)) + .arg(Utils::formatSize(m_systemUsage)) .arg(QString::number((double)m_systemUsage / m_totalCapacity * 100, 'f', 1))); m_appsBar->setToolTip( QString("Apps: %1 (%2%)") - .arg(formatSize(m_appsUsage)) + .arg(Utils::formatSize(m_appsUsage)) .arg(QString::number((double)m_appsUsage / m_totalCapacity * 100, 'f', 1))); m_mediaBar->setToolTip( QString("Media: %1 (%2%)") - .arg(formatSize(m_mediaUsage)) + .arg(Utils::formatSize(m_mediaUsage)) .arg(QString::number((double)m_mediaUsage / m_totalCapacity * 100, 'f', 1))); + m_galleryBar->setToolTip( + QString("Gallery: %1 (%2%)") + .arg(Utils::formatSize(m_galleryUsage)) + .arg(QString::number((double)m_galleryUsage / m_totalCapacity * 100, + 'f', 1))); m_othersBar->setToolTip( QString("Others: %1 (%2%)") - .arg(formatSize(m_othersUsage)) + .arg(Utils::formatSize(m_othersUsage)) .arg(QString::number((double)m_othersUsage / m_totalCapacity * 100, 'f', 1))); m_freeBar->setToolTip( QString("Free: %1 (%2%)") - .arg(formatSize(m_freeSpace)) + .arg(Utils::formatSize(m_freeSpace)) .arg(QString::number((double)m_freeSpace / m_totalCapacity * 100, 'f', 1))); - - // Hide segments with zero usage - // m_systemBar->setVisible(m_systemUsage > 0); - // m_appsBar->setVisible(m_appsUsage > 0); - // m_mediaBar->setVisible(m_mediaUsage > 0); - // m_othersBar->setVisible(m_othersUsage > 0); - // m_freeBar->setVisible(m_freeSpace > 0); } void DiskUsageWidget::fetchData() { @@ -382,9 +403,10 @@ void DiskUsageWidget::fetchData() m_appsUsage = result["appsUsage"].toULongLong(); m_mediaUsage = result["mediaUsage"].toULongLong(); m_freeSpace = result["freeSpace"].toULongLong(); + m_galleryUsage = result["galleryUsage"].toULongLong(); - uint64_t usedKnown = - m_systemUsage + m_appsUsage + m_mediaUsage; + uint64_t usedKnown = m_systemUsage + m_appsUsage + + m_mediaUsage + m_galleryUsage; if (m_totalCapacity > (m_freeSpace + usedKnown)) { m_othersUsage = m_totalCapacity - m_freeSpace - usedKnown; @@ -457,7 +479,7 @@ void DiskUsageWidget::fetchData() } } result["appsUsage"] = QVariant::fromValue(totalAppsSpace); - plist_free(client_opts); // client_opts is consumed by browse, but + plist_free(client_opts); // Media usage uint64_t mediaSpace = 0; @@ -473,6 +495,117 @@ void DiskUsageWidget::fetchData() } result["mediaUsage"] = QVariant::fromValue(mediaSpace); + /* + on older devices if Photos.sqlite is high in size and the device is + connected wirelessly it takes ~5 minutes to read the entire file maybe + skip on wireless connections on old devices (iPhone 6s in this case)? + */ + if (m_device->deviceInfo.is_iPhone && m_device->deviceInfo.isWireless && + !iDescriptor::Utils::isProductTypeNewer( + m_device->deviceInfo.rawProductType, "iPhone8,4")) { + qDebug() << "Skipping gallery usage calculation on older " + "wireless device."; + result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); + return result; + } + + const size_t CHUNK_SIZE = 256 * 1024; + uint8_t *db_data = nullptr; + size_t db_size = 0; + size_t total_size = 0; + + AfcFileHandle *afcHandle = nullptr; + err = ServiceManager::safeAfcFileOpen( + m_device, "/PhotoData/Photos.sqlite", AfcRdOnly, &afcHandle); + + if (err != nullptr) { + qDebug() << "Failed to open Photos.sqlite on device:" + << "Error Code:" << err->code + << "Message:" << err->message; + idevice_error_free(err); + result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); + return result; + } + + while (true) { + uint8_t *chunk = nullptr; + size_t chunk_size = 0; + + IdeviceFfiError *read_err = ServiceManager::safeAfcFileRead( + m_device, afcHandle, &chunk, CHUNK_SIZE, &chunk_size); + + if (read_err != nullptr) { + idevice_error_free(read_err); + break; + } + + if (chunk_size == 0) { + break; // EOF + } + + db_data = (uint8_t *)realloc(db_data, total_size + chunk_size); + memcpy(db_data + total_size, chunk, chunk_size); + total_size += chunk_size; + } + ServiceManager::safeAfcFileClose(m_device, afcHandle); + qDebug() << "Total Photos.sqlite size read:" << total_size; + + // HACK: File is in WAL mode (byte 18 == 0x02). + // we must change it to Legacy mode (0x01) or SQLite will fail to open + // it. + if (total_size > 20 && db_data[18] == 0x02) { + db_data[18] = 0x01; + db_data[19] = 0x01; + } + + sqlite3 *db; + sqlite3_open(":memory:", &db); + + int rc = sqlite3_deserialize(db, "main", db_data, total_size, + total_size, SQLITE_DESERIALIZE_READONLY); + + if (rc != SQLITE_OK) { + qDebug() << "sqlite3_deserialize failed:" << sqlite3_errmsg(db); + sqlite3_close(db); + if (db_data) + free(db_data); + return result; + } + + const char *sql = "SELECT SUM(ZORIGINALFILESIZE) " + "FROM ZADDITIONALASSETATTRIBUTES;"; + + sqlite3_stmt *stmt = nullptr; + + rc = sqlite3_prepare_v2(db, sql, + -1, // read until NULL terminator + &stmt, nullptr); + + if (rc != SQLITE_OK) { + qDebug() << "Failed to prepare statement:" << sqlite3_errmsg(db); + result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); + if (stmt) + sqlite3_finalize(stmt); + sqlite3_close(db); + if (db_data) + idevice_data_free(db_data, total_size); + return result; + } + + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { + qDebug() << "Size" << sqlite3_column_int64(stmt, 0); + result["galleryUsage"] = + QVariant::fromValue(sqlite3_column_int64(stmt, 0)); + } else if (rc != SQLITE_DONE) { + result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); + qDebug() << "sqlite3_step failed:" << sqlite3_errmsg(db); + } + if (stmt) + sqlite3_finalize(stmt); + sqlite3_close(db); + if (db_data) + free(db_data); return result; }); watcher->setFuture(future); diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h index 532457e..1052bb0 100644 --- a/src/diskusagewidget.h +++ b/src/diskusagewidget.h @@ -65,25 +65,27 @@ private: QVBoxLayout *m_dataLayout; QWidget *m_diskBarContainer; QHBoxLayout *m_diskBarLayout; -/*FIXME: NSPopover bug */ + /*FIXME: NSPopover bug */ // #ifdef Q_OS_MAC -// DiskUsageBar *m_systemBar; -// DiskUsageBar *m_appsBar; -// DiskUsageBar *m_mediaBar; -// DiskUsageBar *m_othersBar; -// DiskUsageBar *m_freeBar; -// #else + // DiskUsageBar *m_systemBar; + // DiskUsageBar *m_appsBar; + // DiskUsageBar *m_mediaBar; + // DiskUsageBar *m_othersBar; + // DiskUsageBar *m_freeBar; + // #else QWidget *m_systemBar; QWidget *m_appsBar; QWidget *m_mediaBar; QWidget *m_othersBar; QWidget *m_freeBar; -// #endif + QWidget *m_galleryBar; + // #endif QHBoxLayout *m_legendLayout; QLabel *m_systemLabel; QLabel *m_appsLabel; QLabel *m_mediaLabel; + QLabel *m_galleryLabel; QLabel *m_othersLabel; QLabel *m_freeLabel; @@ -93,6 +95,7 @@ private: uint64_t m_mediaUsage; uint64_t m_othersUsage; uint64_t m_freeSpace; + uint64_t m_galleryUsage; }; #endif // DISKUSAGEWIDGET_H From 4441ec6dc3a203092e9a72cbd6738eb3d3c0087b Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:20:40 +0000 Subject: [PATCH 09/15] fix(core): seg fault --- src/installedappswidget.cpp | 39 ++++--------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 460d79a..1351c7a 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -308,10 +308,8 @@ void InstalledAppsWidget::fetchInstalledApps() auto installedApps = instproxy.browse(client_opts); if (installedApps.is_ok()) { - plist_t apps_plist; installedApps.unwrap(); - // if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { for (const auto &app_info : installedApps.unwrap()) { if (!app_info) continue; @@ -379,8 +377,6 @@ void InstalledAppsWidget::fetchInstalledApps() apps.append(appData); } } - - plist_free(apps_plist); } plist_free(client_opts); } @@ -440,6 +436,10 @@ void InstalledAppsWidget::onAppsDataReady() qDebug() << "Error connecting to SpringBoard services:" << QString::fromUtf8(err->message); } else { + /* + FIXME:springboard_services_connect takes time + MOVE EVERYTHING INTO QTCONCURRENT SO IT DOESN'T BLOCK UI + */ qDebug() << "Successfully connected to SpringBoard services."; } @@ -634,37 +634,6 @@ void InstalledAppsWidget::loadAppContainer(const QString &bundleId) char **dirs = nullptr; size_t count = 0; - // // Use safe wrapper to read directory - // IdeviceFfiError *err = ServiceManager::safeAfcReadDirectory( - // m_device, "/Documents", &dirs, count, afcClient); - - // if (err != nullptr) { - // qDebug() << "Error reading Documents dir:" - // << QString::fromUtf8(err->message); - // result["error"] = QString("Error reading Documents dir: - // %1") - // .arg(QString::fromUtf8(err->message)); - // if (afcClient) - // afc_client_free(afcClient); - // if (houseArrestClient) - // house_arrest_client_free(houseArrestClient); - // return result; - // } - - // QStringList files; - // if (dirs) { - // for (int i = 0; dirs[i]; i++) { - // QString fileName = QString::fromUtf8(dirs[i]); - // if (fileName != "." && fileName != "..") { - // qDebug() << "Found file:" << fileName; - // files.append(fileName); - // } - // } - // } - - // free_directory_listing(dirs, count); - // qDebug() << "Total files found:" << files.size(); - // result["files"] = files; result["afcClient"] = QVariant::fromValue(reinterpret_cast(afcClient)); result["houseArrestClient"] = QVariant::fromValue( From 8fd2c6c76e1d67a20d8aa10e71ee86524816f12a Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:20:55 +0000 Subject: [PATCH 10/15] feat(build): add SQLite3 dependency to CMakeLists.txt --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 573e0bd..3c58a79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ endif() find_package(PkgConfig REQUIRED) find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Network QuickControls2 SerialPort Positioning Location QuickWidgets) - +find_package(SQLite3 REQUIRED) # Add QTermWidget # Prefer CMake-native qtermwidget6, fallback to pkg-config if needed find_package(qtermwidget6 QUIET) @@ -413,6 +413,7 @@ target_link_libraries(iDescriptor PRIVATE ZUpdater ZToast ${IDEVICE_IMPLEMENTATION_LIBS} + SQLite::SQLite3 ) # # Conditionally link libirecovery From b40707082917b6fd7c7885ef01ff9fd871f4c2f3 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:41:14 -0800 Subject: [PATCH 11/15] feat(dnssd): add macAddress field --- src/core/services/dnssd/dnssd_service.cpp | 2 ++ src/core/services/dnssd/dnssd_service.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/core/services/dnssd/dnssd_service.cpp b/src/core/services/dnssd/dnssd_service.cpp index 05dcedb..1d615f3 100644 --- a/src/core/services/dnssd/dnssd_service.cpp +++ b/src/core/services/dnssd/dnssd_service.cpp @@ -174,6 +174,7 @@ void DNSSD_API DnssdService::resolveCallback( pending.hostname = QString::fromUtf8(hosttarget); pending.port = ntohs(port); pending.interfaceIndex = interfaceIndex; + pending.macAddress = serviceName.split('@').first(); // Parse TXT records if (txtLen > 0 && txtRecord) { @@ -288,6 +289,7 @@ void DNSSD_API DnssdService::addrInfoCallback( device.hostname = pending.hostname; device.address = QString::fromUtf8(ip); device.port = pending.port > 0 ? pending.port : 22; // Default to SSH port + device.macAddress = pending.macAddress; qDebug() << "Resolved IP for Apple device:" << device.name << "at" << device.address << ":" << device.port; diff --git a/src/core/services/dnssd/dnssd_service.h b/src/core/services/dnssd/dnssd_service.h index 2bb0337..0818bba 100644 --- a/src/core/services/dnssd/dnssd_service.h +++ b/src/core/services/dnssd/dnssd_service.h @@ -88,6 +88,7 @@ private: uint16_t port; uint32_t interfaceIndex; QMap txt; + QString macAddress; }; QMap m_pendingDevices; }; From d4100c48a9b9ae2cf1db64ade7e6204778bf2a6a Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:49:00 -0800 Subject: [PATCH 12/15] feat(ui): add NetworkDevicesToConnectWidget, update mainwindow --- src/mainwindow.cpp | 32 +-- src/networkdevicemanager.cpp | 6 +- src/networkdevicestoconnectwidget.cpp | 267 ++++++++++++++++++++++++++ src/networkdevicestoconnectwidget.h | 68 +++++++ src/toolboxwidget.cpp | 5 +- src/welcomewidget.cpp | 17 +- 6 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 src/networkdevicestoconnectwidget.cpp create mode 100644 src/networkdevicestoconnectwidget.h diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f2608a0..25f239e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,7 @@ #include "appcontext.h" #include "settingsmanager.h" // #include "devicemonitor.h" -#include "Toast.h" +// #include "Toast.h" #include "networkdevicemanager.h" #include "networkdeviceswidget.h" #include "statusballoon.h" @@ -414,7 +415,10 @@ MainWindow::MainWindow(QWidget *parent) } }); - m_deviceMonitor->start(); + /* If a device is connected before starting the app on slower machines ui + * takes a lot of time to render so delay the monitoring a bit */ + QTimer::singleShot(std::chrono::seconds(1), this, + [this]() { m_deviceMonitor->start(); }); connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [](const std::string &udid, const std::string &wifiMacAddress) { @@ -469,17 +473,17 @@ MainWindow::MainWindow(QWidget *parent) connect(AppContext::sharedInstance(), &AppContext::deviceHeartbeatFailed, this, [this](const QString &macAddress, int tries) { - Toast *toast = new Toast(this); - toast->setAttribute(Qt::WA_DeleteOnClose); - toast->setDuration(8000); // Hide after 8 seconds - toast->setTitle("Heartbeat failed"); - toast->setText( - QString("Heartbeat failed for device with MAC %1. " - "Number of failed attempts: %2") - .arg(macAddress) - .arg(tries)); - toast->setPosition(ToastPosition::BOTTOM_MIDDLE); - toast->show(); + // Toast *toast = new Toast(this); + // toast->setAttribute(Qt::WA_DeleteOnClose); + // toast->setDuration(8000); // Hide after 8 seconds + // toast->setTitle("Heartbeat failed"); + // toast->setText( + // QString("Heartbeat failed for device with MAC %1. " + // "Number of failed attempts: %2") + // .arg(macAddress) + // .arg(tries)); + // toast->setPosition(ToastPosition::BOTTOM_MIDDLE); + // toast->show(); }); // NetworkDevicesWidget *m_networkDevicesWidget = new @@ -532,7 +536,7 @@ MainWindow::~MainWindow() delete ui; m_deviceMonitor->requestInterruption(); // FIXME:QThread: Destroyed while thread '' is still running - // m_deviceMonitor->wait(); + // m_deviceMonitor->wait(); delete m_deviceMonitor; // delete m_updater; // sleep(2); diff --git a/src/networkdevicemanager.cpp b/src/networkdevicemanager.cpp index 1177fb8..7f52159 100644 --- a/src/networkdevicemanager.cpp +++ b/src/networkdevicemanager.cpp @@ -1,4 +1,5 @@ #include "networkdevicemanager.h" +#include NetworkDeviceManager *NetworkDeviceManager::sharedInstance() { @@ -23,6 +24,7 @@ NetworkDeviceManager::NetworkDeviceManager(QObject *parent) : QObject{parent} &NetworkDeviceManager::deviceRemoved); #endif - // Start scanning for network devices - m_networkProvider->startBrowsing(); + /* Helps main ui load a litte faster */ + QTimer::singleShot(std::chrono::seconds(1), this, + [this]() { m_networkProvider->startBrowsing(); }); } diff --git a/src/networkdevicestoconnectwidget.cpp b/src/networkdevicestoconnectwidget.cpp new file mode 100644 index 0000000..a08b4f3 --- /dev/null +++ b/src/networkdevicestoconnectwidget.cpp @@ -0,0 +1,267 @@ +/* + * iDescriptor: A free and open-source idevice management tool. + * + * Copyright (C) 2025 Uncore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "networkdevicestoconnectwidget.h" + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include "appcontext.h" +#include +#include +#include +#include +#include +#include + +NetworkDevicesToConnectWidget::NetworkDevicesToConnectWidget(QWidget *parent) + : QWidget(parent) +{ + setupUI(); + +#ifdef __linux__ + m_networkProvider = new AvahiService(this); + connect(m_networkProvider, &AvahiService::deviceAdded, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceAdded); + connect(m_networkProvider, &AvahiService::deviceRemoved, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceRemoved); +#else + m_networkProvider = new DnssdService(this); + connect(m_networkProvider, &DnssdService::deviceAdded, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceAdded); + connect(m_networkProvider, &DnssdService::deviceRemoved, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceRemoved); +#endif + + // Start scanning for network devices + m_networkProvider->startBrowsing(); + + // Initial device list update + updateDeviceList(); +} + +NetworkDevicesToConnectWidget::~NetworkDevicesToConnectWidget() +{ + if (m_networkProvider) { + m_networkProvider->stopBrowsing(); + } +} + +void NetworkDevicesToConnectWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->setSpacing(10); + + // Status label + m_statusLabel = new QLabel("Scanning for network devices..."); + QFont statusFont = m_statusLabel->font(); + statusFont.setPointSize(12); + statusFont.setWeight(QFont::Medium); + m_statusLabel->setFont(statusFont); + m_statusLabel->setAlignment(Qt::AlignCenter); + mainLayout->addWidget(m_statusLabel); + + // Device group + m_deviceGroup = new QGroupBox("Network Devices"); + QFont groupFont = m_deviceGroup->font(); + groupFont.setPointSize(14); + groupFont.setWeight(QFont::Bold); + m_deviceGroup->setFont(groupFont); + + QVBoxLayout *groupLayout = new QVBoxLayout(m_deviceGroup); + groupLayout->setContentsMargins(5, 15, 5, 5); + groupLayout->setSpacing(0); + + // Scroll area + m_scrollArea = new QScrollArea(); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setMinimumHeight(200); + m_scrollArea->setMaximumHeight(400); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + /* FIXME: We need a better approach to theme awareness */ + connect(qApp, &QApplication::paletteChanged, this, [this]() { + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + }); + + // Scroll content + m_scrollContent = new QWidget(); + m_scrollContent->setContentsMargins(0, 0, 0, 0); + m_deviceLayout = new QVBoxLayout(m_scrollContent); + m_deviceLayout->setContentsMargins(5, 5, 5, 5); + m_deviceLayout->setSpacing(8); + m_deviceLayout->addStretch(); + + m_scrollArea->setWidget(m_scrollContent); + groupLayout->addWidget(m_scrollArea); + + mainLayout->addWidget(m_deviceGroup); + mainLayout->addStretch(); +} + +void NetworkDevicesToConnectWidget::createDeviceCard( + const NetworkDevice &device) +{ + // Main card frame + QWidget *card = new QWidget(); + + QVBoxLayout *cardLayout = new QVBoxLayout(card); + cardLayout->setContentsMargins(12, 10, 12, 10); + cardLayout->setSpacing(4); + + // Device name (primary) + QLabel *nameLabel = new QLabel(device.name); + nameLabel->setWordWrap(true); + QFont nameFont = nameLabel->font(); + nameFont.setPointSize(13); + nameFont.setWeight(QFont::Medium); + nameLabel->setFont(nameFont); + QPalette namePalette = nameLabel->palette(); + namePalette.setColor(QPalette::WindowText, + palette().color(QPalette::WindowText)); + nameLabel->setPalette(namePalette); + + // Device info container + QWidget *infoContainer = new QWidget(); + QHBoxLayout *infoLayout = new QHBoxLayout(infoContainer); + infoLayout->setContentsMargins(0, 0, 0, 0); + infoLayout->setSpacing(12); + + // Address info + QLabel *addressLabel = new QLabel(QString("IP: %1").arg(device.address)); + QFont addressFont = addressLabel->font(); + addressFont.setPointSize(11); + addressLabel->setFont(addressFont); + QPalette addressPalette = addressLabel->palette(); + QColor secondaryColor = palette().color(QPalette::WindowText); + secondaryColor.setAlpha(180); + addressPalette.setColor(QPalette::WindowText, secondaryColor); + addressLabel->setPalette(addressPalette); + + // Port info + QLabel *portLabel = new QLabel(QString("Port: %1").arg(device.port)); + portLabel->setFont(addressFont); + portLabel->setPalette(addressPalette); + + infoLayout->addWidget(addressLabel); + infoLayout->addWidget(portLabel); + infoLayout->addStretch(); + + QPushButton *connectButton = new QPushButton("Connect"); + connectButton->setDefault(true); + connect(connectButton, &QPushButton::clicked, this, + [this, device, connectButton]() { + connectButton->setText("Connecting..."); + connectButton->setEnabled(false); + AppContext::sharedInstance()->tryToConnectToNetworkDevice( + device.macAddress); + }); + infoLayout->addWidget(connectButton); + infoLayout->addSpacing(5); + + // Status indicator + QLabel *statusIndicator = new QLabel("●"); + QFont statusFont = statusIndicator->font(); + statusFont.setPointSize(12); + statusIndicator->setFont(statusFont); + QPalette statusPalette = statusIndicator->palette(); + statusPalette.setColor(QPalette::WindowText, + QColor(52, 199, 89)); // iOS green + statusIndicator->setPalette(statusPalette); + + infoLayout->addWidget(statusIndicator); + + cardLayout->addWidget(nameLabel); + cardLayout->addWidget(infoContainer); + + // Store the device info as property for later removal + card->setProperty("deviceName", device.name); + card->setProperty("deviceAddress", device.address); + + // Insert before the stretch + m_deviceLayout->insertWidget(m_deviceLayout->count() - 1, card); + m_deviceCards.append(card); +} + +void NetworkDevicesToConnectWidget::clearDeviceCards() +{ + for (QWidget *card : m_deviceCards) { + card->deleteLater(); + } + m_deviceCards.clear(); +} + +void NetworkDevicesToConnectWidget::updateDeviceList() +{ + clearDeviceCards(); + + QList devices = m_networkProvider->getNetworkDevices(); + + if (devices.isEmpty()) { + m_statusLabel->setText("No network devices found"); + } else { + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(devices.count())); + + for (const NetworkDevice &device : devices) { + createDeviceCard(device); + } + } +} + +void NetworkDevicesToConnectWidget::onWirelessDeviceAdded( + const NetworkDevice &device) +{ + createDeviceCard(device); + + // Update status + int deviceCount = m_deviceCards.count(); + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(deviceCount)); +} + +void NetworkDevicesToConnectWidget::onWirelessDeviceRemoved( + const QString &deviceName) +{ + // Find and remove the corresponding card + for (int i = 0; i < m_deviceCards.count(); ++i) { + QWidget *card = m_deviceCards[i]; + if (card->property("deviceName").toString() == deviceName) { + m_deviceCards.removeAt(i); + card->deleteLater(); + break; + } + } + + // Update status + int deviceCount = m_deviceCards.count(); + if (deviceCount == 0) { + m_statusLabel->setText("No network devices found"); + } else { + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(deviceCount)); + } +} \ No newline at end of file diff --git a/src/networkdevicestoconnectwidget.h b/src/networkdevicestoconnectwidget.h new file mode 100644 index 0000000..0f22f2b --- /dev/null +++ b/src/networkdevicestoconnectwidget.h @@ -0,0 +1,68 @@ +/* + * iDescriptor: A free and open-source idevice management tool. + * + * Copyright (C) 2025 Uncore + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#ifndef NETWORKDEVICESTOCONNECTWIDGET_H +#define NETWORKDEVICESTOCONNECTWIDGET_H + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include +#include +#include +#include +#include + +class NetworkDevicesToConnectWidget : public QWidget +{ + Q_OBJECT + +public: + explicit NetworkDevicesToConnectWidget(QWidget *parent = nullptr); + ~NetworkDevicesToConnectWidget(); + +private slots: + void onWirelessDeviceAdded(const NetworkDevice &device); + void onWirelessDeviceRemoved(const QString &deviceName); + +private: + void setupUI(); + void createDeviceCard(const NetworkDevice &device); + void clearDeviceCards(); + void updateDeviceList(); + + QGroupBox *m_deviceGroup = nullptr; + QScrollArea *m_scrollArea = nullptr; + QWidget *m_scrollContent = nullptr; + QVBoxLayout *m_deviceLayout = nullptr; + QLabel *m_statusLabel = nullptr; + +#ifdef __linux__ + AvahiService *m_networkProvider = nullptr; +#else + DnssdService *m_networkProvider = nullptr; +#endif + + QList m_deviceCards; +}; + +#endif // NETWORKDEVICESTOCONNECTWIDGET_H \ No newline at end of file diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 8c9d47a..a8d56f1 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -79,8 +79,9 @@ void ToolboxWidget::setupUI() m_scrollArea = new QScrollArea(); m_scrollArea->setWidgetResizable(true); m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setFrameStyle(QFrame::NoFrame); - m_scrollArea->viewport()->setAutoFillBackground(false); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + m_scrollArea->viewport()->setStyleSheet("background: transparent;"); m_contentWidget = new QWidget(); QVBoxLayout *contentLayout = new QVBoxLayout(m_contentWidget); diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 57bb706..4e7fd3f 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -21,6 +21,7 @@ #include "diagnosewidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "networkdevicestoconnectwidget.h" #include "responsiveqlabel.h" #include #include @@ -53,6 +54,8 @@ void WelcomeWidget::setupUI() QPalette palette = m_subtitleLabel->palette(); m_mainLayout->addWidget(m_subtitleLabel); + QHBoxLayout *imageAndWirelessDevicesLayout = new QHBoxLayout(); + m_imageLabel = new ResponsiveQLabel(); m_imageLabel->setPixmap(QPixmap(":/resources/connect.png")); m_imageLabel->setScaledContents(true); @@ -61,7 +64,19 @@ void WelcomeWidget::setupUI() m_imageLabel->setStyleSheet("background: transparent; border: none;"); m_imageLabel->setAlignment(Qt::AlignCenter); - m_mainLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter); + + imageAndWirelessDevicesLayout->addStretch(1); + // m_imageLabel->setMaximumWidth(300); + imageAndWirelessDevicesLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter); + imageAndWirelessDevicesLayout->addStretch(1); + NetworkDevicesToConnectWidget *networkDevicesWidget = + new NetworkDevicesToConnectWidget(); + // FIMXE: resize original image + networkDevicesWidget->setMinimumWidth(350); + imageAndWirelessDevicesLayout->addWidget(networkDevicesWidget); + imageAndWirelessDevicesLayout->addStretch(1); + + m_mainLayout->addLayout(imageAndWirelessDevicesLayout); m_mainLayout->addSpacing(10); m_instructionLabel = createStyledLabel( From 9211187c4e8f672c1fe7636d1625c027dae2b125 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:52:02 -0800 Subject: [PATCH 13/15] refactor(core): update wireless device connection logic to work cross-platform --- src/appcontext.cpp | 208 ++++++++++++++++++++++++------ src/appcontext.h | 2 + src/core/services/init_device.cpp | 3 + src/iDescriptor.h | 8 +- 4 files changed, 179 insertions(+), 42 deletions(-) diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 42b7953..542cac0 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -41,9 +41,17 @@ AppContext *AppContext::sharedInstance() */ AppContext::AppContext(QObject *parent) : QObject{parent} { + cachePairedDevices(); +} - // FIXME: windows and macOS support later - QDir lockdowndir("/var/lib/lockdown"); +void AppContext::cachePairedDevices() +{ + +/* + does not work on macOS because we cannot read /var/db/lockdown without root perm +*/ +#ifndef __APPLE__ + QDir lockdowndir(LOCKDOWN_PATH); if (!lockdowndir.exists()) { return; } @@ -54,6 +62,7 @@ AppContext::AppContext(QObject *parent) : QObject{parent} qDebug() << "Parsing cached pairing files in /var/lib/lockdown:"; for (const QString &fileName : pairingFiles) { + qDebug() << "Found pairing file:" << fileName; plist_t fileData = nullptr; plist_read_from_file( lockdowndir.filePath(fileName).toUtf8().constData(), &fileData, @@ -67,7 +76,7 @@ AppContext::AppContext(QObject *parent) : QObject{parent} PlistNavigator(fileData)["WiFiMACAddress"].getString(); // plist_free(fileData); bool isCompatible = !wifiMacAddress.empty(); - // TODO: !important invalidate old pairing files + // TODO: !important invalidate expired pairing files // sometimes there is no WiFiMACAddress if (!isCompatible) { continue; @@ -81,6 +90,88 @@ AppContext::AppContext(QObject *parent) : QObject{parent} m_pairingFileCache[QString::fromStdString(wifiMacAddress)] = lockdowndir.filePath(fileName); } +#else + // FIXME: implement caching for macOS + /* MacOS */ + qDebug() << "Caching paired network devices from usbmuxd"; + auto conn = UsbmuxdConnection::default_new(0); + if (conn.is_err()) { + qDebug() << "ERROR: Failed to connect to usbmuxd!"; + // return 1; + } + + auto devices = conn.unwrap().get_devices(); + if (devices.is_err()) { + qDebug() << "ERROR: Failed to get device list!"; + // return 1; + } + + for (const auto &device : devices.unwrap()) { + auto conn_type = device.get_connection_type(); + if (conn_type.is_some() && + conn_type.unwrap().to_string() == "Network") { + auto udid = device.get_udid(); + if (udid.is_some()) { + qDebug() << "Found network device with UDID:" + << QString::fromStdString(udid.unwrap()); + + auto pairing_file = + conn.unwrap().get_pair_record(udid.unwrap()); + + uint8_t *plist_data = nullptr; + size_t plist_size = 0; + IdeviceFfiError *error = idevice_pairing_file_serialize( + pairing_file.unwrap().raw(), &plist_data, &plist_size); + + if (error == nullptr) { + plist_t root_node = nullptr; + plist_from_xml((const char *)plist_data, plist_size, + &root_node); + + if (root_node) { + plist_t wifi_mac_node = + plist_dict_get_item(root_node, "WiFiMACAddress"); + + if (wifi_mac_node && + plist_get_node_type(wifi_mac_node) == + PLIST_STRING) { + char *mac_address = nullptr; + plist_get_string_val(wifi_mac_node, &mac_address); + + if (mac_address) { + qDebug() << "Adding to cache" + << QString::fromUtf8(mac_address); + m_pairingFileCache[QString::fromUtf8( + mac_address)] = + QString::fromStdString("/var/db/lockdown/" + + udid.unwrap() + + ".plist"); + free(mac_address); + } + } + + plist_free(root_node); + } + free(plist_data); + } + // qDebug() << "Wireless device UDID:" + // << QString::fromStdString(udid.unwrap()) + // << "Pairing file retrieval" + // << (error == nullptr + // ? "succeeded" + // : QString::fromStdString(error->message)); + // Clean up + // idevice_pairing_file_free(pairing_file.unwrap().raw()); + } + } else if (conn_type.is_some()) { + auto udid = device.get_udid(); + if (udid.is_some()) { + qDebug() << "Found USB device with UDID:" + << QString::fromStdString(udid.unwrap()); + } + } + } +#endif } void AppContext::addDevice(QString udid, @@ -105,15 +196,6 @@ void AppContext::addDevice(QString udid, return; } - QFile pairingFilePath(_pairingFilePath); - if (!pairingFilePath.exists()) { - qDebug() - << "Cannot upgrade to wireless, no pairing file for" - << udid; - return; - } - pairingFilePath.close(); - QList networkDevices = NetworkDeviceManager::sharedInstance() ->m_networkProvider->getNetworkDevices(); @@ -128,7 +210,7 @@ void AppContext::addDevice(QString udid, if (it != networkDevices.constEnd()) { *initResult = init_idescriptor_device( - udid, {it->address, pairingFilePath.fileName()}); + udid, {it->address, _pairingFilePath}); } else { qDebug() << "No network device found with MAC address:" << wifiMacAddress; @@ -145,15 +227,6 @@ void AppContext::addDevice(QString udid, return; } - QFile pairingFilePath(_pairingFilePath); - if (!pairingFilePath.exists()) { - qDebug() - << "Cannot upgrade to wireless, no pairing file for" - << udid; - return; - } - pairingFilePath.close(); - QList networkDevices = NetworkDeviceManager::sharedInstance() ->m_networkProvider->getNetworkDevices(); @@ -168,7 +241,7 @@ void AppContext::addDevice(QString udid, if (it != networkDevices.constEnd()) { *initResult = init_idescriptor_device( - udid, {it->address, pairingFilePath.fileName()}); + udid, {it->address, _pairingFilePath}); } else { qDebug() << "No network device found with MAC address:" << wifiMacAddress; @@ -266,7 +339,8 @@ void AppContext::addDevice(QString udid, .mutex = new std::recursive_mutex(), .imageMounter = initResult->imageMounter, .diagRelay = initResult->diagRelay, - .locationSimulation = initResult->locationSimulation}; + .locationSimulation = initResult->locationSimulation, + .heartbeatThread = initResult->heartbeatThread}; m_devices[device->udid] = device; if (addType == AddType::Regular) { qDebug() << "Regular device added: " << udid; @@ -335,15 +409,22 @@ void AppContext::removeDevice(QString _udid) emit deviceRemoved(udid, device->deviceInfo.wifiMacAddress); emit deviceChange(); - // std::lock_guard lock(*device->mutex); + std::lock_guard lock(*device->mutex); - // if (device->afcClient) - // afc_client_free(device->afcClient); - // if (device->afc2Client) - // afc_client_free(device->afc2Client); + if (device->afcClient) + afc_client_free(device->afcClient); + if (device->afc2Client) + afc_client_free(device->afc2Client); // idevice_free(device->device); - // delete device->mutex; - // delete device; + + if (device->heartbeatThread) { + device->heartbeatThread->requestInterruption(); + device->heartbeatThread->wait(); + delete device->heartbeatThread; + } + + delete device->mutex; + delete device; } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT @@ -427,16 +508,21 @@ void AppContext::addRecoveryDevice(uint64_t ecid) AppContext::~AppContext() { - // for (auto device : m_devices) { - // emit deviceRemoved(device->udid); - // if (device->afcClient) - // afc_client_free(device->afcClient); - // if (device->afc2Client) - // afc_client_free(device->afc2Client); - // idevice_free(device->device); - // delete device->mutex; - // delete device; - // } + // FIXME: mutex? + for (auto device : m_devices) { + emit deviceRemoved(device->udid, device->deviceInfo.wifiMacAddress); + if (device->afcClient) + afc_client_free(device->afcClient); + if (device->afc2Client) + afc_client_free(device->afc2Client); + // idevice_free(device->device); + + if (device->heartbeatThread) { + device->heartbeatThread->requestInterruption(); + device->heartbeatThread->wait(); + delete device->heartbeatThread; + } + } // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // for (auto recoveryDevice : m_recoveryDevices) { @@ -492,6 +578,17 @@ const QString AppContext::getCachedPairingFile(const QString &udid) const // Retrieve the pairing file from the cache if (m_pairingFileCache.contains(udid)) { pairingFile = m_pairingFileCache.value(udid); + } else { + // FIXME: mac support + // IdevicePairingFile* pairing_file = nullptr; + // const char* file_path = udid.toUtf8().constData(); + // IdeviceFfiError* err = idevice_pairing_file_read(file_path, + // &pairing_file); if (err == nullptr || pairing_file == nullptr) { + // pairingFile = QString::fromUtf8(file_path); + // } else { + // qDebug() << "Failed to read pairing file for UDID:" << udid << + // "Error:" << err->message; idevice_error_free(err); + // } } return pairingFile; @@ -500,4 +597,33 @@ const QString AppContext::getCachedPairingFile(const QString &udid) const void AppContext::heartbeatFailed(const QString &macAddress, int tries) { emit deviceHeartbeatFailed(macAddress, tries); +} + +void AppContext::tryToConnectToNetworkDevice(const QString &macAddress) +{ + + // force refresh macAddress-udid mapping + cachePairedDevices(); + + QList networkDevices = + NetworkDeviceManager::sharedInstance() + ->m_networkProvider->getNetworkDevices(); + + auto it = + std::find_if(networkDevices.constBegin(), networkDevices.constEnd(), + [macAddress](const NetworkDevice &device) { + return device.macAddress.compare( + macAddress, Qt::CaseInsensitive) == 0; + }); + + if (it != networkDevices.constEnd()) { + QMetaObject::invokeMethod( + AppContext::sharedInstance(), "addDevice", Qt::QueuedConnection, + Q_ARG(QString, macAddress), + Q_ARG(DeviceMonitorThread::IdeviceConnectionType, + DeviceMonitorThread::CONNECTION_NETWORK), + Q_ARG(AddType, AddType::Wireless), Q_ARG(QString, macAddress)); + } else { + qDebug() << "No network device found with MAC address:" << macAddress; + } } \ No newline at end of file diff --git a/src/appcontext.h b/src/appcontext.h index 49d2584..febac7c 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -38,6 +38,7 @@ public: void cachePairingFile(const QString &udid, const QString &pairingFilePath); const QString getCachedPairingFile(const QString &udid) const; + void tryToConnectToNetworkDevice(const QString &macAddress); // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // QList getAllRecoveryDevices(); // #endif @@ -57,6 +58,7 @@ private: QStringList m_pendingDevices; DeviceSelection m_currentSelection = DeviceSelection(""); QMap m_pairingFileCache; + void cachePairedDevices(); signals: void deviceAdded(iDescriptorDevice *device); void deviceRemoved(const std::string &udid, const std::string &macAddress); diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 7242920..9e28884 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -416,6 +416,8 @@ init_idescriptor_device(const QString &udid, addr_in.sin_port = htons(0); inet_pton(AF_INET, wirelessArgs.ip.toUtf8().constData(), &addr_in.sin_addr); + qDebug() << "Reading pairing file from" << wirelessArgs.pairing_file + << "for udid" << udid; err = idevice_pairing_file_read( wirelessArgs.pairing_file.toUtf8().constData(), &pairing_file); if (err) { @@ -545,6 +547,7 @@ init_idescriptor_device(const QString &udid, result.diagRelay = std::make_shared( DiagnosticsRelay::adopt(diagnostics_relay)); result.locationSimulation = location_simulation; + result.heartbeatThread = heartbeatThread; // TODO cache pairing file path result.deviceInfo.isWireless = isWireless; fullDeviceInfo(infoXml, afc_client, result.diagRelay.get(), result); diff --git a/src/iDescriptor.h b/src/iDescriptor.h index f7ed588..20f1131 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -23,6 +23,7 @@ #include #include #include +#include #include // #include "idevice.h" @@ -80,7 +81,8 @@ #elif __APPLE__ #define LOCKDOWN_PATH "/var/db/lockdown" #else -#define LOCKDOWN_PATH "" +/* Windows */ +#define LOCKDOWN_PATH qgetenv("PROGRAMDATA") + "/Apple/Lockdown" #endif struct BatteryInfo { @@ -214,12 +216,15 @@ struct iDescriptorDevice { IdeviceProviderHandle *provider; DeviceInfo deviceInfo; AfcClientHandle *afcClient; + // nullptr if the device is not jailbroken or doesn't have AFC2 installed AfcClientHandle *afc2Client; LockdowndClientHandle *lockdown; std::recursive_mutex *mutex; ImageMounterHandle *imageMounter; std::shared_ptr diagRelay; LocationSimulationHandle *locationSimulation; + // nullptr on USB devices + QThread *heartbeatThread; }; struct iDescriptorInitDeviceResult { @@ -233,6 +238,7 @@ struct iDescriptorInitDeviceResult { ImageMounterHandle *imageMounter; std::shared_ptr diagRelay; LocationSimulationHandle *locationSimulation; + QThread *heartbeatThread; }; // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT // struct iDescriptorRecoveryDevice { From bdf574db0cb0dc6be1bca349a228d31baa56da0f Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:55:13 -0800 Subject: [PATCH 14/15] refactor(qballoontip): update balloon tip implementation --- src/qballoontip.cpp | 193 ++++++++++++++++++++++---------------------- 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/src/qballoontip.cpp b/src/qballoontip.cpp index 1ec676a..2817a0e 100644 --- a/src/qballoontip.cpp +++ b/src/qballoontip.cpp @@ -66,12 +66,12 @@ QBalloonTip::QBalloonTip(const QIcon &icon, const QString &title, } // Add drop shadow effect - QGraphicsDropShadowEffect *shadowEffect = - new QGraphicsDropShadowEffect(this); - shadowEffect->setBlurRadius(200); - shadowEffect->setColor(QColor(0, 0, 0, 80)); - shadowEffect->setOffset(0, 5); - setGraphicsEffect(shadowEffect); + // QGraphicsDropShadowEffect *shadowEffect = + // new QGraphicsDropShadowEffect(this); + // shadowEffect->setBlurRadius(200); + // shadowEffect->setColor(QColor(0, 0, 0, 80)); + // shadowEffect->setOffset(0, 5); + // setGraphicsEffect(shadowEffect); // QLabel *titleLabel = new QLabel; // titleLabel->installEventFilter(this); @@ -121,102 +121,103 @@ QBalloonTip::QBalloonTip(const QIcon &icon, const QString &title, QBalloonTip::~QBalloonTip() { theSolitaryBalloonTip = nullptr; } -void QBalloonTip::paintEvent(QPaintEvent *) +void QBalloonTip::paintEvent(QPaintEvent *ev) { QPainter painter(this); painter.drawPixmap(rect(), pixmap); + QWidget::paintEvent(ev); } void QBalloonTip::resizeEvent(QResizeEvent *ev) { QWidget::resizeEvent(ev); } void QBalloonTip::balloon(const QPoint &pos, int msecs, bool showArrow) { - this->showArrow = showArrow; - QScreen *screen = QGuiApplication::screenAt(pos); - if (!screen) - screen = QGuiApplication::primaryScreen(); - QRect screenRect = screen->geometry(); - QSize sh = sizeHint(); - const int border = 1; - const int ah = 18, aw = 18, rc = 7; - bool arrowAtTop = (pos.y() + sh.height() + ah < screenRect.height()); + // this->showArrow = showArrow; + // QScreen *screen = QGuiApplication::screenAt(pos); + // if (!screen) + // screen = QGuiApplication::primaryScreen(); + // QRect screenRect = screen->geometry(); + // QSize sh = sizeHint(); + // const int border = 1; + // const int ah = 18, aw = 18, rc = 7; + // bool arrowAtTop = (pos.y() + sh.height() + ah < screenRect.height()); - setContentsMargins(border + 3, border + (arrowAtTop ? ah : 0) + 2, - border + 3, border + (arrowAtTop ? 0 : ah) + 2); - updateGeometry(); - sh = sizeHint(); + // setContentsMargins(border + 3, border + (arrowAtTop ? ah : 0) + 2, + // border + 3, border + (arrowAtTop ? 0 : ah) + 2); + // updateGeometry(); + // sh = sizeHint(); - // Center the balloon relative to the pos point (button center) - int balloonX = pos.x() - sh.width() / 2; + // // Center the balloon relative to the pos point (button center) + // int balloonX = pos.x() - sh.width() / 2; - // Calculate arrow offset from left edge of balloon to center it - int ao = sh.width() / 2 - aw / 2; // Center the arrow on the balloon + // // Calculate arrow offset from left edge of balloon to center it + // int ao = sh.width() / 2 - aw / 2; // Center the arrow on the balloon - int ml, mr, mt, mb; - QSize sz = sizeHint(); - if (!arrowAtTop) { - ml = mt = 0; - mr = sz.width() - 1; - mb = sz.height() - ah - 1; - } else { - ml = 0; - mt = ah; - mr = sz.width() - 1; - mb = sz.height() - 1; - } + // int ml, mr, mt, mb; + // QSize sz = sizeHint(); + // if (!arrowAtTop) { + // ml = mt = 0; + // mr = sz.width() - 1; + // mb = sz.height() - ah - 1; + // } else { + // ml = 0; + // mt = ah; + // mr = sz.width() - 1; + // mb = sz.height() - 1; + // } - QPainterPath path; - path.moveTo(ml + rc, mt); - if (arrowAtTop) { - if (showArrow) { - path.lineTo(ml + ao, mt); - path.lineTo(ml + ao + aw / 2, mt - ah); - path.lineTo(ml + ao + aw, mt); - } - move(qBound(screenRect.left() + 2, balloonX, - screenRect.right() - sh.width() - 2), - pos.y()); - } - path.lineTo(mr - rc, mt); - path.arcTo(QRect(mr - rc * 2, mt, rc * 2, rc * 2), 90, -90); - path.lineTo(mr, mb - rc); - path.arcTo(QRect(mr - rc * 2, mb - rc * 2, rc * 2, rc * 2), 0, -90); - if (!arrowAtTop) { - if (showArrow) { - path.lineTo(mr - ao - aw, mb); - path.lineTo(mr - ao - aw / 2, mb + ah); - path.lineTo(mr - ao, mb); - } - move(qBound(screenRect.left() + 2, balloonX, - screenRect.right() - sh.width() - 2), - pos.y() - sh.height()); - } - path.lineTo(ml + rc, mb); - path.arcTo(QRect(ml, mb - rc * 2, rc * 2, rc * 2), -90, -90); - path.lineTo(ml, mt + rc); - path.arcTo(QRect(ml, mt, rc * 2, rc * 2), 180, -90); + // QPainterPath path; + // path.moveTo(ml + rc, mt); + // if (arrowAtTop) { + // if (showArrow) { + // path.lineTo(ml + ao, mt); + // path.lineTo(ml + ao + aw / 2, mt - ah); + // path.lineTo(ml + ao + aw, mt); + // } + // move(qBound(screenRect.left() + 2, balloonX, + // screenRect.right() - sh.width() - 2), + // pos.y()); + // } + // path.lineTo(mr - rc, mt); + // path.arcTo(QRect(mr - rc * 2, mt, rc * 2, rc * 2), 90, -90); + // path.lineTo(mr, mb - rc); + // path.arcTo(QRect(mr - rc * 2, mb - rc * 2, rc * 2, rc * 2), 0, -90); + // if (!arrowAtTop) { + // if (showArrow) { + // path.lineTo(mr - ao - aw, mb); + // path.lineTo(mr - ao - aw / 2, mb + ah); + // path.lineTo(mr - ao, mb); + // } + // move(qBound(screenRect.left() + 2, balloonX, + // screenRect.right() - sh.width() - 2), + // pos.y() - sh.height()); + // } + // path.lineTo(ml + rc, mb); + // path.arcTo(QRect(ml, mb - rc * 2, rc * 2, rc * 2), -90, -90); + // path.lineTo(ml, mt + rc); + // path.arcTo(QRect(ml, mt, rc * 2, rc * 2), 180, -90); - // Set the mask - QBitmap bitmap = QBitmap(sizeHint()); - bitmap.fill(Qt::color0); - QPainter painter1(&bitmap); - painter1.setPen(QPen(Qt::color1, border)); - painter1.setBrush(QBrush(Qt::color1)); - painter1.drawPath(path); - setMask(bitmap); + // // Set the mask + // QBitmap bitmap = QBitmap(sizeHint()); + // bitmap.fill(Qt::color0); + // QPainter painter1(&bitmap); + // painter1.setPen(QPen(Qt::color1, border)); + // painter1.setBrush(QBrush(Qt::color1)); + // painter1.drawPath(path); + // setMask(bitmap); - // Draw the border with background color - pixmap = QPixmap(sz); - pixmap.fill(Qt::transparent); - QPainter painter2(&pixmap); - painter2.setRenderHint(QPainter::Antialiasing); - bool isDark = isDarkMode(); - QColor lightColor = qApp->palette().color(QPalette::Light); - QColor darkColor = qApp->palette().color(QPalette::Dark); - QColor bgColor = isDark ? lightColor : darkColor; - painter2.setPen(QPen(bgColor.darker(160), border)); - painter2.setBrush(bgColor); - painter2.drawPath(path); + // // Draw the border with background color + // pixmap = QPixmap(sz); + // pixmap.fill(Qt::transparent); + // QPainter painter2(&pixmap); + // painter2.setRenderHint(QPainter::Antialiasing); + // bool isDark = isDarkMode(); + // QColor lightColor = qApp->palette().color(QPalette::Light); + // QColor darkColor = qApp->palette().color(QPalette::Dark); + // QColor bgColor = isDark ? lightColor : darkColor; + // painter2.setPen(QPen(bgColor.darker(160), border)); + // painter2.setBrush(bgColor); + // painter2.drawPath(path); if (msecs > 0) timer.start(msecs, this); @@ -224,8 +225,8 @@ void QBalloonTip::balloon(const QPoint &pos, int msecs, bool showArrow) // Install event filter to detect clicks outside qApp->installEventFilter(this); - // Set initial scale and opacity for animation - setWindowOpacity(0.0); + // // Set initial scale and opacity for animation + // setWindowOpacity(0.0); // Store the transform origin point (center of the widget) QPoint center = rect().center(); @@ -249,15 +250,15 @@ void QBalloonTip::balloon(const QPoint &pos, int msecs, bool showArrow) scaleAnim->setStartValue(startGeometry); scaleAnim->setEndValue(finalGeometry); - QPropertyAnimation *opacityAnim = - new QPropertyAnimation(this, "windowOpacity"); - opacityAnim->setDuration(200); - opacityAnim->setStartValue(0.0); - opacityAnim->setEndValue(1.0); - opacityAnim->setEasingCurve(QEasingCurve::OutCubic); + // QPropertyAnimation *opacityAnim = + // new QPropertyAnimation(this, "windowOpacity"); + // opacityAnim->setDuration(200); + // opacityAnim->setStartValue(0.0); + // opacityAnim->setEndValue(1.0); + // opacityAnim->setEasingCurve(QEasingCurve::OutCubic); - scaleAnim->start(QAbstractAnimation::DeleteWhenStopped); - opacityAnim->start(QAbstractAnimation::DeleteWhenStopped); + // scaleAnim->start(QAbstractAnimation::DeleteWhenStopped); + // opacityAnim->start(QAbstractAnimation::DeleteWhenStopped); } void QBalloonTip::mousePressEvent(QMouseEvent *e) From ea6e346fc62f8113a455edd46f3b3cdef85f182c Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 10:11:13 +0000 Subject: [PATCH 15/15] fix build --- src/core/services/init_device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 9e28884..6520e02 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -323,7 +323,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, } bool newerThaniPhone8 = - is_product_type_newer(rawProductType, std::string("iPhone8,1")); + iDescriptor::Utils::isProductTypeNewer(rawProductType, "iPhone8,1"); uint64_t cycleCount = ioreg["BatteryData"]["CycleCount"].getUInt();