diff --git a/.gitmodules b/.gitmodules index 2735eb4..954804b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,9 @@ [submodule "lib/win-ifuse"] path = lib/win-ifuse url = https://github.com/uncor3/win-ifuse.git -[submodule "lib/zupdater"] - path = lib/zupdater - url = https://github.com/libZQT/ZUpdater -[submodule "lib/uxplay"] - path = lib/uxplay - url = https://github.com/iDescriptor/uxplay +[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/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 64890af..cc09ba3 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,26 +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", - "/usr/include/qt6" - ], - "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 912d071..d6a8410 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -203,7 +203,9 @@ "qabstractbutton": "cpp", "qtnetwork": "cpp", "qtcore": "cpp", - "qbluetoothdevicediscoveryagent": "cpp", - "qbluetoothuuid": "cpp" + "csetjmp": "cpp", + "qthread": "cpp", + "qregularexpression": "cpp", + "qnetworkaccessmanager": "cpp" } } diff --git a/CMakeLists.txt b/CMakeLists.txt index d57be51..5774aa9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,44 +23,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 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) @@ -88,37 +86,88 @@ 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/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 --manifest-path ${IDEVICE_RS_SOURCE_DIR}/Cargo.toml + WORKING_DIRECTORY ${IDEVICE_RS_SOURCE_DIR} + COMMENT "Building idevice-rs FFI libraryy" + 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) pkg_check_modules(HEIF REQUIRED IMPORTED_TARGET libheif) pkg_check_modules(ZIP REQUIRED IMPORTED_TARGET libzip) @@ -133,35 +182,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 - ${CUSTOM_FIND_LIB_ARGS} - ) - 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) @@ -174,8 +209,6 @@ 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 @@ -183,8 +216,102 @@ src/*.cpp src/core/helpers/*.cpp src/core/services/*.cpp src/*.h -src/*.ui +# 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 +# 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) @@ -193,9 +320,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 @@ -203,9 +328,7 @@ if (WIN32) file(GLOB WINDOWS_PLATFORM_SOURCES src/platform/windows/*.cpp src/platform/windows/*.h) list(APPEND PROJECT_SOURCES ${WINDOWS_PLATFORM_SOURCES}) -endif() - -if(LINUX) +else() list(APPEND PROJECT_SOURCES src/core/services/avahi/avahi_service.cpp src/core/services/avahi/avahi_service.h @@ -226,6 +349,7 @@ endif() add_subdirectory(lib/uxplay) add_subdirectory(lib/ipatool-go) add_subdirectory(lib/zupdater) +add_subdirectory(lib/ztoast) if (WIN32) set(NO_DEPLOY_WIN_IFUSE ON) @@ -259,6 +383,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 @@ -270,17 +397,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 @@ -292,19 +419,30 @@ target_link_libraries(iDescriptor PRIVATE uxplay ipatool-go ZUpdater + ZToast + ${IDEVICE_IMPLEMENTATION_LIBS} + SQLite::SQLite3 ) -# 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 - ${CMAKE_CURRENT_SOURCE_DIR}/lib/zupdater/src - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/lib + # 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 @@ -379,7 +517,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 4abae7c..64ed1dc 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 +


@@ -112,7 +118,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 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/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/resources.qrc b/resources.qrc index fc960e4..3a4a582 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 0000000..0fa302d Binary files /dev/null and b/resources/icons/QlementineIconsWireless116.png differ diff --git a/resources/icons/UimProcess.png b/resources/icons/UimProcess.png new file mode 100644 index 0000000..0ff6cff Binary files /dev/null and b/resources/icons/UimProcess.png differ diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 8ad9928..d9ac881 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -43,11 +43,9 @@ #include #include #include -#include -#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) @@ -208,7 +206,6 @@ void AfcExplorerWidget::loadPath(const QString &path) AFCFileTree tree = ServiceManager::safeGetFileTree( m_device, path.toStdString(), true, m_afc); - if (!tree.success) { showErrorState(); return; @@ -294,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 @@ -349,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 @@ -376,6 +378,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()); @@ -404,34 +407,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; } @@ -467,39 +517,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; } @@ -823,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/afcexplorerwidget.h b/src/afcexplorerwidget.h index 6959b91..a20c434 100644 --- a/src/afcexplorerwidget.h +++ b/src/afcexplorerwidget.h @@ -38,7 +38,6 @@ #include #include #include -#include class ExportManager; class ExportProgressDialog; @@ -49,7 +48,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(); @@ -90,7 +89,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; @@ -111,9 +110,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 208bd25..aaa77e3 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,92 +39,328 @@ 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} {} - -void AppContext::addDevice(QString udid, idevice_connection_type conn_type, - AddType addType) +AppContext::AppContext(QObject *parent) : QObject{parent} { - try { - iDescriptorInitDeviceResult initResult = - init_idescriptor_device(udid.toStdString().c_str()); + cachePairedDevices(); +} - qDebug() << "init_idescriptor_device success ?: " << initResult.success; - qDebug() << "init_idescriptor_device error code: " << initResult.error; +void AppContext::cachePairedDevices() +{ - 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(); +/* + 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; + } + + // 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) { + qDebug() << "Found pairing file:" << fileName; + 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); + bool isCompatible = !wifiMacAddress.empty(); + // TODO: !important invalidate expired pairing files + // sometimes there is no WiFiMACAddress + if (!isCompatible) { + continue; + } + qDebug() << "Found pairing file for MAC" + << QString::fromStdString(wifiMacAddress); + + qDebug() << "Caching pairing file for MAC" + << QString::fromStdString(wifiMacAddress) << "Local Path" + << lockdowndir.filePath(fileName); + 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); } - }); - } - } 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, - .device = initResult.device, - .deviceInfo = initResult.deviceInfo, - .afcClient = initResult.afcClient, - .afc2Client = initResult.afc2Client, - }; - m_devices[device->udid] = device; - if (addType == AddType::Regular) { - SettingsManager::sharedInstance()->doIfEnabled( - SettingsManager::Setting::AutoRaiseWindow, []() { - if (MainWindow *mainWindow = MainWindow::sharedInstance()) { - mainWindow->raise(); - mainWindow->activateWindow(); + plist_free(root_node); } - }); - - emit deviceAdded(device); - emit deviceChange(); - return; + 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()); + } } - emit devicePaired(device); - emit deviceChange(); - m_pendingDevices.removeAll(udid); + } +#endif +} +void AppContext::addDevice(QString udid, + DeviceMonitorThread::IdeviceConnectionType conn_type, + AddType addType, QString wifiMacAddress) +{ + + try { + // iDescriptorInitDeviceResult initResult; + auto initResult = std::make_shared(); + QFuture future = QtConcurrent::run([this, udid, conn_type, + addType, wifiMacAddress, + initResult]() { + if (addType == AddType::UpgradeToWireless) { + // 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; + } + + 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, _pairingFilePath}); + } else { + qDebug() << "No network device found with MAC address:" + << wifiMacAddress; + return; + } + } else if (addType == AddType::Wireless) { + // FIXME: its not udid here its macAddress + const QString _pairingFilePath = getCachedPairingFile(udid); + + if (_pairingFilePath.isEmpty()) { + qDebug() << "Cannot upgrade to wireless, no cached pairing " + "file for" + << udid; + return; + } + + 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) { + return device.macAddress.compare( + wifiMacAddress, Qt::CaseInsensitive) == 0; + }); + + if (it != networkDevices.constEnd()) { + *initResult = init_idescriptor_device( + udid, {it->address, _pairingFilePath}); + } else { + qDebug() << "No network device found with MAC address:" + << wifiMacAddress; + return; + } + } + + else { + + *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, + .locationSimulation = initResult->locationSimulation, + .heartbeatThread = initResult->heartbeatThread}; + 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(); } @@ -130,19 +368,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(); @@ -168,7 +406,7 @@ 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); @@ -177,32 +415,39 @@ void AppContext::removeDevice(QString _udid) afc_client_free(device->afcClient); if (device->afc2Client) afc_client_free(device->afc2Client); + // idevice_free(device->device); - idevice_free(device->device); - + if (device->heartbeatThread) { + device->heartbeatThread->requestInterruption(); + device->heartbeatThread->wait(); + delete device->heartbeatThread; + } + + 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; + // std::lock_guard lock(*deviceInfo->mutex); + // delete deviceInfo->mutex; + // delete deviceInfo; } #endif @@ -216,68 +461,76 @@ 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; + // 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() { + // FIXME: mutex? for (auto device : m_devices) { - emit deviceRemoved(device->udid); + 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); - delete device; + // 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) { - emit recoveryDeviceRemoved(recoveryDevice->ecid); - 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) @@ -300,4 +553,77 @@ void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) const DeviceSelection &AppContext::getCurrentDeviceSelection() const { return m_currentSelection; +} + +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, + const QString &pairingFilePath) +{ + m_pairingFileCache.insert(udid, pairingFilePath); +} +const QString AppContext::getCachedPairingFile(const QString &udid) const +{ + QString pairingFile; + + // 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; +} + +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 3ba8c26..febac7c 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,40 @@ public: QList getAllDevices(); explicit AppContext(QObject *parent = nullptr); bool noDevicesConnected() const; + // QMap + void cachePairingFile(const QString &udid, const QString &pairingFilePath); + const QString getCachedPairingFile(const QString &udid) const; -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - QList getAllRecoveryDevices(); -#endif + void tryToConnectToNetworkDevice(const QString &macAddress); + // #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(""); + QMap m_pairingFileCache; + void cachePairedDevices(); 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(); @@ -72,10 +81,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, idevice_connection_type connType, - AddType addType); + 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/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..b85b9b2 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 @@ -29,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) { @@ -37,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); @@ -57,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); @@ -70,19 +78,43 @@ 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_device->device) { - m_statusLabel->setText("Something went wrong (no device ?)"); - m_statusLabel->setStyleSheet( + if (!m_device) { + m_errorLabel->setText("Something went wrong (no device ?)"); + m_errorLabel->setStyleSheet( "QLabel { color: #dc3545; font-size: 18px; font-weight: bold; }"); + m_loadingWidget->showError(); return; } m_statusLabel->setText("Analyzing cable..."); - get_cable_info(m_device->device, m_response); + ServiceManager::getCableInfo(m_device, m_response); analyzeCableInfo(); updateUI(); @@ -97,15 +129,13 @@ 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; } - - 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,14 @@ void CableInfoWidget::updateUI() delete item; } + if (!m_cableInfo.isConnected) { + m_errorLabel->setText( + QString("%1 does not seem to be connected to any cable.") + .arg(m_device->deviceInfo.productType)); + m_loadingWidget->showError(); + return; + } + // Update status and icon based on cable type QString statusText; QString statusStyle; @@ -204,6 +242,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 +323,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 bd28801..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 @@ -29,7 +30,6 @@ #include #include #include -#include class CableInfoWidget : public QWidget { @@ -74,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/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/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/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/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-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 ff41ec9..788e219 100644 --- a/src/core/services/detect_jailbroken.cpp +++ b/src/core/services/detect_jailbroken.cpp @@ -18,29 +18,17 @@ */ #include "../../iDescriptor.h" -#include -// char *possible_jailbreak_paths[] = { -// "/Applications/Cydia.app", -// "/Library/MobileSubstrate/MobileSubstrate.dylib", -// "/bin/bash", -// "/usr/sbin/sshd", -// "/etc/apt", -// NULL -// }; + #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; + if (!afc_list_directory(afc, (std::string(POSSIBLE_ROOT) + "bin").c_str(), + &dirs, &count)) { + free_directory_listing(dirs, count); } - afc_dictionary_free(dirs); - return false; + + return count > 0; } \ No newline at end of file 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; }; 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..2afb964 100644 --- a/src/core/services/get_battery_info.cpp +++ b/src/core/services/get_battery_info.cpp @@ -20,35 +20,26 @@ #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(DiagnosticsRelay *diag_client, plist_t &diagnostics) { - diagnostics_relay_client_t diagnostics_client = nullptr; - try { + 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 + ); - if (diagnostics_relay_client_start_service(idevice, &diagnostics_client, - nullptr) != - DIAGNOSTICS_RELAY_E_SUCCESS) { - qDebug() << "Failed to start diagnostics relay service."; - return; - } + if (!ioreg_result.is_ok()) { + qDebug() << "Failed to query IORegistry:" + << ioreg_result.unwrap_err().message.c_str(); + return; + } - if (diagnostics_relay_query_ioregistry_entry( - diagnostics_client, nullptr, "IOPMPowerSource", &diagnostics) != - DIAGNOSTICS_RELAY_E_SUCCESS && - !diagnostics) { - - 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(); + auto plist_opt = std::move(ioreg_result).unwrap(); + if (plist_opt.is_some()) { + 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 2c7bba5..28afbb8 100644 --- a/src/core/services/get_file_tree.cpp +++ b/src/core/services/get_file_tree.cpp @@ -18,71 +18,109 @@ */ #include "../../iDescriptor.h" +#include "../../servicemanager.h" #include #include -#include -#include #include -AFCFileTree get_file_tree(afc_client_t afcClient, const std::string &path, - bool checkDir) +AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path, + std::optional altAfc) { - + 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, count, altAfc); + + 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; - bool isDir = false; + if (!checkDir) { - isDir = false; - } else 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; + 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, altAfc); + + if (info_err) { + qDebug() << "Failed to get file info for:" << fullPath.c_str() + << "Error:" << info_err->message + << "Code:" << info_err->code; + } + + bool isDir = false; + 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; + 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, count, altAfc); + + if (!link_err) { + isDir = true; + free_directory_listing(dir_contents, count); + } + + if (link_err) { + idevice_error_free(link_err); } } - afc_dictionary_free(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); + free_directory_listing(dirs, count); } + result.success = true; return result; } \ No newline at end of file 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 0062a21..6520e02 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -19,15 +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 "../../heartbeat.h" #include -#include -#include -#include +#include +#include +#include #include +#include +#include std::string safeGetXML(const char *key, pugi::xml_node dict) { @@ -150,7 +154,8 @@ void parseDeviceBattery(PlistNavigator &ioreg, DeviceInfo &d) } DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, - afc_client_t &afcClient, + AfcClientHandle *afcClient, + DiagnosticsRelay *diagRelay, iDescriptorInitDeviceResult &result) { pugi::xml_node dict = doc.child("plist").child("dict"); @@ -194,29 +199,39 @@ 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('.'); - 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 { - 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 +241,21 @@ 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])); + // 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; + 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 +304,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(diagRelay, 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"]; @@ -300,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(); @@ -329,6 +352,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 +364,280 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, } } -iDescriptorInitDeviceResult init_idescriptor_device(const char *udid) +iDescriptorInitDeviceResult +init_idescriptor_device(const QString &udid, + const WirelessInitArgs &wirelessArgs) { - qDebug() << "Initializing iDescriptor device with UDID: " - << QString::fromUtf8(udid); + const bool isWireless = + !wirelessArgs.ip.isEmpty() && !wirelessArgs.pairing_file.isEmpty(); + 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; + HeartbeatClientHandle *heartbeat = nullptr; + HeartbeatThread *heartbeatThread = nullptr; + ImageMounterHandle *image_mounter = nullptr; + DiagnosticsRelayClientHandle *diagnostics_relay = nullptr; + LocationSimulationHandle *location_simulation = nullptr; + plist_t val = nullptr; - idevice_error_t ret = - idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX); + 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 + 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 (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); + 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) { + qDebug() << "Failed to read pairing file"; + 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; - } + err = idevice_tcp_provider_new( + (const idevice_sockaddr *)&addr_in, + const_cast(pairing_file), APP_LABEL, + &provider); + if (err) { + qDebug() << "Failed to create wireless provider"; + goto cleanup; + } + err = heartbeat_connect(provider, &heartbeat); + if (err) { + qDebug() << "Failed to start Heartbeat service"; + goto cleanup; + } + // udid is mac address here for wireless + heartbeatThread = new HeartbeatThread(heartbeat, udid); + heartbeatThread->start(); - if (afc_client_new(device, lockdownService, &afcClient) != AFC_E_SUCCESS) { - qDebug() << "Failed to create AFC client."; + while (!heartbeatThread->initialCompleted()) { + sleep(1); + } - 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; } 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 + err = lockdownd_connect(provider, &lockdown); + if (err) { + qDebug() << "Failed to connect to lockdown"; + 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; + } + + if (err) { + qDebug() << "Failed to connect to Heartbeat client"; + goto cleanup; + } + + err = afc_client_connect(provider, &afc_client); + if (err) { + qDebug() << "Failed to create AFC client"; + goto cleanup; + } + + 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 = 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); + + lockdownd_get_value(lockdown, "EnableWifiConnections", + "com.apple.mobile.wireless_lockdown", &val); + if (val) + plist_print(val); + + result.provider = 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; + // TODO:remove, not really required to get some stuff going so it can be + // optional + result.imageMounter = image_mounter; + 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); + ::QObject::connect(heartbeatThread, &HeartbeatThread::heartbeatFailed, + AppContext::sharedInstance(), + &AppContext::heartbeatFailed); + ::QObject::connect(heartbeatThread, &HeartbeatThread::heartbeatThreadExited, + AppContext::sharedInstance(), &AppContext::removeDevice); 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/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..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"); @@ -97,9 +98,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) { @@ -114,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(); @@ -124,18 +126,15 @@ 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)); + 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()) { + if (info.signature && info.signature_len) { finishWithSuccess(); return; } @@ -148,12 +147,12 @@ 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 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,32 +173,14 @@ void DevDiskImageHelper::onMountButtonClicked() } if (hasDownloadedImage) { - // Mount directly + // // Mount directly + m_downloadingVersion = versionToMount; 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+)"); - } + 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,35 +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) { + 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 3606463..93a72bb 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,81 @@ 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) { + /* Never returns DeviceLockedMountErrorCode when doing + image_mounter_lookup_image but maybe used in future */ + } else if (info.err->code == NotFoundErrorCode) { + // OK, no image mounted + qDebug() << "Mount image: no mounted image found"; + } 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) @@ -668,41 +691,65 @@ 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() { - // 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/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/deviceimagewidget.cpp b/src/deviceimagewidget.cpp index 6723686..fab01cb 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,26 +161,12 @@ QString DeviceImageWidget::getMockupNameFromDisplayName( int DeviceImageWidget::getIosVersionFromDevice() const { - unsigned int version = idevice_get_device_version(m_device->device); + 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; - } - } - - // If all else fails, return unknown version (will use ios26 wallpaper) return 0; } diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 2ef7417..b02f1d0 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 @@ -76,21 +76,22 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) ZIconWidget *shutdownBtn = new ZIconWidget( QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown", - 1.0, this); - connect(shutdownBtn, &ZIconWidget::clicked, this, - [device]() { ToolboxWidget::shutdownDevice(device); }); + this); + shutdownBtn->setIconSize(QSize(20, 20)); + // connect(shutdownBtn, &ZIconWidget::clicked, this, + // [device]() { ToolboxWidget::shutdownDevice(device); }); - ZIconWidget *restartBtn = - new ZIconWidget(QIcon(":/resources/icons/IcTwotoneRestartAlt.png"), - "Restart", 1.0, this); - connect(restartBtn, &ZIconWidget::clicked, this, - [device]() { ToolboxWidget::restartDevice(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); }); - ZIconWidget *recoveryBtn = - new ZIconWidget(QIcon(":/resources/icons/HugeiconsWrench01.png"), - "Recovery", 1.0, this); - connect(recoveryBtn, &ZIconWidget::clicked, this, - [device]() { ToolboxWidget::_enterRecoveryMode(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); }); actionsLayout->addWidget(shutdownBtn); actionsLayout->addWidget(restartBtn); @@ -261,25 +262,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:", @@ -324,7 +327,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 = @@ -343,16 +346,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() {} @@ -372,47 +375,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 eba3500..8ae5cfa 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) { @@ -142,98 +145,102 @@ 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 -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) @@ -330,18 +337,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..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,10 +61,10 @@ 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); @@ -74,8 +81,8 @@ void DeviceMenuWidget::init() // 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"; + if (stackedWidget->widget(index) == + m_galleryWidget) { // Gallery tab m_galleryWidget->load(); } }); @@ -83,6 +90,34 @@ 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) @@ -92,6 +127,7 @@ void DeviceMenuWidget::switchToTab(const QString &tabName) } 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); @@ -103,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 cf0480a..985c2d1 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -39,7 +39,7 @@ 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; GalleryWidget *m_galleryWidget; 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/devicesidebarwidget.cpp b/src/devicesidebarwidget.cpp index a77dc88..3bb339e 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 1ff9a0e..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; @@ -128,6 +129,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) { @@ -152,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/diskusagewidget.cpp b/src/diskusagewidget.cpp index 64eea78..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,9 +31,7 @@ #include #include -#include -#include -#include +using namespace iDescriptor; DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0), @@ -95,43 +97,49 @@ 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 a 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(); 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" @@ -142,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; }"); @@ -154,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); @@ -176,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); @@ -184,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); @@ -221,6 +243,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; @@ -236,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); @@ -251,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); @@ -268,104 +296,97 @@ 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); -/* 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)) + .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() { auto *watcher = new QFutureWatcher(this); @@ -376,14 +397,16 @@ 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(); 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; @@ -393,17 +416,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) { + 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( @@ -413,39 +437,14 @@ void DiskUsageWidget::fetchData() // 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."; - return result; - } + InstallationProxyClientHandle *installationProxyClientHandle = nullptr; + installation_proxy_connect(m_device->provider, + &installationProxyClientHandle); + auto instproxy = + IdeviceFFI::InstallationProxy::adopt(installationProxyClientHandle); - 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 +455,157 @@ 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); // 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); + 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); } - plist_free(node); } result["mediaUsage"] = QVariant::fromValue(mediaSpace); - lockdownd_client_free(lockdownClient); + /* + 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 diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index 13c7e1a..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,37 +33,33 @@ 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, const QList &items, const QString &destinationPath, - std::optional altAfc) + std::optional altAfc) { - if (!device) { + 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,252 +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); -} - -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; - QDateTime modificationTime; - QDateTime birthTime; - // Get file size first - // Example { - // "st_size": 64523, - // "st_blocks": 128, - // "st_nlink": 1, - // "st_ifmt": "S_IFREG", - // "st_mtime": 1754987735634348907, - // "st_birthtime": 1754987735633715011 - // } - - plist_t info = nullptr; - afc_error_t infoResult = ServiceManager::safeAfcGetFileInfoPlist( - device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); - quint64 totalFileSize = 0; - if (infoResult != AFC_E_SUCCESS || !info) { - qDebug() << "File info retrieval failed for" << item.sourcePathOnDevice; - return result; - } - - PlistNavigator fileInfo = PlistNavigator(info); - - bool valid = fileInfo["st_size"].valid(); - if (!valid) { - qDebug() << "File size info not valid for" << item.sourcePathOnDevice; - if (info) - plist_free(info); - return result; - } - - totalFileSize = fileInfo["st_size"].getUInt(); - - valid = fileInfo["st_mtime"].valid(); - if (!valid) { - qDebug() << "File modification time info not valid for" - << item.sourcePathOnDevice; - if (info) - plist_free(info); - return result; - } - - uint64_t modTimeNs = fileInfo["st_mtime"].getUInt(); - // The timestamp from the device is in nanoseconds, convert to seconds (UTC) - modificationTime = QDateTime::fromSecsSinceEpoch(modTimeNs / 1000000000, Qt::UTC); - - valid = fileInfo["st_birthtime"].valid(); - if (!valid) { - qDebug() << "File birth time info not valid for" - << item.sourcePathOnDevice; - if (info) - plist_free(info); - return result; - } - uint64_t birthTimeNs = fileInfo["st_birthtime"].getUInt(); - birthTime = QDateTime::fromSecsSinceEpoch(birthTimeNs / 1000000000, Qt::UTC); - - plist_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; - quint64 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); - } - } - - ServiceManager::safeAfcFileClose(device, handle, altAfc); - - outputFile.flush(); - if (modificationTime.isValid()) { - if (!outputFile.setFileTime(modificationTime, QFileDevice::FileModificationTime)) { - qWarning() << "Could not set modification time for" << outputPath; - } - } - if (birthTime.isValid()) { - if (!outputFile.setFileTime(birthTime, QFileDevice::FileBirthTime)) { - qWarning() << "Could not set birth time for" << outputPath; - } - } - outputFile.close(); - - if (totalBytes == 0) { - result.errorMessage = "No data read from device file"; - QFile::remove(outputPath); // 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('/'); @@ -398,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 949d2e9..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 @@ -78,14 +50,17 @@ 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); 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/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/gallerywidget.cpp b/src/gallerywidget.cpp index 48d4aed..7a8929b 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -38,9 +38,11 @@ #include #include #include +#include #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 @@ -50,32 +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) -{ -} -/*Load is called when the tab is active*/ -void GalleryWidget::load() -{ - if (m_loaded) - return; - - m_loaded = true; - - setupUI(); -} - -void GalleryWidget::setupUI() + 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(); @@ -84,13 +68,47 @@ void GalleryWidget::setupUI() setupPhotoGalleryView(); // Add stacked widget to main layout - m_mainLayout->addWidget(m_stackedWidget); setLayout(m_mainLayout); - // 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); + errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); + m_loadingWidget->setupErrorWidget(errorLayout); + connect(m_retryButton, &QPushButton::clicked, this, [this]() { + m_loadingWidget->showLoading(); + QTimer::singleShot(100, this, &GalleryWidget::reload); + }); + setControlsEnabled(false); // Disable controls until album is selected - loadAlbumList(); +} + +void GalleryWidget::reload() +{ + m_loaded = false; + load(); +} + +/*Load is called when the tab is active*/ +void GalleryWidget::load() +{ + if (m_loaded) + return; + + m_loaded = true; + qDebug() << "Before reading DCIM directory"; + + 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() @@ -99,6 +117,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;"); @@ -120,7 +140,8 @@ 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( + static_cast(PhotoModel::All)); // Default to All m_filterComboBox->setMinimumWidth(100); // Ensure text fits m_filterComboBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -132,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 @@ -150,6 +173,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); @@ -231,9 +255,12 @@ void GalleryWidget::onExportSelected() // 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)); + exportItems.append(ExportItem(filePath, fileName, index)); + ++index; } qDebug() << "Starting export of selected files:" << exportItems.size() @@ -248,47 +275,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() @@ -336,7 +365,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) { @@ -379,7 +408,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, @@ -403,12 +432,11 @@ void GalleryWidget::setupPhotoGalleryView() &GalleryWidget::onPhotoContextMenu); } -void GalleryWidget::loadAlbumList() +void GalleryWidget::loadAlbumList(const AFCFileTree &dcimTree) { - AFCFileTree dcimTree = ServiceManager::safeGetFileTree(m_device, "/DCIM"); - if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; + m_loadingWidget->showError(); QMessageBox::warning(this, "Error", "Could not access DCIM directory on device."); return; @@ -443,6 +471,8 @@ void GalleryWidget::loadAlbumList() } m_albumListView->setModel(albumModel); + m_loadingWidget->stop(); + m_loadingWidget->switchToWidget(m_albumSelectionWidget); } void GalleryWidget::onAlbumSelected(const QString &albumPath) @@ -463,12 +493,13 @@ 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); // 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(); @@ -476,9 +507,17 @@ 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(); + m_loadingWidget->switchToWidget(m_albumSelectionWidget); + + if (m_model) { + m_model->clear(); + } // Disable controls and hide back button setControlsEnabled(false); @@ -545,6 +584,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 4dae062..2e152bb 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,11 +58,11 @@ private slots: void onBackToAlbums(); private: - void setupUI(); + 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); @@ -76,8 +77,9 @@ private: // UI components QVBoxLayout *m_mainLayout; QHBoxLayout *m_controlsLayout; - QStackedWidget *m_stackedWidget; - + ZLoadingWidget *m_loadingWidget; + 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 new file mode 100644 index 0000000..5874a76 --- /dev/null +++ b/src/heartbeat.h @@ -0,0 +1,98 @@ +#ifndef HEARTBEATTHREAD_H +#define HEARTBEATTHREAD_H +#include "iDescriptor.h" +#include +#include + +using namespace IdeviceFFI; + +class HeartbeatThread : public QThread +{ + Q_OBJECT +public: + HeartbeatThread(HeartbeatClientHandle *heartbeat, QString macAddress, + QObject *parent = nullptr) + : QThread(parent), m_hb(Heartbeat::adopt(heartbeat)), + m_macAddress(macAddress) + { + } + + void run() override + { + qDebug() << "Heartbeat thread started"; + try { + 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); + 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 + 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); + 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; + } + + // 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); + } + } + + bool initialCompleted() const { return m_initialCompleted; } + +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-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 dbae75b..20f1131 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -23,16 +23,24 @@ #include #include #include +#include #include -#include -#include -#include -#include -#include -#include -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include -#endif + +// #include "idevice.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -54,10 +62,28 @@ "https://raw.githubusercontent.com/iDescriptor/iDescriptor/refs/heads/" \ "main/DeveloperDiskImages.json" -#define DONATE_URL "https://opencollective.com/idescriptor" - -// 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" +#include "iDescriptor-utils.h" + +#define DeviceLockedMountErrorCode -21 +#define NotFoundErrorCode -14 +#define ServiceNotFoundErrorCode -15 +#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 +/* Windows */ +#define LOCKDOWN_PATH qgetenv("PROGRAMDATA") + "/Apple/Lockdown" +#endif struct BatteryInfo { QString health; @@ -97,6 +123,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 { @@ -173,60 +205,73 @@ struct DeviceInfo { std::string marketingName; std::string regionRaw; std::string region; - unsigned int parsedDeviceVersion; + DeviceVersion parsedDeviceVersion; + std::string wifiMacAddress; + bool isWireless = false; }; struct iDescriptorDevice { std::string udid; - idevice_connection_type conn_type; - idevice_t device; + DeviceMonitorThread::IdeviceConnectionType conn_type; + IdeviceProviderHandle *provider; DeviceInfo deviceInfo; - afc_client_t afcClient; - afc_client_t afc2Client; - bool is_iPhone; - std::recursive_mutex mutex; + 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 { bool success = false; - lockdownd_error_t error; - idevice_t device; + IdeviceFfiError error; + IdeviceProviderHandle *provider; DeviceInfo deviceInfo; - afc_client_t afcClient; - afc_client_t afc2Client; + AfcClientHandle *afcClient; + AfcClientHandle *afc2Client; + LockdowndClientHandle *lockdown; + ImageMounterHandle *imageMounter; + std::shared_ptr diagRelay; + LocationSimulationHandle *locationSimulation; + QThread *heartbeatThread; }; -#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 { @@ -246,6 +291,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) { @@ -293,16 +357,57 @@ 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, - 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; @@ -315,38 +420,52 @@ struct AFCFileTree { std::string currentPath; }; -AFCFileTree get_file_tree(afc_client_t afcClient, - const std::string &path = "/", bool checkDir = true); +AFCFileTree +get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path = "/", + std::optional altAfc = std::nullopt); -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); +struct WirelessInitArgs { + const QString ip; + const QString pairing_file; +}; +iDescriptorInitDeviceResult +init_idescriptor_device(const QString &udid, + const WirelessInitArgs &wirelessArgs = {"", ""}); -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -iDescriptorInitDeviceResultRecovery -init_idescriptor_recovery_device(uint64_t ecid); -#endif -bool set_location(idevice_t device, char *lat, char *lon); +// #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); +// 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); -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 MountedImageInfo { + IdeviceFfiError *err; + uint8_t *signature; + size_t signature_len; }; -plist_t _get_mounted_image(const char *udid); +struct MountedImageResult { + bool success; + IdeviceFfiError *err; +}; -bool restart(std::string udid); +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 @@ -363,66 +482,18 @@ 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); +// std::string safeGetXML(const char *key, pugi::xml_node dict); -/** - * @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); +void get_battery_info(DiagnosticsRelay *diagRelay, plist_t &diagnostics); -/** - * @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(std::string productType, idevice_t idevice, - bool is_iphone, 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); -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 @@ -430,7 +501,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; @@ -439,13 +510,13 @@ struct NetworkDevice { QPixmap load_heic(const QByteArray &data); -QByteArray read_afc_file_to_byte_array(afc_client_t afcClient, +QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, const char *path); 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 { @@ -524,4 +595,87 @@ inline QJsonObject getVersionedConfig(const QJsonObject &rootObj) } } return QJsonObject(); -} \ No newline at end of file +} + +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; +} + +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/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/installedappswidget.cpp b/src/installedappswidget.cpp index 3f6b9a6..3a7108f 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 @@ -34,14 +35,10 @@ #include #include #include -#include -#include -#include -#include -#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) { @@ -50,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) @@ -288,49 +261,22 @@ void InstalledAppsWidget::createContentWidget() // todo: move to services void InstalledAppsWidget::fetchInstalledApps() { - if (!m_device || !m_device->device) { + if (!m_device) { showErrorState("Invalid device"); return; } - + // todo maybe clear m_watcher ? QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; QVariantList apps; - // result["success"] = true; - // result["apps"] = apps; - // return result; - - 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; - } - - 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; - } - - lockdownd_service_descriptor_free(lockdowndService); - lockdowndService = nullptr; + InstallationProxyClientHandle *installationProxyClientHandle = + nullptr; + installation_proxy_connect(m_device->provider, + &installationProxyClientHandle); + auto instproxy = IdeviceFFI::InstallationProxy::adopt( + installationProxyClientHandle); // Get both User and System apps QStringList appTypes = {"User", "System"}; @@ -357,104 +303,83 @@ void InstalledAppsWidget::fetchInstalledApps() 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; + auto installedApps = instproxy.browse(client_opts); + if (installedApps.is_ok()) { + installedApps.unwrap(); - QVariantMap appData; + for (const auto &app_info : installedApps.unwrap()) { + if (!app_info) + continue; - // 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); - } - } + QVariantMap 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); - } - } - - // 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; - } - - appData["type"] = appType; - - if (!appData["bundleId"].toString().isEmpty()) { - apps.append(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 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 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()); } @@ -499,6 +424,22 @@ 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 { + /* + FIXME:springboard_services_connect takes time + MOVE EVERYTHING INTO QTCONCURRENT SO IT DOESN'T BLOCK UI + */ + qDebug() << "Successfully connected to SpringBoard services."; + } + // Create tabs for each app for (const QVariant &appVariant : apps) { QVariantMap appData = appVariant.toMap(); @@ -524,24 +465,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); @@ -576,6 +534,7 @@ void InstalledAppsWidget::selectAppTab(AppTabWidget *tab) QString bundleId = tab->getBundleId(); // Load app container data + // FIXME: handle quickly repeated selections loadAppContainer(bundleId); } @@ -607,7 +566,7 @@ void InstalledAppsWidget::filterApps(const QString &searchText) */ void InstalledAppsWidget::loadAppContainer(const QString &bundleId) { - if (!m_device || !m_device->device) { + if (!m_device) { return; } @@ -636,129 +595,61 @@ 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 (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; - - // 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; - } - - // 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) { + if (err != nullptr) { + qDebug() + << "Error connecting to House Arrest for" << bundleId + << ":" << QString::fromUtf8(err->message); result["error"] = - QString("Container access denied: %1").arg(error_str); - free(error_str); - } else { - result["error"] = "Container access denied"; + QString("Error connecting to House Arrest: %1") + .arg(QString::fromUtf8(err->message)); + return result; } - plist_free(dict); - house_arrest_client_free(houseArrestClient); - 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; + } + + char **dirs = nullptr; + size_t count = 0; + + result["afcClient"] = + QVariant::fromValue(reinterpret_cast(afcClient)); + result["houseArrestClient"] = QVariant::fromValue( + reinterpret_cast(houseArrestClient)); + result["success"] = true; + + } 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); + + result["error"] = QString("Exception: %1").arg(e.what()); } - 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; - } - - // 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["afcClient"] = - QVariant::fromValue(reinterpret_cast(afcClient)); - result["houseArrestClient"] = QVariant::fromValue( - reinterpret_cast(houseArrestClient)); - result["success"] = true; - - 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; - }); + return result; + }); m_containerWatcher->setFuture(future); } @@ -786,10 +677,11 @@ void InstalledAppsWidget::onContainerDataReady() return; } - // Get the AFC clients from the result and store them as member variables - m_houseArrestAfcClient = reinterpret_cast( + // 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( + m_houseArrestClient = reinterpret_cast( result.value("houseArrestClient").value()); if (!m_houseArrestAfcClient) { @@ -816,7 +708,8 @@ void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) void InstalledAppsWidget::cleanupHouseArrestClients() { if (m_houseArrestAfcClient) { - afc_client_free(m_houseArrestAfcClient); + // FIXME: create an issue afc_client_free crashes + // afc_client_free(m_houseArrestAfcClient); m_houseArrestAfcClient = nullptr; } diff --git a/src/installedappswidget.h b/src/installedappswidget.h index 66f13c9..8aae751 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -42,8 +42,6 @@ #include #include #include -#include -#include // Custom App Tab Widget class AppTabWidget : public QGroupBox @@ -52,7 +50,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; } @@ -76,7 +75,6 @@ protected: }; private: - void fetchAppIcon(); void setupUI(); QString m_appName; @@ -114,7 +112,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); @@ -141,8 +139,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 aa3e726..b0338b2 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -29,17 +29,15 @@ #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) { 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( @@ -74,16 +72,8 @@ 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); - - // Defer the initialization to allow the main widget to show first QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); } @@ -118,74 +108,28 @@ 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_shotrClient) { - screenshotr_client_free(m_shotrClient); - m_shotrClient = nullptr; + if (m_screenshotrClient) { + screenshotr_client_free(m_screenshotrClient); + m_screenshotrClient = nullptr; } } bool LiveScreenWidget::initializeScreenshotService(bool notify) { - lockdownd_client_t lockdownClient = nullptr; - lockdownd_service_descriptor_t service = nullptr; - try { m_statusLabel->setText("Connecting to screenshot service..."); - - lockdownd_error_t ldret = lockdownd_client_new_with_handshake( - m_device->device, &lockdownClient, APP_LABEL); - - 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; + IdeviceFfiError *err = + screenshotr_connect(m_device->provider, &m_screenshotrClient); + if (err) { + qDebug() << "Failed to create Screenshotr client"; + return false; // proceed to mount image } - - 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(); @@ -196,50 +140,23 @@ bool LiveScreenWidget::initializeScreenshotService(bool 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); - } } } void LiveScreenWidget::startCapturing() { - if (!m_shotrClient) { + if (!m_screenshotrClient) { qWarning() << "Cannot start capturing: screenshot client not initialized"; return; } - if (m_timer) { - m_timer->start(); - qDebug() << "Started capturing"; - } -} - -void LiveScreenWidget::updateScreenshot() -{ - if (!m_shotrClient) { - qWarning() << "Screenshot client not initialized"; - return; - } - - try { - TakeScreenshotResult result = take_screenshot(m_shotrClient); - - 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 361608f..f3714ac 100644 --- a/src/livescreenwidget.h +++ b/src/livescreenwidget.h @@ -21,11 +21,56 @@ #define LIVESCREEN_H #include "iDescriptor.h" +#include "servicemanager.h" #include +#include #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 { @@ -41,14 +86,13 @@ private: void startCapturing(); iDescriptorDevice *m_device; - QTimer *m_timer; QLabel *m_imageLabel; QLabel *m_statusLabel; - screenshotr_client_t m_shotrClient; - 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/main.cpp b/src/main.cpp index 2723eea..133ae06 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,8 @@ */ #include "mainwindow.h" -#include "settingsmanager.h" +// #include "settingsmanager.h" +#include "iDescriptor.h" #include #include #include @@ -36,13 +37,13 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("iDescriptor"); 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."); - } + // idevice_init_logger(Debug, Disabled, NULL); + // 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 58cf5ef..9138147 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,22 +26,26 @@ #include "ifusediskunmountbutton.h" #include "ifusemanager.h" #include "jailbrokenwidget.h" -#include "releasechangelogdialog.h" -#include "settingswidget.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 #include #include +#include #include #include #include #include "appcontext.h" #include "settingsmanager.h" +// #include "devicemonitor.h" +// #include "Toast.h" +#include "networkdevicemanager.h" +#include "networkdeviceswidget.h" +#include "statusballoon.h" #include #include #include @@ -52,79 +56,85 @@ #include "platform/windows/check_deps.h" #endif -void handleCallback(const idevice_event_t *event, void *userData) -{ - qDebug() << "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() { @@ -161,8 +171,7 @@ MainWindow::MainWindow(QWidget *parent) m_ZTabWidget->addTab(m_mainStackedWidget, "iDevice"); auto *appsWidgetTab = m_ZTabWidget->addTab(AppsWidget::sharedInstance(), "Apps"); - m_ZTabWidget->addTab(ToolboxWidget::sharedInstance(), "Toolbox"); - + m_ZTabWidget->addTab(new ToolboxWidget(this), "Toolbox"); auto *jailbrokenWidget = new JailbrokenWidget(this); m_ZTabWidget->addTab(jailbrokenWidget, "Jailbroken"); m_ZTabWidget->finalizeStyles(); @@ -192,6 +201,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); @@ -226,34 +241,37 @@ MainWindow::MainWindow(QWidget *parent) } #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 { @@ -264,87 +282,215 @@ 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 - QString lastAppVersion = SettingsManager::sharedInstance()->appVersion(); - bool shouldShowReleaseChangelog = lastAppVersion != APP_VERSION; - SettingsManager::sharedInstance()->setAppVersion(APP_VERSION); + // SettingsManager::sharedInstance()->doIfEnabled( + // SettingsManager::Setting::AutoCheckUpdates, [this]() { + // qDebug() << "Checking for updates..."; + // m_updater->checkForUpdates(); + // }); - if (shouldShowReleaseChangelog) { - connect( - m_updater, &ZUpdater::dataAvailable, this, - [this](const QJsonDocument data, bool isUpdateAvailable) { - if (!isUpdateAvailable) { - ReleaseChangelogDialog dialog(data, this); - dialog.exec(); + // 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; } - }, - Qt::SingleShotConnection); - } + qDebug() << "Device added: " << udid; - SettingsManager::sharedInstance()->doIfEnabled( - SettingsManager::Setting::AutoCheckUpdates, [this]() { - qDebug() << "Checking for updates..."; - m_updater->checkForUpdates(); + 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; + } }); + + /* 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) { + 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(), + &NetworkDeviceManager::deviceAdded, this, + [this](const NetworkDevice &device) { + // return; // FIXME: disable for now + 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; + } + 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 + }); + + 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); + // 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() @@ -354,9 +500,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 @@ -372,19 +518,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/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index 1a49f59..edb0ce9 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -18,6 +18,9 @@ */ #include "mediapreviewdialog.h" +#include "appcontext.h" +#include "iDescriptor-ui.h" +#include "imageloader.h" #include "mediastreamermanager.h" #include "photomodel.h" #include @@ -43,13 +46,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 +75,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() @@ -212,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/mediapreviewdialog.h b/src/mediapreviewdialog.h index 5aca232..b80771b 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 @@ -53,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(); @@ -146,7 +146,7 @@ private: bool m_isDraggingTimeline; qint64 m_videoDuration; - afc_client_t m_afcClient; + AfcClientHandle *m_afcClient; }; -#endif // MEDIAPREVIEWDIALOG_H \ No newline at end of file +#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 be59d71..80f6169 100644 --- a/src/mediastreamer.h +++ b/src/mediastreamer.h @@ -25,7 +25,6 @@ #include #include #include -#include QT_BEGIN_NAMESPACE class QTcpSocket; @@ -48,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(); /** @@ -88,7 +88,7 @@ private: qint64 startByte; qint64 endByte; qint64 bytesRemaining; - uint64_t afcHandle; + AfcFileHandle *afcHandle; }; HttpRequest parseHttpRequest(const QByteArray &requestData); @@ -114,7 +114,7 @@ private: QList m_activeConnections; QMutex m_connectionsMutex; - afc_client_t m_afcClient; + AfcClientHandle *m_afcClient; }; -#endif // MEDIASTREAMER_H \ No newline at end of file +#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 587f710..6170bc7 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -26,7 +26,6 @@ #include #include #include -#include /** * @brief Singleton manager for MediaStreamer instances @@ -50,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); /** @@ -81,4 +80,4 @@ private: QMutex m_streamersMutex; }; -#endif // MEDIASTREAMERMANAGER_H \ No newline at end of file +#endif // MEDIASTREAMERMANAGER_H diff --git a/src/networkdevicemanager.cpp b/src/networkdevicemanager.cpp new file mode 100644 index 0000000..7f52159 --- /dev/null +++ b/src/networkdevicemanager.cpp @@ -0,0 +1,30 @@ +#include "networkdevicemanager.h" +#include + +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 + + /* Helps main ui load a litte faster */ + QTimer::singleShot(std::chrono::seconds(1), this, + [this]() { 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 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/photomodel.cpp b/src/photomodel.cpp index dfb12c7..05eca32 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -19,7 +19,8 @@ #include "photomodel.h" #include "iDescriptor.h" -#include "mediastreamermanager.h" +// #include "mediastreamermanager.h" +#include "imageloader.h" #include "servicemanager.h" #include #include @@ -34,42 +35,39 @@ #include #include #include -extern "C" { -#include -#include -#include -#include -} - -// Limit concurrent video thumbnail generation to 2 to prevent resource -// exhaustion -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() { - // Clean up any active watchers - for (auto *watcher : m_activeLoaders.values()) { - if (watcher) { - watcher->cancel(); - watcher->waitForFinished(); - watcher->deleteLater(); - } - } - m_activeLoaders.clear(); - m_loadingPaths.clear(); - m_thumbnailCache.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(); + + beginResetModel(); + m_photos.clear(); + m_allPhotos.clear(); + endResetModel(); + + blockSignals(false); } PhotoModel::~PhotoModel() @@ -78,308 +76,6 @@ PhotoModel::~PhotoModel() clear(); } -QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device, - const QString &filePath, - const QSize &requestedSize) -{ - QPixmap thumbnail; - - uint64_t fileHandle = 0; - - afc_error_t openResult = ServiceManager::safeAfcFileOpen( - device, filePath.toUtf8().constData(), AFC_FOPEN_RDONLY, &fileHandle); - - if (openResult != AFC_E_SUCCESS || fileHandle == 0) { - qWarning() << "Failed to open video file for thumbnail:" << filePath; - return {}; - } - - // Get file size - char **fileInfo = nullptr; - afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( - device, filePath.toUtf8().constData(), &fileInfo); - - 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; - } - } - afc_dictionary_free(fileInfo); - } - - 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; - uint64_t 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)); - uint32_t bytesRead = 0; - - afc_error_t result = ServiceManager::safeAfcFileRead( - ctx->device, ctx->fileHandle, reinterpret_cast(buf), toRead, - &bytesRead); - - if (result != AFC_E_SUCCESS || bytesRead == 0) { - 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; - } - - // Use AFC seek - afc_error_t result = ServiceManager::safeAfcFileSeek( - ctx->device, ctx->fileHandle, newPos, seekWhence); - - if (result != AFC_E_SUCCESS) { - 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) @@ -401,22 +97,14 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const return info.filePath; case Qt::DecorationRole: { - qDebug() << "DecorationRole requested for index:" << index.row(); - + ImageLoader &imgloader = ImageLoader::sharedInstance(); // Check memory cache first - if (QPixmap *cached = m_thumbnailCache.object(info.filePath)) { - qDebug() << "Cache HIT for:" << info.fileName; + 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)) { - 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) || - 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/" @@ -424,18 +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)) { - qDebug() << "Starting load for:" << info.fileName; - 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( @@ -451,154 +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]() { - qDebug() << "Thumbnail load finished for:" << 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 - 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; - }); - } 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)) { - qDebug() << "Loading HEIC image from data for:" << filePath; - 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() @@ -615,16 +154,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 +175,14 @@ 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) { + size_t count = 0; + err = + ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files, count); + if (err) { qDebug() << "Failed to read photo directory:" << photoDir - << "Error:" << readResult; + << "Error:" << err->message; + idevice_error_free(err); return; } @@ -663,7 +206,7 @@ void PhotoModel::populatePhotoPaths() m_allPhotos.append(info); } } - afc_dictionary_free(files); + free_directory_listing(files, count); } // Apply initial filtering and sorting, which will also reset the model @@ -694,16 +237,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++; } } @@ -792,82 +330,26 @@ 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 creation_seconds = info->creation; + QDateTime dateTime = + QDateTime::fromSecsSinceEpoch(creation_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(); } 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; @@ -876,13 +358,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 +void PhotoModel::refreshPhotos() { populatePhotoPaths(); } diff --git a/src/photomodel.h b/src/photomodel.h index bc9866f..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); - -private 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 diff --git a/src/qballoontip.cpp b/src/qballoontip.cpp new file mode 100644 index 0000000..2817a0e --- /dev/null +++ b/src/qballoontip.cpp @@ -0,0 +1,306 @@ +#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(widget ? widget->window() : QApplication::activeWindow(), + Qt::ToolTip), + widget(widget), showArrow(true) +{ + // 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 + // 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 *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()); + + // 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..ea932e7 --- /dev/null +++ b/src/qballoontip.h @@ -0,0 +1,41 @@ +#ifndef QBALLOONTIP_H +#define QBALLOONTIP_H + +#include +#include +#include +#include + +class QBalloonTip : public QWidget +{ + Q_OBJECT +public: + 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; + 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..ba01781 100644 --- a/src/querymobilegestaltwidget.cpp +++ b/src/querymobilegestaltwidget.cpp @@ -22,12 +22,23 @@ #include #include #include +#include +#include #include QueryMobileGestaltWidget::QueryMobileGestaltWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) { + // 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"); + QTimer::singleShot(0, this, &QWidget::close); + return; + } setupUI(); populateKeys(); } @@ -1135,32 +1146,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 cbe9411..dc4a3d7 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -18,146 +18,362 @@ */ #include "servicemanager.h" +#include "iDescriptor.h" +#include -afc_error_t -ServiceManager::safeAfcReadDirectory(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) { - return executeAfcOperation( + return executeAfcClientOperation( device, - [path, dirs](afc_client_t client) { - return afc_read_directory(client, path, dirs); + [path, dirs, &count, device](AfcClientHandle *client) { + return afc_list_directory(client, path, dirs, &count); }, 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) { + [path, info, device](AfcClientHandle *client) { return afc_get_file_info(client, path, info); }, 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); - }, - altAfc); -} - -afc_error_t ServiceManager::safeAfcFileOpen(iDescriptorDevice *device, - const char *path, - afc_file_mode_t mode, - uint64_t *handle, - std::optional altAfc) -{ - return executeAfcOperation( - device, - [path, mode, handle](afc_client_t client) { + [path, mode, handle, device](AfcClientHandle *client) { return afc_file_open(client, path, mode, handle); }, altAfc); } -afc_error_t ServiceManager::safeAfcFileRead(iDescriptorDevice *device, - uint64_t handle, char *data, - uint32_t length, - uint32_t *bytes_read, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileRead(const iDescriptorDevice *device, + AfcFileHandle *handle, uint8_t **data, + uintptr_t length, size_t *bytes_read) { return executeAfcOperation( device, - [handle, data, length, bytes_read](afc_client_t client) { - return afc_file_read(client, handle, data, length, bytes_read); + [data, length, bytes_read](AfcFileHandle *handle) { + return afc_file_read(handle, data, length, bytes_read); }, - 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::safeAfcFileWrite(const iDescriptorDevice *device, + AfcFileHandle *handle, const uint8_t *data, + uint32_t length) { return executeAfcOperation( device, - [handle, data, length, bytes_written](afc_client_t client) { - return afc_file_write(client, handle, data, length, bytes_written); + [data, length](AfcFileHandle *handle) { + return afc_file_write(handle, data, length); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileClose(iDescriptorDevice *device, - uint64_t handle, - std::optional altAfc) +IdeviceFfiError * +ServiceManager::safeAfcFileClose(const iDescriptorDevice *device, + AfcFileHandle *handle) +{ + return executeAfcOperation( + device, [](AfcFileHandle *handle) { return afc_file_close(handle); }, + handle); +} + +IdeviceFfiError * +ServiceManager::safeAfcFileSeek(const iDescriptorDevice *device, + AfcFileHandle *handle, int64_t offset, + int whence) +{ + off_t newPos; + return executeAfcOperation( + device, + [offset, whence, &newPos](AfcFileHandle *handle) { + return afc_file_seek(handle, offset, whence, &newPos); + }, + handle); +} + +IdeviceFfiError * +ServiceManager::safeAfcFileTell(const iDescriptorDevice *device, + AfcFileHandle *handle, off_t *position) { return executeAfcOperation( device, - [handle](afc_client_t client) { - return afc_file_close(client, handle); + [position](AfcFileHandle *handle) { + return afc_file_tell(handle, position); }, - altAfc); + handle); } -afc_error_t ServiceManager::safeAfcFileSeek(iDescriptorDevice *device, - uint64_t handle, int64_t offset, - int whence, - std::optional altAfc) -{ - return executeAfcOperation( - device, - [handle, offset, whence](afc_client_t client) { - return afc_file_seek(client, handle, offset, whence); - }, - 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); -} - -QByteArray -ServiceManager::safeReadAfcFileToByteArray(iDescriptorDevice *device, - const char *path, - std::optional altAfc) +QByteArray ServiceManager::safeReadAfcFileToByteArray( + const iDescriptorDevice *device, const char *path, + std::optional altAfc) { return executeOperation( device, - [path](afc_client_t client) -> QByteArray { - return read_afc_file_to_byte_array(client, path); + [path, device]() -> QByteArray { + return read_afc_file_to_byte_array(device, path); }, altAfc); } -AFCFileTree ServiceManager::safeGetFileTree(iDescriptorDevice *device, - const std::string &path, bool checkDir, - std::optional altAfc) +AFCFileTree +ServiceManager::safeGetFileTree(const iDescriptorDevice *device, + const std::string &path, bool checkDir, + std::optional altAfc) { return executeOperation( device, - [path, checkDir](afc_client_t client) -> AFCFileTree { - return get_file_tree(client, path.c_str(), checkDir); + [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, + std::optional altAfc) +{ + return executeOperation>( + device, + [device, path, checkDir]() -> QFuture { + return QtConcurrent::run([device, path, checkDir]() { + return get_file_tree(device, checkDir, path); + }); + }, + altAfc); +} + +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; + }); +} + +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) { + // 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 + }); +} + +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 7802e3a..7ad2846 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -22,8 +22,8 @@ #include "iDescriptor.h" #include +#include #include -#include #include #include @@ -39,9 +39,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) { return T{}; // Return default-constructed value for the type @@ -61,16 +62,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) { + if (!device || !device->mutex) { + qDebug() << "[executeOperation] Device or mutex is null"; return T{}; // Return default-constructed value for the type } @@ -78,21 +82,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) { return failureValue; @@ -115,8 +122,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) { return; @@ -138,85 +146,149 @@ 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) { - return AFC_E_UNKNOWN_ERROR; + if (!device || !device->mutex) { + // 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 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"}; } std::lock_guard lock(device->mutex); // Double-check device is still valid after acquiring lock if (!device->afcClient) { - return AFC_E_UNKNOWN_ERROR; + 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"; + // c string is not safe in IdeviceFfiError ? + 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, + size_t count, 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); + const iDescriptorDevice *device, const char *path, + std::optional altAfc = std::nullopt); static AFCFileTree - safeGetFileTree(iDescriptorDevice *device, const std::string &path = "/", - bool checkDir = true, - std::optional altAfc = std::nullopt); + 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, + std::optional altAfc = std::nullopt); + 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); + + // 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); + + static IdeviceFfiError * + takeScreenshot(const iDescriptorDevice *device, + ScreenshotrClientHandle *screenshotrClient, + ScreenshotData *screenshot); + + static IdeviceFfiError *enableDevMode(const iDescriptorDevice *device); }; #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 abc35c6..d3f98ed 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -44,47 +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::sharedInstance() -{ - static ToolboxWidget *instance = new ToolboxWidget(); - return instance; -} - ToolboxWidget::ToolboxWidget(QWidget *parent) : QWidget{parent} { setupUI(); @@ -339,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)); } } @@ -348,6 +308,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() @@ -381,6 +349,8 @@ 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; @@ -396,7 +366,7 @@ void ToolboxWidget::onDeviceSelectionChanged() } m_uuid = selectedUdid.toStdString(); - + qDebug() << "[toolboxwidget] Selected device UDID:" << selectedUdid; // Update the selected device in main menu AppContext::sharedInstance()->setCurrentDeviceSelection( DeviceSelection(selectedUdid.toStdString())); @@ -404,7 +374,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) { @@ -415,19 +385,22 @@ void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection) m_uuid = selection.udid; } + } else { + // Clear m_uuid when selection is invalid + m_uuid.clear(); } } -void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice) +void ToolboxWidget::onToolboxClicked(iDescriptorTool tool) { + // final check to make sure device is connected if required iDescriptorDevice *device = AppContext::sharedInstance()->getDevice(m_uuid); - if (!device && requiresDevice) { - QMessageBox::warning( - this, "Device Disconnected ?", - "Device just disconnected, please select a device."); + 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) { @@ -450,7 +423,16 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice) liveScreen->show(); } break; case iDescriptorTool::RecoveryMode: { - _enterRecoveryMode(device); + // Handle entering recovery mode + // 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 = @@ -581,10 +563,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"; } } @@ -605,12 +594,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"; } } @@ -631,33 +625,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(); -} - -void ToolboxWidget::restartAirPlayWindow() -{ - if (!m_airplayWindow) { - onToolboxClicked(iDescriptorTool::Airplayer, false); - return; - } - - connect( - m_airplayWindow, &QObject::destroyed, this, - [this]() { - // give some time for cleanup - QTimer::singleShot(100, this, [this]() { - onToolboxClicked(iDescriptorTool::Airplayer, false); - }); - }, - Qt::SingleShotConnection); - - m_airplayWindow->close(); + // 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 4320564..c92c94f 100644 --- a/src/toolboxwidget.h +++ b/src/toolboxwidget.h @@ -68,6 +68,7 @@ private: QWidget *m_contentWidget; QGridLayout *m_gridLayout; QList m_toolboxes; + QList m_requiresDevice; std::string m_uuid; DevDiskImagesWidget *m_devDiskImagesWidget = nullptr; NetworkDevicesWidget *m_networkDevicesWidget = nullptr; diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp index 4d781b7..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 @@ -45,19 +46,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); @@ -153,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) { @@ -200,6 +185,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 +297,186 @@ 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"); + // 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(); + + 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 { - SettingsManager::sharedInstance()->saveRecentLocation( - latitude, longitude); - - QMessageBox::information(this, "Success", - "Location applied successfully!"); + QMessageBox::information( + this, "Success", + "Developer Mode enabled successfully. Please try " + "applying the location again."); } - }); - connect( - devDiskImageHelper, &DevDiskImageHelper::destroyed, this, - [this]() { - QTimer::singleShot(1000, this, [this]() { - m_applyButton->setText("Apply Location"); - m_applyButton->setEnabled(true); - }); - }, - Qt::SingleShotConnection); - devDiskImageHelper->start(); + } + + 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/welcomewidget.cpp b/src/welcomewidget.cpp index 05764ba..5dbd835 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( diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp new file mode 100644 index 0000000..fdbfd04 --- /dev/null +++ b/src/zloadingwidget.cpp @@ -0,0 +1,115 @@ +#include "zloadingwidget.h" + +#include "qprocessindicator.h" +#include +#include +#include + +ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) + : QStackedWidget{parent}, m_loadingIndicator(new QProcessIndicator()) +{ + m_loadingIndicator->setType(QProcessIndicator::line_rotate); + m_loadingIndicator->setFixedSize(64, 32); + if (start) { + m_loadingIndicator->start(); + } + + // 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); + loadingLayout->addStretch(); + + addWidget(loadingWidget); // Loading widget at index 0 +} + +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() +{ + 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..9ddabf4 --- /dev/null +++ b/src/zloadingwidget.h @@ -0,0 +1,29 @@ +#ifndef ZLOADINGWIDGET_H +#define ZLOADINGWIDGET_H + +#include +#include + +class ZLoadingWidget : public QStackedWidget +{ + Q_OBJECT +public: + explicit ZLoadingWidget(bool start = true, QWidget *parent = nullptr); + ~ZLoadingWidget(); + 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; +signals: +}; + +#endif // ZLOADINGWIDGET_H