diff --git a/.gitmodules b/.gitmodules
index 6c0d5e7..a7feee4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -10,3 +10,6 @@
[submodule "lib/ipatool-go"]
path = lib/ipatool-go
url = https://github.com/uncor3/libipatool-go.git
+[submodule "lib/zupdater"]
+ path = lib/zupdater
+ url = https://github.com/uncor3/ZUpdater
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index be8b15b..0b6e02f 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -1,22 +1,25 @@
{
- "configurations": [
- {
- "name": "Linux",
- "includePath": [
- "${workspaceFolder}/**",
- "/usr/include/qt/**",
- "/usr/local/opt/qt/**",
- "${workspaceFolder}/build/Desktop-Debug/iDescriptor_autogen/include",
- "${workspaceFolder}/build/Desktop-Debug/src/lib/**",
- "/usr/local/include/**",
- "/usr/include/qt6/**"
- ],
- "defines": [],
- "compilerPath": "/usr/bin/gcc",
- "cStandard": "c17",
- "cppStandard": "gnu++17",
- "intelliSenseMode": "linux-gcc-x64"
- }
- ],
- "version": 4
-}
+ "configurations": [
+ {
+ "name": "Linux",
+ "includePath": [
+ "${workspaceFolder}/**",
+ "/usr/include/qt/**",
+ "/usr/local/opt/qt/**",
+ "${workspaceFolder}/build/Desktop-Debug/iDescriptor_autogen/include",
+ "${workspaceFolder}/build/Desktop-Debug/src/lib/**",
+ "/usr/local/include/**",
+ "/usr/include/qt6/**",
+ "/usr/include/qt6/QtBluetooth/**",
+ "${workspaceFolder}/lib/zupdater/src",
+ "/usr/include/qt6/QtConcurrent"
+ ],
+ "defines": [],
+ "compilerPath": "/usr/bin/gcc",
+ "cStandard": "c17",
+ "cppStandard": "gnu++17",
+ "intelliSenseMode": "linux-gcc-x64"
+ }
+ ],
+ "version": 4
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 72a9d0f..4ec39cc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -167,6 +167,41 @@
"qvideowidget": "cpp",
"qhostaddress": "cpp",
"qtcpsocket": "cpp",
- "qstackedwidget": "cpp"
+ "qstackedwidget": "cpp",
+ "qnetworkrequest": "cpp",
+ "qdesktopservices": "cpp",
+ "qsettings": "cpp",
+ "qpropertyanimation": "cpp",
+ "qquickitem": "cpp",
+ "qparallelanimationgroup": "cpp",
+ "qbluetoothdeviceinfo": "cpp",
+ "qlowenergyadvertisingparameters": "cpp",
+ "qlowenergycontroller": "cpp",
+ "qlowenergydescriptordata": "cpp",
+ "qquickstyle": "cpp",
+ "qcheckbox": "cpp",
+ "qpixmap": "cpp",
+ "qstyle": "cpp",
+ "qpainterpath": "cpp",
+ "qpainter": "cpp",
+ "qenterevent": "cpp",
+ "qfiledialog": "cpp",
+ "qgraphicsscene": "cpp",
+ "qpoint": "cpp",
+ "qprocessenvironment": "cpp",
+ "qprocess": "cpp",
+ "qsplitter": "cpp",
+ "qinputdialog": "cpp",
+ "qtreewidget": "cpp",
+ "qmenu": "cpp",
+ "qlistview": "cpp",
+ "qmutexlocker": "cpp",
+ "qmenubar": "cpp",
+ "qstack": "cpp",
+ "qstandarditemmodel": "cpp",
+ "qimage": "cpp",
+ "qabstractbutton": "cpp",
+ "qtnetwork": "cpp",
+ "qtcore": "cpp"
}
}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c5f1828..46e292b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -187,7 +187,7 @@ endif()
add_subdirectory(lib/airplay)
add_subdirectory(lib/ipatool-go)
-
+add_subdirectory(lib/zupdater)
if (WIN32)
set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/idescriptor.rc")
@@ -243,8 +243,13 @@ target_link_libraries(iDescriptor PRIVATE
PkgConfig::ZIP
airplay
ipatool-go
+ ZUpdater
)
-
+
+target_include_directories(iDescriptor PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/lib/zupdater/src
+)
+
if(APPLE)
find_library(CORE_SERVICES_FRAMEWORK CoreServices REQUIRED)
target_link_libraries(iDescriptor PRIVATE
diff --git a/lib/zupdater b/lib/zupdater
new file mode 160000
index 0000000..31ccc0b
--- /dev/null
+++ b/lib/zupdater
@@ -0,0 +1 @@
+Subproject commit 31ccc0bcaebecc482c735e46b00cf4def75718cb
diff --git a/resources.qrc b/resources.qrc
index c32814f..dccfbe9 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -17,6 +17,7 @@
resources/icons/MdiLightMagnify.png
resources/icons/IcBaselineInsertDriveFile.png
resources/icons/MaterialSymbolsLightHome.png
+ resources/icons/MdiGithub.png
resources/icons/app-icon/icon.ico
qml/MapView.qml
@@ -48,5 +49,6 @@
resources/connect.png
resources/airplayer-tutorial.mp4
resources/ipad-mockups/ipad.png
+ resources/DeveloperDiskImages.json
\ No newline at end of file
diff --git a/resources/DeveloperDiskImages.json b/resources/DeveloperDiskImages.json
new file mode 100644
index 0000000..39118b1
--- /dev/null
+++ b/resources/DeveloperDiskImages.json
@@ -0,0 +1,413 @@
+{
+ "10.0": {
+ "Image": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/raw/master/Developer%20Disk%20Image/10.0%20(14A345)/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/raw/master/Developer%20Disk%20Image/10.0%20(14A345)/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "10.1": {
+ "Image": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/raw/master/Developer%20Disk%20Image/10.1%20(14B72)/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/raw/master/Developer%20Disk%20Image/10.1%20(14B72)/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "10.2": {
+ "Image": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/raw/master/Developer%20Disk%20Image/10.2%20(14C5062c)/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/raw/master/Developer%20Disk%20Image/10.2%20(14C5062c)/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "10.3": {
+ "Image": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/blob/master/Developer%20Disk%20Image/10.3%20(14E269)/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/xushuduo/Xcode-iOS-Developer-Disk-Image/blob/master/Developer%20Disk%20Image/10.3%20(14E269)/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "11.0": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "11.1": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.1/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.1/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "11.2": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "11.3": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.3/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.3/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "11.4": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/11.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "12.0": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "12.1": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.1/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.1/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "12.2": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "12.3": {
+ "Image": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.3/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/luannguyenkhoa/LocationSimulator/raw/master/LocationSimulator/12.3/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "12.4": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/12/12.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/12/12.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "12.5": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/12/12.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/12/12.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.0": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.1": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.1/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.1/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.2": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.3": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.3/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.3/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.4": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.5": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.6": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.6/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.6/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "13.7": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.7/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/13/13.7/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.0": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.1": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.1/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.1/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.2": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.3": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.3/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.3/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.4": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.5": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg"
+ ]
+ },
+ "14.6": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.7": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "14.8": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/14/14.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.0": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.1": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.2": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.3": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.4": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.5": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.6": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.6/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.6/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.7": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.7/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.7/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "15.8": {
+ "Image": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/pdso/DeveloperDiskImage/raw/master/15/15.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.0": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.0/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.0/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.1": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.1/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.1/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.2": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.2/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.2/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.3": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.3/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.3/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.4": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.4/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.4/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.5": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.6": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "16.7": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.5/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/16.5/DeveloperDiskImage.dmg.signature"
+ ]
+ },
+ "17.0": {
+ "Image": [
+ "https://github.com/doronz88/DeveloperDiskImage/raw/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg"
+ ],
+ "Trustcache": [
+ "https://github.com/doronz88/DeveloperDiskImage/raw/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache"
+ ],
+ "BuildManifest": [
+ "https://github.com/doronz88/DeveloperDiskImage/raw/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist"
+ ]
+ },
+ "Fallback": {
+ "Image": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/%@/DeveloperDiskImage.dmg"
+ ],
+ "Signature": [
+ "https://github.com/mspvirajpatel/Xcode_Developer_Disk_Images/raw/master/Developer%20Disk%20Image/%@/DeveloperDiskImage.dmg.signature"
+ ]
+ }
+}
diff --git a/resources/icons/MdiGithub.png b/resources/icons/MdiGithub.png
new file mode 100644
index 0000000..8c3db86
Binary files /dev/null and b/resources/icons/MdiGithub.png differ
diff --git a/src/afcexplorerwidget.h b/src/afcexplorerwidget.h
index eb78abf..6ac374d 100644
--- a/src/afcexplorerwidget.h
+++ b/src/afcexplorerwidget.h
@@ -32,6 +32,7 @@ public:
afc_client_t afcClient = nullptr,
QString root = "/", QWidget *parent = nullptr);
void navigateToPath(const QString &path);
+ void goHome();
signals:
void favoritePlaceAdded(const QString &alias, const QString &path);
@@ -96,7 +97,6 @@ private:
const char *local_path);
void updateNavStyles();
void updateButtonStates();
- void goHome();
void goUp();
};
diff --git a/src/appcontext.cpp b/src/appcontext.cpp
index 956498a..04959a7 100644
--- a/src/appcontext.cpp
+++ b/src/appcontext.cpp
@@ -246,9 +246,14 @@ AppContext::~AppContext()
void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection)
{
+ qDebug() << "New selection -"
+ << " Type:" << selection.type
+ << " UUID:" << QString::fromStdString(selection.uuid)
+ << " ECID:" << selection.ecid << " Section:" << selection.section;
if (m_currentSelection.uuid == selection.uuid &&
m_currentSelection.ecid == selection.ecid &&
m_currentSelection.section == selection.section) {
+ qDebug() << "setCurrentDeviceSelection: No change in selection";
return; // No change
}
m_currentSelection = selection;
diff --git a/src/appinstalldialog.cpp b/src/appinstalldialog.cpp
index be15062..0f29c5c 100644
--- a/src/appinstalldialog.cpp
+++ b/src/appinstalldialog.cpp
@@ -8,6 +8,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -27,6 +29,28 @@ AppInstallDialog::AppInstallDialog(const QString &appName,
// App info section
QHBoxLayout *appInfoLayout = new QHBoxLayout();
QLabel *iconLabel = new QLabel();
+ fetchAppIconFromApple(
+ bundleId,
+ [iconLabel](const QPixmap &pixmap) {
+ if (!pixmap.isNull()) {
+ QPixmap scaled =
+ pixmap.scaled(64, 64, Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ QPixmap rounded(64, 64);
+ rounded.fill(Qt::transparent);
+
+ QPainter painter(&rounded);
+ painter.setRenderHint(QPainter::Antialiasing);
+ QPainterPath path;
+ path.addRoundedRect(QRectF(0, 0, 64, 64), 16, 16);
+ painter.setClipPath(path);
+ painter.drawPixmap(0, 0, scaled);
+ painter.end();
+
+ iconLabel->setPixmap(rounded);
+ }
+ },
+ this);
QPixmap icon = QApplication::style()
->standardIcon(QStyle::SP_ComputerIcon)
.pixmap(64, 64);
diff --git a/src/appswidget.cpp b/src/appswidget.cpp
index 959c3de..0abd909 100644
--- a/src/appswidget.cpp
+++ b/src/appswidget.cpp
@@ -6,10 +6,12 @@
#include "appstoremanager.h"
#include "iDescriptor-ui.h"
#include "logindialog.h"
+#include "sponsorwidget.h"
#include "zlineedit.h"
#include
#include
#include
+#include
#include
#include
#include
@@ -23,11 +25,15 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -35,7 +41,90 @@
#include
#include
+// Helper struct for semantic version comparison
+struct AppVersion {
+ int major = 0;
+ int minor = 0;
+ int patch = 0;
+
+ static AppVersion fromString(QString versionString)
+ {
+ // Keep only digits and dots for comparison
+ versionString.remove(QRegularExpression("[^\\d.]"));
+ AppVersion v;
+ QStringList parts = versionString.split('.');
+ if (parts.size() > 0)
+ v.major = parts[0].toInt();
+ if (parts.size() > 1)
+ v.minor = parts[1].toInt();
+ if (parts.size() > 2)
+ v.patch = parts[2].toInt();
+ return v;
+ }
+
+ bool operator<(const AppVersion &other) const
+ {
+ if (major != other.major)
+ return major < other.major;
+ if (minor != other.minor)
+ return minor < other.minor;
+ return patch < other.patch;
+ }
+
+ bool operator==(const AppVersion &other) const
+ {
+ return major == other.major && minor == other.minor &&
+ patch == other.patch;
+ }
+
+ bool operator>(const AppVersion &other) const
+ {
+ return !(*this < other || *this == other);
+ }
+ bool operator<=(const AppVersion &other) const
+ {
+ return (*this < other || *this == other);
+ }
+ bool operator>=(const AppVersion &other) const { return !(*this < other); }
+};
+
+// Checks if the current app version matches a given version condition
+bool versionMatches(const QString ¤tVersionStr,
+ const QString &conditionStr)
+{
+ AppVersion currentVersion = AppVersion::fromString(currentVersionStr);
+ AppVersion conditionVersion = AppVersion::fromString(conditionStr);
+
+ if (conditionStr.startsWith("<="))
+ return currentVersion <= conditionVersion;
+ if (conditionStr.startsWith(">="))
+ return currentVersion >= conditionVersion;
+ if (conditionStr.startsWith("<"))
+ return currentVersion < conditionVersion;
+ if (conditionStr.startsWith(">"))
+ return currentVersion > conditionVersion;
+
+ // Exact match
+ return currentVersion == conditionVersion;
+}
+
+QJsonObject getVersionedConfig(const QJsonObject &rootObj)
+{
+ QStringList keys = rootObj.keys();
+ for (const QString &key : keys) {
+ if (versionMatches(APP_VERSION, key)) {
+ return rootObj[key].toObject();
+ }
+ }
+}
+
// watch for login and logout events
+AppsWidget *AppsWidget::sharedInstance()
+{
+ static AppsWidget instance;
+ return &instance;
+}
+
AppsWidget::AppsWidget(QWidget *parent) : QWidget(parent), m_isLoggedIn(false)
{
m_debounceTimer = new QTimer(this);
@@ -47,8 +136,9 @@ void AppsWidget::setupUI()
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
-
// Header with login
+ m_networkManager = new QNetworkAccessManager(this);
+
QWidget *headerWidget = new QWidget();
headerWidget->setFixedHeight(60);
headerWidget->setStyleSheet("border-bottom: 1px solid #363d32;");
@@ -110,7 +200,7 @@ void AppsWidget::setupUI()
mainLayout->addWidget(m_stackedWidget);
// Show default apps initially
- showDefaultApps();
+ showLoading("Loading apps...");
// Connections
connect(m_loginButton, &QPushButton::clicked, this,
&AppsWidget::onLoginClicked);
@@ -123,6 +213,60 @@ void AppsWidget::setupUI()
&AppsWidget::onAppStoreInitialized);
connect(m_manager, &AppStoreManager::loggedOut, this,
&AppsWidget::onAppStoreInitialized);
+
+ // fetch sponsors
+ QUrl sponsorsUrl("http://localhost:5173/sponsors.json");
+ QNetworkRequest request(sponsorsUrl);
+ QNetworkReply *reply = m_networkManager->get(request);
+ connect(reply, &QNetworkReply::finished, this, [this, reply]() {
+ try {
+ if (reply->error() == QNetworkReply::NoError) {
+ QByteArray responseData = reply->readAll();
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
+ if (jsonDoc.isNull() || !jsonDoc.isObject()) {
+ qDebug() << "Failed to parse sponsors JSON";
+ showDefaultApps();
+ reply->deleteLater();
+ return;
+ }
+
+ QJsonObject rootObj = jsonDoc.object();
+ QJsonObject versioned = getVersionedConfig(rootObj);
+
+ if (versioned.isEmpty()) {
+ qDebug() << "No sponsor configuration found for version"
+ << APP_VERSION << "or default.";
+ showDefaultApps();
+ reply->deleteLater();
+ return;
+ }
+
+ QJsonObject sponsorObj = versioned["sponsors"].toObject();
+ QJsonObject platinumObj = sponsorObj["platinum"].toObject();
+ QJsonObject goldObj = sponsorObj["gold"].toObject();
+ QJsonObject silverObj = sponsorObj["silver"].toObject();
+ QJsonObject bronzeObj = sponsorObj["bronze"].toObject();
+
+ // Store the platinum members to be used when populating the
+ // grid
+ m_platinumSponsors = platinumObj["members"].toArray();
+ m_goldSponsors = goldObj["members"].toArray();
+ m_silverSponsors = silverObj["members"].toArray();
+ m_bronzeSponsors = bronzeObj["members"].toArray();
+
+ if (!m_platinumSponsors.isEmpty()) {
+ qDebug() << "Platinum Sponsors found";
+ }
+ }
+ qDebug() << "Sponsors fetch completed";
+ showDefaultApps();
+ reply->deleteLater();
+ } catch (...) {
+ qDebug() << "Exception occurred while processing sponsors";
+ showDefaultApps();
+ reply->deleteLater();
+ }
+ });
}
void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo)
@@ -242,26 +386,102 @@ void AppsWidget::populateDefaultApps()
if (!gridLayout)
return;
- // Create sample app cards
- createAppCard("Instagram", "com.burbn.instagram",
- "Photo & Video sharing social network", "", gridLayout, 0, 0);
- createAppCard("WhatsApp", "net.whatsapp.WhatsApp",
- "Free messaging and video calling", "", gridLayout, 0, 1);
- createAppCard("Spotify", "com.spotify.client",
- "Music streaming and podcast platform", "", gridLayout, 0, 2);
- createAppCard("YouTube", "com.google.ios.youtube",
- "Video sharing and streaming platform", "", gridLayout, 1, 0);
- createAppCard("X", "com.atebits.Tweetie2", "Social media and microblogging",
- "", gridLayout, 1, 1);
- createAppCard("TikTok", "com.zhiliaoapp.musically",
- "Short-form video hosting service", "", gridLayout, 1, 2);
- createAppCard("Twitch", "tv.twitch", "Live streaming platform", "",
- gridLayout, 2, 0);
- createAppCard("Telegram", "ph.telegra.Telegraph",
- "Cloud-based instant messaging", "", gridLayout, 2, 1);
- createAppCard("Reddit", "com.reddit.Reddit",
- "Social news aggregation platform", "", gridLayout, 2, 2);
+ int row = 0;
+ int col = 0;
+ const int maxCols = 3;
+ // Helper lambda to advance the grid position
+ auto advanceGridPos = [&]() {
+ col++;
+ if (col >= maxCols) {
+ col = 0;
+ row++;
+ }
+ };
+
+ for (const QJsonValue &sponsorValue : m_platinumSponsors) {
+ QJsonObject sponsorObj = sponsorValue.toObject();
+ QString name = sponsorObj.value("name").toString();
+ QString bundleId = sponsorObj.value("bundleId").toString();
+ QString logoUrl = sponsorObj.value("logo").toString();
+ QString description = sponsorObj.value("description").toString();
+ QString url = sponsorObj.value("url").toString();
+
+ createAppCard(name, bundleId, description, logoUrl, url, gridLayout,
+ row, col, SponsorType(SponsorType::Platinum));
+ advanceGridPos();
+ }
+
+ for (const QJsonValue &sponsorValue : m_goldSponsors) {
+ QJsonObject sponsorObj = sponsorValue.toObject();
+ QString name = sponsorObj.value("name").toString();
+ QString bundleId = sponsorObj.value("bundleId").toString();
+ QString description = sponsorObj.value("description").toString();
+ QString logoUrl = sponsorObj.value("logo").toString();
+ QString url = sponsorObj.value("url").toString();
+ createAppCard(name, bundleId, description, logoUrl, url, gridLayout,
+ row, col, SponsorType(SponsorType::Gold));
+ advanceGridPos();
+ }
+
+ if (m_platinumSponsors.empty() && m_goldSponsors.empty()) {
+ createSponsorCard(gridLayout, row, col);
+ advanceGridPos();
+ }
+
+ createAppCard("Instagram", "com.burbn.instagram",
+ "Photo & Video sharing social network", "", "", gridLayout,
+ row, col);
+ advanceGridPos();
+ createAppCard("Spotify", "com.spotify.client",
+ "Music streaming and podcast platform", "", "", gridLayout,
+ row, col);
+ advanceGridPos();
+ createAppCard("YouTube", "com.google.ios.youtube",
+ "Video sharing and streaming platform", "", "", gridLayout,
+ row, col);
+ advanceGridPos();
+ createAppCard("X", "com.atebits.Tweetie2", "Social media and microblogging",
+ "", "", gridLayout, row, col);
+ advanceGridPos();
+ createAppCard("TikTok", "com.zhiliaoapp.musically",
+ "Short-form video hosting service", "", "", gridLayout, row,
+ col);
+ advanceGridPos();
+ createAppCard("Twitch", "tv.twitch", "Live streaming platform", "", "",
+ gridLayout, row, col);
+ advanceGridPos();
+ createAppCard("Telegram", "ph.telegra.Telegraph",
+ "Cloud-based instant messaging", "", "", gridLayout, row,
+ col);
+ advanceGridPos();
+ createAppCard("Reddit", "com.reddit.Reddit",
+ "Social news aggregation platform", "", "", gridLayout, row,
+ col);
+ advanceGridPos();
+
+ for (const QJsonValue &sponsorValue : m_silverSponsors) {
+ QJsonObject sponsorObj = sponsorValue.toObject();
+ QString name = sponsorObj.value("name").toString();
+ QString bundleId = sponsorObj.value("bundleId").toString();
+ QString description = sponsorObj.value("description").toString();
+ QString url = sponsorObj.value("url").toString();
+
+ createAppCard(name, bundleId, description, "", url, gridLayout, row,
+ col, SponsorType(SponsorType::Silver));
+ advanceGridPos();
+ }
+
+ for (const QJsonValue &sponsorValue : m_bronzeSponsors) {
+ QJsonObject sponsorObj = sponsorValue.toObject();
+ QString name = sponsorObj.value("name").toString();
+ QString bundleId = sponsorObj.value("bundleId").toString();
+ QString description = sponsorObj.value("description").toString();
+ QString url = sponsorObj.value("url").toString();
+ createAppCard(name, bundleId, description, "", url, gridLayout, row,
+ col, SponsorType(SponsorType::Bronze));
+ advanceGridPos();
+ }
gridLayout->setRowStretch(gridLayout->rowCount(), 1);
}
@@ -281,10 +501,37 @@ void AppsWidget::clearAppGrid()
}
}
+void AppsWidget::createSponsorCard(QGridLayout *gridLayout, int row, int col)
+{
+ if (!gridLayout)
+ return;
+
+ ClickableWidget *sponsorCard = new ClickableWidget();
+ sponsorCard->setStyleSheet("border: 1px solid #ddd; border-radius: 8px;");
+ sponsorCard->setCursor(Qt::PointingHandCursor);
+ connect(sponsorCard, &ClickableWidget::clicked, this, [this]() {
+ auto sWidget = new SponsorWidget();
+ sWidget->setAttribute(Qt::WA_DeleteOnClose);
+ sWidget->show();
+ });
+ QVBoxLayout *sponsorLayout = new QVBoxLayout(sponsorCard);
+ sponsorLayout->setContentsMargins(12, 12, 12, 12);
+ sponsorLayout->setSpacing(8);
+
+ QLabel *sponsorLabel = new QLabel("Become a Sponsor!");
+ sponsorLabel->setAlignment(Qt::AlignCenter);
+ sponsorLabel->setStyleSheet("font-size: 14px; font-weight: bold;");
+ sponsorLayout->addWidget(sponsorLabel);
+
+ gridLayout->addWidget(sponsorCard, row, col);
+}
+
void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
const QString &description,
- const QString &iconPath, QGridLayout *gridLayout,
- int row, int col)
+ const QString &logoUrl,
+ const QString &websiteUrl,
+ QGridLayout *gridLayout, int row, int col,
+ const SponsorType &sponsorType)
{
QWidget *cardWidget = new QWidget();
@@ -301,74 +548,146 @@ void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
iconLabel->setAlignment(Qt::AlignCenter);
cardLayout->addWidget(iconLabel);
- fetchAppIconFromApple(
- bundleId,
- [iconLabel](const QPixmap &pixmap) {
- if (!pixmap.isNull()) {
- QPixmap scaled =
- pixmap.scaled(64, 64, Qt::KeepAspectRatioByExpanding,
- Qt::SmoothTransformation);
- QPixmap rounded(64, 64);
- rounded.fill(Qt::transparent);
+ // If logoUrl is provided and bundleId is empty, use the logo directly
+ if (!logoUrl.isEmpty()) {
+ QUrl url(logoUrl);
+ QNetworkRequest request(url);
+ QNetworkReply *reply = m_networkManager->get(request);
+ connect(reply, &QNetworkReply::finished, [reply, iconLabel]() {
+ if (reply->error() == QNetworkReply::NoError) {
+ QByteArray data = reply->readAll();
+ QPixmap pixmap;
+ if (pixmap.loadFromData(data)) {
+ QPixmap scaled =
+ pixmap.scaled(64, 64, Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ QPixmap rounded(64, 64);
+ rounded.fill(Qt::transparent);
- QPainter painter(&rounded);
- painter.setRenderHint(QPainter::Antialiasing);
- QPainterPath path;
- path.addRoundedRect(QRectF(0, 0, 64, 64), 16, 16);
- painter.setClipPath(path);
- painter.drawPixmap(0, 0, scaled);
- painter.end();
+ QPainter painter(&rounded);
+ painter.setRenderHint(QPainter::Antialiasing);
+ QPainterPath path;
+ path.addRoundedRect(QRectF(0, 0, 64, 64), 16, 16);
+ painter.setClipPath(path);
+ painter.drawPixmap(0, 0, scaled);
+ painter.end();
- iconLabel->setPixmap(rounded);
+ iconLabel->setPixmap(rounded);
+ }
}
- },
- cardWidget);
+ reply->deleteLater();
+ });
+ } else if (!bundleId.isEmpty()) {
+ // Use Apple's API for app icons
+ fetchAppIconFromApple(
+ bundleId,
+ [iconLabel](const QPixmap &pixmap) {
+ if (!pixmap.isNull()) {
+ QPixmap scaled =
+ pixmap.scaled(64, 64, Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ QPixmap rounded(64, 64);
+ rounded.fill(Qt::transparent);
+
+ QPainter painter(&rounded);
+ painter.setRenderHint(QPainter::Antialiasing);
+ QPainterPath path;
+ path.addRoundedRect(QRectF(0, 0, 64, 64), 16, 16);
+ painter.setClipPath(path);
+ painter.drawPixmap(0, 0, scaled);
+ painter.end();
+
+ iconLabel->setPixmap(rounded);
+ }
+ },
+ cardWidget);
+ }
// Vertical layout for name and description
QVBoxLayout *textLayout = new QVBoxLayout();
- // App name
+ // App name with sponsor indicator
+ QHBoxLayout *nameLayout = new QHBoxLayout();
QLabel *nameLabel = new QLabel(name);
nameLabel->setStyleSheet("font-size: 16px;");
- nameLabel->setAlignment(Qt::AlignCenter);
nameLabel->setWordWrap(true);
- textLayout->addWidget(nameLabel);
+ nameLayout->addWidget(nameLabel);
+
+ // Add sponsor type indicator
+ if (!sponsorType.isEmpty()) {
+ QLabel *sponsorLabel = new QLabel(sponsorType.name);
+ QString textColor = (sponsorType.level == SponsorType::Platinum ||
+ sponsorType.level == SponsorType::Silver)
+ ? "#333"
+ : "white";
+ sponsorLabel->setStyleSheet(QString("font-size: 10px; "
+ "font-weight: bold; "
+ "color: %1; "
+ "background-color: %2; "
+ "border-radius: 4px; "
+ "padding: 2px 6px; "
+ "margin-left: 8px;")
+ .arg(textColor, sponsorType.color));
+ sponsorLabel->setAlignment(Qt::AlignCenter);
+ nameLayout->addWidget(sponsorLabel);
+ }
+
+ nameLayout->addStretch();
+ textLayout->addLayout(nameLayout);
// App description
QLabel *descLabel = new QLabel(description);
descLabel->setStyleSheet("font-size: 12px; color: #666;");
- descLabel->setAlignment(Qt::AlignCenter);
+ descLabel->setAlignment(Qt::AlignLeft);
descLabel->setWordWrap(true);
textLayout->addWidget(descLabel);
- cardLayout->addStretch();
cardLayout->addLayout(textLayout);
QVBoxLayout *buttonsLayout = new QVBoxLayout();
// Install button placeholder
- ZLabel *installLabel = new ZLabel("Install");
- installLabel->setAlignment(Qt::AlignCenter);
- installLabel->setStyleSheet("font-size: 12px; color: #007AFF; font-weight: "
- "bold; background-color: transparent;");
- installLabel->setCursor(Qt::PointingHandCursor);
- installLabel->setFixedHeight(30);
+ if (!bundleId.isEmpty()) {
+ ZLabel *installLabel = new ZLabel("Install");
+ installLabel->setAlignment(Qt::AlignCenter);
+ installLabel->setStyleSheet(
+ "font-size: 12px; color: #007AFF; font-weight: "
+ "bold; background-color: transparent;");
+ installLabel->setCursor(Qt::PointingHandCursor);
+ installLabel->setFixedHeight(30);
- ZLabel *downloadIpaLabel = new ZLabel("Download IPA");
- downloadIpaLabel->setAlignment(Qt::AlignCenter);
- downloadIpaLabel->setCursor(Qt::PointingHandCursor);
+ buttonsLayout->addStretch();
+ buttonsLayout->addWidget(installLabel);
+ connect(installLabel, &ZLabel::clicked, this,
+ [this, name, bundleId, description]() {
+ onAppCardClicked(name, bundleId, description);
+ });
+ }
+ if (websiteUrl.isEmpty()) {
+ ZLabel *downloadIpaLabel = new ZLabel("Download IPA");
+ downloadIpaLabel->setAlignment(Qt::AlignCenter);
+ downloadIpaLabel->setStyleSheet("font-size: 12px; font-weight: "
+ "bold; background-color: transparent;");
+ downloadIpaLabel->setCursor(Qt::PointingHandCursor);
- connect(installLabel, &ZLabel::clicked, this,
- [this, name, bundleId, description]() {
- onAppCardClicked(name, bundleId, description);
- });
-
- connect(downloadIpaLabel, &ZLabel::clicked, this,
+ connect(
+ downloadIpaLabel, &ZLabel::clicked, this,
[this, name, bundleId]() { onDownloadIpaClicked(name, bundleId); });
- buttonsLayout->addStretch();
- buttonsLayout->addWidget(installLabel);
- buttonsLayout->addWidget(downloadIpaLabel);
+ buttonsLayout->addWidget(downloadIpaLabel);
+ } else {
+ ZLabel *websiteLabel = new ZLabel("Website");
+ websiteLabel->setStyleSheet("font-size: 12px; font-weight: "
+ "bold; background-color: transparent;");
+ websiteLabel->setAlignment(Qt::AlignCenter);
+ websiteLabel->setCursor(Qt::PointingHandCursor);
+
+ connect(websiteLabel, &ZLabel::clicked, this, [this, websiteUrl]() {
+ QDesktopServices::openUrl(QUrl(websiteUrl));
+ });
+ buttonsLayout->addWidget(websiteLabel);
+ }
+
buttonsLayout->addStretch();
cardLayout->addLayout(buttonsLayout);
@@ -434,7 +753,6 @@ void AppsWidget::onSearchTextChanged() { m_debounceTimer->start(300); }
void AppsWidget::performSearch()
{
-
QString searchTerm = m_searchEdit->text().trimmed();
if (searchTerm.isEmpty()) {
showDefaultApps();
@@ -510,7 +828,8 @@ void AppsWidget::onSearchFinished(bool success, const QString &results)
QString bundleId = appObj.value("bundleId").toString();
QString description = "Version: " + appObj.value("version").toString();
- createAppCard(name, bundleId, description, "", gridLayout, row, col);
+ createAppCard(name, bundleId, description, "", "", gridLayout, row,
+ col);
col++;
if (col >= maxCols) {
diff --git a/src/appswidget.h b/src/appswidget.h
index 9c13b4d..469dac1 100644
--- a/src/appswidget.h
+++ b/src/appswidget.h
@@ -9,8 +9,10 @@
#include
#include
#include
+#include
#include
#include
+#include
#include
#include
#include
@@ -20,16 +22,52 @@
#include
#include
+struct SponsorType {
+ enum Level { None, Bronze, Silver, Gold, Platinum };
+
+ Level level;
+ QString name;
+ QString color;
+
+ SponsorType(Level l = None) : level(l)
+ {
+ switch (l) {
+ case Platinum:
+ name = "PLATINUM";
+ color = "#E5E4E2"; // Platinum silver-white
+ break;
+ case Gold:
+ name = "GOLD";
+ color = "#FFD700"; // Gold
+ break;
+ case Silver:
+ name = "SILVER";
+ color = "#C0C0C0"; // Silver
+ break;
+ case Bronze:
+ name = "BRONZE";
+ color = "#CD7F32"; // Bronze
+ break;
+ default:
+ name = "";
+ color = "";
+ break;
+ }
+ }
+
+ bool isEmpty() const { return level == None; }
+};
+
class AppsWidget : public QWidget
{
Q_OBJECT
public:
explicit AppsWidget(QWidget *parent = nullptr);
-
-private slots:
- void onLoginClicked();
+ static AppsWidget *sharedInstance();
void onAppCardClicked(const QString &appName, const QString &bundleId,
const QString &description);
+private slots:
+ void onLoginClicked();
void onDownloadIpaClicked(const QString &name, const QString &bundleId);
void onSearchTextChanged();
void performSearch();
@@ -39,8 +77,10 @@ private slots:
private:
void setupUI();
void createAppCard(const QString &name, const QString &bundleId,
- const QString &description, const QString &iconPath,
- QGridLayout *gridLayout, int row, int col);
+ const QString &description, const QString &logoUrl,
+ const QString &websiteUrl, QGridLayout *gridLayout,
+ int row, int col,
+ const SponsorType &sponsorType = SponsorType());
void setupDefaultAppsPage();
void setupLoadingPage();
void setupErrorPage();
@@ -49,6 +89,7 @@ private:
void showError(const QString &message);
void clearAppGrid();
void populateDefaultApps();
+ void createSponsorCard(QGridLayout *gridLayout, int row, int col);
QStackedWidget *m_stackedWidget;
QWidget *m_defaultAppsPage;
@@ -63,11 +104,18 @@ private:
QLabel *m_statusLabel;
bool m_isLoggedIn;
AppStoreManager *m_manager;
+ QNetworkAccessManager *m_networkManager = nullptr;
// Search
QLineEdit *m_searchEdit;
QTimer *m_debounceTimer;
QAction *m_searchAction;
+
+ // Sponsors
+ QJsonArray m_platinumSponsors;
+ QJsonArray m_goldSponsors;
+ QJsonArray m_silverSponsors;
+ QJsonArray m_bronzeSponsors;
};
#endif // APPSWIDGET_H
diff --git a/src/core/services/get_mounted_image.cpp b/src/core/services/get_mounted_image.cpp
index 0f8bd70..ac5e097 100644
--- a/src/core/services/get_mounted_image.cpp
+++ b/src/core/services/get_mounted_image.cpp
@@ -41,7 +41,7 @@ plist_t _get_mounted_image(const char *udid)
mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
plist_t result = NULL;
size_t sig_length = 0;
- char *imagetype = "Developer";
+ const char *imagetype = "Developer";
if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid,
diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp
index 2b051fd..6bd5a0d 100644
--- a/src/core/services/init_device.cpp
+++ b/src/core/services/init_device.cpp
@@ -174,6 +174,15 @@ DeviceInfo fullDeviceInfo(const pugi::xml_document &doc,
d.firmwareVersion = safeGet("FirmwareVersion");
d.productVersion = safeGet("ProductVersion");
+ QString q_version = QString::fromStdString(d.productVersion);
+ QStringList parts = q_version.split('.');
+
+ int major = (parts.length() > 0) ? parts[0].toInt() : 0;
+ int minor = (parts.length() > 1) ? parts[1].toInt() : 0;
+ int patch = (parts.length() > 2) ? parts[2].toInt() : 0;
+
+ d.parsedDeviceVersion = IDESCRIPTOR_DEVICE_VERSION(major, minor, patch);
+
/*DiskInfo*/
try {
d.diskInfo.totalDiskCapacity =
diff --git a/src/core/services/mount_dev_image.cpp b/src/core/services/mount_dev_image.cpp
index 5a9a68a..0443e23 100644
--- a/src/core/services/mount_dev_image.cpp
+++ b/src/core/services/mount_dev_image.cpp
@@ -59,17 +59,14 @@ static const char *imagetype = NULL;
static const char PKG_PATH[] = "PublicStaging";
static const char PATH_PREFIX[] = "/private/var/mobile/Media";
-#ifndef SOURCE_DIR
-#define SOURCE_DIR "."
-#endif
-
static ssize_t mim_upload_cb(void *buf, size_t size, void *userdata)
{
return fread(buf, 1, size, (FILE *)userdata);
}
// extend the mobile_image_mounter_error_t type and return sucess if there is
// already a disk image
-mobile_image_mounter_error_t mount_dev_image(const char *udid,
+mobile_image_mounter_error_t mount_dev_image(idevice_t device,
+ unsigned int device_version,
const char *image_dir_path)
{
mobile_image_mounter_client_t mim = NULL;
@@ -79,7 +76,6 @@ mobile_image_mounter_error_t mount_dev_image(const char *udid,
lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
afc_client_t afc = NULL;
lockdownd_service_descriptor_t service = NULL;
- idevice_t device = NULL;
char *image_path = NULL;
char *image_sig_path = NULL;
FILE *f = NULL;
@@ -87,7 +83,6 @@ mobile_image_mounter_error_t mount_dev_image(const char *udid,
plist_t mount_options = NULL;
char *targetname = NULL;
char *mountname = NULL;
- unsigned int device_version = 0;
disk_image_upload_type_t disk_image_upload_type =
DISK_IMAGE_UPLOAD_TYPE_AFC;
@@ -95,14 +90,6 @@ mobile_image_mounter_error_t mount_dev_image(const char *udid,
plist_t result = NULL;
size_t sig_length = 0;
- if (IDEVICE_E_SUCCESS !=
- idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX)) {
- qDebug() << "ERROR: Could not create idevice!";
- res = -1;
- goto leave;
- }
-
- device_version = get_device_version(device);
if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(
device, &lckd, TOOL_NAME))) {
qDebug() << "ERROR: Could not connect to lockdownd service!";
@@ -110,7 +97,7 @@ mobile_image_mounter_error_t mount_dev_image(const char *udid,
goto leave;
}
- if (device_version >= IDEVICE_DEVICE_VERSION(7, 0, 0)) {
+ if (device_version >= IDESCRIPTOR_DEVICE_VERSION(7, 0, 0)) {
disk_image_upload_type = DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE;
}
@@ -152,7 +139,7 @@ mobile_image_mounter_error_t mount_dev_image(const char *udid,
qDebug() << "Using image:" << image_path;
qDebug() << "Using signature:" << image_sig_path;
- if (device_version >= IDEVICE_DEVICE_VERSION(16, 0, 0)) {
+ if (device_version >= IDESCRIPTOR_DEVICE_VERSION(16, 0, 0)) {
uint8_t dev_mode_status = 0;
plist_t val = NULL;
ldret = lockdownd_get_value(lckd, "com.apple.security.mac.amfi",
@@ -195,14 +182,14 @@ mobile_image_mounter_error_t mount_dev_image(const char *udid,
goto leave;
}
image_size = fst.st_size;
- if (device_version < IDEVICE_DEVICE_VERSION(17, 0, 0) &&
+ if (device_version < IDESCRIPTOR_DEVICE_VERSION(17, 0, 0) &&
stat(image_sig_path, &fst) != 0) {
qDebug() << "ERROR: stat:" << image_sig_path << ":" << strerror(errno);
res = -1;
goto leave;
}
- if (device_version < IDEVICE_DEVICE_VERSION(17, 0, 0)) {
+ if (device_version < IDESCRIPTOR_DEVICE_VERSION(17, 0, 0)) {
f = fopen(image_sig_path, "rb");
if (!f) {
qDebug() << "Error opening signature file" << image_sig_path << ":"
@@ -604,9 +591,6 @@ leave:
if (lckd) {
lockdownd_client_free(lckd);
}
- if (device) {
- idevice_free(device);
- }
if (image_path) {
free(image_path);
}
diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp
index 18da300..61220c4 100644
--- a/src/devdiskimagehelper.cpp
+++ b/src/devdiskimagehelper.cpp
@@ -70,20 +70,23 @@ void DevDiskImageHelper::setupUI()
void DevDiskImageHelper::start()
{
- // if (m_mode == Mode::AutoMount) {
- // hide();
- // } else {
- // show();
- // }
-
m_loadingIndicator->start();
showStatus("Please wait...");
- // Start download check in background
- DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
+ unsigned int device_version = get_device_version(m_device->device);
+ unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
+ unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
- // Check mounted image status
- QTimer::singleShot(500, this, &DevDiskImageHelper::checkAndMount);
+ // we dont have developer disk images for ios 6 and below
+ if (deviceMajorVersion > 5) {
+ // TODO: maybe check isMountAvailable and finishWithFailure if false
+ const bool isMountAvailable =
+ DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
+ QTimer::singleShot(500, this, &DevDiskImageHelper::checkAndMount);
+ } else {
+ finishWithSuccess();
+ return;
+ }
}
void DevDiskImageHelper::checkAndMount()
@@ -91,7 +94,8 @@ void DevDiskImageHelper::checkAndMount()
GetMountedImageResult result =
DevDiskManager::sharedInstance()->getMountedImage(
m_device->udid.c_str());
-
+ qDebug() << "checkAndMount result:" << result.success
+ << result.message.c_str() << QString::fromStdString(result.sig);
if (!result.success) {
showRetryUI(QString::fromStdString(result.message));
return;
@@ -103,6 +107,8 @@ void DevDiskImageHelper::checkAndMount()
finishWithSuccess();
return;
}
+
+ onMountButtonClicked();
}
void DevDiskImageHelper::onMountButtonClicked()
@@ -139,8 +145,8 @@ void DevDiskImageHelper::onMountButtonClicked()
showStatus("Mounting developer disk image...");
mobile_image_mounter_error_t err =
- DevDiskManager::sharedInstance()->mountImage(
- versionToMount, QString::fromStdString(m_device->udid));
+ DevDiskManager::sharedInstance()->mountImage(versionToMount,
+ m_device);
m_isMounting = false;
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
@@ -199,8 +205,7 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version,
showStatus("Download complete. Mounting...");
mobile_image_mounter_error_t err =
- DevDiskManager::sharedInstance()->mountImage(
- version, QString::fromStdString(m_device->udid));
+ DevDiskManager::sharedInstance()->mountImage(version, m_device);
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
showStatus("Developer disk image mounted successfully");
diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp
index 628e22e..86bfd37 100644
--- a/src/devdiskimageswidget.cpp
+++ b/src/devdiskimageswidget.cpp
@@ -51,7 +51,6 @@ DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device,
[this](QListWidgetItem *item) {
m_mountButton->setEnabled(item != nullptr);
});
- // connect
}
void DevDiskImagesWidget::setupUi()
@@ -76,25 +75,6 @@ void DevDiskImagesWidget::setupUi()
mountLayout->addWidget(m_check_mountedButton);
layout->addLayout(mountLayout);
- auto *pathLayout = new QHBoxLayout();
- // main path/info row (no shadow)
- auto *pathWidget = new QWidget();
- pathWidget->setLayout(pathLayout);
- QLabel *tipLabel =
- new QLabel("You can change the download path from settings :");
- tipLabel->setStyleSheet("font-size: 9px;");
- pathLayout->addWidget(tipLabel);
- QPushButton *openSettingsButton = new QPushButton("Open Settings");
- openSettingsButton->setSizePolicy(QSizePolicy::Preferred,
- QSizePolicy::Preferred);
- pathLayout->addWidget(openSettingsButton);
- pathLayout->addStretch();
- connect(openSettingsButton, &QPushButton::clicked, this, [this]() {
- SettingsManager::sharedInstance()->showSettingsDialog();
- });
- pathLayout->setContentsMargins(10, 10, 10, 10);
- layout->addWidget(pathWidget);
-
m_stackedWidget = new QStackedWidget(this);
layout->addWidget(m_stackedWidget);
@@ -128,13 +108,13 @@ void DevDiskImagesWidget::setupUi()
m_stackedWidget->addWidget(m_imageListWidget);
- if (DevDiskManager::sharedInstance()->isImageListReady()) {
+ m_processIndicator->start();
+ m_stackedWidget->setCurrentIndex(0); // Show loading page
+ // TODO: we may force to refetch most up to date image list
+ QTimer::singleShot(500, this, [this]() {
displayImages();
m_stackedWidget->setCurrentWidget(m_imageListWidget);
- } else {
- m_processIndicator->start();
- m_stackedWidget->setCurrentIndex(0); // Show loading page
- }
+ });
}
void DevDiskImagesWidget::fetchImages()
@@ -258,6 +238,7 @@ void DevDiskImagesWidget::displayImages()
auto *downloadButton =
new QPushButton(info.isDownloaded ? "Re-download" : "Download");
+ downloadButton->setDefault(true);
downloadButton->setProperty("version", info.version);
connect(downloadButton, &QPushButton::clicked, this,
&DevDiskImagesWidget::onDownloadButtonClicked);
@@ -584,7 +565,7 @@ void DevDiskImagesWidget::mountImage(const QString &version)
m_mountButton->setText("Mounting...");
mobile_image_mounter_error_t err =
- DevDiskManager::sharedInstance()->mountImage(version, udid);
+ DevDiskManager::sharedInstance()->mountImage(version, m_currentDevice);
auto updateUI = [&]() {
m_mountButton->setEnabled(true);
diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp
index ca1e56a..797a9aa 100644
--- a/src/devdiskmanager.cpp
+++ b/src/devdiskmanager.cpp
@@ -23,29 +23,55 @@ DevDiskManager *DevDiskManager::sharedInstance()
DevDiskManager::DevDiskManager(QObject *parent) : QObject{parent}
{
m_networkManager = new QNetworkAccessManager(this);
- m_isImageListReady = false; // Explicitly set initial state
- fetchImageList();
+ populateImageList();
}
-QNetworkReply *DevDiskManager::fetchImageList()
+/*
+ * if we have DeveloperDiskImages.json in docs read from there if not populate
+ * with the file in resources and then try to update it
+ */
+void DevDiskManager::populateImageList()
{
+ QString localPath = QDir(SettingsManager::sharedInstance()->homePath())
+ .filePath("DeveloperDiskImages.json");
+ qDebug() << "Looking for DeveloperDiskImages.json at" << localPath;
+ QFile localFile(localPath);
+
+ if (localFile.exists() && localFile.open(QIODevice::ReadOnly)) {
+ m_imageListJsonData = localFile.readAll();
+ localFile.close();
+ qDebug() << "Loaded DeveloperDiskImages.json from local cache.";
+ } else {
+ QFile qrcFile(":/resources/DeveloperDiskImages.json");
+ if (qrcFile.open(QIODevice::ReadOnly)) {
+ m_imageListJsonData = qrcFile.readAll();
+ qrcFile.close();
+ qDebug() << "Loaded DeveloperDiskImages.json from QRC resources.";
+ } else {
+ qWarning()
+ << "Could not open DeveloperDiskImages.json from QRC. "
+ "Image list will be empty until network fetch succeeds.";
+ }
+ }
+ // TODO:change url
QUrl url("https://raw.githubusercontent.com/uncor3/resources/refs/heads/"
"main/DeveloperDiskImages.json");
QNetworkRequest request(url);
auto *reply = m_networkManager->get(request);
- connect(reply, &QNetworkReply::finished, this, [this, reply]() {
- if (reply->error() != QNetworkReply::NoError) {
- emit imageListFetched(false, reply->errorString());
- } else {
+ connect(reply, &QNetworkReply::finished, this, [this, localPath, reply]() {
+ if (reply->error() == QNetworkReply::NoError) {
+ // FIXME: better have this settings
+ QDir().mkdir(QDir::homePath() + "/.idescriptor");
m_imageListJsonData = reply->readAll();
- m_isImageListReady = true; // Set the flag on success
- emit imageListFetched(true);
+ QFile file(localPath);
+ if (file.open(QIODevice::WriteOnly)) {
+ file.write(m_imageListJsonData);
+ file.close();
+ }
}
reply->deleteLater();
});
-
- return reply;
}
QMap> DevDiskManager::parseDiskDir()
@@ -291,9 +317,8 @@ bool DevDiskManager::isImageDownloaded(const QString &version,
return QFile::exists(dmgPath) && QFile::exists(sigPath);
}
-bool DevDiskManager::downloadCompatibleImageInternal(iDescriptorDevice *device)
+bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
{
-
unsigned int device_version = get_device_version(device->device);
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
@@ -353,33 +378,8 @@ bool DevDiskManager::downloadCompatibleImageInternal(iDescriptorDevice *device)
return false;
}
-bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
-{
- if (m_isImageListReady) {
- // If the list is already fetched, run the logic immediately.
- return downloadCompatibleImageInternal(device);
- } else {
- // Otherwise, connect to the signal and wait.
- qDebug() << "Image list not ready, waiting for it to be fetched...";
- connect(
- this, &DevDiskManager::imageListFetched, this,
- [this, device](bool success) {
- if (success) {
- qDebug() << "Image list is now ready. Retrying download...";
- downloadCompatibleImageInternal(device);
- } else {
- qDebug() << "Failed to fetch image list. Cannot download.";
- }
- },
- Qt::SingleShotConnection);
-
- // The operation is now asynchronous, the immediate return value
- // indicates that the process has started.
- return true;
- }
-}
-
-bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
+// FIXME:DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED
+bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
{
unsigned int device_version = get_device_version(device->device);
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
@@ -400,7 +400,7 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
qDebug() << "Attempting to mount image version" << info.version
<< "on device:" << device->udid.c_str();
if (MOBILE_IMAGE_MOUNTER_E_SUCCESS ==
- mountImage(info.version, device->udid.c_str())) {
+ mountImage(info.version, device)) {
qDebug() << "Mounted existing image version" << info.version
<< "on device:" << device->udid.c_str();
return true;
@@ -434,7 +434,7 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
if (success && finishedVersion == versionToDownload) {
qDebug() << "Download finished for" << finishedVersion
<< ". Now attempting to mount.";
- mountImage(finishedVersion, device->udid.c_str());
+ mountImage(finishedVersion, device);
// TODO: You might want to emit another signal here to
// notify the UI of the final mount result.
} else if (!success) {
@@ -473,33 +473,9 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
return false;
}
-// FIXME:DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED
-bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
-{
- if (m_isImageListReady) {
- // If the list is already fetched, run the logic immediately.
- return mountCompatibleImageInternal(device);
- } else {
- // Otherwise, connect to the signal and wait.
- qDebug() << "Image list not ready, waiting for it to be fetched...";
- connect(
- this, &DevDiskManager::imageListFetched, this,
- [this, device](bool success) {
- if (success) {
- qDebug() << "Image list is now ready. Retrying mount...";
- mountCompatibleImageInternal(device);
- } else {
- qDebug() << "Failed to fetch image list. Cannot mount.";
- }
- },
- Qt::SingleShotConnection);
- return true;
- }
-}
-
-mobile_image_mounter_error_t DevDiskManager::mountImage(const QString &version,
- const QString &udid)
+mobile_image_mounter_error_t
+DevDiskManager::mountImage(const QString &version, iDescriptorDevice *device)
{
const QString downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
@@ -508,7 +484,8 @@ mobile_image_mounter_error_t DevDiskManager::mountImage(const QString &version,
}
QString versionPath = QDir(downloadPath).filePath(version);
- return mount_dev_image(udid.toUtf8().constData(),
+ return mount_dev_image(device->device,
+ device->deviceInfo.parsedDeviceVersion,
versionPath.toUtf8().constData());
}
@@ -632,6 +609,14 @@ bool DevDiskManager::compareSignatures(const char *signature_file_path,
return matches;
}
+/*
+ older devices return something like this:
+ {
+ "ImagePresent": true,
+ "ImageSignature": <7b16200b 2ead1830 a59809d1 51e9060b ... 8a 9844eb07
+ e0b8e0>, "Status": "Complete"
+ }
+*/
GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
{
/*
@@ -640,6 +625,7 @@ GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
dictionary
*/
plist_t result = _get_mounted_image(udid);
+ plist_print(result);
const char *lockedErr = "DeviceLocked";
PlistNavigator r = PlistNavigator(result);
@@ -655,6 +641,22 @@ GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
return GetMountedImageResult{false, "", "Unknown error"};
}
+ // for older devices
+ bool image_present = r["ImagePresent"].getBool();
+
+ /* FIXME: returning a madeup sig because iDescriptorTool::MountDevImage
+ * depends on it in toolboxwidget.cpp we can read the actual signature from
+ * the device but it’s not really necessary since we
+ * just need to know if there is an image mounted or
+ * not also we would need to check the ios version
+ * to know whether to treat ImageSignature as plist array or data
+ */
+ if (image_present) {
+ plist_free(result);
+ return GetMountedImageResult{true, "FIXME",
+ "There is already an image mounted."};
+ }
+
plist_t sig_array_node = r["ImageSignature"].getNode();
if (sig_array_node == NULL) {
@@ -680,6 +682,4 @@ GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
true, "", "No disk image mounted (No signature found)"};
}
return GetMountedImageResult{true, mounted_sig_str, "Success"};
-}
-
-bool DevDiskManager::isImageListReady() const { return m_isImageListReady; }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/devdiskmanager.h b/src/devdiskmanager.h
index 294555f..ca5b72c 100644
--- a/src/devdiskmanager.h
+++ b/src/devdiskmanager.h
@@ -17,9 +17,6 @@ public:
explicit DevDiskManager(QObject *parent = nullptr);
static DevDiskManager *sharedInstance();
- // TODO:public or private?
- // Image list management
- QNetworkReply *fetchImageList();
QList parseImageList(int deviceMajorVersion,
int deviceMinorVersion,
const char *mounted_sig,
@@ -35,7 +32,7 @@ public:
// Mount operations
mobile_image_mounter_error_t mountImage(const QString &version,
- const QString &udid);
+ iDescriptorDevice *device);
bool unmountImage();
// Signature comparison
@@ -46,7 +43,6 @@ public:
GetMountedImageResult getMountedImage(const char *udid);
bool mountCompatibleImage(iDescriptorDevice *device);
bool downloadCompatibleImage(iDescriptorDevice *device);
- bool isImageListReady() const;
signals:
void imageListFetched(bool success,
@@ -76,14 +72,11 @@ private:
QMap m_activeDownloads;
QMap> parseDiskDir();
- // TODO:move this to header
- bool m_isImageListReady = false;
QList
getImagesSorted(QMap> imageFiles,
int deviceMajorVersion, int deviceMinorVersion,
const char *mounted_sig, uint64_t mounted_sig_len);
- bool mountCompatibleImageInternal(iDescriptorDevice *device);
- bool downloadCompatibleImageInternal(iDescriptorDevice *device);
+ void populateImageList();
};
#endif // DEVDISKMANAGER_H
diff --git a/src/deviceimagewidget.cpp b/src/deviceimagewidget.cpp
index 1ea2012..f8da224 100644
--- a/src/deviceimagewidget.cpp
+++ b/src/deviceimagewidget.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include
@@ -11,12 +12,13 @@ DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
-
m_imageLabel = new ResponsiveQLabel(this);
m_imageLabel->setMinimumWidth(200);
m_imageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_imageLabel->setStyleSheet("background: transparent; border: none;");
+ m_mockupName = getMockupNameFromDisplayName(
+ QString::fromStdString(m_device->deviceInfo.productType));
layout->addWidget(m_imageLabel);
setupDeviceImage();
@@ -46,11 +48,15 @@ void DeviceImageWidget::setupDeviceImage()
QString DeviceImageWidget::getDeviceMockupPath() const
{
- QString displayName =
- QString::fromStdString(m_device->deviceInfo.productType);
- QString mockupName = getMockupNameFromDisplayName(displayName);
+ if (m_mockupName == "iPad") {
+ return QString(":/resources/ipad-mockups/ipad.png");
+ }
+ if (m_mockupName == "unknown") {
+ return QString(":/resources/ipad-mockups/ipad.png");
+ }
- return QString(":/resources/iphone-mockups/iphone-%1.png").arg(mockupName);
+ return QString(":/resources/iphone-mockups/iphone-%1.png")
+ .arg(m_mockupName);
}
QString DeviceImageWidget::getWallpaperPath() const
@@ -96,7 +102,6 @@ QString DeviceImageWidget::getWallpaperPath() const
// For future versions, use the latest available wallpaper
wallpaperVersion = "ios26";
}
-
return QString(":/resources/ios-wallpapers/iphone-%1.png")
.arg(wallpaperVersion);
}
@@ -128,9 +133,10 @@ QString DeviceImageWidget::getMockupNameFromDisplayName(
return "4";
} else if (displayName.contains("iPhone 3", Qt::CaseInsensitive)) {
return "3";
+ } else if (displayName.contains("iPad", Qt::CaseInsensitive)) {
+ return "iPad";
} else {
- // Unknown device, use iPhone X as default
- return "x";
+ return "unknown";
}
}
@@ -227,62 +233,92 @@ QPixmap DeviceImageWidget::createCompositeImage() const
return mockup; // Return just the mockup
}
- // Start with the mockup as the base layer
- QPixmap composite = mockup.copy();
+ // Create composite with mockup dimensions
+ QPixmap composite(mockup.size());
+ composite.fill(Qt::transparent);
+
QPainter painter(&composite);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
- // Use pre-calculated screen areas for optimal performance
QRect screenRect;
- QString mockupName = getMockupNameFromDisplayName(
- QString::fromStdString(m_device->deviceInfo.productType));
+ // QString mockupName = "16";
- if (mockupName == "3") {
+ bool useRoundedCorners = false;
+ int cornerRadius = 45;
+ bool isUnknown = (m_mockupName == "unknown");
+
+ if (m_mockupName == "3") {
screenRect = QRect(145, 72, 209, 310);
- } else if (mockupName == "4") {
+ } else if (m_mockupName == "4") {
screenRect = QRect(414, 181, 380, 548);
- } else if (mockupName == "5") {
+ } else if (m_mockupName == "5") {
screenRect = QRect(27, 106, 304, 537);
- } else if (mockupName == "6") {
+ } else if (m_mockupName == "6") {
screenRect = QRect(68, 348, 1279, 2270);
- } else if (mockupName == "x") {
- screenRect = QRect(245, 429, 2389, 5003);
- } else if (mockupName == "15") {
- screenRect = QRect(15, 49, 337, 688);
- } else if (mockupName == "16") {
- screenRect = QRect(17, 54, 333, 682);
+ } else if (m_mockupName == "x") {
+ screenRect = QRect(245, 200, 2389, 5303);
+ useRoundedCorners = true;
+ } else if (m_mockupName == "15") {
+ screenRect = QRect(15, 18, 337, 715);
+ useRoundedCorners = true;
+ } else if (m_mockupName == "16") {
+ screenRect = QRect(17, 18, 333, 715);
+ useRoundedCorners = true;
+ } else if (m_mockupName == "iPad") {
+ screenRect = QRect(33, 36, 471, 680);
+ } else if (m_mockupName == "unknown") {
+ screenRect = QRect(33, 36, 471, 680);
} else {
- // Fallback for unknown devices
screenRect = QRect(mockup.width() * 0.12, mockup.height() * 0.08,
mockup.width() * 0.76, mockup.height() * 0.84);
}
- // Draw wallpaper BEHIND the mockup (into the screen area)
QPixmap scaledWallpaper = wallpaper.scaled(
screenRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+
+ // Create a clipping path with rounded corners
+ if (useRoundedCorners) {
+ QPainterPath clipPath;
+ clipPath.addRoundedRect(screenRect, cornerRadius, cornerRadius);
+ painter.setClipPath(clipPath);
+ }
+
painter.drawPixmap(screenRect, scaledWallpaper);
- // Draw current time in the center of the screen
+ if (useRoundedCorners) {
+ painter.setClipping(false);
+ }
+
+ painter.drawPixmap(0, 0, mockup);
+
+ // Draw question mark for unknown devices
+ if (isUnknown) {
+ QFont questionFont;
+ questionFont.setFamily("SF Pro Display, Helvetica, Arial");
+ int questionSize = screenRect.width() / 3;
+ questionFont.setPointSize(questionSize);
+ questionFont.setWeight(QFont::Bold);
+ painter.setFont(questionFont);
+
+ // Question mark shadow
+ painter.setPen(QColor(0, 0, 0, 150));
+ painter.drawText(screenRect.adjusted(3, 3, 3, 3), Qt::AlignCenter, "?");
+
+ // Question mark main
+ painter.setPen(QColor(255, 255, 255, 255));
+ painter.drawText(screenRect, Qt::AlignCenter, "?");
+ }
+
QString currentTime = QDateTime::currentDateTime().toString("hh:mm");
- // Setup text rendering with better font sizing
QFont timeFont;
timeFont.setFamily("SF Pro Display, Helvetica, Arial");
-
- // Scale font size based on screen dimensions
int fontSize = screenRect.width() / 5;
timeFont.setPointSize(fontSize);
timeFont.setWeight(QFont::Light);
-
painter.setFont(timeFont);
- // Draw text shadow for better readability
- painter.setPen(QColor(0, 0, 0, 150));
- painter.drawText(screenRect.adjusted(2, 2, 2, 2), Qt::AlignCenter,
- currentTime);
-
- // Draw main text - perfectly centered in the screen area
painter.setPen(QColor(255, 255, 255, 255));
painter.drawText(screenRect, Qt::AlignCenter, currentTime);
diff --git a/src/deviceimagewidget.h b/src/deviceimagewidget.h
index a1bc10b..7f68cad 100644
--- a/src/deviceimagewidget.h
+++ b/src/deviceimagewidget.h
@@ -19,6 +19,7 @@ private slots:
void updateTime();
private:
+ QString m_mockupName;
void setupDeviceImage();
QString getDeviceMockupPath() const;
QString getWallpaperPath() const;
diff --git a/src/devicemanagerwidget.cpp b/src/devicemanagerwidget.cpp
index d5c2a53..de85b18 100644
--- a/src/devicemanagerwidget.cpp
+++ b/src/devicemanagerwidget.cpp
@@ -18,7 +18,10 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
// Apply settings-based behavior for switching to new device
SettingsManager::sharedInstance()->doIfEnabled(
SettingsManager::Setting::SwitchToNewDevice,
- [this, device]() { setCurrentDevice(device->udid); });
+ [this, device]() {
+ AppContext::sharedInstance()->setCurrentDeviceSelection(
+ DeviceSelection(device->udid));
+ });
emit updateNoDevicesConnected();
});
@@ -26,6 +29,10 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
[this](const std::string &uuid) {
removeDevice(uuid);
+ auto devices = AppContext::sharedInstance()->getAllDevices();
+ if (!devices.isEmpty())
+ AppContext::sharedInstance()->setCurrentDeviceSelection(
+ DeviceSelection(devices.first()->udid));
emit updateNoDevicesConnected();
});
@@ -44,6 +51,13 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
connect(AppContext::sharedInstance(), &AppContext::devicePaired, this,
[this](iDescriptorDevice *device) {
addPairedDevice(device);
+ SettingsManager::sharedInstance()->doIfEnabled(
+ SettingsManager::Setting::SwitchToNewDevice,
+ [this, device]() {
+ AppContext::sharedInstance()->setCurrentDeviceSelection(
+ DeviceSelection(device->udid));
+ });
+
emit updateNoDevicesConnected();
});
@@ -107,12 +121,6 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
m_stackedWidget->addWidget(deviceWidget);
m_deviceWidgets[device->udid] =
std::pair{deviceWidget, m_sidebar->addDevice(tabTitle, device->udid)};
-
- // todo
- // If this is the first device, make it current
- // if (m_currentDeviceIndex == -1) {
- // setCurrentDevice(deviceIndex);
- // }
}
void DeviceManagerWidget::addRecoveryDevice(
@@ -248,10 +256,10 @@ void DeviceManagerWidget::removeDevice(const std::string &uuid)
m_sidebar->removeDevice(uuid);
deviceWidget->deleteLater();
- // TODO:
- if (m_deviceWidgets.count() > 0) {
- setCurrentDevice(m_deviceWidgets.firstKey());
- }
+ // // TODO:
+ // if (m_deviceWidgets.count() > 0) {
+ // setCurrentDevice(m_deviceWidgets.firstKey());
+ // }
}
}
@@ -268,13 +276,13 @@ void DeviceManagerWidget::setCurrentDevice(const std::string &uuid)
m_currentDeviceUuid = uuid;
- // Update stacked widget
QWidget *widget = m_deviceWidgets[uuid].first;
m_stackedWidget->setCurrentWidget(widget);
- // Update sidebar selection through the AppContext to keep state consistent
- AppContext::sharedInstance()->setCurrentDeviceSelection(
- DeviceSelection(uuid));
+ // This creates a feedback loop. The widget should only react to state
+ // changes from AppContext, not create them here.
+ // AppContext::sharedInstance()->setCurrentDeviceSelection(
+ // DeviceSelection(uuid));
}
std::string DeviceManagerWidget::getCurrentDevice() const
diff --git a/src/fileexplorerwidget.cpp b/src/fileexplorerwidget.cpp
index 6ecb621..efefa8b 100644
--- a/src/fileexplorerwidget.cpp
+++ b/src/fileexplorerwidget.cpp
@@ -164,8 +164,10 @@ void FileExplorerWidget::onSidebarItemClicked(QTreeWidgetItem *item, int column)
Q_UNUSED(column);
if (item == m_defaultAfcItem) {
+ static_cast(m_stackedWidget->widget(0))->goHome();
m_stackedWidget->setCurrentIndex(0);
} else if (item == m_jailbrokenAfcItem) {
+ static_cast(m_stackedWidget->widget(1))->goHome();
m_stackedWidget->setCurrentIndex(1);
}
diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h
index 98f6094..df20fec 100644
--- a/src/iDescriptor-ui.h
+++ b/src/iDescriptor-ui.h
@@ -1,4 +1,5 @@
#pragma once
+#include
#include
#include
#include
@@ -104,16 +105,18 @@ public:
}
}
};
-class ZIconWidget : public QWidget
+
+class ZIconWidget : public QAbstractButton
{
Q_OBJECT
public:
ZIconWidget(const QIcon &icon, const QString &tooltip,
QWidget *parent = nullptr)
- : QWidget(parent), m_icon(icon), m_iconSize(24, 24), m_pressed(false)
+ : QAbstractButton(parent), m_icon(icon)
{
setToolTip(tooltip);
setFixedSize(32, 32);
+ setIconSize(QSize(24, 24));
setCursor(Qt::PointingHandCursor);
connect(qApp, &QApplication::paletteChanged, this,
[this]() { update(); });
@@ -124,14 +127,6 @@ public:
m_icon = ZIcon(icon);
update();
}
- void setIconSize(const QSize &size)
- {
- m_iconSize = size;
- update();
- }
-
-signals:
- void clicked();
protected:
void paintEvent(QPaintEvent *event) override
@@ -141,59 +136,23 @@ protected:
painter.setRenderHint(QPainter::Antialiasing);
// Draw background circle when hovered or pressed
- if (underMouse() || m_pressed) {
+ if (underMouse() || isDown()) {
QColor bgColor = palette().color(QPalette::Highlight);
- bgColor.setAlpha(m_pressed ? 60 : 30);
+ bgColor.setAlpha(isDown() ? 60 : 30);
painter.setBrush(bgColor);
painter.setPen(Qt::NoPen);
painter.drawEllipse(rect().adjusted(2, 2, -2, -2));
}
QRect iconRect = rect();
- iconRect.setSize(m_iconSize);
+ iconRect.setSize(iconSize()); // Use iconSize() from QAbstractButton
iconRect.moveCenter(rect().center());
m_icon.paint(&painter, iconRect, palette());
}
- void mousePressEvent(QMouseEvent *event) override
- {
- if (event->button() == Qt::LeftButton) {
- m_pressed = true;
- update();
- }
- QWidget::mousePressEvent(event);
- }
-
- void mouseReleaseEvent(QMouseEvent *event) override
- {
- if (event->button() == Qt::LeftButton && m_pressed) {
- m_pressed = false;
- update();
- if (rect().contains(event->pos())) {
- emit clicked();
- }
- }
- QWidget::mouseReleaseEvent(event);
- }
-
- void enterEvent(QEnterEvent *event) override
- {
- Q_UNUSED(event)
- update();
- }
-
- void leaveEvent(QEvent *event) override
- {
- Q_UNUSED(event)
- m_pressed = false;
- update();
- }
-
private:
ZIcon m_icon;
- QSize m_iconSize;
- bool m_pressed;
};
enum class iDescriptorTool {
diff --git a/src/iDescriptor.h b/src/iDescriptor.h
index ee9f995..f6967a7 100644
--- a/src/iDescriptor.h
+++ b/src/iDescriptor.h
@@ -1,4 +1,5 @@
#pragma once
+#include
#include
#include
#include
@@ -16,8 +17,8 @@
#define TOOL_NAME "iDescriptor"
#define APP_LABEL "iDescriptor"
-#define APP_VERSION "0.0.1"
-#define APP_COPYRIGHT "© 2023 Uncore. All rights reserved."
+#define APP_VERSION "0.1.0"
+#define APP_COPYRIGHT "© 2025 Uncore. All rights reserved."
#define AFC2_SERVICE_NAME "com.apple.afc2"
#define RECOVERY_CLIENT_CONNECTION_TRIES 3
#define APPLE_VENDOR_ID 0x05ac
@@ -138,6 +139,7 @@ struct DeviceInfo {
std::string marketingName;
std::string regionRaw;
std::string region;
+ unsigned int parsedDeviceVersion;
};
struct iDescriptorDevice {
@@ -292,9 +294,9 @@ bool shutdown(idevice_t device);
TakeScreenshotResult take_screenshot(screenshotr_client_t shotr);
-mobile_image_mounter_error_t mount_dev_image(const char *udid,
+mobile_image_mounter_error_t mount_dev_image(idevice_t device,
+ unsigned int device_version,
const char *image_dir_path);
-
struct GetMountedImageResult {
bool success;
std::string sig;
@@ -404,14 +406,8 @@ bool isDarkMode();
instproxy_error_t install_IPA(idevice_t device, afc_client_t afc,
const char *filePath);
-typedef struct _idevice_private {
- char *udid;
- uint32_t mux_id;
- enum idevice_connection_type conn_type;
- void *conn_data;
- int version;
- int device_class;
-};
+#define IDESCRIPTOR_DEVICE_VERSION(maj, min, patch) \
+ ((((maj) & 0xFF) << 16) | (((min) & 0xFF) << 8) | ((patch) & 0xFF))
/*
we need this because idevice_get_device_version
@@ -420,9 +416,45 @@ typedef struct _idevice_private {
*/
inline unsigned int get_device_version(idevice_t _device)
{
- _idevice_private *idevice = reinterpret_cast<_idevice_private *>(_device);
- if (!idevice) {
+ if (!_device) {
return 0;
}
- return static_cast(idevice->version);
+
+ lockdownd_client_t lockdown = NULL;
+ if (lockdownd_client_new_with_handshake(
+ _device, &lockdown, "iDescriptor") != LOCKDOWN_E_SUCCESS) {
+ return 0;
+ }
+
+ plist_t node = NULL;
+ if (lockdownd_get_value(lockdown, NULL, "ProductVersion", &node) !=
+ LOCKDOWN_E_SUCCESS) {
+ lockdownd_client_free(lockdown);
+ return 0;
+ }
+
+ unsigned int version_number = 0;
+ if (node && plist_get_node_type(node) == PLIST_STRING) {
+ char *version_string = NULL;
+ plist_get_string_val(node, &version_string);
+ if (version_string) {
+ QString q_version = QString(version_string);
+ QStringList parts = q_version.split('.');
+
+ int major = (parts.length() > 0) ? parts[0].toInt() : 0;
+ int minor = (parts.length() > 1) ? parts[1].toInt() : 0;
+ int patch = (parts.length() > 2) ? parts[2].toInt() : 0;
+
+ version_number = IDESCRIPTOR_DEVICE_VERSION(major, minor, patch);
+
+ free(version_string);
+ }
+ }
+
+ if (node) {
+ plist_free(node);
+ }
+ lockdownd_client_free(lockdown);
+
+ return version_number;
}
\ No newline at end of file
diff --git a/src/ifusediskunmountbutton.cpp b/src/ifusediskunmountbutton.cpp
index 00784b4..08931ff 100644
--- a/src/ifusediskunmountbutton.cpp
+++ b/src/ifusediskunmountbutton.cpp
@@ -1,14 +1,13 @@
#include "ifusediskunmountbutton.h"
+#include "iDescriptor-ui.h"
#include
#include
iFuseDiskUnmountButton::iFuseDiskUnmountButton(const QString &path,
QWidget *parent)
- : QPushButton{parent}
+ : ZIconWidget{QIcon(":/resources/icons/ClarityHardDiskSolidAlerted.png"),
+ "Unmount iFuse at " + path, parent}
{
- setIcon(QIcon(":/resources/icons/ClarityHardDiskSolidAlerted.png"));
- setToolTip("Unmount iFuse at " + path);
- setFlat(true);
setCursor(Qt::PointingHandCursor);
setFixedSize(24, 24);
}
diff --git a/src/ifusediskunmountbutton.h b/src/ifusediskunmountbutton.h
index 1e7573b..eaeedd4 100644
--- a/src/ifusediskunmountbutton.h
+++ b/src/ifusediskunmountbutton.h
@@ -1,9 +1,9 @@
#ifndef IFUSEDISKUNMOUNTBUTTON_H
#define IFUSEDISKUNMOUNTBUTTON_H
-#include
+#include "iDescriptor-ui.h"
-class iFuseDiskUnmountButton : public QPushButton
+class iFuseDiskUnmountButton : public ZIconWidget
{
Q_OBJECT
public:
diff --git a/src/ifusewidget.cpp b/src/ifusewidget.cpp
index 2d969ec..b9f83aa 100644
--- a/src/ifusewidget.cpp
+++ b/src/ifusewidget.cpp
@@ -112,6 +112,7 @@ void iFuseWidget::updateDeviceComboBox()
QList devices =
AppContext::sharedInstance()->getAllDevices();
+ m_deviceComboBox->blockSignals(true);
m_deviceComboBox->clear();
m_deviceComboBox->setEnabled(true);
m_mountButton->setEnabled(true);
@@ -123,6 +124,7 @@ void iFuseWidget::updateDeviceComboBox()
m_deviceComboBox->addItem(displayText,
QString::fromStdString(device->udid));
}
+ m_deviceComboBox->blockSignals(false);
// Try to find and select the device passed to the widget
int deviceIndex = -1;
@@ -404,7 +406,12 @@ void iFuseWidget::updateUI()
AppContext::sharedInstance()->getAllDevices();
if (devices.isEmpty()) {
- close();
+ m_device = nullptr;
+ m_deviceComboBox->clear();
+ m_deviceComboBox->setEnabled(false);
+ m_mountButton->setEnabled(false);
+ m_mountPathLabel->setText("No device connected.");
+ deleteLater();
return;
}
updateDeviceComboBox();
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index a9b7403..f4e8154 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -168,7 +168,7 @@ MainWindow::MainWindow(QWidget *parent)
// Add tabs with icons
QIcon deviceIcon(":/resources/icons/MdiLightningBolt.png");
m_customTabWidget->addTab(m_mainStackedWidget, deviceIcon, "iDevice");
- m_customTabWidget->addTab(new AppsWidget(this), "Apps");
+ m_customTabWidget->addTab(AppsWidget::sharedInstance(), "Apps");
m_customTabWidget->addTab(new ToolboxWidget(this), "Toolbox");
auto *jailbrokenWidget = new JailbrokenWidget(this);
@@ -185,17 +185,23 @@ MainWindow::MainWindow(QWidget *parent)
// Qt::SingleShotConnection);
// settings button
- QPushButton *settingsButton = new QPushButton();
- settingsButton->setIcon(
- QIcon(":/resources/icons/MingcuteSettings7Line.png"));
- settingsButton->setToolTip("Settings");
- settingsButton->setFlat(true);
+ ZIconWidget *settingsButton = new ZIconWidget(
+ QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings");
settingsButton->setCursor(Qt::PointingHandCursor);
settingsButton->setFixedSize(24, 24);
- connect(settingsButton, &QPushButton::clicked, this, [this]() {
+ connect(settingsButton, &ZIconWidget::clicked, this, [this]() {
SettingsManager::sharedInstance()->showSettingsDialog();
});
+ ZIconWidget *githubButton = new ZIconWidget(
+ QIcon(":/resources/icons/MdiGithub.png"), "iDescriptor on GitHub");
+ githubButton->setCursor(Qt::PointingHandCursor);
+ githubButton->setFixedSize(24, 24);
+ connect(githubButton, &ZIconWidget::clicked, this, []() {
+ QDesktopServices::openUrl(
+ QUrl("https://github.com/uncor3/iDescriptor"));
+ });
+
m_connectedDeviceCountLabel = new QLabel("iDescriptor: no devices");
m_connectedDeviceCountLabel->setContentsMargins(5, 0, 5, 0);
m_connectedDeviceCountLabel->setStyleSheet(
@@ -204,6 +210,7 @@ MainWindow::MainWindow(QWidget *parent)
ui->statusbar->addWidget(m_connectedDeviceCountLabel);
ui->statusbar->setContentsMargins(0, 0, 0, 0);
ui->statusbar->addPermanentWidget(settingsButton);
+ ui->statusbar->addPermanentWidget(githubButton);
#ifdef __linux__
QList mounted_iFusePaths = iFuseManager::getMountPoints();
@@ -243,6 +250,56 @@ MainWindow::MainWindow(QWidget *parent)
}
qDebug() << "Subscribed to device events successfully.";
createMenus();
+ // Example usage with customization
+
+ UpdateProcedure updateProcedure;
+
+ switch (ZUpdater::detectPlatform()) {
+ // todo: adjust for portable
+ case Platform::Windows:
+ updateProcedure = UpdateProcedure{
+ true,
+ false,
+ true,
+ "The application will now quit to install the update.",
+ "Do you want to install the downloaded update now?",
+ };
+ break;
+ // todo: adjust for pkg managers
+ case Platform::MacOS:
+ updateProcedure = UpdateProcedure{
+ false,
+ false,
+ true,
+ "The application will now quit to install the update.",
+ "Do you want to install the downloaded update now?",
+ };
+ break;
+ // todo: adjust for pkg managers
+ case Platform::Linux:
+ updateProcedure = UpdateProcedure{
+ false,
+ true,
+ false,
+ "There is new update available",
+ "Would you like to download it now?",
+ };
+ break;
+ default:
+ updateProcedure = UpdateProcedure{
+ false, false, false, "", "",
+ };
+ }
+
+ m_updater = new ZUpdater("uncor3/libtest", APP_VERSION, "iDescriptor",
+ updateProcedure,
+ false, // isPortable - set to true if running
+ // portable version on Windows
+ false, // isPackageManaged - set to true if
+ // installed via package manager on Linux
+ this);
+ qDebug() << "Checking for updates...";
+ m_updater->checkForUpdates();
}
void MainWindow::createMenus()
@@ -280,5 +337,6 @@ MainWindow::~MainWindow()
idevice_event_unsubscribe();
irecv_device_event_unsubscribe(context);
delete ui;
+ delete m_updater;
sleep(2); // Give some time for cleanup to finish
-}
\ No newline at end of file
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 7347203..a6c2de9 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -1,5 +1,7 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
+#include "ZDownloader.h"
+#include "ZUpdater.h"
#include "customtabwidget.h"
#include "devicemanagerwidget.h"
#include "iDescriptor.h"
@@ -34,5 +36,6 @@ private:
DeviceManagerWidget *m_deviceManager;
QStackedWidget *m_mainStackedWidget;
QLabel *m_connectedDeviceCountLabel;
+ ZUpdater *m_updater = nullptr;
};
#endif // MAINWINDOW_H
diff --git a/src/realtimescreenwidget.cpp b/src/realtimescreenwidget.cpp
index 5dc1459..9a53aa1 100644
--- a/src/realtimescreenwidget.cpp
+++ b/src/realtimescreenwidget.cpp
@@ -12,7 +12,7 @@
#include
#include
#include
-
+// todo: rename to livescreenwidget
RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device,
QWidget *parent)
: QWidget{parent}, m_device(device), m_timer(nullptr),
@@ -87,6 +87,11 @@ RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device,
}
});
+ const bool initializeScreenshotServiceSuccess =
+ initializeScreenshotService(false);
+ if (initializeScreenshotServiceSuccess)
+ return;
+
// Start the initialization process - auto-mount mode
auto *helper = new DevDiskImageHelper(m_device, this);
@@ -97,9 +102,9 @@ RealtimeScreenWidget::RealtimeScreenWidget(iDescriptorDevice *device,
if (success) {
// for some reason it does not work immediately, so delay a bit
- QTimer::singleShot(
- 1000, this,
- &RealtimeScreenWidget::initializeScreenshotService);
+ QTimer::singleShot(1000, this, [this]() {
+ initializeScreenshotService(true);
+ });
} else {
m_statusLabel->setText("Failed to mount developer disk image");
QMessageBox::critical(this, "Mount Failed",
@@ -123,7 +128,7 @@ RealtimeScreenWidget::~RealtimeScreenWidget()
}
}
-void RealtimeScreenWidget::initializeScreenshotService()
+bool RealtimeScreenWidget::initializeScreenshotService(bool notify)
{
lockdownd_client_t lockdownClient = nullptr;
lockdownd_service_descriptor_t service = nullptr;
@@ -136,11 +141,12 @@ void RealtimeScreenWidget::initializeScreenshotService()
if (ldret != LOCKDOWN_E_SUCCESS) {
m_statusLabel->setText("Failed to connect to lockdown service");
- QMessageBox::critical(this, "Connection Failed",
- "Could not connect to lockdown service.\n"
- "Error code: " +
- QString::number(ldret));
- return;
+ if (notify)
+ QMessageBox::critical(this, "Connection Failed",
+ "Could not connect to lockdown service.\n"
+ "Error code: " +
+ QString::number(ldret));
+ return false;
}
lockdownd_error_t lerr = lockdownd_start_service(
@@ -151,14 +157,17 @@ void RealtimeScreenWidget::initializeScreenshotService()
if (lerr != LOCKDOWN_E_SUCCESS) {
m_statusLabel->setText("Failed to start screenshot service");
- QMessageBox::critical(
- this, "Service Failed",
- "Could not start screenshot service on device.\n"
- "Please ensure the developer disk image is properly mounted.");
+ qDebug() << lerr << "lockdownd_start_service";
+ if (notify)
+ QMessageBox::critical(
+ this, "Service Failed",
+ "Could not start screenshot service on device.\n"
+ "Please ensure the developer disk image is properly "
+ "mounted.");
if (service) {
lockdownd_service_descriptor_free(service);
}
- return;
+ return false;
}
screenshotr_error_t screrr =
@@ -169,22 +178,25 @@ void RealtimeScreenWidget::initializeScreenshotService()
qDebug() << screrr << "screenshotr_client_new";
if (screrr != SCREENSHOTR_E_SUCCESS) {
m_statusLabel->setText("Failed to create screenshot client");
- QMessageBox::critical(this, "Client Failed",
- "Could not create screenshot client.\n"
- "Error code: " +
- QString::number(screrr));
- return;
+ if (notify)
+ QMessageBox::critical(this, "Client Failed",
+ "Could not create screenshot client.\n"
+ "Error code: " +
+ QString::number(screrr));
+ return false;
}
// Successfully initialized, start capturing
m_statusLabel->setText("Capturing at " + QString::number(m_fps) +
" FPS");
startCapturing();
-
+ return true;
} catch (const std::exception &e) {
m_statusLabel->setText("Exception occurred");
- QMessageBox::critical(this, "Exception",
- QString("Exception occurred: %1").arg(e.what()));
+ if (notify)
+ QMessageBox::critical(
+ this, "Exception",
+ QString("Exception occurred: %1").arg(e.what()));
if (lockdownClient) {
lockdownd_client_free(lockdownClient);
diff --git a/src/realtimescreenwidget.h b/src/realtimescreenwidget.h
index 2fd832e..8a825f5 100644
--- a/src/realtimescreenwidget.h
+++ b/src/realtimescreenwidget.h
@@ -17,7 +17,7 @@ public:
~RealtimeScreenWidget();
private:
- void initializeScreenshotService();
+ bool initializeScreenshotService(bool notify);
void updateScreenshot();
void startCapturing();
diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp
index 03e92c9..1d96c54 100644
--- a/src/settingsmanager.cpp
+++ b/src/settingsmanager.cpp
@@ -4,8 +4,6 @@
#include
#include
-#define DEFAULT_DEVDISKIMGPATH "./devdiskimages"
-
SettingsManager *SettingsManager::sharedInstance()
{
static SettingsManager instance;
@@ -46,13 +44,13 @@ QString SettingsManager::devdiskimgpath() const
QString SettingsManager::downloadPath() const
{
return m_settings
- ->value("downloadPath", SettingsManager::docsPath() + "/devdiskimages")
+ ->value("downloadPath", SettingsManager::homePath() + "/devdiskimages")
.toString();
}
-QString SettingsManager::docsPath()
+QString SettingsManager::homePath()
{
- return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) +
+ return QStandardPaths::writableLocation(QStandardPaths::HomeLocation) +
"/.idescriptor";
}
@@ -154,6 +152,7 @@ void SettingsManager::doIfEnabled(Setting setting, std::function action)
return;
}
+ qDebug() << "enabled" << switchToNewDevice();
if (shouldExecute && action) {
action();
}
@@ -161,12 +160,12 @@ void SettingsManager::doIfEnabled(Setting setting, std::function action)
void SettingsManager::resetToDefaults()
{
- setDownloadPath(DEFAULT_DEVDISKIMGPATH);
+ setDownloadPath(SettingsManager::homePath() + "/devdiskimages");
setAutoCheckUpdates(true);
setAutoRaiseWindow(true);
setSwitchToNewDevice(true);
#ifndef __APPLE__
- setUnmountiFuseOnExit(true);
+ setUnmountiFuseOnExit(false);
#endif
setTheme("System Default");
setConnectionTimeout(30);
diff --git a/src/settingsmanager.h b/src/settingsmanager.h
index 5ddef7a..343ddcc 100644
--- a/src/settingsmanager.h
+++ b/src/settingsmanager.h
@@ -26,7 +26,7 @@ public:
Theme,
ConnectionTimeout
};
- static QString docsPath();
+ static QString homePath();
// Existing methods
QString devdiskimgpath() const;
void clearKeys(const QString &keyPrefix);
diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp
index 8505871..2adce92 100644
--- a/src/settingswidget.cpp
+++ b/src/settingswidget.cpp
@@ -253,8 +253,7 @@ void SettingsWidget::saveSettings()
void SettingsWidget::resetToDefaults()
{
- SettingsManager *sm = SettingsManager::sharedInstance();
- sm->resetToDefaults();
+ SettingsManager::sharedInstance()->resetToDefaults();
// Reload UI with default values
loadSettings();
diff --git a/src/sponsorappcard.cpp b/src/sponsorappcard.cpp
new file mode 100644
index 0000000..eca869a
--- /dev/null
+++ b/src/sponsorappcard.cpp
@@ -0,0 +1,117 @@
+#include "sponsorappcard.h"
+#include "appswidget.h"
+#include "iDescriptor-ui.h"
+#include "iDescriptor.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+SponsorAppCard::SponsorAppCard(QWidget *parent) : QWidget{parent}
+{
+
+ QHBoxLayout *layout = new QHBoxLayout(this);
+ setMaximumHeight(200);
+ setMaximumWidth(500);
+ setObjectName("SponsorAppCard");
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ setStyleSheet("QWidget#SponsorAppCard { border: 1px solid #ddd; "
+ "border-radius: 8px; }");
+ layout->setContentsMargins(15, 15, 15, 15);
+ layout->setSpacing(15);
+
+ // App icon
+ QLabel *iconLabel = new QLabel();
+ QPixmap placeholderIcon = QApplication::style()
+ ->standardIcon(QStyle::SP_ComputerIcon)
+ .pixmap(64, 64);
+ iconLabel->setPixmap(placeholderIcon);
+ iconLabel->setAlignment(Qt::AlignCenter);
+ layout->addWidget(iconLabel);
+
+ QString name = "Shopify";
+ QString bundleId = "com.jadedpixel.shopify";
+ QString description =
+ "Create an online store within minutes and start selling.";
+ QString websiteUrl = "https://www.shopify.com";
+
+ fetchAppIconFromApple(
+ bundleId,
+ [iconLabel](const QPixmap &pixmap) {
+ if (!pixmap.isNull()) {
+ QPixmap scaled =
+ pixmap.scaled(64, 64, Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ QPixmap rounded(64, 64);
+ rounded.fill(Qt::transparent);
+
+ QPainter painter(&rounded);
+ painter.setRenderHint(QPainter::Antialiasing);
+ QPainterPath path;
+ path.addRoundedRect(QRectF(0, 0, 64, 64), 16, 16);
+ painter.setClipPath(path);
+ painter.drawPixmap(0, 0, scaled);
+ painter.end();
+
+ iconLabel->setPixmap(rounded);
+ }
+ },
+ this);
+
+ // Vertical layout for name and description
+ QVBoxLayout *textLayout = new QVBoxLayout();
+
+ // App name
+ QLabel *nameLabel = new QLabel(name);
+ nameLabel->setStyleSheet("font-size: 16px;");
+ nameLabel->setAlignment(Qt::AlignCenter);
+ nameLabel->setWordWrap(true);
+ textLayout->addWidget(nameLabel);
+
+ // App description
+ QLabel *descLabel = new QLabel(description);
+ descLabel->setStyleSheet("font-size: 12px; color: #666;");
+ descLabel->setAlignment(Qt::AlignCenter);
+ descLabel->setWordWrap(true);
+ textLayout->addWidget(descLabel);
+
+ layout->addLayout(textLayout);
+
+ QVBoxLayout *buttonsLayout = new QVBoxLayout();
+
+ // Install button placeholder
+ ZLabel *installLabel = new ZLabel("Install");
+ installLabel->setAlignment(Qt::AlignCenter);
+ installLabel->setStyleSheet("font-size: 12px; color: #007AFF; font-weight: "
+ "bold; background-color: transparent;");
+ installLabel->setCursor(Qt::PointingHandCursor);
+ installLabel->setFixedHeight(30);
+
+ ZLabel *websiteLabel = new ZLabel("Website");
+ websiteLabel->setStyleSheet("font-size: 12px; font-weight: "
+ "bold; background-color: transparent;");
+ websiteLabel->setAlignment(Qt::AlignCenter);
+ websiteLabel->setCursor(Qt::PointingHandCursor);
+
+ connect(installLabel, &ZLabel::clicked, this,
+ [this, name, bundleId, description]() {
+ AppsWidget::sharedInstance()->onAppCardClicked(name, bundleId,
+ description);
+ });
+
+ connect(websiteLabel, &ZLabel::clicked, this, [this, websiteUrl]() {
+ QDesktopServices::openUrl(QUrl(websiteUrl));
+ });
+
+ buttonsLayout->addStretch();
+ buttonsLayout->addWidget(installLabel);
+ buttonsLayout->addWidget(websiteLabel);
+ buttonsLayout->addStretch();
+
+ layout->addLayout(buttonsLayout);
+ // gridLayout->addWidget(cardWidget, row, col);
+}
diff --git a/src/sponsorappcard.h b/src/sponsorappcard.h
new file mode 100644
index 0000000..a44966a
--- /dev/null
+++ b/src/sponsorappcard.h
@@ -0,0 +1,15 @@
+#ifndef SPONSORAPPCARD_H
+#define SPONSORAPPCARD_H
+
+#include
+
+class SponsorAppCard : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit SponsorAppCard(QWidget *parent = nullptr);
+
+signals:
+};
+
+#endif // SPONSORAPPCARD_H
diff --git a/src/sponsorwidget.cpp b/src/sponsorwidget.cpp
new file mode 100644
index 0000000..7ef3941
--- /dev/null
+++ b/src/sponsorwidget.cpp
@@ -0,0 +1,25 @@
+#include "sponsorwidget.h"
+#include "sponsorappcard.h"
+#include
+#include
+
+SponsorWidget::SponsorWidget(QWidget *parent) : QWidget(parent)
+{
+ setLayout(new QVBoxLayout(this));
+ QLabel *sponsorTitle = new QLabel("Would you like to sponsor us?");
+ sponsorTitle->setAlignment(Qt::AlignCenter);
+
+ QLabel *sponsorDesc =
+ new QLabel("This app is open-source and free to use. "
+ "And in order to keep it that way, we rely on donations. "
+ "Consider becoming a sponsor to support "
+ "and promote your app/brand here");
+ sponsorDesc->setWordWrap(true);
+ layout()->addWidget(sponsorTitle);
+ layout()->addWidget(sponsorDesc);
+ QLabel *sponsorIconLabel = new QLabel("Example:");
+ layout()->addWidget(sponsorIconLabel);
+ SponsorAppCard *card = new SponsorAppCard(this);
+ layout()->addWidget(card);
+ layout()->setAlignment(card, Qt::AlignCenter);
+}
diff --git a/src/sponsorwidget.h b/src/sponsorwidget.h
new file mode 100644
index 0000000..f8d0982
--- /dev/null
+++ b/src/sponsorwidget.h
@@ -0,0 +1,13 @@
+#ifndef SPONSORWIDGET_H
+#define SPONSORWIDGET_H
+
+#include
+
+class SponsorWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ SponsorWidget(QWidget *parent = nullptr);
+};
+
+#endif // SPONSORWIDGET_H
diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp
index 8384def..e1ecc6b 100644
--- a/src/toolboxwidget.cpp
+++ b/src/toolboxwidget.cpp
@@ -116,10 +116,7 @@ void ToolboxWidget::setupUI()
QList mainToolWidgets;
mainToolWidgets.append(
- {iDescriptorTool::Airplayer,
- "Start an airplayer service to cast your device screen "
- "(does not require a device to be connected)",
- false, ""});
+ {iDescriptorTool::Airplayer, "Cast your device screen ", false, ""});
mainToolWidgets.append({iDescriptorTool::VirtualLocation,
"Simulate GPS location on your device", true, ""});
mainToolWidgets.append(
@@ -284,6 +281,7 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool,
void ToolboxWidget::updateDeviceList()
{
+ m_deviceCombo->blockSignals(true);
m_deviceCombo->clear();
QList devices =
@@ -292,21 +290,24 @@ void ToolboxWidget::updateDeviceList()
if (devices.isEmpty()) {
m_deviceCombo->addItem("No device connected");
m_deviceCombo->setEnabled(false);
- m_uuid.clear(); // No device, clear uuid
} else {
m_deviceCombo->setEnabled(true);
- QString shortUdid =
- QString::fromStdString(devices.first()->udid).left(8) + "...";
for (iDescriptorDevice *device : devices) {
+ QString shortUdid =
+ QString::fromStdString(device->udid).left(8) + "...";
m_deviceCombo->addItem(
QString::fromStdString(device->deviceInfo.productType) + " / " +
shortUdid,
QString::fromStdString(device->udid));
}
- // TODO:
- m_uuid = devices.first()->udid;
- m_currentDevice = devices.first(); // Set current device to first one
}
+
+ // After rebuilding the list, explicitly sync the UI to match the
+ // state from AppContext. This avoids creating a feedback loop.
+ onCurrentDeviceChanged(
+ AppContext::sharedInstance()->getCurrentDeviceSelection());
+
+ m_deviceCombo->blockSignals(false);
}
void ToolboxWidget::updateToolboxStates()
@@ -339,25 +340,14 @@ void ToolboxWidget::updateUI()
void ToolboxWidget::onDeviceSelectionChanged()
{
- // Handle device selection change
QString selectedUdid = m_deviceCombo->currentData().toString();
- qDebug() << "Selected device UDID:" << selectedUdid;
-
- // Update m_uuid and m_currentDevice if a valid device is selected
- QList devices =
- AppContext::sharedInstance()->getAllDevices();
- for (iDescriptorDevice *device : devices) {
- if (QString::fromStdString(device->udid) == selectedUdid) {
- m_uuid = device->udid;
- m_currentDevice = device;
- // Also update the AppContext to keep everything in sync
- AppContext::sharedInstance()->setCurrentDeviceSelection(
- DeviceSelection(m_uuid));
- return;
- }
+ if (selectedUdid.isEmpty()) {
+ return;
}
- m_uuid.clear();
- m_currentDevice = nullptr;
+
+ // Update the selected device in main menu
+ AppContext::sharedInstance()->setCurrentDeviceSelection(
+ DeviceSelection(selectedUdid.toStdString()));
}
void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection)
@@ -366,24 +356,19 @@ void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection)
int index =
m_deviceCombo->findData(QString::fromStdString(selection.uuid));
if (index != -1) {
- // Block signals to prevent recursive calls
+ // Block signals to prevent recursive calls when we update the UI
m_deviceCombo->blockSignals(true);
m_deviceCombo->setCurrentIndex(index);
m_deviceCombo->blockSignals(false);
- // Update internal state
m_uuid = selection.uuid;
- QList devices =
- AppContext::sharedInstance()->getAllDevices();
- for (iDescriptorDevice *device : devices) {
- if (device->udid == selection.uuid) {
- m_currentDevice = device;
- break;
- }
- }
+ m_currentDevice =
+ AppContext::sharedInstance()->getDevice(selection.uuid);
}
} else {
- // TODO: recovery and no device selection
+ // Handle recovery, pending, or no device selection
+ m_uuid.clear();
+ m_currentDevice = nullptr;
}
}
@@ -434,7 +419,7 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool)
return;
}
- if (result.success || result.sig.empty()) {
+ if (result.success && result.sig.empty()) {
bool devImgSuccess =
DevDiskManager::sharedInstance()->mountCompatibleImage(
m_currentDevice);
@@ -450,6 +435,12 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool)
}
}
+ QMessageBox::information(
+ this, "Success",
+ QString("There is already a developer image mounted on device %1.")
+ .arg(QString::fromStdString(
+ m_currentDevice->deviceInfo.productType)));
+
} break;
case iDescriptorTool::VirtualLocation: {
// Handle virtual location functionality
@@ -506,6 +497,8 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool)
case iDescriptorTool::iFuse: {
if (!m_ifuseWidget) {
m_ifuseWidget = new iFuseWidget(m_currentDevice);
+ qDebug() << "Created iFuseWidget"
+ << m_currentDevice->deviceInfo.productType.c_str();
m_ifuseWidget->setAttribute(Qt::WA_DeleteOnClose);
connect(m_ifuseWidget, &QObject::destroyed, this,
[this]() { m_ifuseWidget = nullptr; });