fix Windows build

- Updated README.md to include CMake command for installation.
- Improved win-deploy.cmake to handle executable path issues and added detailed logging for deployment steps.
- Introduced checks for runtime dependencies and optimized DLL copying logic to avoid unnecessary copies.
- Added additional MinGW runtime DLLs required for GStreamer and FFmpeg.
- Created idescriptor.rc for application versioning and resource management.
- Updated resources.qrc to include application icon.
- Modified AppsWidget to improve UI for install and download actions.
- Adjusted dnssd_service.h to conditionally include headers based on platform.
- Enhanced install_ipa.cpp with additional includes for better compatibility.
- Updated main.cpp to set up environment variables for GStreamer on Windows.
- Improved mainwindow.cpp to add a no devices detected page and integrate dependency checks.
- Cleaned up mainwindow.ui by removing unnecessary layout elements.
- Implemented check_deps.cpp and check_deps.h for verifying required dependencies on Windows.
- Created diagnose_dialog.cpp and diagnose_dialog.h for a dialog to display dependency checks.
- Developed diagnose_widget.cpp and diagnose_widget.h to manage and display dependency items.
- Enhanced sshterminalwidget.cpp to improve terminal handling on Windows.
- Updated welcomewidget.cpp to refine UI layout and spacing for better aesthetics.
This commit is contained in:
uncor3
2025-10-24 00:24:58 -07:00
parent 9d21afc5aa
commit 4b81d7bdf1
20 changed files with 1014 additions and 121 deletions
View File
+173
View File
@@ -0,0 +1,173 @@
name: Build Windows
on:
workflow_dispatch:
env:
QT_VERSION: "6.7.2"
GO_VERSION: "1.23.0"
LIBPLIST_VER: "2.7.0"
LIBTATSU_VER: "1.0.5"
LIBIMOBILEDEVICE_GLUE_VER: "1.3.2"
LIBIMOBILEDEVICE_VER: "1.4.0"
LIBIRECOVERY_VER: "1.3.1"
LIBUSBMUXD_VER: "2.1.1"
jobs:
build-windows:
runs-on: windows-2022
defaults:
run:
shell: msys2 {0}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: "recursive"
token: ${{ secrets.PAT }}
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: mingw64
release: false
update: false
install: >-
coreutils
base-devel
git
mingw-w64-x86_64-gcc
make
libtool
autoconf
automake-wrapper
mingw-w64-x86_64-cmake
mingw-w64-x86_64-qt6-base
mingw-w64-x86_64-qt6-svg
mingw-w64-x86_64-qt6-multimedia
mingw-w64-x86_64-qt6-location
mingw-w64-x86_64-qt6-positioning
mingw-w64-x86_64-qt6-serialport
mingw-w64-x86_64-pugixml
mingw-w64-x86_64-libusb
mingw-w64-x86_64-qrencode
mingw-w64-x86_64-curl
mingw-w64-x86_64-openssl
mingw-w64-x86_64-libzip
mingw-w64-x86_64-go
mingw-w64-x86_64-nsis
p7zip
mingw-w64-x86_64-gstreamer
mingw-w64-x86_64-gst-plugins-base
mingw-w64-x86_64-gst-plugins-good
mingw-w64-x86_64-gst-plugins-bad
mingw-w64-x86_64-gst-plugins-ugly
mingw-w64-x86_64-gst-libav
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.x"
- name: Install WiX Toolset
run: dotnet tool install --global wix
- name: Download and Extract Bonjour SDK
run: |
wget "https://gsf-fl.softonic.com/b18/d44/cbdcd43ad683cf6760d45ce891c3035044/bonjoursdksetup.exe?Expires=1757906571&Signature=af8dfce8a002130f34c0e93e17b8dd96d547ff69&url=https://bonjour.en.softonic.com/&Filename=bonjoursdksetup.exe" -O bonjour-sdk.exe
EXPECTED_HASH="4ff2aae8205aec31b06743782cfcadce"
ACTUAL_HASH=$(md5sum bonjour-sdk.exe | awk '{print $1}')
echo "Expected MD5: $EXPECTED_HASH"
echo "Actual MD5: $ACTUAL_HASH"
if [ "$ACTUAL_HASH" != "$EXPECTED_HASH" ]; then
echo "::error::Checksum mismatch!"
exit 1
fi
echo "Checksum verified. Extracting installer..."
7z x bonjour-sdk.exe -oBonjourInstaller
- name: Install Bonjour Runtime
shell: powershell
run: |
$currentPath = Get-Location
$msiFilePath = Join-Path -Path $currentPath -ChildPath "BonjourInstaller\Bonjour64.msi"
$msiexecArguments = "/i `"$msiFilePath`" /qn"
Write-Host "Installing Bonjour Runtime..."
Start-Process msiexec -ArgumentList $msiexecArguments -NoNewWindow -Wait
- name: Install Bonjour SDK
shell: powershell
run: |
$currentPath = Get-Location
$msiFilePath = Join-Path -Path $currentPath -ChildPath "BonjourInstaller\BonjourSDK64.msi"
$msiexecArguments = "/i `"$msiFilePath`" /qn"
Write-Host "Installing Bonjour SDK..."
Start-Process msiexec -ArgumentList $msiexecArguments -NoNewWindow -Wait
"BONJOUR_SDK_HOME=C:/Program Files/Bonjour SDK" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Build libimobiledevice suite (versioned tarballs)
run: |
set -euo pipefail
workspace="$PWD"
tmp="$PWD/_tmp_libs"
mkdir -p "$tmp"
cd "$tmp"
base_url="https://github.com/libimobiledevice"
libs=( "libplist" "libtatsu" "libimobiledevice-glue" "libimobiledevice" "libirecovery" "libusbmuxd" )
for name in "${libs[@]}"; do
ver_var=$(echo "${name^^}_VER" | sed 's/-/_/g')
ver="${!ver_var:-}"
if [ -z "$ver" ]; then
echo "Version for $name not set (env var $ver_var)"
exit 1
fi
archive="${name}-${ver}.tar.bz2"
url="${base_url}/${name}/releases/download/${ver}/${archive}"
echo "=== Processing $name $ver ==="
echo "URL: $url"
curl -L -o "$archive" "$url"
echo "Extracting $archive"
tar xjf "$archive"
srcdir="${name}-${ver}"
pushd "$srcdir"
./configure --without-cython
make -j"$(nproc)"
make install
popd
echo "Installed $name $ver"
done
# cleanup
cd "$workspace"
rm -rf "$tmp"
- name: Configure CMake
run: |
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
- name: Build with CMake
run: cmake --build build --config Release
- name: Install & CPack
working-directory: build
run: |
cmake --install .
cpack .
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: iDescriptor-Windows-Installer
path: build/artifacts/*
+123 -87
View File
@@ -9,6 +9,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Platform-specific paths for libraries built from source
if(WIN32)
include_directories("C:/msys64/mingw64/include")
link_directories("C:/msys64/mingw64/lib")
set(PKG_CONFIG_EXECUTABLE "C:/msys64/mingw64/bin/pkg-config.exe")
list(APPEND CMAKE_PREFIX_PATH "C:/lxqt")
set(CUSTOM_LIB_PATH "C:/msys64/mingw64/lib")
set(CUSTOM_BIN_PATH "C:/msys64/mingw64/bin")
set(CUSTOM_INCLUDE_PATH "C:/msys64/mingw64/include")
@@ -31,25 +35,19 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Networ
# Add QTermWidget
pkg_check_modules(QTERMWIDGET REQUIRED IMPORTED_TARGET qtermwidget6)
# Add Avahi for network device discovery
if (APPLE)
# On macOS, use the built-in Bonjour framework instead of Avahi
find_library(CORE_SERVICES_FRAMEWORK CoreServices REQUIRED)
add_definitions(-DUSE_DNS_SD)
message(STATUS "Using macOS Bonjour framework for network service discovery")
else()
# On Linux and Windows, use Avahi
add_definitions(-DUSE_AVAHI)
message(STATUS "Using Avahi for network service discovery")
pkg_check_modules(AVAHI_CLIENT REQUIRED IMPORTED_TARGET avahi-client)
if(WIN32)
# Get the path to the Qt bin directory
get_target_property(QT_BIN_PATH Qt${QT_VERSION_MAJOR}::Core IMPORTED_LOCATION_RELEASE)
if(NOT QT_BIN_PATH)
get_target_property(QT_BIN_PATH Qt${QT_VERSION_MAJOR}::Core IMPORTED_LOCATION_DEBUG)
endif()
if(NOT QT_BIN_PATH)
get_target_property(QT_BIN_PATH Qt${QT_VERSION_MAJOR}::Core IMPORTED_LOCATION)
endif()
get_filename_component(QT_BIN_PATH ${QT_BIN_PATH} DIRECTORY)
message(STATUS "Found Qt bin directory: ${QT_BIN_PATH}")
endif()
# pkg_check_modules(AVAHI_CLIENT REQUIRED IMPORTED_TARGET avahi-client)
# pkg_check_modules(AVAHI_COMMON REQUIRED IMPORTED_TARGET avahi-common)
# Link directly to libraries in /usr/local/lib instead of using pkg-config
# Force NO_DEFAULT_PATH to only search in /usr/local/lib
find_library(IMOBILEDEVICE_LIBRARY
NAMES imobiledevice-1.0
PATHS ${CUSTOM_LIB_PATH}
@@ -64,13 +62,6 @@ find_library(IMOBILEDEVICE_GLUE_LIBRARY
REQUIRED
)
# find_library(PLIST_LIBRARY
# NAMES plist-2.0
# PATHS /usr/local/lib
# NO_DEFAULT_PATH
# REQUIRED
# )
find_library(TATSU_LIBRARY
NAMES tatsu
PATHS ${CUSTOM_LIB_PATH}
@@ -89,18 +80,10 @@ find_library(IRECOVERY_LIBRARY
REQUIRED
)
# # Add missing libraries
# find_library(USBMUXD_LIBRARY
# NAMES usbmuxd-2.0
# PATHS /usr/local/lib
# NO_DEFAULT_PATH
# REQUIRED
# )
if(WIN32)
# On MSYS2, these are found in the standard mingw64 prefix
find_library(SSL_LIBRARY NAMES ssl REQUIRED)
find_library(CRYPTO_LIBRARY NAMES crypto REQUIRED)
find_library(SSL_LIBRARY NAMES ssl PATHS C:/msys64/mingw64/lib REQUIRED)
find_library(CRYPTO_LIBRARY NAMES crypto PATHS C:/msys64/mingw64/lib REQUIRED)
else()
find_library(SSL_LIBRARY
NAMES ssl
@@ -116,11 +99,7 @@ else()
endif()
# Add libssh for SSH connections
find_library(SSH_LIBRARY
NAMES ssh
PATHS ${CUSTOM_LIB_PATH} /usr/lib /usr/lib/x86_64-linux-gnu
REQUIRED
)
pkg_check_modules(SSH REQUIRED IMPORTED_TARGET libssh)
# Apple-specific crypto libraries for SSH
if(APPLE)
@@ -128,37 +107,11 @@ if(APPLE)
find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
endif()
# Remove frida support for now
# find_library(FRIDA_LIBRARY
# NAMES frida-core
# PATHS /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu
# REQUIRED
# )
# find_library(ZIP_LIBRARY
# NAMES zip
# PATHS /usr/lib /usr/lib/x86_64-linux-gnu
# REQUIRED
# )
pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml)
pkg_check_modules(USB REQUIRED IMPORTED_TARGET libusb-1.0)
pkg_check_modules(PLIST REQUIRED IMPORTED_TARGET libplist-2.0)
# Use system plist library instead of manually built one - exclude /usr/local/lib
# find_library(PLIST_LIBRARY
# NAMES plist-2.0
# PATHS /usr/lib
# NO_DEFAULT_PATH
# REQUIRED
# )
# set(PROJECT_SOURCES
# src/*.cpp
# src/*.h
# src/*.ui
# resources.qrc
# )
file(GLOB PROJECT_SOURCES
src/*.cpp
@@ -177,6 +130,16 @@ if(APPLE)
)
endif()
if (WIN32)
list(APPEND PROJECT_SOURCES
src/core/services/dnssd/dnssd_service.cpp
src/core/services/dnssd/dnssd_service.h
)
file(GLOB WINDOWS_PLATFORM_SOURCES src/platform/windows/*.cpp src/platform/windows/*.h)
list(APPEND PROJECT_SOURCES ${WINDOWS_PLATFORM_SOURCES})
endif()
if(LINUX)
list(APPEND PROJECT_SOURCES
src/core/services/avahi/avahi_service.cpp
@@ -189,9 +152,9 @@ endif()
add_subdirectory(lib/airplay)
add_subdirectory(lib/ipatool-go)
if (WIN32)
# todo
set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/resources/todo.rc")
set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/idescriptor.rc")
qt_add_executable(iDescriptor
MANUAL_FINALIZATION
${PROJECT_SOURCES}
@@ -228,14 +191,11 @@ target_link_libraries(iDescriptor PRIVATE
Qt6::QuickControls2
${IMOBILEDEVICE_LIBRARY}
${IMOBILEDEVICE_GLUE_LIBRARY}
# ${PLIST_LIBRARY}
${TATSU_LIBRARY}
${IRECOVERY_LIBRARY}
${SSL_LIBRARY}
${CRYPTO_LIBRARY}
${SSH_LIBRARY}
# ${FRIDA_LIBRARY}
# ${ZIP_LIBRARY}
PkgConfig::SSH
PkgConfig::PUGIXML
PkgConfig::USB
PkgConfig::PLIST
@@ -246,16 +206,24 @@ target_link_libraries(iDescriptor PRIVATE
airplay
ipatool-go
)
if(NOT APPLE)
if(APPLE)
find_library(CORE_SERVICES_FRAMEWORK CoreServices REQUIRED)
target_link_libraries(iDescriptor PRIVATE
PkgConfig::AVAHI_CLIENT
# PkgConfig::AVAHI_COMMON
)
${CORE_SERVICES_FRAMEWORK})
message(STATUS "Using macOS Bonjour framework for network service discovery")
endif()
elseif (WIN32)
find_path(DNSSD_INCLUDE_DIR dns_sd.h HINTS ${BONJOUR_SDK}/Include )
target_include_directories( iDescriptor PRIVATE ${DNSSD_INCLUDE_DIR} )
message( STATUS "Using Bonjour SDK for network service discovery" )
endif()
else()
target_link_libraries(iDescriptor PRIVATE
${CORE_SERVICES_FRAMEWORK}
PkgConfig::AVAHI_CLIENT
# PkgConfig::AVAHI_COMMON
)
message(STATUS "Using Avahi for network service discovery")
endif()
@@ -285,26 +253,94 @@ set_target_properties(iDescriptor PROPERTIES
)
include(GNUInstallDirs)
install(TARGETS iDescriptor
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Set the installation directory to be within the build folder
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/dist")
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(iDescriptor)
endif()
# Copy runtime DLLs to build directory after building
if(WIN32)
if(WIN32 AND NOT DEFINED NO_DEPLOY)
add_custom_command(TARGET iDescriptor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Starting Windows deployment..."
COMMAND ${CMAKE_COMMAND}
-DEXECUTABLE_PATH=${CMAKE_CURRENT_BINARY_DIR}/iDescriptor.exe
-DMSYS2_BIN_PATH=${CUSTOM_BIN_PATH}
-DOUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR}
-DEXECUTABLE_PATH=$<TARGET_FILE:iDescriptor>
-DQT_BIN_PATH=${QT_BIN_PATH}
-DMSYS2_BIN_PATH=C:/msys64/mingw64/bin
-DOUTPUT_DIR=$<TARGET_FILE_DIR:iDescriptor>
-DQML_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/qml
-P ${CMAKE_CURRENT_LIST_DIR}/cmake/win-deploy.cmake
COMMENT "Deploying Windows application with all dependencies"
VERBATIM
)
endif()
endif()
if(WIN32)
install(CODE "
message(STATUS \"Deploying dependencies to installation directory for packaging...\")
message(STATUS \"CMAKE_INSTALL_PREFIX: \${CMAKE_INSTALL_PREFIX}\")
# copy executable to dist dir
file(MAKE_DIRECTORY \"\${CMAKE_INSTALL_PREFIX}\")
# file(COPY <TARGET_FILE:iDescriptor> DESTINATION CMAKE_INSTALL_PREFIX)
file(COPY ${CMAKE_CURRENT_BINARY_DIR}/iDescriptor.exe DESTINATION ${CMAKE_INSTALL_PREFIX})
# Check if file exists before deployment
set(EXECUTABLE_PATH \"\${CMAKE_INSTALL_PREFIX}/iDescriptor.exe\")
message(STATUS \"Looking for executable at: \${EXECUTABLE_PATH}\")
if(EXISTS \"\${EXECUTABLE_PATH}\")
message(STATUS \"SUCCESS: Executable found at \${EXECUTABLE_PATH}\")
else()
message(STATUS \"ERROR: Executable NOT found at \${EXECUTABLE_PATH}\")
endif()
execute_process(
COMMAND \"${CMAKE_COMMAND}\"
-DEXECUTABLE_PATH=\${EXECUTABLE_PATH}
-DQT_BIN_PATH=\"${QT_BIN_PATH}\"
-DMSYS2_BIN_PATH=\"C:/msys64/mingw64/bin\"
-DOUTPUT_DIR=\"\${CMAKE_INSTALL_PREFIX}\"
-DQML_SOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/qml\"
-P \"${CMAKE_CURRENT_LIST_DIR}/cmake/win-deploy.cmake\"
)
")
endif()
# Packaging Configuration (CPack)
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "iDescriptor Application")
set(CPACK_PACKAGE_VENDOR "iDescriptor")
set(CPACK_PACKAGE_CONTACT "support@idescriptor.com")
set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_BINARY_DIR}/artifacts")
if(WIN32)
set(CPACK_GENERATOR "WIX;ZIP")
string(UUID CPACK_WIX_PRODUCT_GUID NAMESPACE "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" NAME "${PROJECT_NAME}" TYPE MD5)
string(UUID CPACK_WIX_UPGRADE_GUID NAMESPACE "d6c5b4a3-f2e1-d0c9-b8a7-f6e5d4c3b2a1" NAME "${PROJECT_NAME}" TYPE MD5)
set(CPACK_WIX_UI_REF "WixUI_InstallDir")
set(CPACK_WIX_INSTALL_SCOPE "perMachine")
set(CPACK_WIX_PROGRAM_MENU_FOLDER "${PROJECT_NAME}")
set(CPACK_PACKAGE_EXECUTABLES "iDescriptor" "iDescriptor")
set(CPACK_WIX_CREATE_DESKTOP_SHORTCUT "iDescriptor")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/icons/app-icon/icon.ico")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rtf")
set(CPACK_WIX_LICENSE_RTF "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rtf")
endif()
set(CPACK_WIX_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-win64-setup")
set(CPACK_ARCHIVE_COMPONENT_INSTALL TRUE)
set(CPACK_ARCHIVE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}-win64-portable")
# Tell CPack to use the pre-built dist directory
set(CPACK_INSTALLED_DIRECTORIES "${CMAKE_INSTALL_PREFIX};.")
# Prevent CPack from running install again
set(CPACK_INSTALL_CMAKE_PROJECTS "")
endif()
include(CPack)
+135 -14
View File
@@ -1,16 +1,57 @@
# Windows deployment script for Qt applications with MinGW/MSYS2
# This script handles Qt deployment, runtime DLL copying, and GStreamer plugins
if(NOT EXISTS ${EXECUTABLE_PATH})
message(FATAL_ERROR "Executable not found: ${EXECUTABLE_PATH}")
# Strip quotes from all path variables if they exist
string(REPLACE "\"" "" EXECUTABLE_PATH "${EXECUTABLE_PATH}")
string(REPLACE "\"" "" OUTPUT_DIR "${OUTPUT_DIR}")
string(REPLACE "\"" "" QT_BIN_PATH "${QT_BIN_PATH}")
string(REPLACE "\"" "" MSYS2_BIN_PATH "${MSYS2_BIN_PATH}")
if(QML_SOURCE_DIR)
string(REPLACE "\"" "" QML_SOURCE_DIR "${QML_SOURCE_DIR}")
endif()
message("=== Starting Windows deployment for: ${EXECUTABLE_PATH} ===")
message("Debug info:")
message(" EXECUTABLE_PATH: ${EXECUTABLE_PATH}")
message(" OUTPUT_DIR: ${OUTPUT_DIR}")
message(" QT_BIN_PATH: ${QT_BIN_PATH}")
message(" MSYS2_BIN_PATH: ${MSYS2_BIN_PATH}")
# Step 1: Run windeployqt6 to copy Qt-related DLLs and plugins
message("Running windeployqt6 to deploy Qt dependencies...")
if(NOT EXISTS ${EXECUTABLE_PATH})
message(STATUS "Executable not found: ${EXECUTABLE_PATH}")
message(STATUS "Checking if it's a path issue...")
# Try to find the executable with different path formats
get_filename_component(DIR_PATH ${EXECUTABLE_PATH} DIRECTORY)
get_filename_component(FILE_NAME ${EXECUTABLE_PATH} NAME)
message(STATUS "Directory path: ${DIR_PATH}")
message(STATUS "File name: ${FILE_NAME}")
# List contents of the directory
if(EXISTS ${DIR_PATH})
message(STATUS "Directory exists, listing contents:")
file(GLOB DIR_CONTENTS "${DIR_PATH}/*")
foreach(ITEM ${DIR_CONTENTS})
message(STATUS " Found: ${ITEM}")
endforeach()
else()
message(STATUS "Directory does not exist: ${DIR_PATH}")
endif()
message(FATAL_ERROR "Executable not found: ${EXECUTABLE_PATH}")
endif()
message("SUCCESS: Executable found at: ${EXECUTABLE_PATH}")
message("Running windeployqt6 to deploy Qt dependencies (without compiler runtime)...")
message("Executing: ${QT_BIN_PATH}/windeployqt6.exe --dir ${OUTPUT_DIR} --plugindir ${OUTPUT_DIR}/plugins ${EXECUTABLE_PATH}")
execute_process(
COMMAND ${MSYS2_BIN_PATH}/windeployqt6.exe --dir ${OUTPUT_DIR} --plugindir ${OUTPUT_DIR}/plugins ${EXECUTABLE_PATH}
COMMAND ${QT_BIN_PATH}/windeployqt6.exe --dir ${OUTPUT_DIR} --plugindir ${OUTPUT_DIR}/plugins ${EXECUTABLE_PATH}
RESULT_VARIABLE WINDEPLOYQT_RESULT
OUTPUT_VARIABLE WINDEPLOYQT_OUTPUT
ERROR_VARIABLE WINDEPLOYQT_ERROR
@@ -25,22 +66,56 @@ endif()
# Step 2: Find and copy runtime dependencies using GET_RUNTIME_DEPENDENCIES
message("Analyzing runtime dependencies for: ${EXECUTABLE_PATH}")
# Get the build directory to exclude DLLs already there
get_filename_component(BUILD_DIR ${EXECUTABLE_PATH} DIRECTORY)
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES ${EXECUTABLE_PATH}
RESOLVED_DEPENDENCIES_VAR DLLS
PRE_EXCLUDE_REGEXES "^api-ms-" "^ext-ms-" "^AVRT" "^avrt" "^MSVCP" "^VCRUNTIME" "^ucrtbase"
POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" ".*SysWOW64/.*\\.dll" ".*Windows/.*\\.dll" ".*Microsoft.VC.*"
DIRECTORIES ${MSYS2_BIN_PATH} $ENV{PATH}
PRE_EXCLUDE_REGEXES "^api-ms-" "^ext-ms-" "^AVRT" "^avrt" "^MSVCP" "^VCRUNTIME" "^ucrtbase" "^libgcc_s_seh-1\\.dll$" "^libstdc\\+\\+-6\\.dll$" "^libwinpthread-1\\.dll$" "^Qt.*\\.dll$" "^libgstreamer-1\\.0-0\\.dll$" "^libgstbase-1\\.0-0\\.dll$" "^libgobject-2\\.0-0\\.dll$" "^libglib-2\\.0-0\\.dll$" "^libintl-8\\.dll$" "^libiconv-2\\.dll$"
#PRE_EXCLUDE_REGEXES "^api-ms-" "^ext-ms-" "^AVRT" "^avrt" "^MSVCP" "^VCRUNTIME" "^ucrtbase"
POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" ".*SysWOW64/.*\\.dll" ".*Windows/.*\\.dll" ".*Microsoft.VC.*" ".*Qt.*\\.dll$"
DIRECTORIES ${QT_BIN_PATH} ${MSYS2_BIN_PATH} "C:/lxqt/lib" $ENV{PATH}
)
set(COPIED_DLLS 0)
foreach(DLL ${DLLS})
get_filename_component(DLL_NAME ${DLL} NAME)
message("Copying dependency: ${DLL_NAME}")
file(COPY ${DLL} DESTINATION ${OUTPUT_DIR})
get_filename_component(DLL_DIR ${DLL} DIRECTORY)
# Skip if DLL is from the build directory (avoid copying to itself)
if("${DLL_DIR}" STREQUAL "${BUILD_DIR}")
message(" Skipping ${DLL_NAME} (already in build directory)")
continue()
endif()
set(DEST_FILE "${OUTPUT_DIR}/${DLL_NAME}")
# Check if we need to copy (file doesn't exist or is different)
set(SHOULD_COPY TRUE)
if(EXISTS ${DEST_FILE})
file(SIZE ${DLL} SOURCE_SIZE)
file(SIZE ${DEST_FILE} DEST_SIZE)
if(SOURCE_SIZE EQUAL DEST_SIZE)
file(TIMESTAMP ${DLL} SOURCE_TIME)
file(TIMESTAMP ${DEST_FILE} DEST_TIME)
if(NOT SOURCE_TIME IS_NEWER_THAN DEST_TIME)
set(SHOULD_COPY FALSE)
endif()
endif()
endif()
if(SHOULD_COPY)
message(" Copying dependency: ${DLL_NAME}")
file(COPY ${DLL} DESTINATION ${OUTPUT_DIR})
math(EXPR COPIED_DLLS "${COPIED_DLLS} + 1")
else()
message(" Skipping ${DLL_NAME} (already up to date)")
endif()
endforeach()
list(LENGTH DLLS DLL_COUNT)
message("Successfully copied ${DLL_COUNT} runtime DLL dependencies")
list(LENGTH DLLS TOTAL_DLLS)
message("Processed ${TOTAL_DLLS} runtime dependencies, copied ${COPIED_DLLS} files")
# Step 3: Copy GStreamer plugins
message("Copying GStreamer plugins...")
@@ -62,7 +137,8 @@ else()
message(WARNING "No GStreamer plugins found in ${MSYS2_BIN_PATH}/../lib/gstreamer-1.0/")
endif()
# Step 4: Copy additional MinGW runtime DLLs that might be missed
# Step 4: Manually copy the correct MSYS2 MinGW runtime DLLs.
# This ensures the versions required by GStreamer/FFmpeg are used.
set(ADDITIONAL_DLLS
"libgcc_s_seh-1.dll"
"libstdc++-6.dll"
@@ -73,9 +149,45 @@ set(ADDITIONAL_DLLS
"libglib-2.0-0.dll"
"libintl-8.dll"
"libiconv-2.dll"
"libfdk-aac-2.dll"
"libfaad-2.dll"
"avfilter-10.dll"
"libopenal-1.dll"
"libgstaudio-1.0-0.dll"
"libgstvideo-1.0-0.dll"
"liborc-0.4-0.dll"
"libgstpbutils-1.0-0.dll"
"libgsttag-1.0-0.dll"
"libgstlibav.dll"
"libass-9.dll"
"libfontconfig-1.dll"
"libharfbuzz-0.dll"
"libexpat-1.dll"
"libfreetype-6.dll"
"libpng16-16.dll"
"libgraphite2.dll"
"libfribidi-0.dll"
"libunibreak-6.dll"
"liblcms2-2.dll"
"libvpl-2.dll"
"libzimg-2.dll"
"libdovi.dll"
"libshaderc_shared.dll"
"vulkan-1.dll"
"libvidstab.dll"
"libgomp-1.dll"
"postproc-58.dll"
"libplacebo-351.dll"
"libspirv-cross-c-shared.dll"
"libva.dll"
"libxml2-16.dll"
"libva_win32.dll"
"libpcre2-8-0.dll"
"libffi-8.dll"
"libgmodule-2.0-0.dll"
)
message("Copying additional MinGW runtime DLLs...")
message("Copying additional MinGW runtime DLLs from MSYS2...")
foreach(DLL_NAME ${ADDITIONAL_DLLS})
set(DLL_PATH "${MSYS2_BIN_PATH}/${DLL_NAME}")
if(EXISTS ${DLL_PATH})
@@ -84,4 +196,13 @@ foreach(DLL_NAME ${ADDITIONAL_DLLS})
endif()
endforeach()
message("Copying GStreamer helper executables...")
set(GST_LIBEXEC_PATH "${MSYS2_BIN_PATH}/../libexec/gstreamer-1.0")
file(COPY "${GST_LIBEXEC_PATH}/gst-plugin-scanner.exe" DESTINATION "${OUTPUT_DIR}/libexec/gstreamer-1.0")
file(COPY "${GST_LIBEXEC_PATH}/gst-ptp-helper.exe" DESTINATION "${OUTPUT_DIR}/libexec/gstreamer-1.0")
message("Copying executables")
file(COPY C:/msys64/mingw64/bin/iproxy.exe DESTINATION ${OUTPUT_DIR})
message("=== Windows deployment completed ===")
+32
View File
@@ -0,0 +1,32 @@
#include "winver.h"
IDI_ICON1 ICON "resources/icons/app-icon/icon.ico"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGS 0x0L
FILEFLAGSMASK 0x3fL
FILEOS 0x00040004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "CompanyName", "iDescriptor"
VALUE "FileDescription", "iDescriptor - iDevice Management Tool"
VALUE "FileVersion", "1.0.0.0"
VALUE "LegalCopyright", "Copyright (C) 2025 iDescriptor"
VALUE "InternalName", "idescriptor"
VALUE "OriginalFilename", "idescriptor.exe"
VALUE "ProductName", "iDescriptor"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
END
+1 -1
View File
@@ -17,7 +17,7 @@
<file>resources/icons/MdiLightMagnify.png</file>
<file>resources/icons/IcBaselineInsertDriveFile.png</file>
<file>resources/icons/MaterialSymbolsLightHome.png</file>
<!-- <file>resources/icons/icon.png</file> -->
<file>resources/icons/app-icon/icon.ico</file>
<file>qml/MapView.qml</file>
<!-- TODO: -->
<!-- <file>resources/dump.js</file> -->
+5 -4
View File
@@ -349,14 +349,15 @@ void AppsWidget::createAppCard(const QString &name, const QString &bundleId,
// Install button placeholder
ZLabel *installLabel = new ZLabel("Install");
installLabel->setAlignment(Qt::AlignCenter);
ZLabel *downloadIpaLabel = new ZLabel("Download IPA");
downloadIpaLabel->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);
connect(installLabel, &ZLabel::clicked, this,
[this, name, bundleId, description]() {
onAppCardClicked(name, bundleId, description);
+5 -1
View File
@@ -11,7 +11,11 @@
#include <map>
#include <string>
#ifdef WIN32
#include "dns_sd.h"
#else
#include <dns_sd.h>
#endif
class DnssdService : public QObject
{
@@ -69,4 +73,4 @@ private:
QMap<QString, PendingDevice> m_pendingDevices;
};
#endif // DNSSD_SERVICE_H
#endif // DNSSD_SERVICE_H
+10 -1
View File
@@ -1,12 +1,16 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#define _GNU_SOURCE 1
#define __USE_GNU 1
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
@@ -14,11 +18,16 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef WIN32
#include <signal.h>
#endif
#include <libimobiledevice/afc.h>
#include <libimobiledevice/installation_proxy.h>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
#include <libimobiledevice/notification_proxy.h>
#include <plist/plist.h>
+40 -2
View File
@@ -1,17 +1,55 @@
#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QStyleFactory>
#include <QtGlobal>
#include <stdlib.h> // For getenv
#ifdef WIN32
#include "platform/windows/check_deps.h"
#endif
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
#ifdef WIN32
// This setup MUST be done before the QApplication object is created.
QString appPath = QCoreApplication::applicationDirPath();
QString gstPluginPath =
QDir::toNativeSeparators(appPath + "/gstreamer-1.0");
QString gstPluginScannerPath =
QDir::toNativeSeparators(appPath + "/gstreamer-1.0/gst-plugin-scanner");
// Add the application's directory to the PATH so GStreamer plugins can find
// their dependencies (e.g., FFmpeg DLLs).
const char *oldPath = getenv("PATH");
QString newPath = appPath + ";" + QString(oldPath);
qputenv("PATH", newPath.toUtf8());
qputenv("GST_PLUGIN_PATH", gstPluginPath.toUtf8());
qDebug() << "GST_PLUGIN_PATH=" << gstPluginPath;
printf("GST_PLUGIN_PATH=%s\n", gstPluginPath.toUtf8().data());
qputenv("GST_REGISTRY_REUSE_PLUGIN_SCANNER", "yes");
qDebug() << "GST_REGISTRY_REUSE_PLUGIN_SCANNER=yes";
printf("GST_REGISTRY_REUSE_PLUGIN_SCANNER=yes\n");
qputenv("GST_PLUGIN_SYSTEM_PATH", gstPluginPath.toUtf8());
qDebug() << "GST_PLUGIN_SYSTEM_PATH=" << gstPluginPath;
printf("GST_PLUGIN_SYSTEM_PATH=%s\n", gstPluginPath.toUtf8().data());
qputenv("GST_DEBUG", "GST_PLUGIN_LOADING:5");
qDebug() << "GST_DEBUG=GST_PLUGIN_LOADING:5";
printf("GST_DEBUG=GST_PLUGIN_LOADING:5\n");
qputenv("GST_PLUGIN_SCANNER_1_0", gstPluginScannerPath.toUtf8());
qDebug() << "GST_PLUGIN_SCANNER_1_0=" << gstPluginScannerPath;
printf("GST_PLUGIN_SCANNER_1_0=%s\n", gstPluginScannerPath.toUtf8().data());
#endif
QCoreApplication::setOrganizationName("iDescriptor");
// QCoreApplication::setOrganizationDomain("iDescriptor.com");
QCoreApplication::setApplicationName("iDescriptor");
// QCoreApplication::setApplicationVersion(IDESCRIPTOR_VERSION);
QApplication::setStyle(QStyleFactory::create("macOS"));
// QApplication::setStyle(QStyleFactory::create("Windows"));
#ifndef __APPLE__
QApplication::setStyle(QStyleFactory::create("Fusion"));
#endif
MainWindow *w = MainWindow::sharedInstance();
w->show();
+23
View File
@@ -29,6 +29,10 @@
#include <QMessageBox>
#include <libusb-1.0/libusb.h>
#ifdef WIN32
#include "platform/windows/diagnose_widget.h"
#endif
void handleCallback(const idevice_event_t *event, void *userData)
{
printf("Device event received: ");
@@ -134,6 +138,25 @@ MainWindow::MainWindow(QWidget *parent)
// Welcome page (shown when no devices are connected)
WelcomeWidget *welcomePage = new WelcomeWidget(this);
// No devices page
QWidget *noDevicesPage = new QWidget();
QVBoxLayout *noDeviceLayout = new QVBoxLayout(noDevicesPage);
noDeviceLayout->addStretch();
QHBoxLayout *labelLayout = new QHBoxLayout();
labelLayout->addStretch();
QLabel *noDeviceLabel = new QLabel("No devices detected");
noDeviceLabel->setAlignment(Qt::AlignCenter);
labelLayout->addWidget(noDeviceLabel);
labelLayout->addStretch();
noDeviceLayout->addLayout(labelLayout);
#ifdef WIN32
// Add diagnose widget to check dependencies
// DiagnoseWidget *diagnoseWidget = new DiagnoseWidget();
// noDeviceLayout->addWidget(diagnoseWidget);
#endif
noDeviceLayout->addStretch();
m_deviceManager = new DeviceManagerWidget(this);
-2
View File
@@ -21,8 +21,6 @@
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<layout class="QHBoxLayout" name="horizontalLayout">
</layout>
</widget>
<resources/>
<connections/>
+70
View File
@@ -0,0 +1,70 @@
#include <string>
#include <windows.h>
bool CheckRegistry(HKEY hKeyRoot, LPCSTR subKey, LPCSTR displayNameToFind)
{
HKEY hKey;
if (RegOpenKeyExA(hKeyRoot, subKey, 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
return false;
}
char keyName[256];
DWORD keyNameSize = sizeof(keyName);
DWORD index = 0;
while (RegEnumKeyExA(hKey, index++, keyName, &keyNameSize, NULL, NULL, NULL,
NULL) == ERROR_SUCCESS) {
HKEY appKey;
if (RegOpenKeyExA(hKey, keyName, 0, KEY_READ, &appKey) ==
ERROR_SUCCESS) {
char displayName[256];
DWORD displayNameSize = sizeof(displayName);
if (RegQueryValueExA(appKey, "DisplayName", NULL, NULL,
(LPBYTE)displayName,
&displayNameSize) == ERROR_SUCCESS) {
if (strcmp(displayName, displayNameToFind) == 0) {
RegCloseKey(appKey);
RegCloseKey(hKey);
return true;
}
}
RegCloseKey(appKey);
}
keyNameSize = sizeof(keyName);
}
RegCloseKey(hKey);
return false;
}
bool IsAppleMobileDeviceSupportInstalled()
{
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"Apple Mobile Device Support")) {
return true;
}
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\WOW6432Node\\Microsoft\\Wi"
"ndows\\CurrentVersion\\Uninstall",
"Apple Mobile Device Support")) {
return true;
}
return false;
}
bool IsWinFspInstalled()
{
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"WinFsp 2025")) {
return true;
}
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\WOW6432Node\\Microsoft\\Wi"
"ndows\\CurrentVersion\\Uninstall",
"WinFsp 2025")) {
return true;
}
return false;
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef CHECK_DEPS_H
#define CHECK_DEPS_H
bool IsAppleMobileDeviceSupportInstalled();
bool IsWinFspInstalled();
#endif // CHECK_DEPS_H
+40
View File
@@ -0,0 +1,40 @@
#include "diagnose_dialog.h"
#include <QApplication>
DiagnoseDialog::DiagnoseDialog(QWidget *parent) : QDialog(parent)
{
setupUI();
setWindowTitle("System Dependencies");
setModal(true);
resize(500, 400);
// Set clean close behavior
setAttribute(Qt::WA_DeleteOnClose, true);
}
void DiagnoseDialog::setupUI()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10);
// Add the main diagnose widget
m_diagnoseWidget = new DiagnoseWidget();
// Close button
QHBoxLayout *buttonLayout = new QHBoxLayout();
buttonLayout->addStretch();
m_closeButton = new QPushButton("Close");
m_closeButton->setMinimumWidth(80);
connect(m_closeButton, &QPushButton::clicked, this,
&DiagnoseDialog::onCloseClicked);
buttonLayout->addWidget(m_closeButton);
// Layout assembly
mainLayout->addWidget(m_diagnoseWidget, 1);
mainLayout->addLayout(buttonLayout);
}
void DiagnoseDialog::onCloseClicked() { accept(); }
+28
View File
@@ -0,0 +1,28 @@
#ifndef DIAGNOSE_DIALOG_H
#define DIAGNOSE_DIALOG_H
#include "diagnose_widget.h"
#include <QDialog>
#include <QHBoxLayout>
#include <QPushButton>
#include <QVBoxLayout>
class DiagnoseDialog : public QDialog
{
Q_OBJECT
public:
explicit DiagnoseDialog(QWidget *parent = nullptr);
private slots:
void onCloseClicked();
private:
void setupUI();
DiagnoseWidget *m_diagnoseWidget;
QPushButton *m_closeButton;
};
#endif // DIAGNOSE_DIALOG_H
+234
View File
@@ -0,0 +1,234 @@
#include "diagnose_widget.h"
#include "check_deps.h"
#include <QApplication>
#include <QDesktopServices>
#include <QMessageBox>
#include <QTimer>
#include <QUrl>
DependencyItem::DependencyItem(const QString &name, const QString &description,
QWidget *parent)
: QWidget(parent), m_name(name)
{
setFixedHeight(80);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(10, 5, 10, 5);
// Left side - info
QVBoxLayout *infoLayout = new QVBoxLayout();
m_nameLabel = new QLabel(name);
QFont nameFont = m_nameLabel->font();
nameFont.setBold(true);
nameFont.setPointSize(nameFont.pointSize() + 1);
m_nameLabel->setFont(nameFont);
m_descriptionLabel = new QLabel(description);
m_descriptionLabel->setWordWrap(true);
infoLayout->addWidget(m_nameLabel);
infoLayout->addWidget(m_descriptionLabel);
// Middle - status
m_statusLabel = new QLabel("Checking...");
m_statusLabel->setMinimumWidth(100);
m_statusLabel->setAlignment(Qt::AlignCenter);
// Right side - actions
QVBoxLayout *actionLayout = new QVBoxLayout();
m_installButton = new QPushButton("Install");
m_installButton->setMinimumWidth(80);
m_installButton->setVisible(false);
connect(m_installButton, &QPushButton::clicked, this,
&DependencyItem::onInstallClicked);
m_progressBar = new QProgressBar();
m_progressBar->setRange(0, 0); // Indeterminate
m_progressBar->setVisible(false);
m_progressBar->setMaximumHeight(20);
actionLayout->addWidget(m_installButton);
actionLayout->addWidget(m_progressBar);
actionLayout->addStretch();
layout->addLayout(infoLayout, 1);
layout->addWidget(m_statusLabel);
layout->addLayout(actionLayout);
}
void DependencyItem::setInstalled(bool installed)
{
setChecking(false);
if (installed) {
m_statusLabel->setText("✓ Installed");
m_statusLabel->setStyleSheet("color: green; font-weight: bold;");
m_installButton->setVisible(false);
} else {
m_statusLabel->setText("✗ Not Installed");
m_statusLabel->setStyleSheet("color: red; font-weight: bold;");
m_installButton->setVisible(true);
}
}
void DependencyItem::setChecking(bool checking)
{
if (checking) {
m_statusLabel->setText("Checking...");
m_statusLabel->setStyleSheet("color: gray;");
m_installButton->setVisible(false);
m_progressBar->setVisible(false);
}
}
void DependencyItem::onInstallClicked() { emit installRequested(m_name); }
DiagnoseWidget::DiagnoseWidget(QWidget *parent) : QWidget(parent)
{
setupUI();
// Add dependency items
addDependencyItem("Apple Mobile Device Support",
"Required for iOS device communication");
addDependencyItem("WinFsp",
"Required for filesystem operations and mounting");
// Auto-check on startup
QTimer::singleShot(100, this, &DiagnoseWidget::checkDependencies);
}
void DiagnoseWidget::setupUI()
{
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setSpacing(10);
// Title and summary
QLabel *titleLabel = new QLabel("Dependency Check");
QFont titleFont = titleLabel->font();
titleFont.setBold(true);
titleFont.setPointSize(titleFont.pointSize() + 2);
titleLabel->setFont(titleFont);
m_summaryLabel = new QLabel("Checking system dependencies...");
// Check button
m_checkButton = new QPushButton("Refresh Check");
m_checkButton->setMaximumWidth(150);
connect(m_checkButton, &QPushButton::clicked, this,
&DiagnoseWidget::checkDependencies);
// Scroll area for dependency items
m_scrollArea = new QScrollArea();
m_scrollArea->setWidgetResizable(true);
// m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarNever);
m_scrollArea->setMinimumHeight(200);
m_itemsWidget = new QWidget();
m_itemsLayout = new QVBoxLayout(m_itemsWidget);
m_itemsLayout->setSpacing(5);
m_itemsLayout->addStretch();
m_scrollArea->setWidget(m_itemsWidget);
// Layout assembly
QHBoxLayout *headerLayout = new QHBoxLayout();
headerLayout->addWidget(titleLabel);
headerLayout->addStretch();
headerLayout->addWidget(m_checkButton);
m_mainLayout->addLayout(headerLayout);
m_mainLayout->addWidget(m_summaryLabel);
m_mainLayout->addWidget(m_scrollArea, 1);
}
void DiagnoseWidget::addDependencyItem(const QString &name,
const QString &description)
{
DependencyItem *item = new DependencyItem(name, description);
item->setProperty("name", name); // Set the name property for identification
connect(item, &DependencyItem::installRequested, this,
&DiagnoseWidget::onInstallRequested);
m_dependencyItems.append(item);
// Insert before the stretch
m_itemsLayout->insertWidget(m_itemsLayout->count() - 1, item);
}
void DiagnoseWidget::checkDependencies()
{
m_summaryLabel->setText("Checking system dependencies...");
m_checkButton->setEnabled(false);
// Reset all items to checking state
for (DependencyItem *item : m_dependencyItems) {
item->setChecking(true);
}
// Simulate async checking with timer
QTimer::singleShot(500, [this]() {
int installedCount = 0;
int totalCount = m_dependencyItems.size();
for (DependencyItem *item : m_dependencyItems) {
bool installed = false;
QString itemName = item->property("name").toString();
if (itemName == "Apple Mobile Device Support") {
installed = IsAppleMobileDeviceSupportInstalled();
} else if (itemName == "WinFsp") {
installed = IsWinFspInstalled();
}
item->setInstalled(installed);
if (installed)
installedCount++;
}
// Update summary
if (installedCount == totalCount) {
m_summaryLabel->setText(
QString("All dependencies are installed (%1/%2)")
.arg(installedCount)
.arg(totalCount));
m_summaryLabel->setStyleSheet("color: green; font-weight: bold;");
} else {
m_summaryLabel->setText(
QString("Missing dependencies (%1/%2 installed)")
.arg(installedCount)
.arg(totalCount));
m_summaryLabel->setStyleSheet("color: red; font-weight: bold;");
}
m_checkButton->setEnabled(true);
});
}
void DiagnoseWidget::onInstallRequested(const QString &name)
{
QString url;
QString message;
if (name == "Apple Mobile Device Support") {
url = "https://support.apple.com/downloads/itunes";
message = "Apple Mobile Device Support is typically installed with "
"iTunes.\n\n"
"Would you like to open the iTunes download page?";
} else if (name == "WinFsp") {
url = "https://winfsp.dev/rel/";
message = "WinFsp can be downloaded from the official website.\n\n"
"Would you like to open the WinFsp download page?";
}
if (!url.isEmpty()) {
int ret = QMessageBox::question(this, "Install " + name, message,
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
QDesktopServices::openUrl(QUrl(url));
}
}
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef DIAGNOSE_WIDGET_H
#define DIAGNOSE_WIDGET_H
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
class DependencyItem : public QWidget
{
Q_OBJECT
public:
explicit DependencyItem(const QString &name, const QString &description,
QWidget *parent = nullptr);
void setInstalled(bool installed);
void setChecking(bool checking);
signals:
void installRequested(const QString &name);
private slots:
void onInstallClicked();
private:
QString m_name;
QLabel *m_nameLabel;
QLabel *m_descriptionLabel;
QLabel *m_statusLabel;
QPushButton *m_installButton;
QProgressBar *m_progressBar;
};
class DiagnoseWidget : public QWidget
{
Q_OBJECT
public:
explicit DiagnoseWidget(QWidget *parent = nullptr);
public slots:
void checkDependencies();
private slots:
void onInstallRequested(const QString &name);
private:
void setupUI();
void addDependencyItem(const QString &name, const QString &description);
QVBoxLayout *m_mainLayout;
QVBoxLayout *m_itemsLayout;
QPushButton *m_checkButton;
QLabel *m_summaryLabel;
QScrollArea *m_scrollArea;
QWidget *m_itemsWidget;
QList<DependencyItem *> m_dependencyItems;
};
#endif // DIAGNOSE_WIDGET_H
+14 -3
View File
@@ -24,8 +24,8 @@ SSHTerminalWidget::SSHTerminalWidget(const ConnectionInfo &connectionInfo,
m_sshChannel(nullptr), m_iproxyProcess(nullptr), m_sshConnected(false),
m_isInitialized(false), m_currentState(TerminalState::Loading)
{
setWindowTitle(
QString("SSH Terminal - %1").arg(m_connectionInfo.deviceName));
setWindowTitle(QString("SSH Terminal / %1 - iDescriptor")
.arg(m_connectionInfo.deviceName));
setMinimumSize(800, 600);
setupUI();
@@ -130,8 +130,11 @@ void SSHTerminalWidget::setupActionState()
menu.exec(m_terminal->mapToGlobal(pos));
}
});
#ifdef WIN32
m_terminal->startTerminalEmulation();
#else
m_terminal->startTerminalTeletype();
#endif
m_terminal->setStyleSheet("padding: 5px;");
actionLayout->addWidget(m_terminal);
@@ -459,8 +462,12 @@ void SSHTerminalWidget::checkSshData()
int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer,
sizeof(buffer), 0);
if (nbytes > 0) {
#ifdef WIN32
m_terminal->receiveData(buffer, nbytes);
#else
// Write data to terminal's PTY
write(m_terminal->getPtySlaveFd(), buffer, nbytes);
#endif
}
}
@@ -471,7 +478,11 @@ void SSHTerminalWidget::checkSshData()
sizeof(buffer), 1);
if (nbytes > 0) {
// Write stderr data to terminal's PTY
#ifdef WIN32
m_terminal->receiveData(buffer, nbytes);
#else
write(m_terminal->getPtySlaveFd(), buffer, nbytes);
#endif
}
}
+7 -6
View File
@@ -14,7 +14,7 @@ void WelcomeWidget::setupUI()
{
// Main layout with proper spacing and margins
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setContentsMargins(40, 60, 40, 60);
m_mainLayout->setContentsMargins(5, 5, 5, 5);
m_mainLayout->setSpacing(0);
// Add top stretch
@@ -34,21 +34,22 @@ void WelcomeWidget::setupUI()
palette.color(QPalette::WindowText).lighter(140));
m_subtitleLabel->setPalette(palette);
m_mainLayout->addWidget(m_subtitleLabel);
m_mainLayout->addSpacing(40);
m_mainLayout->addSpacing(10);
m_imageLabel = new ResponsiveQLabel();
m_imageLabel->setPixmap(QPixmap(":/resources/connect.png"));
// Let the pixmap scale while preserving aspect ratio
m_imageLabel->setScaledContents(true);
// Prefer centered, not full-width expansion
m_imageLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_imageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// Cap size so it stays nicely centered on large windows
m_imageLabel->setMaximumSize(480, 320);
// m_imageLabel->setMaximumSize(480, 320);
m_imageLabel->setStyleSheet("background: transparent; border: none;");
m_imageLabel->setAlignment(Qt::AlignCenter);
m_mainLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter);
m_mainLayout->addSpacing(32);
m_mainLayout->addSpacing(10);
// Instruction text
m_instructionLabel = createStyledLabel(
@@ -60,7 +61,7 @@ void WelcomeWidget::setupUI()
instructionPalette.color(QPalette::WindowText).lighter(120));
m_instructionLabel->setPalette(instructionPalette);
m_mainLayout->addWidget(m_instructionLabel);
m_mainLayout->addSpacing(24);
m_mainLayout->addSpacing(10);
// GitHub link
m_githubLabel =