From 35c5985f470ad2a410a3cf03237ffca1346f29ef Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 2 Mar 2026 23:25:06 +0000 Subject: [PATCH] detect recovery devices, cleanup code , use const pointers wherever possible, refactor img thumbnail loading, refactor gallery widget --- CMakeLists.txt | 49 ++- com.idescriptor.idescriptor.json | 99 ++--- resources.qrc | 1 + .../LetsIconsHorizontalDownLeftMainLight.png | Bin 0 -> 9670 bytes src/afcexplorerwidget.cpp | 3 +- src/afcexplorerwidget.h | 4 +- src/appcontext.cpp | 149 +++++--- src/appcontext.h | 26 +- src/core/helpers/parse_recovery_mode.cpp | 39 -- .../helpers/read_afc_file_to_byte_array.cpp | 7 +- src/core/services/init_device.cpp | 111 +++--- src/devdiskimagehelper.cpp | 26 +- src/devdiskimagehelper.h | 6 +- src/devdiskmanager.cpp | 6 +- src/devdiskmanager.h | 6 +- src/deviceimagewidget.cpp | 3 +- src/deviceimagewidget.h | 4 +- src/deviceinfowidget.cpp | 16 +- src/deviceinfowidget.h | 4 +- src/devicemanagerwidget.cpp | 180 +++++---- src/devicemanagerwidget.h | 29 +- src/devicemenuwidget.cpp | 3 +- src/devicemenuwidget.h | 6 +- src/diskusagewidget.cpp | 7 +- src/diskusagewidget.h | 4 +- src/exportmanager.cpp | 2 +- src/exportmanager.h | 3 +- src/fileexplorerwidget.cpp | 2 +- src/fileexplorerwidget.h | 4 +- src/gallerywidget.cpp | 27 +- src/gallerywidget.h | 4 +- src/iDescriptor-ui.h | 2 + src/iDescriptor-utils.h | 26 +- src/iDescriptor.h | 70 ++-- src/imageloader.cpp | 229 ++++++++---- src/imageloader.h | 16 +- src/imagetask.h | 24 +- src/installedappswidget.cpp | 115 +++--- src/installedappswidget.h | 8 +- src/mainwindow.cpp | 67 +++- src/mediapreviewdialog.cpp | 156 +++----- src/mediapreviewdialog.h | 35 +- src/mediastreamer.cpp | 51 ++- src/mediastreamer.h | 27 +- src/mediastreamermanager.cpp | 4 +- src/mediastreamermanager.h | 30 +- src/networkdevicestoconnectwidget.cpp | 54 ++- src/networkdevicestoconnectwidget.h | 3 + src/opensshterminalwidget.cpp | 9 +- src/opensshterminalwidget.h | 7 +- src/photoimportdialog.cpp | 18 +- src/photoimportdialog.h | 8 +- src/photomodel.cpp | 115 +++--- src/photomodel.h | 19 +- src/recoverydeviceinfowidget.cpp | 246 ++++++------ src/recoverydeviceinfowidget.h | 2 +- src/servicemanager.cpp | 86 +++-- src/toolboxwidget.cpp | 41 +- src/toolboxwidget.h | 6 +- src/virtuallocationwidget.cpp | 352 ++++++++++-------- src/welcomewidget.cpp | 3 +- src/wirelessgalleryimportwidget.cpp | 2 +- src/zloadingwidget.cpp | 53 ++- src/zloadingwidget.h | 36 +- 64 files changed, 1490 insertions(+), 1260 deletions(-) create mode 100644 resources/icons/LetsIconsHorizontalDownLeftMainLight.png delete mode 100644 src/core/helpers/parse_recovery_mode.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 16d2d0f..c095d48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,13 @@ cmake_minimum_required(VERSION 3.16) -project(iDescriptor VERSION 0.3.0 LANGUAGES CXX) +project(iDescriptor VERSION 0.3.0 LANGUAGES C CXX) if(WIN32) set(PKG_CONFIG_EXECUTABLE "C:/msys64/mingw64/bin/pkg-config.exe" CACHE FILEPATH "" FORCE) endif() # Feature options -set(PACKAGE_MANAGER_HINT "" CACHE STRING "Name of package manager(s) used to manage this build (e.g. paru, yay, pamac)") +option(ENABLE_RECOVERY_DEVICE_SUPPORT "Enable recovery device support (requires libirecovery)" ON) +set(PACKAGE_MANAGER_HINT "" CACHE STRING "Name of package manager(s) used to manage future updates (e.g. paru, yay, pamac), only used if PACKAGE_MANAGER_MANAGED is ON)") option(PACKAGE_MANAGER_MANAGED "Build as package manager managed version (auto updates will be handled by the package manager)" OFF) option(DEPLOY "Deploy the application (WIN32 only)" ON) @@ -40,7 +41,7 @@ if(WIN32) 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 + # TODO: do we need this on macOS? # 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}") @@ -92,7 +93,6 @@ set(IDEVICE_RS_LIB_PATH ${IDEVICE_RS_SOURCE_DIR}/target/debug/libidevice_ffi.a) if(APPLE) add_custom_command( OUTPUT ${IDEVICE_RS_LIB_PATH} - # Ensure rustc is visible in the build environment COMMAND ${CMAKE_COMMAND} -E env "PATH=$ENV{HOME}/.cargo/bin:/usr/local/bin:$ENV{PATH}" "RUSTC=/usr/local/bin/rustc" @@ -101,6 +101,7 @@ add_custom_command( COMMENT "Building idevice-rs FFI libraryy" VERBATIM ) +# We must use +stable-x86_64-pc-windows-gnu on Windows elseif(WIN32) add_custom_command( OUTPUT ${IDEVICE_RS_LIB_PATH} @@ -109,6 +110,7 @@ add_custom_command( COMMENT "Building idevice-rs FFI libraryy" VERBATIM ) +# Linux else() add_custom_command( OUTPUT ${IDEVICE_RS_LIB_PATH} @@ -166,7 +168,7 @@ 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) + pkg_check_modules(UDEV REQUIRED IMPORTED_TARGET libudev) target_link_libraries(idevice_cpp PUBLIC PkgConfig::UDEV dl m) elseif(APPLE) find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED) @@ -218,6 +220,20 @@ endif() pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml) +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() file(GLOB PROJECT_SOURCES src/*.h @@ -229,6 +245,15 @@ src/base/*.h resources.qrc ) +if (NOT ENABLE_RECOVERY_DEVICE_SUPPORT) + list(REMOVE_ITEM PROJECT_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/recoverydeviceinfowidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/recoverydeviceinfowidget.h + src/recoverydeviceinfowidget.cpp + src/recoverydeviceinfowidget.h + ) +endif() + if(APPLE) list(APPEND PROJECT_SOURCES src/platform/macos/macos.mm @@ -324,6 +349,11 @@ target_link_libraries(iDescriptor PRIVATE SQLite::SQLite3 ) +if(ENABLE_RECOVERY_DEVICE_SUPPORT) + target_link_libraries(iDescriptor PRIVATE ${IRECOVERY_LIBRARY}) + target_compile_definitions(iDescriptor PRIVATE ENABLE_RECOVERY_DEVICE_SUPPORT) +endif() + target_include_directories(iDescriptor PRIVATE # Put idevice-rs includes FIRST ${IDEVICE_CPP_INCLUDE_DIR} @@ -401,6 +431,15 @@ set_target_properties(iDescriptor PROPERTIES ) if (UNIX AND NOT APPLE) + + # Required on Linux to find libirecovery-1.0.so.5 at runtime + if (ENABLE_RECOVERY_DEVICE_SUPPORT) + set_target_properties(iDescriptor PROPERTIES + # Control library search order - system libs first, then /usr/local/lib + INSTALL_RPATH "/usr/lib/x86_64-linux-gnu:/usr/lib:/usr/local/lib:$ORIGIN" + ) + endif() + # Add install rules for the project # include(GNUInstallDirs) diff --git a/com.idescriptor.idescriptor.json b/com.idescriptor.idescriptor.json index bcb58b8..9cb125b 100644 --- a/com.idescriptor.idescriptor.json +++ b/com.idescriptor.idescriptor.json @@ -10,6 +10,10 @@ "add-ld-path": "." } }, + "sdk-extensions": [ + "org.freedesktop.Sdk.Extension.rust-stable", + "org.freedesktop.Sdk.Extension.llvm20" + ], "cleanup-commands": ["mkdir -p ${FLATPAK_DEST}/lib/ffmpeg"], "command": "iDescriptor", "finish-args": [ @@ -19,20 +23,8 @@ "--share=network", "--device=all", "--socket=system-bus", - "--filesystem=/run/usbmuxd:rw", - "--filesystem=/run/udev:ro", - "--filesystem=/dev:ro", - "--filesystem=/sys/bus:ro", - "--filesystem=/sys/class:ro", - "--filesystem=/sys/devices:ro", + "--filesystem=/run/usbmuxd", "--system-talk-name=org.freedesktop.Avahi", - "--talk-name=org.libimobiledevice.usbmuxd", - "--system-talk-name=org.libimobiledevice.usbmuxd", - "--system-talk-name=org.freedesktop.UDisks2", - "--system-talk-name=org.freedesktop.login1", - "--system-talk-name=org.freedesktop.NetworkManager", - "--system-talk-name=org.freedesktop.UPower", - "--system-talk-name=org.freedesktop.systemd1", "--talk-name=org.freedesktop.Platform.ffmpeg-full", "--env=QT_MEDIA_BACKEND=ffmpeg", "--env=AVAHI_COMPAT_NOWARN=1" @@ -90,7 +82,6 @@ { "name": "libplist", "buildsystem": "autotools", - "config-opts": ["--without-cython"], "sources": [ { "type": "git", @@ -98,26 +89,6 @@ } ] }, - { - "name": "libimobiledevice-glue", - "buildsystem": "autotools", - "sources": [ - { - "type": "git", - "url": "https://github.com/libimobiledevice/libimobiledevice-glue.git" - } - ] - }, - { - "name": "libtatsu", - "buildsystem": "autotools", - "sources": [ - { - "type": "git", - "url": "https://github.com/libimobiledevice/libtatsu.git" - } - ] - }, { "name": "libusb", "config-opts": ["--disable-static", "--enable-udev"], @@ -133,26 +104,6 @@ "install -Dm644 COPYING ${FLATPAK_DEST}/share/licenses/libusb/COPYING" ] }, - { - "name": "libusbmuxd", - "buildsystem": "autotools", - "sources": [ - { - "type": "git", - "url": "https://github.com/libimobiledevice/libusbmuxd.git" - } - ] - }, - { - "name": "libimobiledevice", - "buildsystem": "autotools", - "sources": [ - { - "type": "git", - "url": "https://github.com/libimobiledevice/libimobiledevice.git" - } - ] - }, { "name": "libdaemon", "buildsystem": "autotools", @@ -204,16 +155,6 @@ } ] }, - { - "name": "libirecovery", - "buildsystem": "autotools", - "sources": [ - { - "type": "git", - "url": "https://github.com/libimobiledevice/libirecovery.git" - } - ] - }, { "name": "libde265", "buildsystem": "autotools", @@ -271,22 +212,36 @@ }, { "name": "iDescriptor", - "buildsystem": "cmake-ninja", + "buildsystem": "simple", "build-options": { + "append-path": "/usr/lib/sdk/rust-stable/bin", + "env": { + "CARGO_HOME": "/run/build/iDescriptor/cargo", + "CARGO_NET_OFFLINE": "true", + "RUST_BACKTRACE": "1", + "CUSTOM_LIB_PATH": "/app/lib", + "CUSTOM_INCLUDE_PATH": "/app/include", + "CUSTOM_PKGCONFIG_PATH": "/app/lib/pkgconfig", + "GO_EXECUTABLE": "/app/sdk/golang/bin/go", + "FLATPAK_BUILD": "ON" + }, "build-args": ["--share=network"] }, - "config-opts": [ - "-DCUSTOM_LIB_PATH=/app/lib", - "-DCUSTOM_INCLUDE_PATH=/app/include", - "-DCUSTOM_PKGCONFIG_PATH=/app/lib/pkgconfig", - "-DGO_EXECUTABLE=/app/sdk/golang/bin/go", - "-DFLATPAK_BUILD=ON" + "build-commands": [ + "cargo --offline fetch --manifest-path lib/idevice-rs/Cargo.toml --verbose", + "cargo build --offline --release --all-features --manifest-path lib/idevice-rs/Cargo.toml", + "cmake -B build -DGO_EXECUTABLE=$GO_EXECUTABLE .", + "cmake --build build --config Release", + "install -Dm0755 build/iDescriptor ${FLATPAK_DEST}/bin/iDescriptor" ], "sources": [ { "type": "dir", "path": "." - } + }, + "cargo-sources.idevice-rs.json", + "cargo-sources.idevice-rs.idevice.json", + "cargo-sources.idevice-rs.cpp.plist_ffi.json" ] } ] diff --git a/resources.qrc b/resources.qrc index 3a4a582..93991bc 100644 --- a/resources.qrc +++ b/resources.qrc @@ -37,6 +37,7 @@ resources/icons/MaterialSymbolsFolder.png resources/icons/QlementineIconsWireless116.png resources/icons/UimProcess.png + resources/icons/LetsIconsHorizontalDownLeftMainLight.png qml/MapView.qml resources/iphone.png resources/ios-wallpapers/iphone-ios4.png diff --git a/resources/icons/LetsIconsHorizontalDownLeftMainLight.png b/resources/icons/LetsIconsHorizontalDownLeftMainLight.png new file mode 100644 index 0000000000000000000000000000000000000000..ba3badf1920d9b2cda559f22d91fc09d9b2ca66e GIT binary patch literal 9670 zcmeHt`Cp7%{P>y2GfYKBQQf2_lqfZ>C@MWIw5nXCO*OdJnhMe8nrAL5u5Mf;r5amB zTF_!kLzXNlTZJ?ek_ru#O4H2eea5}t*Y}stU+}qpna*?0dtcA{*yH2nGDcNb6(Kan z&DHrAgb4VPK&+AQ(;NHjJ3<`f=Ir1b8~W+B{kbBykWo?(M+XPN-YcT7cW>Qv)O0Pa zCRH`3yP7quU2%iIgfe`bzh||$&3?%U6=SXOC-V3ExE?p0asOGy?xpJO>7YcO%#IC`Qd

N9H|4{#@kpH6|QsN+)*O7hKs<}PQ0V2j> z;+hu_DAqob!|OhJ=XFhc+E*j!(YbPxM)l*w&tQvXB26pJU3w*Sf00FA*YW7zXGrGH z>~Lf-B#u<7CPdg|!K<-~t_!>9J-XWyrW2x5DSlcIY^uzb?R{AaweGLS*gnbgZhj&} ztdI+fAPV(Nqn@_%?RxCPm?VEvRJcV(iaD}8Id>dDIjUak=;ZMqCq7qUOx(Zd@z**r#0c*XYF&o$U+y_;?l zi6(3ta!7s8o4M6r8M+YxyWzg+hA zFrD_&rd?faQYH@ta2g`%khhM}-BZr~>-EuDFR0$urzV-sh& zk7g$$uD6AsBOu&D*f~-15~2Nq3QBP;3oQQVr6(vTnZqHSKRD8JScs9qQ^6`Thb-lAf$k1`_OYb3kgwv*2M^=Yt_#t-=&U` zpIr4|_LYBXgQ$`O=j=~J+)Y~!F9POO2}g=~E7^&K#$8>x7*Ho0amZVpE{C79ke|*N zteXyy?_Q7@@@(*FgEAtaKsu_q!AT7j-mZ0l1Lv9XyXoSooa(gVl&K^2m#;q#wy5R;;KLZOm_vqCQk;3x72J80BWkT7&kIzAaX-8 z)(>wbNa3@ENmI2Y<3R>9&OdgzMcm1BACKnFx{RU782~n6R=>It`gpiU4?eJ#Pv(%z zPx!Y6GK9nlLuJW6LbFT$EPUidP?KCv;^Hn^0CMHLy6vrkv^8~UjL3XEn=mJvdoH08 zm)hCDAjc4rhvRp}kg7}Ol0kX(AQa-SKx^n^(tjkRG`q09!C02>71dzMjL7AYUi23{7o?c>MVc^7-jm6y`Q6 z9bEUoGL)aZ&IChPFQ-`f&Sw955zA8Z!vsP;#V^*qalHLx6J+_S*myE#bT2TvJDm{i zruD&0NDu*QW-lRP#_Aj4^=D;J-x7(t%}cg02$7d{Dnutu>3|AdzLx zLDG)g3P|EK6#)DR=MWSlEf@>Mxdui!zmO0uGO57uA}-KLT&991?r(G%VZ)Q4H^ITA~6$J-k4!lyD(Nz#?D0zHptGjRmxFeWiB_%q(0Horz}ws=s|%>4u68N8NV7)F7!JJ> zI``$x!XqM*Fm$33_r{}N2*E@$J5&B*)RVq#?obpau(S!1E*iS=j#QrzyG?gkts{Q$ z_(h##NKi-|2kuMCHh!MM%@*xkcQ5Zt+Q!>tt`zBU` zw3Bx<2Tqs16YE93Smw!d6GRw7JG;4Rdi-XAcyV9l)~~vUU3kU1Ao*S^HgJXoe=<*gScG-u#JL3QZ$WYI^N4RzoLh(lAkxs$0a6UMuU6Z8P*%4no z7+laYPHqELnn|Di-AV^xY^jYdyG1CX>oC$7l$G+aL5jCLwc??6ye)tIkv%N&ERVR%Ik-B9nP9_kRNs33y zSjeW;7Ag~ic+J$4YnM{!5~@3?lxMN1m2X&yq6~$1aa;;7Q^nPa%rj49U#>e^L!-;X))SuBxTJ~{LBfjq9I-?w0D6Gc!Zi2>?Z zH`{}OJIQJ`Qol9F5`Xxp6*bVE`|6tzmDJt~Q3?POi$y_bEgi0kIchSKNX4F8U!)MC z-*#lOHTx6P-k!`;tq7{VJxbD5Y#vx!97 zUE)B}>K}5U2S9>IwRhF_{A=Brx|wD}S&FiYs6O`Y(Z4VrJ^8qAG-4lDYrqHl$nEXo z%k2c!J~BNUw$)UxnLOX#*?WPX-CwDeVSn;hhR7WBhy-cCQQAwZhen88x9c}hJCDIg z;tP${kRW5lbdVxFK-6k*?(+ZBgz@;Ofl=XS5%e%=&VXPKkaozNj>wJsegjSO2tjC) zLi3(E|5|a(kho+INvmAHc?grDao3n5?SFq~!6yqto0pQ~mqvs&7B6`I{O zk*h)QhakAYsRBa8Nx9$&>(Lxo3*8z$#{XOYIVB>sr!T!aI={qVV=>EBE<>sRHP~z~ zAaqi(mL;ukJox7XG%`HH2$DW$KV5C_Guu6FGCH*O@)1ll0?a@5ngK}*GS_r~oFA~V zlfzc6X9yEd%mQyHNdxcyV}}~jv|OU>3{CcrRmfEMk?XA2WOsIT+&Ad%dM!UJVbbpV z6LB9-fGYQ7KqK|c^E4LOz`^oR zlwNxNJ_Ib}JMB}muvCdvw%tRf&v?lRaFZ_|sq_Yj5BtIEY-Ai+@)h&v1OYZ;W2p;C zKc2t35=5|G20nT&GW{`=<`$N)L2=O_WK5*PRVE94I#-J+)&P?=ujlbL5+X)^B6yXE zhs|QpJ#;apak$JfOdcE7{Vj5IPM#*3FUs=++E$gO-*tJNH zv=37!=P%x}h(aX`u_S+irn5qG3Vo5a$Gee$&(lyM%{h_Mx0j7N19fpfwFES^#OCRy z5Q1{qz7$L<6wuXGjIYT{S!l|(+6>4TC5v$e(3H1a`SfjlOq=(B&FKdd1-*3i|v>IdQf*tJ-cBJNQeIIt|%MD>ynHfR;r5Wp`J6|zJ zRIG1xyJC@CT)?l3eWBoe2&z^?WmDho+fr;LV=*?x|7t`Asp(B3^yGy$K`RzOFSt@@ z_^9%gKa2Iu%)_ugK$6dvNF$qTf}W}X;85z8&(f;U|7Dc$2=$2#ZV0V--^${@ zLb=5#iV-SwsnC}@Qo`IoRe={#<_L;eI%30OQ}pcbk5-Rb#GP%2=^b{nXA4@0BZ(-9 z->tqZJq+d3O`DP{lN6td$#+M(7d$&Z64YAn!i!`{Yn^g-_~rPae6N?`B9F0rLt$Wn zr!(gqwT=aL-uT<#O_JVxk>&ktzHB$mdR;ma?uz9Yvv`4DP6&X-571=*CXuEcrKwoK z7DLD(*LSz6jYs!u;}>jt)@niaemr|wg zXU#v~9(Qe)l()88lVuXM1`du2|E16ft0mPSug{{lrt01U3-ly9j+SmkJTTJk*g~%C5Y~WpF$^#qI5nj9AP7~c+vHvQ>T6>j8#a2w6F=tWup{_1yH4eu)MESprbT z60!AxYWqb7=kSf^0gxRI&ccRmX^x$Ky(e;X#C$mE3|IB<+p1f|jpC0G-~r1h^r106 ziEp1U8$y5=$6yHSv_8ZQ*dKD2Q#luKy-Rq`i5SgW;ZpkmWpH4GXK1IW3KAyL6li8rjP z&5Mq!7Bg#mOki*I1UCBX)46IzK-fv(g%)|i_;Ffu;$1!Yzm4NG;ErzT{T1^7zMF8Cy^QbjU{45y+P-CH?a>ze9nR$Sb;v@r!7FH7f^3iF^HQ3yzft|!Z zdgQ9|wvbPwlFC_wQv1_gDX$F9Y|g9dDZaTDCtJA9QmCoiAYNZS=rY_=vGJ&~31bki zKo31$>WZF4pOrQD2COqDzLy$=I|Z97)eH{t`O`b8xIC#DF3xaSRb`==$d`XURb<(> zRNS2?mt`sFFxNCs$gGb6dd;?nt@ba?%R@0)6D>B%_G;*o@+Z%Hd&s=|n?=h_eI+3~ zf)FiTudcAOsZm}Wxu<9({q>ce+$kdLPh~?=Nf01=cgq)LOElW7Bp%g>2XmhECEl^? zjghC^SnPuN@zxHQhk9*gJ*nR-8?l?iw@Px^7^JQUwRv<6+k)-LyNx8$vFH2Dut$^e zs^_$jp4=`b^RiNJ`$X;xiIJNf|5{|Km2A*P!3}P4RBBl5oCb=YBp18h!G_5xw!%WO zj7l64B%7C;wczf7W@QSs``{YYa%}H$>GMM*ma!_;B*a<14FAbdJdWM+wE^?3p6l|6QUYm zt?JERezWm$-*GyawSrPHN6ErjQ8M1{jPSEkZfoky#M@;plydum$Q5sY_42b(ZcorL zLhq^-hl(2g3gt3h%O`$I%_Mx1D-^L9!uHl2`wU3B-A*K2-x zK10~pDz2KcOo^wEsqdk=!#JcWtH`1(`naX_T1F@kFWOg3o6PCl$?iLSH=8LaXi zo)&}#);`%W^z6-E<*nfZG3ZCjJl^N-n8W!6NwfI97Ajq^BbrYrj=B%SoONr%-zQ0w zq&R2ei92Z>f5}__Vsc;bjWMRFY;dBb5i=L!-cnW2m^(urz1RJ-$%z=SSly+9XSk_} zBlQ=b-Y&Lnv-NYU11HScjnmFxL_MnS z3`T5^F8>-o5*hN!U!T$ko50l-cywtu?P@4&!G|~bD|z3-vdz99yYR|&TbFs>#giQo zn^@f(xE>r-G(ZfxD88m5ufb{V=#n}kC;OMcB}$jb5Vb$~gCAbH@W?qUP_;Yd*52%a z{lGGxb1e(=q%6C`Zu&xj=LF{7h zx1T#;)1{F$srK&39HY%jjLw?r(-DjDc-xUarJw9b(_z_#=< z<+iIJ5cpwlz8W|l6M_Ua54M+VJGxc5Z7q0#w<8|>lfT#jhUnM!m{VPc%GX`DA9KO8a zY|KED)x$PQ=`Z0*%`p@{eDE3v1KXEE zCXxHtJ`wL#@OhRo2YuX-%vMg=@IHnYCs&+y@x~+Jp)q#KM3@D#K9KCDAINN#Gd$(G z71m=?DD0Lgz{Y%uP&^3e5hF(CM$c|givTkVn zfDe@{1zW0k@Y{n@$20ib|Hb-jTf&e_oNE^pSZ%Tt$ z$xqcxaRTlIyyvvk80WvQm;;2Z6t_H|Q=BpX$qg(igRR>qKQ2LxA!b znVYNs-ps10vXLZ=UH(lRI!T^mAEoZ3>) zIQg%~ApwmLJUp$xTXcK52HY91*4W{K_XR+{B&3@*X%ccdJh#==ItZ0BClk<;d+ADX zFg|=fVV&}IYB`0hk6r2@=aKK~R4#QV1}a(srYii(&j*!x*@VI1_}ZK`d!Gw8&tk`L z+|2H?;Glit>Z^WH6>Mp$zj#n_`%TLO4!v924TJt|E{pWc9dn^eQqYk<`+fCi! RKLf!(w}oELMUK2b{s)&<_7(sD literal 0 HcmV?d00001 diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 5769e2d..6fd5b50 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -44,7 +44,8 @@ #include #include -AfcExplorerWidget::AfcExplorerWidget(iDescriptorDevice *device, bool favEnabled, +AfcExplorerWidget::AfcExplorerWidget(const iDescriptorDevice *device, + bool favEnabled, AfcClientHandle *afcClient, QString root, QWidget *parent) : QWidget(parent), m_device(device), m_favEnabled(favEnabled), diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h index a20c434..51f0c66 100644 --- a/src/afcexplorerwidget.h +++ b/src/afcexplorerwidget.h @@ -46,7 +46,7 @@ class AfcExplorerWidget : public QWidget { Q_OBJECT public: - explicit AfcExplorerWidget(iDescriptorDevice *device = nullptr, + explicit AfcExplorerWidget(const iDescriptorDevice *device = nullptr, bool favEnabled = false, AfcClientHandle *afcClient = nullptr, QString root = "/", QWidget *parent = nullptr); @@ -87,7 +87,7 @@ private: ZIconWidget *m_homeButton; ZIconWidget *m_upButton; ZIconWidget *m_enterButton; - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; bool m_favEnabled; AfcClientHandle *m_afc; QString m_errorMessage; diff --git a/src/appcontext.cpp b/src/appcontext.cpp index b31f96f..4224477 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -25,6 +25,7 @@ #include "networkdevicemanager.h" #include #include +#include #include #include #include @@ -98,11 +99,13 @@ void AppContext::cachePairedDevices() auto conn = UsbmuxdConnection::default_new(0); if (conn.is_err()) { qDebug() << "ERROR: Failed to connect to usbmuxd!"; + return; } auto devices = conn.unwrap().get_devices(); if (devices.is_err()) { qDebug() << "ERROR: Failed to get device list!"; + return; } for (const auto &device : devices.unwrap()) { @@ -190,6 +193,12 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, { emit initStarted(uniq); + + if (auto device = getDevice(uniq)) { + emit deviceAlreadyExists(uniq); + return; + } + try { auto initResult = std::make_shared(); @@ -237,7 +246,7 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, watcher->setFuture(future); connect( watcher, &QFutureWatcher::finished, this, - [this, uniq, initResult, addType, conn_type, watcher]() { + [this, uniq, initResult, addType, conn_type, watcher]() mutable { watcher->deleteLater(); qDebug() << "init_idescriptor_device success ?: " << initResult->success; @@ -245,6 +254,9 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, if (!initResult->success) { qDebug() << "Failed to initialize device with" << uniq; emit initFailed(uniq); + // TODO:it could also be password protected, so check for + // that Initialization failed, cleaning up resources. + // PasswordProtected if (initResult->error && initResult->error->code == PairingDialogResponsePending) { if (addType == AddType::Regular) { @@ -266,7 +278,8 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, } }); // FIXME: free properly and move to a better place - QtConcurrent::run([uniq, this]() { + QThreadPool::globalInstance()->start([uniq, + this]() { UsbmuxdConnectionHandle *usbmuxd_conn = nullptr; UsbmuxdAddrHandle *addr_handle = nullptr; IdeviceProviderHandle *provider = nullptr; @@ -433,6 +446,16 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, } qDebug() << "Device initialized: " << uniq; + /* + We need this because wireless devices get initialized + with Mac addresses. Even though a Mac address is unique, we + are better off using the "UDID" as the unique identifier. + This is only required for wireless devices, Usb devices + already use the UDID but it doesn't hurt to set it for them + as well + */ + uniq.set(initResult->deviceInfo.UniqueDeviceID, false); + iDescriptorDevice *device = new iDescriptorDevice{ .udid = uniq.get().toStdString(), .conn_type = conn_type, @@ -444,8 +467,10 @@ void AppContext::addDevice(iDescriptor::Uniq uniq, .diagRelay = initResult->diagRelay, .heartbeatThread = initResult->heartbeatThread}; m_devices[device->udid] = device; - if (addType == AddType::Regular) { - qDebug() << "Regular device added: " << uniq; + if (addType == AddType::Wireless || + addType == AddType::UpgradeToWireless || + addType == AddType::Regular) { + qDebug() << "Wireless device added: " << uniq; // SettingsManager::sharedInstance()->doIfEnabled( // SettingsManager::Setting::AutoRaiseWindow, []() { // if (MainWindow *mainWindow = @@ -477,12 +502,6 @@ int AppContext::getConnectedDeviceCount() const // #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 - */ void AppContext::removeDevice(QString _udid) { const std::string udid = _udid.toStdString(); @@ -513,7 +532,11 @@ void AppContext::removeDevice(QString _udid) device->deviceInfo.isWireless); emit deviceChange(); + qDebug() << "Waiting to acquire lock for device cleanup: " + << QString::fromStdString(udid); std::lock_guard lock(device->mutex); + qDebug() << "Acquired lock, cleaning up device: " + << QString::fromStdString(udid); // FIXME: implement proper cleanup if (device->afcClient) @@ -524,7 +547,7 @@ void AppContext::removeDevice(QString _udid) if (device->heartbeatThread) { device->heartbeatThread->requestInterruption(); - device->heartbeatThread->wait(); + // device->heartbeatThread->wait(); delete device->heartbeatThread; } @@ -534,30 +557,29 @@ void AppContext::removeDevice(QString _udid) #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); + iDescriptorRecoveryDevice *deviceInfo = m_recoveryDevices.value(ecid); + m_recoveryDevices.remove(ecid); - // emit recoveryDeviceRemoved(ecid); + emit recoveryDeviceRemoved(ecid); + // TODO: do we need this ? // emit deviceChange(); - // std::lock_guard lock(*deviceInfo->mutex); - // delete deviceInfo->mutex; - // delete deviceInfo; + std::lock_guard lock(deviceInfo->mutex); + delete deviceInfo; } #endif -iDescriptorDevice *AppContext::getDevice(const std::string &udid) +iDescriptorDevice *AppContext::getDevice(const std::string &uniq) { - return m_devices.value(udid, nullptr); + return m_devices.value(uniq, nullptr); } QList AppContext::getAllDevices() @@ -565,12 +587,12 @@ 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 @@ -586,27 +608,35 @@ bool AppContext::noDevicesConnected() const #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT void AppContext::addRecoveryDevice(uint64_t ecid) { - // iDescriptorInitDeviceResultRecovery res = - // init_idescriptor_recovery_device(ecid); + auto res = std::make_shared(); - // if (!res.success) { - // qDebug() << "Failed to initialize recovery device with ECID: " - // << QString::number(ecid); - // qDebug() << "Error code: " << res.error; - // return; - // } + QFuture future = QtConcurrent::run( + [this, ecid, res]() { init_idescriptor_recovery_device(ecid, *res); }); + QFutureWatcher *watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, this, + [this, ecid, res, watcher]() { + watcher->deleteLater(); + if (!res->success) { + qDebug() + << "Failed to initialize recovery device with ECID: " + << QString::number(ecid); + qDebug() << "Error code: " << res->error; + return; + } - // iDescriptorRecoveryDevice *recoveryDevice = new - // iDescriptorRecoveryDevice(); recoveryDevice->ecid = - // res.deviceInfo.ecid; recoveryDevice->mode = res.mode; - // recoveryDevice->cpid = res.deviceInfo.cpid; - // recoveryDevice->bdid = res.deviceInfo.bdid; - // recoveryDevice->displayName = res.displayName; - // recoveryDevice->mutex = new std::recursive_mutex(); + iDescriptorRecoveryDevice *recoveryDevice = + new iDescriptorRecoveryDevice(); + recoveryDevice->ecid = res->deviceInfo.ecid; + recoveryDevice->mode = res->mode; + recoveryDevice->cpid = res->deviceInfo.cpid; + recoveryDevice->bdid = res->deviceInfo.bdid; + recoveryDevice->displayName = res->displayName; - // m_recoveryDevices[res.deviceInfo.ecid] = recoveryDevice; - // emit recoveryDeviceAdded(recoveryDevice); - // emit deviceChange(); + m_recoveryDevices[res->deviceInfo.ecid] = recoveryDevice; + emit recoveryDeviceAdded(recoveryDevice); + emit deviceChange(); + }); } #endif @@ -631,21 +661,16 @@ AppContext::~AppContext() } } - // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - // for (auto recoveryDevice : m_recoveryDevices) { - // emit recoveryDeviceRemoved(recoveryDevice->ecid); - // delete recoveryDevice->mutex; - // delete recoveryDevice; - // } - // #endif +#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + for (auto recoveryDevice : m_recoveryDevices) { + emit recoveryDeviceRemoved(recoveryDevice->ecid); + delete recoveryDevice; + } +#endif } void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) { - qDebug() << "New selection -" - << " Type:" << selection.type - << " UDID:" << QString::fromStdString(selection.udid) - << " ECID:" << selection.ecid << " Section:" << selection.section; if (m_currentSelection.type == selection.type && m_currentSelection.udid == selection.udid && m_currentSelection.ecid == selection.ecid && @@ -712,4 +737,4 @@ void AppContext::tryToConnectToNetworkDevice(const NetworkDevice &device) void AppContext::emitNoPairingFileForWirelessDevice(const QString &udid) { emit noPairingFileForWirelessDevice(udid); -} \ No newline at end of file +} diff --git a/src/appcontext.h b/src/appcontext.h index 279b2e5..4461a30 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -34,14 +34,13 @@ 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; void tryToConnectToNetworkDevice(const NetworkDevice &device); - // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - // QList getAllRecoveryDevices(); - // #endif +#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT + QList getAllRecoveryDevices(); +#endif ~AppContext(); int getConnectedDeviceCount() const; @@ -52,24 +51,25 @@ public: 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(); void emitNoPairingFileForWirelessDevice(const QString &udid); signals: - void deviceAdded(iDescriptorDevice *device); + void deviceAdded(const iDescriptorDevice *device); void deviceRemoved(const std::string &udid, const std::string &macAddress, const std::string &ipAddress, bool wasWireless); - void devicePaired(iDescriptorDevice *device); + void devicePaired(const iDescriptorDevice *device); void devicePasswordProtected(const QString &udid); - // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT - // void recoveryDeviceAdded(const iDescriptorRecoveryDevice - // *deviceInfo); void recoveryDeviceRemoved(uint64_t ecid); - // #endif + void deviceAlreadyExists(const iDescriptor::Uniq &uniq); +#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); // only fired on wireless devices when we have no pairing file for them diff --git a/src/core/helpers/parse_recovery_mode.cpp b/src/core/helpers/parse_recovery_mode.cpp deleted file mode 100644 index 05db856..0000000 --- a/src/core/helpers/parse_recovery_mode.cpp +++ /dev/null @@ -1,39 +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 "libirecovery.h" -#include - -std::string parse_recovery_mode(irecv_mode productType) -{ - switch (productType) { - case irecv_mode::IRECV_K_RECOVERY_MODE_1: - case irecv_mode::IRECV_K_RECOVERY_MODE_2: - case irecv_mode::IRECV_K_RECOVERY_MODE_3: - case irecv_mode::IRECV_K_RECOVERY_MODE_4: - return "Recovery Mode"; - case irecv_mode::IRECV_K_WTF_MODE: - return "WTF Mode"; - case irecv_mode::IRECV_K_DFU_MODE: - case irecv_mode::IRECV_K_PORT_DFU_MODE: - return "DFU Mode"; - default: - return "Unknown Mode"; - } -} 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 9739de7..197ce82 100644 --- a/src/core/helpers/read_afc_file_to_byte_array.cpp +++ b/src/core/helpers/read_afc_file_to_byte_array.cpp @@ -47,10 +47,7 @@ QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, 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) { ServiceManager::safeAfcFileClose(device, handle); @@ -75,10 +72,8 @@ QByteArray read_afc_file_to_byte_array(const iDescriptorDevice *device, return QByteArray(); } - // 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 + afc_file_read_data_free(chunkData, bytesRead); ServiceManager::safeAfcFileClose(device, handle); diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index 84c7f4f..b5827c2 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -21,9 +21,6 @@ #include "../../iDescriptor.h" // #include "../../servicemanager.h" #include "../../appcontext.h" -#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -#include "libirecovery.h" -#endif #include "../../heartbeat.h" #include @@ -206,6 +203,7 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc, d.firmwareVersion = safeGet("FirmwareVersion"); d.productVersion = safeGet("ProductVersion"); d.wifiMacAddress = safeGet("WiFiAddress"); + d.UniqueDeviceID = safeGet("UniqueDeviceID"); QString q_version = QString::fromStdString(d.productVersion); QStringList parts = q_version.split('.'); @@ -549,6 +547,9 @@ void init_idescriptor_device(const iDescriptor::Uniq &uniq, if (val) plist_print(val); + afc_client_set_timeout(afc_client, + 5000); // Set AFC client timeout to 5 seconds + result.provider = provider; result.success = true; result.afcClient = afc_client; @@ -593,64 +594,62 @@ cleanup: } } -// #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT -// iDescriptorInitDeviceResultRecovery -// init_idescriptor_recovery_device(uint64_t ecid) -// { -// qDebug() << "Initializing iDescriptor recovery device with ECID: " << -// ecid; iDescriptorInitDeviceResultRecovery result = {}; +#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +void init_idescriptor_recovery_device( + uint64_t ecid, iDescriptorInitDeviceResultRecovery &result) +{ + qDebug() << "Initializing iDescriptor recovery device with ECID: " << ecid; + result = {}; -// irecv_client_t client = nullptr; -// const irecv_device_info *deviceInfo = nullptr; -// irecv_device_t device = nullptr; -// const DeviceDatabaseInfo *info = nullptr; + irecv_client_t client = nullptr; + const irecv_device_info *deviceInfo = nullptr; + irecv_device_t device = nullptr; + const DeviceDatabaseInfo *info = nullptr; -// irecv_error_t ret = irecv_open_with_ecid_and_attempts( -// &client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES); + irecv_error_t ret = irecv_open_with_ecid_and_attempts( + &client, ecid, RECOVERY_CLIENT_CONNECTION_TRIES); -// if (ret != IRECV_E_SUCCESS) { -// qDebug() << "Failed to open recovery client with ECID:" << ecid -// << "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; + } -// 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; -// } + 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; + } -// 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; -// } + 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; + } -// 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."; -// } + if (irecv_devices_get_device_by_client(client, &device) == + IRECV_E_SUCCESS && + device && device->hardware_model) { + qDebug() << "Recovery device hardware_model: " + << device->hardware_model; + info = + DeviceDatabase::findByHwModel(std::string(device->hardware_model)); + } else { + qDebug() << "Could not resolve hardware_model from client."; + } -// result.displayName = -// info ? (info->displayName ? info->displayName : info->marketingName) -// : "Unknown Device"; -// result.deviceInfo = *deviceInfo; -// result.success = true; + result.displayName = + info ? (info->displayName ? info->displayName : info->marketingName) + : "Unknown Device"; + result.deviceInfo = *deviceInfo; + result.success = true; -// cleanup: -// if (client) { -// irecv_close(client); -// } - -// return result; -// } -// #endif // ENABLE_RECOVERY_DEVICE_SUPPORT +cleanup: + if (client) { + irecv_close(client); + } +} +#endif // ENABLE_RECOVERY_DEVICE_SUPPORT diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index 95cab5b..1d7380a 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -29,7 +29,7 @@ #include #include -DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device, +DevDiskImageHelper::DevDiskImageHelper(const iDescriptorDevice *device, QWidget *parent) : QDialog(parent), m_device(device) { @@ -119,7 +119,10 @@ void DevDiskImageHelper::start() QString::number(deviceMajorVersion) + "."); } } else { - finishWithSuccess(); + showStatus("Developer disk image is not available for iOS version " + + QString::number(deviceMajorVersion) + + ". Please use a device with iOS 6 or above.", + true); return; } } @@ -224,7 +227,7 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version, paths.second.toStdString().c_str()); if (err == nullptr) { - return finishWithSuccess(); + return finishWithSuccess(true); } qDebug() << "onImageDownloadFinished:" << err->code @@ -265,10 +268,21 @@ void DevDiskImageHelper::showStatus(const QString &message, bool isError) show(); } -void DevDiskImageHelper::finishWithSuccess() +/* + waiting is sometimes required because services + may not become available + as soon as the img is mounted +*/ +void DevDiskImageHelper::finishWithSuccess(bool wait) { - m_loadingIndicator->stop(); - accept(); + auto handler = [this]() { + m_loadingIndicator->stop(); + accept(); + }; + if (wait) { + return QTimer::singleShot(3000, handler); + } + handler(); } void DevDiskImageHelper::finishWithError(const QString &errorMessage) diff --git a/src/devdiskimagehelper.h b/src/devdiskimagehelper.h index a589448..2c1b9ec 100644 --- a/src/devdiskimagehelper.h +++ b/src/devdiskimagehelper.h @@ -32,7 +32,7 @@ class DevDiskImageHelper : public QDialog { Q_OBJECT public: - explicit DevDiskImageHelper(iDescriptorDevice *device, + explicit DevDiskImageHelper(const iDescriptorDevice *device, QWidget *parent = nullptr); // Start the mounting process @@ -55,10 +55,10 @@ private: void showStatus(const QString &message, bool isError = false); void showMountUI(); void showRetryUI(const QString &errorMessage); - void finishWithSuccess(); + void finishWithSuccess(bool wait = false); void finishWithError(const QString &errorMessage); - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; QLabel *m_statusLabel; QProcessIndicator *m_loadingIndicator; diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp index 3bdf20a..5175008 100644 --- a/src/devdiskmanager.cpp +++ b/src/devdiskmanager.cpp @@ -322,7 +322,7 @@ bool DevDiskManager::isImageDownloaded(const QString &version, return QFile::exists(dmgPath) && QFile::exists(sigPath); } -bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device, +bool DevDiskManager::downloadCompatibleImage(const iDescriptorDevice *device, std::function callback) { QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); @@ -403,7 +403,7 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device, } // FIXME:DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED -bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device) +bool DevDiskManager::mountCompatibleImage(const iDescriptorDevice *device) { QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); @@ -493,7 +493,7 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device) } bool DevDiskManager::mountImage(const QString &version, - iDescriptorDevice *device) + const iDescriptorDevice *device) { const QString downloadPath = SettingsManager::sharedInstance()->devdiskimgpath(); diff --git a/src/devdiskmanager.h b/src/devdiskmanager.h index 6d49071..f9d27ae 100644 --- a/src/devdiskmanager.h +++ b/src/devdiskmanager.h @@ -49,7 +49,7 @@ public: // Mount operations - bool mountImage(const QString &version, iDescriptorDevice *device); + bool mountImage(const QString &version, const iDescriptorDevice *device); bool unmountImage(); std::pair getPathsForVersion(const QString &version); @@ -58,8 +58,8 @@ public: const char *mounted_sig, uint64_t mounted_sig_len); QByteArray getImageListData() const { return m_imageListJsonData; } - bool mountCompatibleImage(iDescriptorDevice *device); - bool downloadCompatibleImage(iDescriptorDevice *device, + bool mountCompatibleImage(const iDescriptorDevice *device); + bool downloadCompatibleImage(const iDescriptorDevice *device, std::function callback); signals: diff --git a/src/deviceimagewidget.cpp b/src/deviceimagewidget.cpp index fab01cb..fa45a27 100644 --- a/src/deviceimagewidget.cpp +++ b/src/deviceimagewidget.cpp @@ -26,7 +26,8 @@ #include #include -DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent) +DeviceImageWidget::DeviceImageWidget(const iDescriptorDevice *device, + QWidget *parent) : QWidget(parent), m_device(device) { QVBoxLayout *layout = new QVBoxLayout(this); diff --git a/src/deviceimagewidget.h b/src/deviceimagewidget.h index c4e4b8a..eb898b6 100644 --- a/src/deviceimagewidget.h +++ b/src/deviceimagewidget.h @@ -30,7 +30,7 @@ class DeviceImageWidget : public QWidget Q_OBJECT public: - explicit DeviceImageWidget(iDescriptorDevice *device, + explicit DeviceImageWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); ~DeviceImageWidget(); @@ -47,7 +47,7 @@ private: QPixmap createCompositeImage() const; QRect findScreenArea(const QPixmap &mockup) const; - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; ResponsiveQLabel *m_imageLabel; QTimer *m_timeUpdateTimer; diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index bb58bf8..c72dac6 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -25,6 +25,7 @@ #include "iDescriptor.h" #include "infolabel.h" #include "privateinfolabel.h" +#include "toolboxwidget.h" #include #include #include @@ -44,7 +45,8 @@ #include #include -DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) +DeviceInfoWidget::DeviceInfoWidget(const iDescriptorDevice *device, + QWidget *parent) : QWidget(parent), m_device(device) { // Main layout with horizontal orientation @@ -77,22 +79,22 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown", 1.0, this); shutdownBtn->setIconSize(QSize(20, 20)); - // connect(shutdownBtn, &ZIconWidget::clicked, this, - // [device]() { ToolboxWidget::shutdownDevice(device); }); + connect(shutdownBtn, &ZIconWidget::clicked, this, + [device]() { ToolboxWidget::shutdownDevice(device); }); ZIconWidget *restartBtn = new ZIconWidget(QIcon(":/resources/icons/IcTwotoneRestartAlt.png"), "Restart", 1.0, this); restartBtn->setIconSize(QSize(20, 20)); - // connect(restartBtn, &ZIconWidget::clicked, this, - // [device]() { ToolboxWidget::restartDevice(device); }); + connect(restartBtn, &ZIconWidget::clicked, this, + [device]() { ToolboxWidget::restartDevice(device); }); ZIconWidget *recoveryBtn = new ZIconWidget(QIcon(":/resources/icons/HugeiconsWrench01.png"), "Recovery", 1.0, this); recoveryBtn->setIconSize(QSize(20, 20)); - // connect(recoveryBtn, &ZIconWidget::clicked, this, - // [device]() { ToolboxWidget::_enterRecoveryMode(device); }); + connect(recoveryBtn, &ZIconWidget::clicked, this, + [device]() { ToolboxWidget::enterRecoveryMode(device); }); actionsLayout->addWidget(shutdownBtn); actionsLayout->addWidget(restartBtn); diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 4d6e0be..0f7d7e4 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -31,7 +31,7 @@ class DeviceInfoWidget : public QWidget { Q_OBJECT public: - explicit DeviceInfoWidget(iDescriptorDevice *device, + explicit DeviceInfoWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); ~DeviceInfoWidget(); // added destructor @@ -39,7 +39,7 @@ private slots: void onBatteryMoreClicked(); private: - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; QTimer *m_updateTimer; void updateBatteryInfo(); void updateChargingStatusIcon(); diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp index d80ffac..662fd0b 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 @@ -33,7 +33,7 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent) setupUI(); connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this, - [this](iDescriptorDevice *device) { + [this](const iDescriptorDevice *device) { addDevice(device); // Apply settings-based behavior for switching to new device @@ -70,7 +70,7 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent) }); connect(AppContext::sharedInstance(), &AppContext::devicePaired, this, - [this](iDescriptorDevice *device) { + [this](const iDescriptorDevice *device) { addPairedDevice(device); // SettingsManager::sharedInstance()->doIfEnabled( // SettingsManager::Setting::SwitchToNewDevice, @@ -82,22 +82,19 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent) 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) { @@ -130,7 +127,7 @@ void DeviceManagerWidget::setupUI() &DeviceManagerWidget::onDeviceSelectionChanged); } -void DeviceManagerWidget::addDevice(iDescriptorDevice *device) +void DeviceManagerWidget::addDevice(const iDescriptorDevice *device) { if (m_deviceWidgets.contains(device->udid)) { qWarning() << "Device already exists:" @@ -150,52 +147,51 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device) 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 &uniq, bool locked) { @@ -222,27 +218,26 @@ void DeviceManagerWidget::addPendingDevice(const QString &uniq, bool locked) 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) +void DeviceManagerWidget::addPairedDevice(const iDescriptorDevice *device) { qDebug() << "Device paired:" << QString::fromStdString(device->udid); @@ -336,19 +331,18 @@ 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 deb8a83..d926bb9 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 @@ -51,14 +51,14 @@ private slots: private: void setupUI(); - void addDevice(iDescriptorDevice *device); + void addDevice(const 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 addPairedDevice(const iDescriptorDevice *device); void removePendingDevice(const QString &udid); QHBoxLayout *m_mainLayout; @@ -72,12 +72,11 @@ 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 1d4f1d6..1c180fa 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -31,7 +31,8 @@ #include #include -DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent) +DeviceMenuWidget::DeviceMenuWidget(const iDescriptorDevice *device, + QWidget *parent) : QWidget{parent}, m_device(device) { QVBoxLayout *mainLayout = new QVBoxLayout(this); diff --git a/src/devicemenuwidget.h b/src/devicemenuwidget.h index 985c2d1..385481e 100644 --- a/src/devicemenuwidget.h +++ b/src/devicemenuwidget.h @@ -31,15 +31,15 @@ class DeviceMenuWidget : public QWidget { Q_OBJECT public: - explicit DeviceMenuWidget(iDescriptorDevice *device, + explicit DeviceMenuWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); void switchToTab(const QString &tabName); void init(); ~DeviceMenuWidget(); private: - QStackedWidget *stackedWidget; // Pointer to the stacked widget - iDescriptorDevice *m_device; // Pointer to the iDescriptor device + QStackedWidget *stackedWidget; + const iDescriptorDevice *m_device; DeviceInfoWidget *m_deviceInfoWidget; InstalledAppsWidget *m_installedAppsWidget; GalleryWidget *m_galleryWidget; diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index bf4face..cf5333a 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -33,7 +33,8 @@ extern "C" { using namespace iDescriptor; -DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) +DiskUsageWidget::DiskUsageWidget(const iDescriptorDevice *device, + QWidget *parent) : QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0), m_systemUsage(0), m_appsUsage(0), m_mediaUsage(0), m_othersUsage(0), m_freeSpace(0) @@ -501,11 +502,11 @@ void DiskUsageWidget::fetchData() /* 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)? + skip on wireless connections on old devices than iPhone10,1 (iPhone 8) */ if (m_device->deviceInfo.is_iPhone && m_device->deviceInfo.isWireless && !iDescriptor::Utils::isProductTypeNewer( - m_device->deviceInfo.rawProductType, "iPhone8,4")) { + m_device->deviceInfo.rawProductType, "iPhone10,1")) { qDebug() << "Skipping gallery usage calculation on older " "wireless device."; result["galleryUsage"] = QVariant::fromValue(uint64_t(0)); diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h index 1052bb0..b6a5c5e 100644 --- a/src/diskusagewidget.h +++ b/src/diskusagewidget.h @@ -35,7 +35,7 @@ class DiskUsageWidget : public QWidget { Q_OBJECT public: - explicit DiskUsageWidget(iDescriptorDevice *device, + explicit DiskUsageWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); private: @@ -45,7 +45,7 @@ private: enum State { Loading, Ready, Error }; - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; State m_state; QString m_errorMessage; diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index 511d769..1d3843a 100644 --- a/src/exportmanager.cpp +++ b/src/exportmanager.cpp @@ -52,7 +52,7 @@ ExportManager::~ExportManager() m_activeJobs.clear(); } -QUuid ExportManager::startExport(iDescriptorDevice *device, +QUuid ExportManager::startExport(const iDescriptorDevice *device, const QList &items, const QString &destinationPath, std::optional altAfc) diff --git a/src/exportmanager.h b/src/exportmanager.h index 1da8c71..fb37373 100644 --- a/src/exportmanager.h +++ b/src/exportmanager.h @@ -48,7 +48,8 @@ public: ExportManager(const ExportManager &) = delete; ExportManager &operator=(const ExportManager &) = delete; - QUuid startExport(iDescriptorDevice *device, const QList &items, + QUuid startExport(const iDescriptorDevice *device, + const QList &items, const QString &destinationPath, std::optional altAfc = std::nullopt); diff --git a/src/fileexplorerwidget.cpp b/src/fileexplorerwidget.cpp index 0b7c024..db7df22 100644 --- a/src/fileexplorerwidget.cpp +++ b/src/fileexplorerwidget.cpp @@ -42,7 +42,7 @@ #include #include -FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device, +FileExplorerWidget::FileExplorerWidget(const iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) { diff --git a/src/fileexplorerwidget.h b/src/fileexplorerwidget.h index 7aa68d8..2a0a898 100644 --- a/src/fileexplorerwidget.h +++ b/src/fileexplorerwidget.h @@ -39,7 +39,7 @@ class FileExplorerWidget : public QWidget { Q_OBJECT public: - explicit FileExplorerWidget(iDescriptorDevice *device, + explicit FileExplorerWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); private slots: @@ -50,7 +50,7 @@ private: QStackedWidget *m_stackedWidget; AfcClientHandle *currentAfcClient; QTreeWidget *m_sidebarTree; - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; // Tree items QTreeWidgetItem *m_defaultAfcItem; diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index b9d2359..2b23791 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -50,7 +50,7 @@ https://github.com/ScottKjr3347/iOS_Local_PL_Photos.sqlite_Queries */ -GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) +GalleryWidget::GalleryWidget(const iDescriptorDevice *device, QWidget *parent) : QWidget{parent}, m_device(device), m_model(nullptr), m_albumSelectionWidget(nullptr), m_albumListView(nullptr), m_photoGalleryWidget(nullptr), m_listView(nullptr), m_backButton(nullptr) @@ -479,6 +479,18 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) m_model = new PhotoModel(m_device, getCurrentFilterType(), this); m_listView->setModel(m_model); + connect(m_model, &PhotoModel::albumPathSet, this, [this]() { + // Switch to photo gallery view once album is loaded + m_loadingWidget->switchToWidget(m_photoGalleryWidget); + // Enable controls and show back button + setControlsEnabled(true); + m_backButton->show(); + }); + + connect(m_model, &PhotoModel::timedOut, this, [this]() { + m_loadingWidget->showError("Timed out loading album"); + }); + // Update export button states based on selection connect(m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this]() { @@ -488,25 +500,14 @@ 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_loadingWidget->switchToWidget(m_photoGalleryWidget); - // Enable controls and show back button - setControlsEnabled(true); - m_backButton->show(); + m_loadingWidget->showLoading(); } void GalleryWidget::onBackToAlbums() { - if (m_model) { - // disconnect(m_model, &PhotoModel::thumbnailNeedsToBeLoaded, m_model, - // &PhotoModel::requestThumbnail); - } - // Switch back to album selection view m_loadingWidget->switchToWidget(m_albumSelectionWidget); diff --git a/src/gallerywidget.h b/src/gallerywidget.h index 2e152bb..096cc25 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -44,7 +44,7 @@ class GalleryWidget : public QWidget Q_OBJECT public: - explicit GalleryWidget(iDescriptorDevice *device, + explicit GalleryWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); void load(); ~GalleryWidget(); @@ -70,7 +70,7 @@ private: void onPhotoContextMenu(const QPoint &pos); PhotoModel::FilterType getCurrentFilterType() const; - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; bool m_loaded = false; QString m_currentAlbumPath; diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index eb30f9a..7092de8 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -57,6 +57,8 @@ #define COLOR_HYPERLINK QColor("#FF7FFFD4") #endif +#define THUMBNAIL_SIZE QSize(128, 128) + inline QString mergeStyles(QWidget *widget, const QString &newStyles) { if (!widget) { diff --git a/src/iDescriptor-utils.h b/src/iDescriptor-utils.h index 93b8cee..7a34a1a 100644 --- a/src/iDescriptor-utils.h +++ b/src/iDescriptor-utils.h @@ -59,8 +59,19 @@ public: bool isMac() const { return m_isMac; }; bool isUdid() const { return !m_isMac; } + void set(const QString &uniq, bool isMac = false) + { + m_uniq = uniq; + m_isMac = isMac; + }; + void set(const std::string &uniq, bool isMac = false) + { + m_uniq = QString::fromStdString(uniq); + m_isMac = isMac; + }; const QString &get() const { return m_uniq; } operator QString() const { return m_uniq; } + operator std::string() const { return m_uniq.toStdString(); } private: QString m_uniq; @@ -139,8 +150,21 @@ public: static bool isVideoFile(const QString &fileName) { - /* known iPhone video file extensions */ + /* known iPhone video file extensions (AVI and MKV is not common but it + * may be some from some app)*/ return fileName.endsWith(".MOV", Qt::CaseInsensitive) || + fileName.endsWith(".MP4", Qt::CaseInsensitive) || + fileName.endsWith(".M4V", Qt::CaseInsensitive) || + fileName.endsWith(".AVI", Qt::CaseInsensitive) || + fileName.endsWith(".MKV", Qt::CaseInsensitive); + } + + static bool isGalleryFile(const QString &fileName) + { + return fileName.endsWith(".JPG", Qt::CaseInsensitive) || + fileName.endsWith(".PNG", Qt::CaseInsensitive) || + fileName.endsWith(".HEIC", Qt::CaseInsensitive) || + fileName.endsWith(".MOV", Qt::CaseInsensitive) || fileName.endsWith(".MP4", Qt::CaseInsensitive) || fileName.endsWith(".M4V", Qt::CaseInsensitive); } diff --git a/src/iDescriptor.h b/src/iDescriptor.h index 3a4c057..d4a935e 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -68,11 +69,16 @@ ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF)) #include "devicemonitor.h" #include "iDescriptor-utils.h" +#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT +#include +#endif #define DeviceLockedMountErrorCode -21 #define NotFoundErrorCode -14 #define ServiceNotFoundErrorCode -15 #define PairingDialogResponsePending -28 +#define InvalidServiceErrorCode -59 +#define TimeoutErrorCode -71 #define DISK_IMAGE_TYPE_DEVELOPER "Developer" @@ -214,6 +220,8 @@ struct DeviceInfo { bool isWireless = false; // empty on USB devices std::string ipAddress; + /* same as udid on iDescriptorDevice */ + std::string UniqueDeviceID; }; struct iDescriptorDevice { @@ -242,34 +250,35 @@ struct iDescriptorInitDeviceResult { std::shared_ptr diagRelay; 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; +}; +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; +}; + +std::string parse_recovery_mode(irecv_mode productType); + +void init_idescriptor_recovery_device(uint64_t ecid, + iDescriptorInitDeviceResultRecovery &res); +#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; -// }; - -// #endif - void warn(const QString &message, const QString &title = "Warning", QWidget *parent = nullptr); @@ -402,15 +411,8 @@ public: } }; -// 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 - struct MediaEntry { std::string name; bool isDir; @@ -440,14 +442,6 @@ void init_idescriptor_device(const iDescriptor::Uniq &uniq, iDescriptorInitDeviceResult &result, 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); - -// bool shutdown(idevice_t device); - IdeviceFfiError *mount_dev_image(const iDescriptorDevice *device, const char *image_file, const char *signature_file); @@ -467,8 +461,6 @@ MountedImageInfo _get_mounted_image(const iDescriptorDevice *device); void mounted_image_info_free(MountedImageInfo &info); -// bool restart(std::string udid); - enum class ImageCompatibility { Compatible, // Exact match or known compatible version MaybeCompatible, // Major version matches but minor doesn't diff --git a/src/imageloader.cpp b/src/imageloader.cpp index c8fd5da..edf75cf 100644 --- a/src/imageloader.cpp +++ b/src/imageloader.cpp @@ -15,6 +15,7 @@ extern "C" { ImageLoader::ImageLoader(QObject *parent) : QObject(parent) { + // TODO: maybe finetune to hardware ? m_pool.setMaxThreadCount(10); // 350 MB cache for thumbnails m_cache.setMaxCost(350 * 1024 * 1024); @@ -22,51 +23,144 @@ ImageLoader::ImageLoader(QObject *parent) : QObject(parent) bool ImageLoader::isLoading(const QString &path) { - return m_pending.contains(path); + QMutexLocker locker(&m_mutex); + return m_pendingTasks.contains(path); } void ImageLoader::requestThumbnail(const iDescriptorDevice *device, - const QString &path, int priority, - unsigned int row) + const QString &path, unsigned int row) { if (auto *cached = m_cache.object(path)) { emit thumbnailReady(path, *cached, row); return; } - if (m_pending.contains(path)) - return; + { + QMutexLocker locker(&m_mutex); + if (m_pendingTasks.contains(path)) + return; + } - m_pending.insert(path); + auto *task = new ImageTask(device, path, row); - // FIXME: qsize - auto *task = new ImageTask(device, path, QSize(128, 128), row); + { + QMutexLocker locker(&m_mutex); + m_pendingTasks[path] = task; + } connect(task, &ImageTask::finished, this, &ImageLoader::onTaskFinished, Qt::QueuedConnection); + // Use row as priority + m_pool.start(task, row); +} + +/* + this method should not load from cache + because cached images are already scaled down + we need the original image +*/ +void ImageLoader::requestImageWithCallback( + const iDescriptorDevice *device, const QString &path, int priority, + std::function callback) +{ + + /* + FIXME: priority is passed as row + nothing dangerous but a bit hacky, should be handled better + */ //scale=false + auto *task = new ImageTask(device, path, priority, false); + + /* + TODO: should we do this ? + this function is meant for the media preview dialog, + which only loads a image at a time + and not really related to the thumbnails in the photomodel + */ + // m_pendingTasks[path] = task; + + connect( + task, &ImageTask::finished, this, + [this, path, callback](const QString &, const QPixmap &pixmap, + unsigned int row) { callback(pixmap); }, + Qt::QueuedConnection); + m_pool.start(task, priority); } void ImageLoader::cancelThumbnail(const QString &path) { - m_pending.remove(path); + qDebug() << "Attempting to cancel thumbnail loading for" << path; + + QMutexLocker locker(&m_mutex); + + if (!m_pendingTasks.contains(path)) { + return; + } + + ImageTask *task = m_pendingTasks.value(path); + if (task && m_pool.tryTake(task)) { + qDebug() << "Cancelled thumbnail loading for" << path; + m_pendingTasks.remove(path); + delete task; + } else { + m_pendingTasks.remove(path); + } } void ImageLoader::clear() { - m_pending.clear(); + qDebug() << "Clearing ImageLoader cache and pending tasks"; + + m_pool.clear(); + + { + QMutexLocker locker(&m_mutex); + + for (auto it = m_pendingTasks.begin(); it != m_pendingTasks.end();) { + ImageTask *task = it.value(); + if (task && m_pool.tryTake(task)) { + qDebug() << "Cancelled pending task"; + delete task; + } + it = m_pendingTasks.erase(it); + } + } + + /* + TODO: This could make the UI unresponsive but + maybe a good approch to handle + async cancellation properly(wireless) + Wait for any running tasks to complete + */ + // m_pool.waitForDone(); + + { + QMutexLocker locker(&m_mutex); + m_pendingTasks.clear(); + } + m_cache.clear(); } void ImageLoader::onTaskFinished(const QString &path, const QPixmap &pixmap, unsigned int row) { - // Stale? - if (!m_pending.contains(path)) - return; + ImageTask *task = nullptr; - m_pending.remove(path); + { + QMutexLocker locker(&m_mutex); + + if (!m_pendingTasks.contains(path)) { + return; + } + + task = m_pendingTasks.take(path); + } + + if (task) { + delete task; + } // Cache m_cache.insert(path, new QPixmap(pixmap)); @@ -74,18 +168,56 @@ void ImageLoader::onTaskFinished(const QString &path, const 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) +// almost a copy of loadThumbnailFromDevice but without any scaling logic +QPixmap ImageLoader::loadImage(const iDescriptorDevice *device, + const QString &filePath) { - // 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 + return {}; + } + + if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { + QPixmap img = load_heic(imageData); + return img.isNull() ? QPixmap() : img; + } + + QBuffer buffer(&imageData); + buffer.open(QIODevice::ReadOnly); + + QImageReader reader(&buffer); + if (reader.canRead()) { + 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 pixmap; + if (pixmap.loadFromData(imageData)) { + return pixmap; + } + + qDebug() << "Could not decode image data for:" << filePath; + return {}; +} + +QPixmap ImageLoader::loadThumbnailFromDevice(const iDescriptorDevice *device, + const QString &filePath, + const QSize &size) +{ + QByteArray imageData = ServiceManager::safeReadAfcFileToByteArray( + device, filePath.toUtf8().constData()); + + if (imageData.isEmpty()) { + qDebug() << "Could not read from device:" << filePath; + return {}; } if (filePath.endsWith(".HEIC", Qt::CaseInsensitive)) { @@ -95,14 +227,11 @@ QPixmap ImageLoader::loadThumbnailFromDevice(const iDescriptorDevice *device, 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()) { @@ -146,10 +275,8 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, // Get file size AfcFileInfo info = {}; - IdeviceFfiError - *err_info = // Use distinct variable name for the error from GetFileInfo - ServiceManager::safeAfcGetFileInfo( - device, filePath.toUtf8().constData(), &info); + IdeviceFfiError *err_info = ServiceManager::safeAfcGetFileInfo( + device, filePath.toUtf8().constData(), &info); uint64_t fileSize = 0; if (err_info) { @@ -157,12 +284,11 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, << "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 + afc_file_info_free(&info); if (fileSize == 0) { ServiceManager::safeAfcFileClose(device, fileHandle); @@ -189,7 +315,7 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, StreamContext *streamCtx = new StreamContext{device, fileHandle, fileSize, 0}; - // Custom read function that reads from device on-demand + // Custom read function auto readPacket = [](void *opaque, uint8_t *buf, int bufSize) -> int { StreamContext *ctx = static_cast(opaque); @@ -201,10 +327,8 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, 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 + uint8_t *read_data_ptr = nullptr; - // Call safeAfcFileRead to get the data into a newly allocated buffer IdeviceFfiError *err = ServiceManager::safeAfcFileRead( ctx->device, ctx->fileHandle, &read_data_ptr, toRead, &bytesRead); @@ -238,9 +362,7 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, // `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 + afc_file_read_data_free(read_data_ptr, bytesRead); } else { qWarning() << "AFC readPacket: read_data_ptr was null but " "bytesRead > 0. This is unexpected."; @@ -285,7 +407,7 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, if (err) { qDebug() << "AFC seek error:" << err->message << "code:" << err->code; - // idevice_error_free(err); + idevice_error_free(err); return -1; } @@ -444,6 +566,12 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, QImage imgCopy = img.copy(); // Scale to requested size + /* + TODO: scaling might become optional + if we ever needed the raw frame, + might need to abstract the main logic to get the frame + and handle scaling separately + */ thumbnail = QPixmap::fromImage( imgCopy.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -472,30 +600,3 @@ ImageLoader::generateVideoThumbnailFFmpeg(const iDescriptorDevice *device, 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 index 55ada91..84cd25d 100644 --- a/src/imageloader.h +++ b/src/imageloader.h @@ -3,19 +3,20 @@ #include "iDescriptor.h" #include +#include #include +#include #include #include #include #include +class ImageTask; + 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() { @@ -23,7 +24,11 @@ public: return instance; } void requestThumbnail(const iDescriptorDevice *device, const QString &path, - int priority, unsigned int row = 0); + unsigned int row = 0); + void + requestImageWithCallback(const iDescriptorDevice *device, + const QString &path, int priority, + std::function callback); void cancelThumbnail(const QString &path); bool isLoading(const QString &path); void clear(); @@ -46,7 +51,8 @@ private slots: private: QThreadPool m_pool; - QSet m_pending; + QHash m_pendingTasks; + QMutex m_mutex; }; #endif // IMAGELOADER_H \ No newline at end of file diff --git a/src/imagetask.h b/src/imagetask.h index 8f723ea..a5cf50a 100644 --- a/src/imagetask.h +++ b/src/imagetask.h @@ -1,6 +1,7 @@ #ifndef IMAGETASK_H #define IMAGETASK_H +#include "iDescriptor-ui.h" #include "iDescriptor.h" #include #include @@ -15,11 +16,10 @@ 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) + unsigned int row, bool scale = true) + : m_device(device), m_path(path), m_isThumbnail(scale), m_row(row) { - setAutoDelete(true); + setAutoDelete(false); } signals: @@ -32,20 +32,26 @@ protected: if (isVideo) { QPixmap thumbnail = ImageLoader::generateVideoThumbnailFFmpeg( - m_device, m_path, m_thumbnailSize); + m_device, m_path, THUMBNAIL_SIZE); 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); + if (m_isThumbnail) { + QPixmap image = ImageLoader::loadThumbnailFromDevice( + m_device, m_path, THUMBNAIL_SIZE); + emit finished(m_path, image, m_row); + } else { + qDebug() << "Loading full image for:" << m_path; + QPixmap image = ImageLoader::loadImage(m_device, m_path); + emit finished(m_path, image, m_row); + } } } private: const iDescriptorDevice *m_device; QString m_path; - QSize m_thumbnailSize; + bool m_isThumbnail; unsigned int m_row; }; diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 24ccbbb..8898e9c 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -46,8 +46,7 @@ AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, setMinimumWidth(100); setCursor(Qt::PointingHandCursor); - setupUI(); - m_iconLabel->setPixmap(icon); + setupUI(icon); } void AppTabWidget::setSelected(bool selected) @@ -56,21 +55,25 @@ void AppTabWidget::setSelected(bool selected) updateStyles(); } -void AppTabWidget::setupUI() +void AppTabWidget::setupUI(const QPixmap &icon) { QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setContentsMargins(10, 8, 10, 8); mainLayout->setSpacing(10); - // m_defaultBg = this->palette().color(QPalette::Window); + // Icon label m_iconLabel = new QLabel(); m_iconLabel->setFixedSize(32, 32); m_iconLabel->setScaledContents(true); - QPixmap placeholderIcon = QApplication::style() - ->standardIcon(QStyle::SP_ComputerIcon) - .pixmap(32, 32); - m_iconLabel->setPixmap(placeholderIcon); + if (!icon.isNull()) { + m_iconLabel->setPixmap(icon); + } else { + QPixmap placeholderIcon = QApplication::style() + ->standardIcon(QStyle::SP_ComputerIcon) + .pixmap(32, 32); + m_iconLabel->setPixmap(placeholderIcon); + } mainLayout->addWidget(m_iconLabel); // Text container @@ -133,7 +136,7 @@ void AppTabWidget::updateStyles() } } -InstalledAppsWidget::InstalledAppsWidget(iDescriptorDevice *device, +InstalledAppsWidget::InstalledAppsWidget(const iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) { @@ -149,7 +152,14 @@ InstalledAppsWidget::InstalledAppsWidget(iDescriptorDevice *device, fetchInstalledApps(); } -InstalledAppsWidget::~InstalledAppsWidget() { cleanupHouseArrestClients(); } +InstalledAppsWidget::~InstalledAppsWidget() +{ + cleanupHouseArrestClients(); + if (m_springboardClient) { + springboard_services_free(m_springboardClient); + m_springboardClient = nullptr; + } +} void InstalledAppsWidget::setupUI() { @@ -269,6 +279,17 @@ void InstalledAppsWidget::fetchInstalledApps() QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; QVariantList apps; + // fetch icon from springboard service + IdeviceFfiError *err = nullptr; + if (!m_springboardClient) { + err = springboard_services_connect(m_device->provider, + &m_springboardClient); + if (err) { + qDebug() << "Error connecting to SpringBoard services:" + << QString::fromUtf8(err->message); + idevice_error_free(err); + } + } try { InstallationProxyClientHandle *installationProxyClientHandle = @@ -370,7 +391,36 @@ void InstalledAppsWidget::fetchInstalledApps() appData["type"] = appType; - if (!appData["bundleId"].toString().isEmpty()) { + QString bundleId = appData["bundleId"].toString(); + + if (m_springboardClient && !bundleId.isEmpty()) { + void *out_result; + size_t out_result_len; + + // FIXME: free out_result + // there is no springboard_services_free_data or + // similar, create an issue + err = springboard_services_get_icon( + m_springboardClient, + bundleId.toUtf8().constData(), &out_result, + &out_result_len); + if (err != nullptr) { + qWarning() << "Error getting icon for" + << appData.value("bundleId") << ":" + << QString::fromUtf8(err->message); + idevice_error_free(err); + } else { + QByteArray byteArray( + reinterpret_cast(out_result), + static_cast(out_result_len)); + QImage image; + image.loadFromData(byteArray); + QPixmap pixmap = QPixmap::fromImage(image); + appData["icon"] = pixmap; + } + } + + if (!bundleId.isEmpty()) { apps.append(appData); } } @@ -424,22 +474,6 @@ 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(); @@ -447,6 +481,8 @@ void InstalledAppsWidget::onAppsDataReady() QString bundleId = appData.value("bundleId").toString(); QString version = appData.value("version").toString(); QString appType = appData.value("type").toString(); + QPixmap icon = appData.value("icon").value(); + bool fileSharingEnabled = appData.value("fileSharingEnabled", false).toBool(); @@ -465,26 +501,7 @@ void InstalledAppsWidget::onAppsDataReady() tabName += " (System)"; } - if (springboardClient) { - void *out_result; - size_t out_result_len; - - 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); - } - } + createAppTab(tabName, bundleId, version, icon); // Select first tab if available if (!m_appTabs.isEmpty()) { @@ -716,7 +733,8 @@ void InstalledAppsWidget::cleanupHouseArrestClients() } if (m_houseArrestClient) { - house_arrest_client_free(m_houseArrestClient); + // FIXME: crash + // house_arrest_client_free(m_houseArrestClient); m_houseArrestClient = nullptr; } } @@ -743,6 +761,7 @@ void InstalledAppsWidget::createLeftPanel() searchLayout->addWidget(m_searchEdit); // File sharing filter checkbox + // FIXME: crash when toggled m_fileSharingCheckBox = new QCheckBox("Show Only File Sharing Enabled"); m_fileSharingCheckBox->setChecked(true); m_fileSharingCheckBox->setStyleSheet("QCheckBox { font-size: 10px; }"); diff --git a/src/installedappswidget.h b/src/installedappswidget.h index 8aae751..2716601 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -43,7 +43,6 @@ #include #include -// Custom App Tab Widget class AppTabWidget : public QGroupBox { Q_OBJECT @@ -75,7 +74,7 @@ protected: }; private: - void setupUI(); + void setupUI(const QPixmap &icon); QString m_appName; QString m_bundleId; @@ -93,7 +92,7 @@ class InstalledAppsWidget : public QWidget Q_OBJECT public: - explicit InstalledAppsWidget(iDescriptorDevice *device, + explicit InstalledAppsWidget(const iDescriptorDevice *device, QWidget *parent = nullptr); ~InstalledAppsWidget(); @@ -120,7 +119,7 @@ private: void loadAppContainer(const QString &bundleId); void cleanupHouseArrestClients(); - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; QHBoxLayout *m_mainLayout; QStackedWidget *m_stackedWidget; QWidget *m_loadingWidget; @@ -144,6 +143,7 @@ private: // App data storage QList m_appTabs; AppTabWidget *m_selectedTab = nullptr; + SpringBoardServicesClientHandle *m_springboardClient = nullptr; }; #endif // INSTALLEDAPPSWIDGET_H \ No newline at end of file diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2130697..6e57631 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -36,11 +36,9 @@ #include #include "appcontext.h" -#include "settingsmanager.h" -// #include "devicemonitor.h" -// #include "Toast.h" #include "networkdevicemanager.h" #include "networkdeviceswidget.h" +#include "settingsmanager.h" #include "statusballoon.h" #include #include @@ -51,7 +49,29 @@ #include "platform/windows/win_common.h" #endif -using namespace IdeviceFFI; +#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() { @@ -134,6 +154,20 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) StatusBalloon *statusBalloon = StatusBalloon::sharedInstance(); statusLayout->addWidget(statusBalloon->getButton()); + + ZIconWidget *welcomeMenu = new ZIconWidget( + QIcon(":/resources/icons/LetsIconsHorizontalDownLeftMainLight.png"), + "Switch to Welcome Menu"); + connect(welcomeMenu, &ZIconWidget::clicked, this, [this, welcomeMenu]() { + if (m_mainStackedWidget->currentIndex() != 0) { + welcomeMenu->setToolTip("Switch to Connected Devices"); + return m_mainStackedWidget->setCurrentIndex(0); + } + welcomeMenu->setToolTip("Switch to Welcome Menu"); + m_mainStackedWidget->setCurrentIndex(1); + }); + + statusLayout->addWidget(welcomeMenu); statusLayout->addStretch(1); statusLayout->setContentsMargins(0, 0, 0, 0); @@ -174,19 +208,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(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) { @@ -195,7 +227,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) // << res; // } // qDebug() << "Subscribed to device events successfully."; - // createMenus(); + createMenus(); // UpdateProcedure updateProcedure; // bool packageManagerManaged = false; @@ -285,7 +317,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) // m_updater->checkForUpdates(); // }); - // Usage in main thread: m_deviceMonitor = new DeviceMonitorThread(this); connect( m_deviceMonitor, &DeviceMonitorThread::deviceEvent, this, diff --git a/src/mediapreviewdialog.cpp b/src/mediapreviewdialog.cpp index edb0ce9..5c075c2 100644 --- a/src/mediapreviewdialog.cpp +++ b/src/mediapreviewdialog.cpp @@ -47,20 +47,12 @@ #include #include -MediaPreviewDialog::MediaPreviewDialog(iDescriptorDevice *device, +MediaPreviewDialog::MediaPreviewDialog(const iDescriptorDevice *device, AfcClientHandle *afcClient, const QString &filePath, QWidget *parent) : QDialog(parent), m_device(device), m_filePath(filePath), - m_isVideo(isVideoFile(filePath)), m_mainLayout(nullptr), - m_controlsLayout(nullptr), m_imageView(nullptr), m_imageScene(nullptr), - m_pixmapItem(nullptr), m_videoWidget(nullptr), m_mediaPlayer(nullptr), - m_videoControlsLayout(nullptr), m_playPauseBtn(nullptr), - m_stopBtn(nullptr), m_repeatBtn(nullptr), m_timelineSlider(nullptr), - m_timeLabel(nullptr), m_volumeSlider(nullptr), m_volumeLabel(nullptr), - m_progressTimer(nullptr), m_loadingLabel(nullptr), m_statusLabel(nullptr), - m_zoomInBtn(nullptr), m_zoomOutBtn(nullptr), m_zoomResetBtn(nullptr), - m_fitToWindowBtn(nullptr), m_zoomFactor(1.0), m_isRepeatEnabled(true), - m_isDraggingTimeline(false), m_videoDuration(0), m_afcClient(afcClient) + m_isVideo(iDescriptor::Utils::isVideoFile(filePath)), + m_afcClient(afcClient) { setWindowTitle(QFileInfo(filePath).fileName() + " - iDescriptor"); @@ -97,28 +89,14 @@ void MediaPreviewDialog::setupUI() m_mainLayout->setContentsMargins(0, 0, 0, 0); m_mainLayout->setSpacing(0); - // Loading label - m_loadingLabel = new QLabel("Loading...", this); - m_loadingLabel->setAlignment(Qt::AlignCenter); - m_loadingLabel->setStyleSheet( - "QLabel { font-size: 16px; color: #666; padding: 20px; }"); - m_mainLayout->addWidget(m_loadingLabel); + m_loadingWidget = new ZLoadingWidget(); + m_mainLayout->addWidget(m_loadingWidget); if (m_isVideo) { setupVideoView(); } else { setupImageView(); } - - // Status bar - // more margin because of border radius on macOS - m_statusLabel = new QLabel(this); -#ifdef Q_OS_MAC - m_statusLabel->setStyleSheet("QLabel { margin-left: 15px; }"); -#else - m_statusLabel->setStyleSheet("QLabel { margin-left: 5px; }"); -#endif - m_mainLayout->addWidget(m_statusLabel); } void MediaPreviewDialog::setupImageView() @@ -129,7 +107,7 @@ void MediaPreviewDialog::setupImageView() m_imageView->setDragMode(QGraphicsView::ScrollHandDrag); m_imageView->setRenderHint(QPainter::Antialiasing); m_imageView->setVisible(false); - m_mainLayout->addWidget(m_imageView); + m_loadingWidget->setupContentWidget(m_imageView); // Controls layout m_controlsLayout = new QHBoxLayout(); @@ -166,7 +144,7 @@ void MediaPreviewDialog::setupVideoView() m_videoWidget->setVisible(false); m_videoWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_mainLayout->addWidget(m_videoWidget, 1); // Give it stretch factor 1 + m_loadingWidget->setupContentWidget(m_videoWidget); // Media player m_mediaPlayer = new QMediaPlayer(this); @@ -189,13 +167,10 @@ void MediaPreviewDialog::setupVideoView() &MediaPreviewDialog::onMediaPlayerStateChanged); connect(m_mediaPlayer, &QMediaPlayer::errorOccurred, this, [this](QMediaPlayer::Error error, const QString &errorString) { - qDebug() << "MediaPlayer Error:" << error << errorString; - m_statusLabel->setText("Error: " + errorString); - m_loadingLabel->setText("Error: " + errorString); - m_loadingLabel->show(); - m_videoWidget->hide(); + m_loadingWidget->showError("Error playing video: " + + errorString); }); - // Setup progress timer for smooth updates + m_progressTimer = new QTimer(this); connect(m_progressTimer, &QTimer::timeout, this, &MediaPreviewDialog::updateVideoProgress); @@ -211,22 +186,17 @@ void MediaPreviewDialog::loadMedia() void MediaPreviewDialog::loadImage() { - auto future = QtConcurrent::run( - [this]() { return ImageLoader::loadImage(m_device, m_filePath); }); - - auto *watcher = new QFutureWatcher(this); - connect(watcher, &QFutureWatcher::finished, this, - [this, watcher]() { - QPixmap pixmap = watcher->result(); - if (!pixmap.isNull()) { - m_originalPixmap = pixmap; - onImageLoaded(); - } else { - onImageLoadFailed(); - } - watcher->deleteLater(); - }); - watcher->setFuture(future); + auto callback = [this](const QPixmap &pixmap) { + if (!pixmap.isNull()) { + onImageLoaded(pixmap); + } else { + onImageLoadFailed(); + } + }; + // 99999 is so that it gets the highest priority in the queue + unsigned int priority = 99999; + ImageLoader::sharedInstance().requestImageWithCallback(m_device, m_filePath, + priority, callback); } void MediaPreviewDialog::loadVideo() @@ -238,20 +208,22 @@ void MediaPreviewDialog::loadVideo() m_device, m_afcClient, m_filePath); qDebug() << "Streaming video from URL:" << streamUrl; if (streamUrl.isEmpty()) { - m_statusLabel->setText("Failed to start video stream"); + // TODO: connect to retry signal to attempt restarting the stream + m_loadingWidget->showError("Failed to start video stream"); return; } m_mediaPlayer->setSource(streamUrl); m_mediaPlayer->play(); - m_loadingLabel->hide(); - m_statusLabel->setText( - QString("Playing: %1").arg(QFileInfo(m_filePath).fileName())); + m_loadingWidget->stop(); + // m_statusLabel->setText( + // QString("Playing: %1").arg(QFileInfo(m_filePath).fileName())); } -void MediaPreviewDialog::onImageLoaded() +void MediaPreviewDialog::onImageLoaded(const QPixmap &pixmap) { - m_loadingLabel->hide(); + m_originalPixmap = pixmap; + m_imageView->setVisible(true); // Add pixmap to scene @@ -259,19 +231,22 @@ void MediaPreviewDialog::onImageLoaded() m_imageScene->setSceneRect(m_originalPixmap.rect()); // Fit to window initially - fitToWindow(); - + // TODO:why QTimer::singleShot is required here ? + // fitToWindow(); + QTimer::singleShot(0, this, &MediaPreviewDialog::fitToWindow); + m_loadingWidget->stop(); // Update status - m_statusLabel->setText(QString("Image: %1 (%2x%3)") - .arg(QFileInfo(m_filePath).fileName()) - .arg(m_originalPixmap.width()) - .arg(m_originalPixmap.height())); + // m_statusLabel->setText(QString("Image: %1 (%2x%3)") + // .arg(QFileInfo(m_filePath).fileName()) + // .arg(m_originalPixmap.width()) + // .arg(m_originalPixmap.height())); } void MediaPreviewDialog::onImageLoadFailed() { - m_loadingLabel->setText("Failed to load image"); - m_statusLabel->setText("Error loading image"); + // TODO: connect to retry signal to attempt reloading the image + m_loadingWidget->showError("Failed to load image"); + // m_statusLabel->setText("Error loading image"); } void MediaPreviewDialog::wheelEvent(QWheelEvent *event) @@ -425,35 +400,17 @@ void MediaPreviewDialog::zoom(double factor) updateZoomStatus(); } -// void MediaPreviewDialog::updateZoomStatus() -// { -// if (!m_isVideo && !m_originalPixmap.isNull()) { -// m_statusLabel->setText(QString("Image: %1 (%2x%3) - Zoom: %4%") -// .arg(QFileInfo(m_filePath).fileName()) -// .arg(m_originalPixmap.width()) -// .arg(m_originalPixmap.height()) -// .arg(qRound(m_zoomFactor * 100))); -// } -// } - void MediaPreviewDialog::updateZoomStatus() { if (!m_isVideo && !m_originalPixmap.isNull()) { - m_statusLabel->setText(QString("Image: %1 (%2x%3) - Zoom: %4%") - .arg(QFileInfo(m_filePath).fileName()) - .arg(m_originalPixmap.width()) - .arg(m_originalPixmap.height()) - .arg(qRound(m_zoomFactor * 100))); + // m_statusLabel->setText(QString("Image: %1 (%2x%3) - Zoom: %4%") + // .arg(QFileInfo(m_filePath).fileName()) + // .arg(m_originalPixmap.width()) + // .arg(m_originalPixmap.height()) + // .arg(qRound(m_zoomFactor * 100))); } } -bool MediaPreviewDialog::isVideoFile(const QString &filePath) const -{ - const QString lower = filePath.toLower(); - return lower.endsWith(".mov") || lower.endsWith(".mp4") || - lower.endsWith(".avi") || lower.endsWith(".m4v"); -} - void MediaPreviewDialog::setupVideoControls() { // Create video controls layout @@ -525,7 +482,7 @@ void MediaPreviewDialog::setupVideoControls() m_videoControlsLayout->addWidget(m_playPauseBtn); m_videoControlsLayout->addWidget(m_stopBtn); m_videoControlsLayout->addWidget(m_repeatBtn); - m_videoControlsLayout->addWidget(m_timelineSlider, 1); // Stretch factor 1 + m_videoControlsLayout->addWidget(m_timelineSlider, 1); m_videoControlsLayout->addWidget(m_timeLabel); m_videoControlsLayout->addWidget(m_volumeLabel); m_videoControlsLayout->addWidget(m_volumeSlider); @@ -563,8 +520,6 @@ void MediaPreviewDialog::onRepeatToggled(bool enabled) m_repeatBtn->setStyleSheet( enabled ? "QPushButton { background-color: #4CAF50; color: white; }" : ""); - - qDebug() << "Repeat mode:" << (enabled ? "ON" : "OFF"); } void MediaPreviewDialog::onTimelinePressed() @@ -665,9 +620,9 @@ void MediaPreviewDialog::onMediaPlayerDurationChanged(qint64 duration) if (duration > 0) { QString durationStr; formatTime(duration, durationStr); - m_statusLabel->setText(QString("Video: %1 - Duration: %2") - .arg(QFileInfo(m_filePath).fileName()) - .arg(durationStr)); + // m_statusLabel->setText(QString("Video: %1 - Duration: %2") + // .arg(QFileInfo(m_filePath).fileName()) + // .arg(durationStr)); } } @@ -742,10 +697,11 @@ void MediaPreviewDialog::onVolumeChanged(int value) } } +#ifdef __APPLE__ bool MediaPreviewDialog::event(QEvent *event) { - // FIXME: lets implement this on all dialogs - // catch platform Close (Cmd+W on macOS) + // TODO: implement this on all dialogs + // catch platform close (Cmd+W on macOS) if (event->type() == QEvent::ShortcutOverride) { if (auto *ke = dynamic_cast(event)) { const Qt::KeyboardModifiers mods = ke->modifiers(); @@ -755,12 +711,8 @@ bool MediaPreviewDialog::event(QEvent *event) close(); return true; } - if (ke->key() == Qt::Key_Escape) { - ke->accept(); - close(); - return true; - } } } return QDialog::event(event); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/mediapreviewdialog.h b/src/mediapreviewdialog.h index b80771b..792b88c 100644 --- a/src/mediapreviewdialog.h +++ b/src/mediapreviewdialog.h @@ -22,6 +22,7 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "zloadingwidget.h" #include #include #include @@ -37,21 +38,12 @@ #include #include -/** - * @brief A dialog for previewing images and videos from iOS devices - * - * Features: - * - Image viewing with zoom and pan using QGraphicsView - * - Video streaming with timeline scrubbing support - * - Asynchronous loading from device - * - Proper memory management - */ class MediaPreviewDialog : public QDialog { Q_OBJECT public: - explicit MediaPreviewDialog(iDescriptorDevice *device, + explicit MediaPreviewDialog(const iDescriptorDevice *device, AfcClientHandle *afcClient, const QString &filePath, QWidget *parent = nullptr); @@ -61,10 +53,11 @@ protected: void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void resizeEvent(QResizeEvent *event) override; - bool event(QEvent *event) override; // handle ShortcutOverride - +#ifdef __APPLE__ + bool event(QEvent *event) override; +#endif private slots: - void onImageLoaded(); + void onImageLoaded(const QPixmap &pixmap); void onImageLoadFailed(); void zoomIn(); void zoomOut(); @@ -96,10 +89,9 @@ private: void updateZoomStatus(); void updateVideoTimeDisplay(); void formatTime(qint64 milliseconds, QString &timeString); - bool isVideoFile(const QString &filePath) const; // Core data - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; QString m_filePath; bool m_isVideo; @@ -127,10 +119,6 @@ private: QLabel *m_volumeLabel; QTimer *m_progressTimer; - // Common components - QLabel *m_loadingLabel; - QLabel *m_statusLabel; - // Control buttons QPushButton *m_zoomInBtn; QPushButton *m_zoomOutBtn; @@ -138,15 +126,16 @@ private: QPushButton *m_fitToWindowBtn; // State - double m_zoomFactor; + double m_zoomFactor = 1.0; QPixmap m_originalPixmap; // Video state - bool m_isRepeatEnabled; - bool m_isDraggingTimeline; - qint64 m_videoDuration; + bool m_isRepeatEnabled = true; + bool m_isDraggingTimeline = false; + qint64 m_videoDuration = 0; AfcClientHandle *m_afcClient; + ZLoadingWidget *m_loadingWidget; }; #endif // MEDIAPREVIEWDIALOG_H diff --git a/src/mediastreamer.cpp b/src/mediastreamer.cpp index 5e07248..22b1526 100644 --- a/src/mediastreamer.cpp +++ b/src/mediastreamer.cpp @@ -30,7 +30,7 @@ #include #include -MediaStreamer::MediaStreamer(iDescriptorDevice *device, +MediaStreamer::MediaStreamer(const iDescriptorDevice *device, AfcClientHandle *afcClient, const QString &filePath, QObject *parent) : QTcpServer(parent), m_device(device), m_afcClient(afcClient), @@ -63,10 +63,10 @@ QUrl MediaStreamer::getUrl() const if (!isListening()) { return QUrl(); } - // todo pass folder/filename + return QUrl(QString("http://127.0.0.1:%1/%2") .arg(serverPort()) - .arg(QFileInfo(m_filePath).fileName())); + .arg(QUrl::toPercentEncoding(m_filePath))); } bool MediaStreamer::isListening() const { return QTcpServer::isListening(); } @@ -235,7 +235,6 @@ void MediaStreamer::handleRequest(QTcpSocket *socket, response += "\r\n"; socket->write(response); - // Remove blocking call - let Qt handle when bytes are actually written // For HEAD requests, don't send body if (request.method == "HEAD") { @@ -259,14 +258,12 @@ void MediaStreamer::sendErrorResponse(QTcpSocket *socket, int statusCode, .toUtf8(); socket->write(response); - // Remove blocking call socket->disconnectFromHost(); } void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, qint64 endByte) { - // Create a new streaming context for this request StreamingContext *context = new StreamingContext(); context->socket = socket; context->device = m_device; @@ -276,12 +273,10 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, context->bytesRemaining = endByte - startByte + 1; context->afcHandle = nullptr; - qDebug() << "m_filepath" << m_filePath; const QByteArray pathBytes = m_filePath.toUtf8(); - IdeviceFfiError *err_open = // Use distinct variable name - ServiceManager::safeAfcFileOpen(m_device, pathBytes.constData(), - AfcRdOnly, &context->afcHandle); + IdeviceFfiError *err_open = ServiceManager::safeAfcFileOpen( + m_device, pathBytes.constData(), AfcRdOnly, &context->afcHandle); if (err_open || context->afcHandle == 0) { qWarning() << "Failed to open file on device:" << m_filePath; @@ -290,15 +285,16 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, return; } - // Seek to start position if needed + /* TODO: can it be optimized by doing + SEEK_END 2 /* Seek from end of file. ??? + */ if (startByte > 0) { - // 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; - // FIXME: m_afcClient must be passed as alt afc + IdeviceFfiError *err = ServiceManager::safeAfcFileClose(m_device, context->afcHandle); if (err) { @@ -310,9 +306,6 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, } } - qDebug() << "Starting non-blocking stream for range" << startByte << "-" - << endByte << "(" << context->bytesRemaining << "bytes)"; - // Store context as socket property for cleanup socket->setProperty("streamingContext", QVariant::fromValue(static_cast(context))); @@ -321,15 +314,16 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, connect(socket, &QTcpSocket::bytesWritten, this, [this, context](qint64 bytes) { Q_UNUSED(bytes) + // Check if context is still valid QTcpSocket *senderSocket = qobject_cast(sender()); if (!senderSocket || senderSocket->property("streamingContext").isNull()) { - return; // Context already cleaned up + return; } + // Continue streaming when socket buffer has space - if (context->socket->bytesToWrite() < - 32768) { // Keep buffer below 32KB + if (context->socket->bytesToWrite() < 50000) { streamNextChunk(context); } }); @@ -339,7 +333,7 @@ void MediaStreamer::streamFileRange(QTcpSocket *socket, qint64 startByte, QTcpSocket *senderSocket = qobject_cast(sender()); if (!senderSocket || senderSocket->property("streamingContext").isNull()) { - return; // Already cleaned up + return; } cleanupStreamingContext(context); }); @@ -421,7 +415,7 @@ void MediaStreamer::streamNextChunk(StreamingContext *context) return; } - const int CHUNK_SIZE = 256 * 1024; + const int CHUNK_SIZE = 64 * 1024; const uint32_t bytesToRead = static_cast( qMin(static_cast(CHUNK_SIZE), context->bytesRemaining)); @@ -441,9 +435,10 @@ void MediaStreamer::streamNextChunk(StreamingContext *context) 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); - } + // FIXME: in such situation, freeing shouldn't be safe ? + // if (chunkData) { + // afc_file_read_data_free(chunkData, 0); + // } cleanupStreamingContext(context); return; } @@ -475,7 +470,7 @@ void MediaStreamer::streamNextChunk(StreamingContext *context) return; } - if (context->socket->bytesToWrite() >= 1024 * 1024) { + if (context->socket->bytesToWrite() >= 50000) { // Wait for bytesWritten signal return; } else { @@ -505,9 +500,7 @@ void MediaStreamer::cleanupStreamingContext(StreamingContext *context) } if (context->afcHandle != 0) { - // FIXME: m_afcClient must be passed as alt afc - ServiceManager::safeAfcFileClose(context->device, context->afcHandle - /*m_afcClient*/); + ServiceManager::safeAfcFileClose(context->device, context->afcHandle); context->afcHandle = 0; } @@ -517,7 +510,7 @@ void MediaStreamer::cleanupStreamingContext(StreamingContext *context) disconnect(context->socket, &QTcpSocket::disconnected, this, nullptr); context->socket->disconnectFromHost(); - context->socket = nullptr; // Prevent further access + context->socket = nullptr; } qDebug() << "Streaming context cleaned up for" diff --git a/src/mediastreamer.h b/src/mediastreamer.h index 80f6169..50cf259 100644 --- a/src/mediastreamer.h +++ b/src/mediastreamer.h @@ -30,38 +30,17 @@ QT_BEGIN_NAMESPACE class QTcpSocket; QT_END_NAMESPACE -/** - * @brief A lightweight HTTP server for streaming media files from iOS devices - * - * This class implements an HTTP server that supports: - * - Basic HTTP GET requests - * - HTTP Range requests for video scrubbing - * - Streaming from AFC (Apple File Conduit) without loading entire file into - * memory - * - Thread-safe operation - * - * The server automatically shuts down when the client disconnects. - */ class MediaStreamer : public QTcpServer { Q_OBJECT public: - explicit MediaStreamer(iDescriptorDevice *device, + explicit MediaStreamer(const iDescriptorDevice *device, AfcClientHandle *afcClient, const QString &filePath, QObject *parent = nullptr); ~MediaStreamer(); - /** - * @brief Get the URL that clients should use to connect to this server - * @return URL in format http://127.0.0.1:port - */ QUrl getUrl() const; - - /** - * @brief Check if the server started successfully - * @return true if server is listening, false otherwise - */ bool isListening() const; protected: @@ -83,7 +62,7 @@ private: struct StreamingContext { QTcpSocket *socket; - iDescriptorDevice *device; + const iDescriptorDevice *device; QString filePath; qint64 startByte; qint64 endByte; @@ -102,7 +81,7 @@ private: QString getMimeType() const; // Core data - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; QString m_filePath; // File info cache diff --git a/src/mediastreamermanager.cpp b/src/mediastreamermanager.cpp index b2e96c2..7200a38 100644 --- a/src/mediastreamermanager.cpp +++ b/src/mediastreamermanager.cpp @@ -30,7 +30,7 @@ MediaStreamerManager *MediaStreamerManager::sharedInstance() return &instance; } -QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device, +QUrl MediaStreamerManager::getStreamUrl(const iDescriptorDevice *device, AfcClientHandle *afcClient, const QString &filePath) { @@ -65,7 +65,7 @@ QUrl MediaStreamerManager::getStreamUrl(iDescriptorDevice *device, return QUrl(); } - // Store the streamer info + // FIXME: device pointer can become dangling if device is disconnected StreamerInfo info; info.streamer = streamer; info.device = device; diff --git a/src/mediastreamermanager.h b/src/mediastreamermanager.h index 6170bc7..191e4f4 100644 --- a/src/mediastreamermanager.h +++ b/src/mediastreamermanager.h @@ -27,40 +27,16 @@ #include #include -/** - * @brief Singleton manager for MediaStreamer instances - * - * This class manages MediaStreamer instances to avoid creating multiple - * streamers for the same file. It automatically cleans up unused streamers - * and provides thread-safe access. - */ class MediaStreamerManager { public: - /** - * @brief Get the singleton instance - * @return The MediaStreamerManager instance - */ static MediaStreamerManager *sharedInstance(); - /** - * @brief Get or create a streamer for the specified file - * @param device The iOS device - * @param filePath The file path on the device - * @return URL to stream the file, or empty URL if failed - */ - QUrl getStreamUrl(iDescriptorDevice *device, AfcClientHandle *afcClient, - const QString &filePath); + QUrl getStreamUrl(const iDescriptorDevice *device, + AfcClientHandle *afcClient, const QString &filePath); - /** - * @brief Release a streamer for the specified file - * @param filePath The file path to release - */ void releaseStreamer(const QString &filePath); - /** - * @brief Clean up all inactive streamers - */ void cleanup(); private: @@ -69,7 +45,7 @@ private: private: struct StreamerInfo { MediaStreamer *streamer; - iDescriptorDevice *device; + const iDescriptorDevice *device; int refCount; }; diff --git a/src/networkdevicestoconnectwidget.cpp b/src/networkdevicestoconnectwidget.cpp index 6c42e07..b1ef6b1 100644 --- a/src/networkdevicestoconnectwidget.cpp +++ b/src/networkdevicestoconnectwidget.cpp @@ -130,6 +130,17 @@ void NetworkDeviceCard::noPairingFile() }); } +void NetworkDeviceCard::connected() +{ + m_connectButton->setText("Connected"); + m_connectButton->setEnabled(false); + + QTimer::singleShot(10000, this, [this]() { + m_connectButton->setText("Connect"); + m_connectButton->setEnabled(true); + }); +} + void NetworkDeviceCard::initStarted() { m_connectButton->setText("Connecting..."); @@ -168,6 +179,10 @@ NetworkDevicesToConnectWidget::NetworkDevicesToConnectWidget(QWidget *parent) &NetworkDevicesToConnectWidget::onDeviceInitFailed); connect(AppContext::sharedInstance(), &AppContext::initStarted, this, &NetworkDevicesToConnectWidget::onDeviceInitStarted); + connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this, + &NetworkDevicesToConnectWidget::onDeviceAdded); + connect(AppContext::sharedInstance(), &AppContext::deviceAlreadyExists, + this, &NetworkDevicesToConnectWidget::onDeviceAlreadyExists); } NetworkDevicesToConnectWidget::~NetworkDevicesToConnectWidget() @@ -313,20 +328,47 @@ void NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice( } // udid or mac address -void NetworkDevicesToConnectWidget::onDeviceInitFailed(const QString &udid) +void NetworkDevicesToConnectWidget::onDeviceInitFailed(const QString &uniq) { - NetworkDeviceCard *deviceCard = m_deviceCards[udid]; + NetworkDeviceCard *deviceCard = m_deviceCards[uniq]; if (deviceCard) { - qDebug() << "Calling failed() on device card for" << udid; + qDebug() << "Calling failed() on device card for" << uniq; deviceCard->failed(); } } -void NetworkDevicesToConnectWidget::onDeviceInitStarted(const QString &udid) +void NetworkDevicesToConnectWidget::onDeviceInitStarted(const QString &uniq) { - NetworkDeviceCard *deviceCard = m_deviceCards[udid]; + NetworkDeviceCard *deviceCard = m_deviceCards[uniq]; if (deviceCard) { - qDebug() << "Calling initStarted() on device card for" << udid; + qDebug() << "Calling initStarted() on device card for" << uniq; deviceCard->initStarted(); } +} + +void NetworkDevicesToConnectWidget::onDeviceAdded( + const iDescriptorDevice *device) +{ + NetworkDeviceCard *deviceCard = m_deviceCards[QString::fromStdString( + device->deviceInfo.wifiMacAddress)]; + if (deviceCard) { + qDebug() << "Calling connected() on device card for" + << QString::fromStdString(device->deviceInfo.wifiMacAddress); + deviceCard->connected(); + return; + } + qDebug() << "No device card found for" + << QString::fromStdString(device->deviceInfo.wifiMacAddress); +} + +void NetworkDevicesToConnectWidget::onDeviceAlreadyExists( + const iDescriptor::Uniq &uniq) +{ + NetworkDeviceCard *deviceCard = m_deviceCards[QString(uniq.get())]; + if (deviceCard) { + qDebug() << "Calling connected() on device card for" << uniq.get(); + deviceCard->connected(); + return; + } + qDebug() << "No device card found for" << uniq.get(); } \ No newline at end of file diff --git a/src/networkdevicestoconnectwidget.h b/src/networkdevicestoconnectwidget.h index 90959c9..7faec08 100644 --- a/src/networkdevicestoconnectwidget.h +++ b/src/networkdevicestoconnectwidget.h @@ -49,6 +49,7 @@ public: void failed(); void noPairingFile(); void initStarted(); + void connected(); }; class NetworkDevicesToConnectWidget : public QWidget @@ -65,6 +66,8 @@ private slots: void onNoPairingFileForWirelessDevice(const QString &macAddress); void onDeviceInitFailed(const QString &udid); void onDeviceInitStarted(const QString &udid); + void onDeviceAdded(const iDescriptorDevice *device); + void onDeviceAlreadyExists(const iDescriptor::Uniq &uniq); private: void setupUI(); diff --git a/src/opensshterminalwidget.cpp b/src/opensshterminalwidget.cpp index 39dbeb6..10654fe 100644 --- a/src/opensshterminalwidget.cpp +++ b/src/opensshterminalwidget.cpp @@ -184,7 +184,7 @@ void OpenSSHTerminalWidget::clearDeviceButtons() } } -void OpenSSHTerminalWidget::addWiredDevice(iDescriptorDevice *device) +void OpenSSHTerminalWidget::addWiredDevice(const iDescriptorDevice *device) { QString deviceName = QString::fromStdString(device->deviceInfo.deviceName); QString udid = QString::fromStdString(device->udid); @@ -192,8 +192,9 @@ void OpenSSHTerminalWidget::addWiredDevice(iDescriptorDevice *device) QRadioButton *radioButton = new QRadioButton(displayText); radioButton->setProperty("deviceType", "wired"); - radioButton->setProperty("devicePointer", - QVariant::fromValue(static_cast(device))); + radioButton->setProperty( + "devicePointer", + QVariant::fromValue(static_cast(device))); radioButton->setProperty("udid", udid); m_deviceButtonGroup->addButton(radioButton); @@ -214,7 +215,7 @@ void OpenSSHTerminalWidget::addWirelessDevice(const NetworkDevice &device) m_wirelessDevicesLayout->addWidget(radioButton); } -void OpenSSHTerminalWidget::onWiredDeviceAdded(iDescriptorDevice *device) +void OpenSSHTerminalWidget::onWiredDeviceAdded(const iDescriptorDevice *device) { addWiredDevice(device); } diff --git a/src/opensshterminalwidget.h b/src/opensshterminalwidget.h index 7a04b21..02565d1 100644 --- a/src/opensshterminalwidget.h +++ b/src/opensshterminalwidget.h @@ -47,7 +47,7 @@ public: ~OpenSSHTerminalWidget(); private slots: void onOpenSSHTerminal(); - void onWiredDeviceAdded(iDescriptorDevice *device); + void onWiredDeviceAdded(const iDescriptorDevice *device); void onWiredDeviceRemoved(const std::string &udid); void onWirelessDeviceAdded(const NetworkDevice &device); void onWirelessDeviceRemoved(const QString &deviceName); @@ -57,7 +57,7 @@ private: void setupDeviceSelectionUI(QVBoxLayout *layout); void updateDeviceList(); void clearDeviceButtons(); - void addWiredDevice(iDescriptorDevice *device); + void addWiredDevice(const iDescriptorDevice *device); void addWirelessDevice(const NetworkDevice &device); void resetSelection(); @@ -82,8 +82,7 @@ private: iDescriptorDevice *m_selectedWiredDevice = nullptr; NetworkDevice m_selectedNetworkDevice; - // Legacy device pointer (kept for compatibility) - iDescriptorDevice *m_device = nullptr; + const iDescriptorDevice *m_device = nullptr; // SSH components ssh_session m_sshSession; diff --git a/src/photoimportdialog.cpp b/src/photoimportdialog.cpp index aaf2541..d2b71db 100644 --- a/src/photoimportdialog.cpp +++ b/src/photoimportdialog.cpp @@ -31,11 +31,8 @@ #include #include -PhotoImportDialog::PhotoImportDialog(const QStringList &files, - bool hasDirectories, QWidget *parent) - : QDialog(parent), selectedFiles(files), - containsDirectories(hasDirectories), m_httpServer(nullptr), - m_mediaPlayer(nullptr) +PhotoImportDialog::PhotoImportDialog(const QStringList &files, QWidget *parent) + : QDialog(parent), selectedFiles(files) { setupUI(); setModal(true); @@ -59,16 +56,6 @@ void PhotoImportDialog::setupUI() { QVBoxLayout *mainLayout = new QVBoxLayout(this); - // Warning label for directories - if (containsDirectories) { - warningLabel = - new QLabel("⚠️ Warning: Selected items contain directories. All " - "gallery-compatible files will be included.", - this); - warningLabel->setWordWrap(true); - mainLayout->addWidget(warningLabel); - } - // File list QLabel *listLabel = new QLabel( QString("Files to be served (%1 items):").arg(selectedFiles.size()), @@ -230,6 +217,7 @@ void PhotoImportDialog::onServerStarted() void PhotoImportDialog::onDownloadProgress(const QString &fileName, int bytesDownloaded, int totalBytes) { + // TODO: bring in a progress bar each item m_progressLabel->setText(QString("Downloaded: %1 (%2 KB)") .arg(fileName) .arg(bytesDownloaded / 1024)); diff --git a/src/photoimportdialog.h b/src/photoimportdialog.h index a8ca23a..566d3fb 100644 --- a/src/photoimportdialog.h +++ b/src/photoimportdialog.h @@ -25,20 +25,20 @@ #include #include #include +#include #include #include +#include #include #include -#include #include -#include class PhotoImportDialog : public QDialog { Q_OBJECT public: - explicit PhotoImportDialog(const QStringList &files, bool hasDirectories, + explicit PhotoImportDialog(const QStringList &files, QWidget *parent = nullptr); ~PhotoImportDialog(); @@ -52,10 +52,8 @@ private slots: private: QStringList selectedFiles; - bool containsDirectories; QListWidget *fileList; - QLabel *warningLabel; QLabel *qrCodeLabel; QStackedWidget *m_instructionStack; QLabel *m_instructionLabel; diff --git a/src/photomodel.cpp b/src/photomodel.cpp index 05eca32..024e05c 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -34,40 +34,27 @@ #include #include #include -#include -PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType, +PhotoModel::PhotoModel(const iDescriptorDevice *device, FilterType filterType, QObject *parent) - : QAbstractListModel(parent), m_device(device), m_thumbnailSize(120, 120), - m_sortOrder(NewestFirst), m_filterType(filterType) + : QAbstractListModel(parent), m_device(device), m_sortOrder(NewestFirst), + m_filterType(filterType) { - connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this, - &PhotoModel::onThumbnailReady); } void PhotoModel::clear() { - blockSignals(true); - - // // Clean up any active watchers - // for (auto *watcher : m_activeLoaders.values()) { - // if (watcher) { - // watcher->disconnect(); - // watcher->cancel(); - // // watcher->waitForFinished(); - // watcher->deleteLater(); - // } - // } - // m_activeLoaders.clear(); - // m_loadingPaths.clear(); - // m_thumbnailCache.clear(); + QMutexLocker locker(&m_mutex); + disconnect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, + this, &PhotoModel::onThumbnailReady); beginResetModel(); m_photos.clear(); m_allPhotos.clear(); endResetModel(); - blockSignals(false); + qDebug() << "Cleared PhotoModel data"; + ImageLoader::sharedInstance().clear(); } PhotoModel::~PhotoModel() @@ -112,8 +99,7 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const } } - imgloader.requestThumbnail(m_device, info.filePath, index.row(), - index.row()); + imgloader.requestThumbnail(m_device, info.filePath, index.row()); if (iDescriptor::Utils::isVideoFile(info.fileName)) { return QIcon(":/resources/icons/video-x-generic.png"); @@ -134,19 +120,33 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const void PhotoModel::onThumbnailReady(const QString &path, const QPixmap &pixmap, unsigned int row) { - if (m_photos[row].filePath == path) { - QModelIndex idx = createIndex(row, 0); - emit dataChanged(idx, idx, {Qt::DecorationRole}); + // check bounds + if (row < m_photos.size()) { + const PhotoInfo &photo = m_photos.at(row); + if (photo.filePath == path) { + QModelIndex idx = createIndex(row, 0); + emit dataChanged(idx, idx, {Qt::DecorationRole}); + } + } else { + // FIXME: happens when we filter down to videos only + qDebug() << "Out of bounds in PhotoModel::onThumbnailReady"; } } -void PhotoModel::populatePhotoPaths() +bool isTimeoutError(IdeviceFfiError *err) { - // TODO:beginResetModel called on PhotoModel(0x600002d12a40) without calling - // endResetModel first + return err && err->code == TimeoutErrorCode; +} + +bool PhotoModel::populatePhotoPaths() +{ + // FIXME:DEADLOCK? + // QMutexLocker locker(&m_mutex); + connect(&ImageLoader::sharedInstance(), &ImageLoader::thumbnailReady, this, + &PhotoModel::onThumbnailReady); if (m_albumPath.isEmpty()) { qDebug() << "No album path set, skipping population"; - return; + return false; } m_allPhotos.clear(); @@ -160,9 +160,13 @@ void PhotoModel::populatePhotoPaths() ServiceManager::safeAfcGetFileInfo(m_device, albumPathCStr, &albumInfo); if (err) { qDebug() << "Album path does not exist or cannot be accessed:" - << m_albumPath << "Error:" << err->message; + << m_albumPath << "Error:" << err->message + << "Code:" << err->code; + if (isTimeoutError(err)) { + emit timedOut(); + } idevice_error_free(err); - return; + return false; } // FIXME: should we continue if albumInfo is null? if (albumInfo.size) { @@ -182,27 +186,23 @@ void PhotoModel::populatePhotoPaths() if (err) { qDebug() << "Failed to read photo directory:" << photoDir << "Error:" << err->message; + if (isTimeoutError(err)) { + emit timedOut(); + } idevice_error_free(err); - return; + return false; } if (files) { for (int i = 0; files[i]; i++) { QString fileName = QString::fromUtf8(files[i]); - if (fileName.endsWith(".JPG", Qt::CaseInsensitive) || - fileName.endsWith(".PNG", Qt::CaseInsensitive) || - fileName.endsWith(".HEIC", Qt::CaseInsensitive) || - fileName.endsWith(".MOV", Qt::CaseInsensitive) || - fileName.endsWith(".MP4", Qt::CaseInsensitive) || - fileName.endsWith(".M4V", Qt::CaseInsensitive)) { - + if (iDescriptor::Utils::isGalleryFile(fileName)) { PhotoInfo info; info.filePath = m_albumPath + "/" + fileName; info.fileName = fileName; info.thumbnailRequested = false; info.fileType = determineFileType(fileName); info.dateTime = extractDateTimeFromFile(info.filePath); - m_allPhotos.append(info); } } @@ -214,6 +214,7 @@ void PhotoModel::populatePhotoPaths() qDebug() << "Loaded" << m_allPhotos.size() << "media files from device"; qDebug() << "After filtering:" << m_photos.size() << "items shown"; + return true; } // Sorting and filtering methods @@ -235,6 +236,7 @@ void PhotoModel::setFilterType(FilterType filter) void PhotoModel::applyFilterAndSort() { + QMutexLocker locker(&m_mutex); beginResetModel(); // Filter photos @@ -330,15 +332,15 @@ QStringList PhotoModel::getFilteredFilePaths() const // Helper methods QDateTime PhotoModel::extractDateTimeFromFile(const QString &filePath) const { - AfcFileInfo *info = nullptr; + AfcFileInfo info = {}; IdeviceFfiError *err = ServiceManager::safeAfcGetFileInfo( - m_device, filePath.toUtf8().constData(), info); - if (!err && info) { - uint64_t creation_seconds = info->creation; + m_device, filePath.toUtf8().constData(), &info); + if (!err && info.creation) { + uint64_t creation_seconds = info.creation; QDateTime dateTime = QDateTime::fromSecsSinceEpoch(creation_seconds, Qt::UTC); - // afc_file_info_free(info); + afc_file_info_free(&info); if (dateTime.isValid()) { return dateTime; } @@ -362,7 +364,24 @@ void PhotoModel::setAlbumPath(const QString &albumPath) clear(); m_albumPath = albumPath; - populatePhotoPaths(); + QFutureWatcher *futureWatcher = new QFutureWatcher(this); + QFuture future = + QtConcurrent::run([this]() { return populatePhotoPaths(); }); + futureWatcher->setFuture(future); + connect(futureWatcher, &QFutureWatcher::finished, this, + [this, futureWatcher]() { + futureWatcher->deleteLater(); + bool success = futureWatcher->result(); + if (success) { + qDebug() << "Finished populating photo paths for album:" + << m_albumPath; + emit albumPathSet(); + } else { + // qDebug() << "Failed to populate photo paths for album:" + // << m_albumPath; + // emit albumPathFailed(); + } + }); } - +// TODO:REMOVE void PhotoModel::refreshPhotos() { populatePhotoPaths(); } diff --git a/src/photomodel.h b/src/photomodel.h index c0aaaaf..a9adfb5 100644 --- a/src/photomodel.h +++ b/src/photomodel.h @@ -26,10 +26,12 @@ #include #include #include +#include +#include #include -#include #include #include +#include struct PhotoInfo { QString filePath; @@ -50,7 +52,7 @@ public: enum FilterType { All, ImagesOnly, VideosOnly }; - explicit PhotoModel(iDescriptorDevice *device, FilterType filterType, + explicit PhotoModel(const iDescriptorDevice *device, FilterType filterType, QObject *parent = nullptr); ~PhotoModel(); @@ -83,20 +85,19 @@ public: private: // Data members - iDescriptorDevice *m_device; + const iDescriptorDevice *m_device; QString m_albumPath; QList m_allPhotos; // All photos from device QList m_photos; // Currently filtered/sorted photos - // Thumbnail management - QSize m_thumbnailSize; - // Sorting and filtering SortOrder m_sortOrder; FilterType m_filterType; + QMutex m_mutex; + // Helper methods - void populatePhotoPaths(); + bool populatePhotoPaths(); void applyFilterAndSort(); void sortPhotos(QList &photos) const; bool matchesFilter(const PhotoInfo &info) const; @@ -107,6 +108,10 @@ private: private slots: void onThumbnailReady(const QString &path, const QPixmap &pixmap, unsigned int row); + +signals: + void albumPathSet(); + void timedOut(); }; #endif // PHOTOMODEL_H \ No newline at end of file diff --git a/src/recoverydeviceinfowidget.cpp b/src/recoverydeviceinfowidget.cpp index 56243cb..b4ce007 100644 --- a/src/recoverydeviceinfowidget.cpp +++ b/src/recoverydeviceinfowidget.cpp @@ -28,143 +28,157 @@ #include #include -RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget(const void *info, - QWidget *parent) +std::string parse_recovery_mode(irecv_mode productType) +{ + switch (productType) { + case irecv_mode::IRECV_K_RECOVERY_MODE_1: + case irecv_mode::IRECV_K_RECOVERY_MODE_2: + case irecv_mode::IRECV_K_RECOVERY_MODE_3: + case irecv_mode::IRECV_K_RECOVERY_MODE_4: + return "Recovery Mode"; + case irecv_mode::IRECV_K_WTF_MODE: + return "WTF Mode"; + case irecv_mode::IRECV_K_DFU_MODE: + case irecv_mode::IRECV_K_PORT_DFU_MODE: + return "DFU Mode"; + default: + return "Unknown Mode"; + } +} + +RecoveryDeviceInfoWidget::RecoveryDeviceInfoWidget( + const iDescriptorRecoveryDevice *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 1d96e3d..67920ce 100644 --- a/src/recoverydeviceinfowidget.h +++ b/src/recoverydeviceinfowidget.h @@ -27,7 +27,7 @@ class RecoveryDeviceInfoWidget : public QWidget Q_OBJECT public: - explicit RecoveryDeviceInfoWidget(const void *info, + explicit RecoveryDeviceInfoWidget(const iDescriptorRecoveryDevice *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 9f48934..9f79b56 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -223,96 +223,102 @@ IdeviceFfiError *ServiceManager::exportFileToPath( std::atomic *cancelRequested) { qDebug() - << "[serviceManager::exportFileToPath] Exporting file from device path:" + << "[ServiceManager::exportFileToPath] Exporting file from device path:" << device_path << "to local path:" << local_path; + + // FIXME : use execute afc op 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); + IdeviceFfiError *err = + afc_file_open(device->afcClient, device_path, + AfcFopenMode::AfcRdOnly, &afcHandle); - if (err_open != nullptr) { + if (err != nullptr) { qDebug() << "Failed to open file on device:" << device_path - << "Error Code:" << err_open->code - << "Message:" << err_open->message; - return err_open; + << "Error Code:" << err->code + << "Message:" << err->message; + return err; } 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); + IdeviceFfiError *err_close = afc_file_close(afcHandle); if (err_close != nullptr) { - // idevice_error_free(err_close); + idevice_error_free(err_close); } - return new IdeviceFfiError{1, "FAILED_TO_OPEN_LOCAL_FILE"}; + return new IdeviceFfiError{1, "Failed to open local file"}; } - qDebug() << "Local file opened successfully"; - const size_t CHUNK_SIZE = 256 * 1024; // 256KB chunks + // 256KB chunks + const size_t CHUNK_SIZE = 256 * 1024; 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); + err = afc_get_file_info(device->afcClient, device_path, &fileInfo); qint64 totalFileSize = 0; - if (info_err == nullptr) { - totalFileSize = fileInfo.size; - // afc_file_info_free(&fileInfo); - } else { - // idevice_error_free(info_err); - } + if (err != nullptr) + return err; + + totalFileSize = fileInfo.size; + afc_file_info_free(&fileInfo); - 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"}; + err = afc_file_close(afcHandle); + if (err != nullptr) { + idevice_error_free(err); + } + // FIXME: we need error codes + return new IdeviceFfiError{1, "Transfer cancelled"}; } - read_err = safeAfcFileRead(device, afcHandle, &chunkData, - CHUNK_SIZE, &bytesRead); + err = afc_file_read(afcHandle, &chunkData, CHUNK_SIZE, + &bytesRead); - if (read_err != nullptr) { - qDebug() << "Error reading file:" << read_err->message; + if (err != nullptr) { + qDebug() << "Error reading file:" << err->message; fclose(out); - safeAfcFileClose(device, afcHandle); - return read_err; + IdeviceFfiError *err_close = afc_file_close(afcHandle); + if (err_close != nullptr) { + idevice_error_free(err_close); + } + return err; } if (bytesRead == 0) { - // End of file reached + // End of file break; } - // Write chunk to local file + // Write chunk 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"}; + IdeviceFfiError *err_close = afc_file_close(afcHandle); + if (err_close != nullptr) { + idevice_error_free(err_close); + } + return new IdeviceFfiError{1, + "Failed to write to local file"}; } totalBytesRead += bytesRead; - // Report progress if (progressCallback) { progressCallback(totalBytesRead, totalFileSize); } @@ -320,13 +326,13 @@ IdeviceFfiError *ServiceManager::exportFileToPath( fclose(out); - IdeviceFfiError *err_close = safeAfcFileClose(device, afcHandle); + IdeviceFfiError *err_close = afc_file_close(afcHandle); if (err_close != nullptr) { qDebug() << "Failed to close AFC file:" << err_close->message; return err_close; } - return nullptr; // Success + return nullptr; }); } diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index bc06876..14e9104 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -456,16 +456,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice) liveScreen->show(); } break; case iDescriptorTool::RecoveryMode: { - // 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(); + enterRecoveryMode(device); } break; case iDescriptorTool::MountDevImage: { DevDiskImageHelper *devDiskImageHelper = @@ -547,8 +538,6 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice) #endif case iDescriptorTool::CableInfoWidget: { CableInfoWidget *cableInfoWidget = new CableInfoWidget(device); - cableInfoWidget->setAttribute(Qt::WA_DeleteOnClose); - cableInfoWidget->resize(600, 400); cableInfoWidget->show(); } break; case iDescriptorTool::NetworkDevices: { @@ -569,7 +558,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool, bool requiresDevice) } } -void ToolboxWidget::restartDevice(iDescriptorDevice *device) +void ToolboxWidget::restartDevice(const iDescriptorDevice *device) { QMessageBox msgBox; msgBox.setWindowTitle("Restart Device"); @@ -598,7 +587,7 @@ void ToolboxWidget::restartDevice(iDescriptorDevice *device) } } -void ToolboxWidget::shutdownDevice(iDescriptorDevice *device) +void ToolboxWidget::shutdownDevice(const iDescriptorDevice *device) { QMessageBox msgBox; @@ -629,7 +618,7 @@ void ToolboxWidget::shutdownDevice(iDescriptorDevice *device) } } -void ToolboxWidget::_enterRecoveryMode(iDescriptorDevice *device) +void ToolboxWidget::enterRecoveryMode(const iDescriptorDevice *device) { QMessageBox msgBox; msgBox.setWindowTitle("Enter Recovery Mode"); @@ -645,18 +634,16 @@ void ToolboxWidget::_enterRecoveryMode(iDescriptorDevice *device) return; } - // 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"; - // } + IdeviceFfiError *error = lockdownd_enter_recovery(device->lockdown); + if (error != nullptr) { + QMessageBox::warning(nullptr, "Enter Recovery Mode Failed", + "Failed to enter recovery mode: " + + QString::fromStdString(error->message)); + idevice_error_free(error); + } else { + QMessageBox::information(nullptr, "Enter Recovery Mode Initiated", + "Device will enter recovery mode."); + } } void ToolboxWidget::restartAirPlayWidget() diff --git a/src/toolboxwidget.h b/src/toolboxwidget.h index 03d1111..52bf8f5 100644 --- a/src/toolboxwidget.h +++ b/src/toolboxwidget.h @@ -44,9 +44,9 @@ class ToolboxWidget : public QWidget Q_OBJECT public: explicit ToolboxWidget(QWidget *parent = nullptr); - static void restartDevice(iDescriptorDevice *device); - static void shutdownDevice(iDescriptorDevice *device); - static void _enterRecoveryMode(iDescriptorDevice *device); + static void restartDevice(const iDescriptorDevice *device); + static void shutdownDevice(const iDescriptorDevice *device); + static void enterRecoveryMode(const iDescriptorDevice *device); static ToolboxWidget *sharedInstance(); void restartAirPlayWidget(); private slots: diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp index 3a20cfc..ab82626 100644 --- a/src/virtuallocationwidget.cpp +++ b/src/virtuallocationwidget.cpp @@ -152,8 +152,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent) this->deleteLater(); } }); - - // DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device); } void VirtualLocation::onQuickWidgetStatusChanged(QQuickWidget::Status status) @@ -170,8 +168,7 @@ void VirtualLocation::onQuickWidgetStatusChanged(QQuickWidget::Status status) void VirtualLocation::onInputChanged() { - // Update map when input changes (with slight delay to avoid too frequent - // updates) + // Update map when input changes m_updateTimer.setSingleShot(true); m_updateTimer.setInterval(500); // 500ms delay @@ -301,186 +298,251 @@ 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(); - // } - - // if (!success) { - // return; - // } - - // // Apply location - // emit locationChanged(latitude, longitude); - // updateMapFromInputs(); - - // // 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())); - - // 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"); + LocationSimulationServiceHandle *locationSim = nullptr; + IdeviceFfiError *err = nullptr; + err = lockdown_location_simulation_connect(m_device->provider, + &locationSim); + if (err != NULL) { + qDebug() << "Failed to connect to location simulation service:" + << err->code << err->message; + if (err->code != InvalidServiceErrorCode) { + QMessageBox::warning( + this, "Error", + QString( + "Failed to connect to location simulation service: %1") + .arg(err->message)); + idevice_error_free(err); + m_applyButton->setEnabled(true); + return; + } + + idevice_error_free(err); + DevDiskImageHelper *devDiskImageHelper = + new DevDiskImageHelper(m_device, this); + connect( + devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, + this, + [this, latitude, longitude, devDiskImageHelper](bool success) { + if (devDiskImageHelper) { + devDiskImageHelper->deleteLater(); + } + + if (!success) { + // mounter will show its own error message, just + // re-enable the button here + m_applyButton->setEnabled(true); + return; + } + IdeviceFfiError *err = nullptr; + LocationSimulationServiceHandle *locationSim = nullptr; + err = lockdown_location_simulation_connect( + m_device->provider, &locationSim); + + if (err != NULL) { + qDebug() << "Failed to connect to location simulation " + "service after mounting disk image:" + << err->code << err->message; + QMessageBox::warning( + this, "Error", + QString("Failed to connect to location simulation " + "service after mounting disk image: %1") + .arg(err->message)); + idevice_error_free(err); + m_applyButton->setEnabled(true); + return; + } + + err = lockdown_location_simulation_set( + locationSim, + m_latitudeEdit->text().toStdString().c_str(), + m_longitudeEdit->text().toStdString().c_str()); + if (err != NULL) { + qDebug() + << "Failed to set location simulation:" << err->code + << err->message; + QMessageBox::warning( + this, "Error", + QString("Failed to set location simulation: %1") + .arg(err->message)); + idevice_error_free(err); + } + + lockdown_location_simulation_free(locationSim); + QMessageBox::information(this, "Success", + "Location applied successfully!"); + + SettingsManager::sharedInstance()->saveRecentLocation( + latitude, longitude); + m_applyButton->setEnabled(true); + return; + }); + connect( + devDiskImageHelper, &DevDiskImageHelper::destroyed, this, + [this]() { + QTimer::singleShot(1000, this, [this]() { + m_applyButton->setText("Apply Location"); + m_applyButton->setEnabled(true); + }); + }, + Qt::SingleShotConnection); + return devDiskImageHelper->start(); + } + + err = lockdown_location_simulation_set( + locationSim, m_latitudeEdit->text().toStdString().c_str(), + m_longitudeEdit->text().toStdString().c_str()); + if (err != NULL) { + qDebug() << "Failed to set location simulation:" << err->code + << err->message; + QMessageBox::warning( + this, "Error", + QString("Failed to set location simulation: %1") + .arg(err->message)); + idevice_error_free(err); + } + + lockdown_location_simulation_free(locationSim); + QMessageBox::information(this, "Success", + "Location applied successfully!"); m_applyButton->setEnabled(true); return; } + /* iOS 17 and above */ IdeviceFfiError *err = nullptr; - // Connect to CoreDeviceProxy + IdeviceFfiError *err_reveal = nullptr; CoreDeviceProxyHandle *core_device = NULL; + uint16_t rsd_port; + AdapterHandle *adapter = NULL; + ReadWriteOpaque *stream = NULL; + RsdHandshakeHandle *handshake = NULL; + RemoteServerHandle *remote_server = NULL; + LocationSimulationHandle *location_sim = 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); + qDebug() << "Failed to connect to CoreDeviceProxy:" << err->code + << err->message; + goto cleanup; } - // 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); + qDebug() << "Failed to get server RSD port:" << err->code + << err->message; + goto cleanup; } - // 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); + qDebug() << "Failed to create TCP adapter:" << err->code + << err->message; + goto cleanup; } - // 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); + qDebug() << "Failed to connect to RSD port:" << err->code + << err->message; + goto cleanup; } - 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); + qDebug() << "Failed to perform RSD handshake:" << err->code + << err->message; + goto cleanup; } - // 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 is not found. Enable Developer " - "Mode on the device?", - QMessageBox::Yes | QMessageBox::No); - if (res == QMessageBox::Yes) { - IdeviceFfiError *devmodeErr = - ServiceManager::enableDevMode(m_device); - if (devmodeErr != NULL) { - QMessageBox::warning( - this, "Error", - QString("Failed to enable Developer Mode:\n%1") - .arg(devmodeErr->message)); - idevice_error_free(devmodeErr); - } else { - QMessageBox::information( - this, "Success", - "Developer Mode enabled successfully. Please try " - "applying the location again."); - } + err_reveal = + ServiceManager::revealDeveloperModeOptionInUI(m_device); + if (err_reveal != NULL) { + qDebug() << "Failed to reveal developer mode option in UI:" + << err_reveal->code << err_reveal->message; + QMessageBox::warning( + this, "Error", + "Developer Mode Not Enabled and failed to " + "reveal developer mode option in UI:\n" + + QString::fromStdString(err_reveal->message)); + goto cleanup; } - - 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); + // TODO: create a widget showing instructions on how to enable dev + // mode, + QMessageBox::information(this, "Developer Mode Not Enabled", + "Please enable Developer " + "Mode on the device to use this " + "feature."); + goto cleanup; } else { - printf("Successfully set location to %.6f, %.6f\n", latitude, - longitude); + qDebug() << "Failed to connect to remote server:" << err->code + << err->message; + QMessageBox::warning(this, "Connection Failed", + "Failed to connect to device's remote " + "service:\n" + + QString::fromStdString(err->message)); + goto cleanup; } } - // // 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!"); + err = location_simulation_new(remote_server, &location_sim); + if (err != NULL) { + qDebug() << "Failed to create location simulation client:" << err->code + << err->message; + QMessageBox::warning(this, "Error", + "Failed to create location simulation client:\n" + + QString::fromStdString(err->message)); + goto cleanup; + } + + err = location_simulation_set(location_sim, latitude, longitude); + if (err != NULL) { + qDebug() << "Failed to set location:" << err->code << err->message; + QMessageBox::warning(this, "Error", + "Failed to set location on device:\n" + + QString::fromStdString(err->message)); + } else { + qDebug() << "Successfully set location to" << latitude << longitude; + QMessageBox::information(this, "Success", + "Location applied successfully!"); + SettingsManager::sharedInstance()->saveRecentLocation(latitude, + longitude); + } + +cleanup: + if (location_sim) { + location_simulation_free(location_sim); + } + if (remote_server) { + remote_server_free(remote_server); + } + if (handshake) { + rsd_handshake_free(handshake); + } + if (adapter) { + adapter_close(adapter); + adapter_free(adapter); + } + // FIXME: seg faults + // if (stream) { + // idevice_stream_free(stream); // } + if (core_device) { + core_device_proxy_free(core_device); + } + if (err) { + idevice_error_free(err); + } + if (err_reveal) { + idevice_error_free(err_reveal); + } + m_applyButton->setEnabled(true); } void VirtualLocation::loadRecentLocations(QVBoxLayout *layout) diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 2d41a27..0204394 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -87,8 +87,7 @@ void WelcomeWidget::setupUI() []() { QDesktopServices::openUrl(QUrl(REPO_URL)); }); QPalette githubPalette = m_githubLabel->palette(); - githubPalette.setColor(QPalette::WindowText, - COLOR_HYPERLINK); // Apple blue + githubPalette.setColor(QPalette::WindowText, COLOR_HYPERLINK); m_githubLabel->setPalette(githubPalette); m_mainLayout->addWidget(m_githubLabel, 0, Qt::AlignCenter); diff --git a/src/wirelessgalleryimportwidget.cpp b/src/wirelessgalleryimportwidget.cpp index 8a2c4f3..5fc3cb0 100644 --- a/src/wirelessgalleryimportwidget.cpp +++ b/src/wirelessgalleryimportwidget.cpp @@ -190,7 +190,7 @@ void WirelessGalleryImportWidget::onImportPhotos() return; } - PhotoImportDialog dialog(m_selectedFiles, false, this); + PhotoImportDialog dialog(m_selectedFiles, this); dialog.exec(); } diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp index 3925032..5266712 100644 --- a/src/zloadingwidget.cpp +++ b/src/zloadingwidget.cpp @@ -22,7 +22,13 @@ ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) loadingLayout->addWidget(m_loadingIndicator); loadingLayout->addStretch(); - addWidget(loadingWidget); // Loading widget at index 0 + m_errorWidget = new ZLoadingErrorWidget(this); + connect(static_cast(m_errorWidget), + &ZLoadingErrorWidget::retryClicked, this, + [this]() { emit retryClicked(); }); + + addWidget(loadingWidget); + addWidget(m_errorWidget); } void ZLoadingWidget::setupContentWidget(QWidget *contentWidget) @@ -41,32 +47,24 @@ void ZLoadingWidget::setupContentWidget(QLayout *contentLayout) void ZLoadingWidget::setupErrorWidget(QWidget *errorWidget) { + if (m_errorWidget) { + m_errorWidget->deleteLater(); + } m_errorWidget = errorWidget; addWidget(m_errorWidget); } void ZLoadingWidget::setupErrorWidget(QLayout *errorLayout) { + if (m_errorWidget) { + m_errorWidget->deleteLater(); + } m_errorWidget = new QWidget(); m_errorWidget->setLayout(errorLayout); addWidget(m_errorWidget); } -void ZLoadingWidget::setupErrorWidget(const QString &errorMessage) -{ - m_errorWidget = new QWidget(); - QVBoxLayout *errorLayout = new QVBoxLayout(m_errorWidget); - errorLayout->setAlignment(Qt::AlignCenter); - - QLabel *errorLabel = new QLabel(errorMessage); - errorLabel->setAlignment(Qt::AlignCenter); - errorLabel->setStyleSheet("QLabel { color: red; }"); - errorLayout->addWidget(errorLabel); - - addWidget(m_errorWidget); -} - void ZLoadingWidget::setupAditionalWidget(QWidget *customWidget) { addWidget(customWidget); @@ -76,6 +74,9 @@ void ZLoadingWidget::switchToWidget(QWidget *widget) { int index = indexOf(widget); if (index != -1) { + if (m_loadingIndicator) { + m_loadingIndicator->stop(); + } setCurrentIndex(index); } } @@ -85,9 +86,8 @@ void ZLoadingWidget::stop(bool showContent) if (m_loadingIndicator) { m_loadingIndicator->stop(); } - if (showContent) { - // FIXME: dont use hardcoded index - setCurrentIndex(1); + if (showContent && m_contentWidget) { + switchToWidget(m_contentWidget); } } @@ -99,13 +99,26 @@ void ZLoadingWidget::showError() } } +void ZLoadingWidget::showError(const QString &errorMessage) +{ + m_loadingIndicator->stop(); + if (m_errorWidget) { + // FIXME: can be handled better + // maybe subclass ZLoadingWidget for custom error widget? + if (auto errorWidget = + qobject_cast(m_errorWidget)) { + errorWidget->setText(errorMessage); + } + setCurrentWidget(m_errorWidget); + } +} + void ZLoadingWidget::showLoading() { if (m_loadingIndicator) { m_loadingIndicator->start(); } - // FIXME: dont use hardcoded index - setCurrentIndex(0); + setCurrentWidget(m_loadingIndicator->parentWidget()); } ZLoadingWidget::~ZLoadingWidget() diff --git a/src/zloadingwidget.h b/src/zloadingwidget.h index f046715..5029bdd 100644 --- a/src/zloadingwidget.h +++ b/src/zloadingwidget.h @@ -1,8 +1,41 @@ #ifndef ZLOADINGWIDGET_H #define ZLOADINGWIDGET_H +#include +#include #include #include +#include + +class ZLoadingErrorWidget : public QWidget +{ + Q_OBJECT +public: + ZLoadingErrorWidget(QWidget *parent = nullptr) + { + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->addStretch(); + m_errorLabel = new QLabel("An error occurred.", this); + m_errorLabel->setAlignment(Qt::AlignCenter); + m_errorLabel->setStyleSheet("QLabel { color: red; }"); + layout->addWidget(m_errorLabel); + m_retryButton = new QPushButton("Retry", this); + m_retryButton->setMaximumWidth(m_retryButton->sizeHint().width()); + layout->addWidget(m_retryButton); + layout->addStretch(); + connect(m_retryButton, &QPushButton::clicked, this, + [this]() { emit retryClicked(); }); + } + + void setText(const QString &text) { m_errorLabel->setText(text); }; + +private: + QLabel *m_errorLabel; + QPushButton *m_retryButton; +signals: + void retryClicked(); +}; class ZLoadingWidget : public QStackedWidget { @@ -15,17 +48,18 @@ public: 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(); + void showError(const QString &errorMessage); private: class QProcessIndicator *m_loadingIndicator = nullptr; QWidget *m_contentWidget = nullptr; QWidget *m_errorWidget = nullptr; signals: + void retryClicked(); }; #endif // ZLOADINGWIDGET_H