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 # Platform-specific paths for libraries built from source
if(WIN32) 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_LIB_PATH "C:/msys64/mingw64/lib")
set(CUSTOM_BIN_PATH "C:/msys64/mingw64/bin") set(CUSTOM_BIN_PATH "C:/msys64/mingw64/bin")
set(CUSTOM_INCLUDE_PATH "C:/msys64/mingw64/include") set(CUSTOM_INCLUDE_PATH "C:/msys64/mingw64/include")
@@ -31,25 +35,19 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Networ
# Add QTermWidget # Add QTermWidget
pkg_check_modules(QTERMWIDGET REQUIRED IMPORTED_TARGET qtermwidget6) pkg_check_modules(QTERMWIDGET REQUIRED IMPORTED_TARGET qtermwidget6)
if(WIN32)
# Add Avahi for network device discovery # Get the path to the Qt bin directory
if (APPLE) get_target_property(QT_BIN_PATH Qt${QT_VERSION_MAJOR}::Core IMPORTED_LOCATION_RELEASE)
# On macOS, use the built-in Bonjour framework instead of Avahi if(NOT QT_BIN_PATH)
find_library(CORE_SERVICES_FRAMEWORK CoreServices REQUIRED) get_target_property(QT_BIN_PATH Qt${QT_VERSION_MAJOR}::Core IMPORTED_LOCATION_DEBUG)
add_definitions(-DUSE_DNS_SD) endif()
message(STATUS "Using macOS Bonjour framework for network service discovery") if(NOT QT_BIN_PATH)
else() get_target_property(QT_BIN_PATH Qt${QT_VERSION_MAJOR}::Core IMPORTED_LOCATION)
# On Linux and Windows, use Avahi endif()
add_definitions(-DUSE_AVAHI) get_filename_component(QT_BIN_PATH ${QT_BIN_PATH} DIRECTORY)
message(STATUS "Using Avahi for network service discovery") message(STATUS "Found Qt bin directory: ${QT_BIN_PATH}")
pkg_check_modules(AVAHI_CLIENT REQUIRED IMPORTED_TARGET avahi-client)
endif() 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 find_library(IMOBILEDEVICE_LIBRARY
NAMES imobiledevice-1.0 NAMES imobiledevice-1.0
PATHS ${CUSTOM_LIB_PATH} PATHS ${CUSTOM_LIB_PATH}
@@ -64,13 +62,6 @@ find_library(IMOBILEDEVICE_GLUE_LIBRARY
REQUIRED REQUIRED
) )
# find_library(PLIST_LIBRARY
# NAMES plist-2.0
# PATHS /usr/local/lib
# NO_DEFAULT_PATH
# REQUIRED
# )
find_library(TATSU_LIBRARY find_library(TATSU_LIBRARY
NAMES tatsu NAMES tatsu
PATHS ${CUSTOM_LIB_PATH} PATHS ${CUSTOM_LIB_PATH}
@@ -89,18 +80,10 @@ find_library(IRECOVERY_LIBRARY
REQUIRED REQUIRED
) )
# # Add missing libraries
# find_library(USBMUXD_LIBRARY
# NAMES usbmuxd-2.0
# PATHS /usr/local/lib
# NO_DEFAULT_PATH
# REQUIRED
# )
if(WIN32) if(WIN32)
# On MSYS2, these are found in the standard mingw64 prefix # On MSYS2, these are found in the standard mingw64 prefix
find_library(SSL_LIBRARY NAMES ssl REQUIRED) find_library(SSL_LIBRARY NAMES ssl PATHS C:/msys64/mingw64/lib REQUIRED)
find_library(CRYPTO_LIBRARY NAMES crypto REQUIRED) find_library(CRYPTO_LIBRARY NAMES crypto PATHS C:/msys64/mingw64/lib REQUIRED)
else() else()
find_library(SSL_LIBRARY find_library(SSL_LIBRARY
NAMES ssl NAMES ssl
@@ -116,11 +99,7 @@ else()
endif() endif()
# Add libssh for SSH connections # Add libssh for SSH connections
find_library(SSH_LIBRARY pkg_check_modules(SSH REQUIRED IMPORTED_TARGET libssh)
NAMES ssh
PATHS ${CUSTOM_LIB_PATH} /usr/lib /usr/lib/x86_64-linux-gnu
REQUIRED
)
# Apple-specific crypto libraries for SSH # Apple-specific crypto libraries for SSH
if(APPLE) if(APPLE)
@@ -128,37 +107,11 @@ if(APPLE)
find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED) find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
endif() 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(PUGIXML REQUIRED IMPORTED_TARGET pugixml)
pkg_check_modules(USB REQUIRED IMPORTED_TARGET libusb-1.0) pkg_check_modules(USB REQUIRED IMPORTED_TARGET libusb-1.0)
pkg_check_modules(PLIST REQUIRED IMPORTED_TARGET libplist-2.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 file(GLOB PROJECT_SOURCES
src/*.cpp src/*.cpp
@@ -177,6 +130,16 @@ if(APPLE)
) )
endif() 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) if(LINUX)
list(APPEND PROJECT_SOURCES list(APPEND PROJECT_SOURCES
src/core/services/avahi/avahi_service.cpp src/core/services/avahi/avahi_service.cpp
@@ -189,9 +152,9 @@ endif()
add_subdirectory(lib/airplay) add_subdirectory(lib/airplay)
add_subdirectory(lib/ipatool-go) add_subdirectory(lib/ipatool-go)
if (WIN32) if (WIN32)
# todo set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/idescriptor.rc")
set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/resources/todo.rc")
qt_add_executable(iDescriptor qt_add_executable(iDescriptor
MANUAL_FINALIZATION MANUAL_FINALIZATION
${PROJECT_SOURCES} ${PROJECT_SOURCES}
@@ -228,14 +191,11 @@ target_link_libraries(iDescriptor PRIVATE
Qt6::QuickControls2 Qt6::QuickControls2
${IMOBILEDEVICE_LIBRARY} ${IMOBILEDEVICE_LIBRARY}
${IMOBILEDEVICE_GLUE_LIBRARY} ${IMOBILEDEVICE_GLUE_LIBRARY}
# ${PLIST_LIBRARY}
${TATSU_LIBRARY} ${TATSU_LIBRARY}
${IRECOVERY_LIBRARY} ${IRECOVERY_LIBRARY}
${SSL_LIBRARY} ${SSL_LIBRARY}
${CRYPTO_LIBRARY} ${CRYPTO_LIBRARY}
${SSH_LIBRARY} PkgConfig::SSH
# ${FRIDA_LIBRARY}
# ${ZIP_LIBRARY}
PkgConfig::PUGIXML PkgConfig::PUGIXML
PkgConfig::USB PkgConfig::USB
PkgConfig::PLIST PkgConfig::PLIST
@@ -246,16 +206,24 @@ target_link_libraries(iDescriptor PRIVATE
airplay airplay
ipatool-go ipatool-go
) )
if(NOT APPLE) if(APPLE)
find_library(CORE_SERVICES_FRAMEWORK CoreServices REQUIRED)
target_link_libraries(iDescriptor PRIVATE target_link_libraries(iDescriptor PRIVATE
PkgConfig::AVAHI_CLIENT ${CORE_SERVICES_FRAMEWORK})
# PkgConfig::AVAHI_COMMON 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() else()
target_link_libraries(iDescriptor PRIVATE target_link_libraries(iDescriptor PRIVATE
${CORE_SERVICES_FRAMEWORK} PkgConfig::AVAHI_CLIENT
# PkgConfig::AVAHI_COMMON
) )
message(STATUS "Using Avahi for network service discovery")
endif() endif()
@@ -285,26 +253,94 @@ set_target_properties(iDescriptor PROPERTIES
) )
include(GNUInstallDirs) include(GNUInstallDirs)
install(TARGETS iDescriptor
BUNDLE DESTINATION . # Set the installation directory to be within the build folder
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/dist")
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6) if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(iDescriptor) qt_finalize_executable(iDescriptor)
endif() endif()
# Copy runtime DLLs to build directory after building # Copy runtime DLLs to build directory after building
if(WIN32) if(WIN32 AND NOT DEFINED NO_DEPLOY)
add_custom_command(TARGET iDescriptor POST_BUILD add_custom_command(TARGET iDescriptor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Starting Windows deployment..." COMMAND ${CMAKE_COMMAND} -E echo "Starting Windows deployment..."
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-DEXECUTABLE_PATH=${CMAKE_CURRENT_BINARY_DIR}/iDescriptor.exe -DEXECUTABLE_PATH=$<TARGET_FILE:iDescriptor>
-DMSYS2_BIN_PATH=${CUSTOM_BIN_PATH} -DQT_BIN_PATH=${QT_BIN_PATH}
-DOUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR} -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 -P ${CMAKE_CURRENT_LIST_DIR}/cmake/win-deploy.cmake
COMMENT "Deploying Windows application with all dependencies" COMMENT "Deploying Windows application with all dependencies"
VERBATIM 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 # Windows deployment script for Qt applications with MinGW/MSYS2
# This script handles Qt deployment, runtime DLL copying, and GStreamer plugins # This script handles Qt deployment, runtime DLL copying, and GStreamer plugins
if(NOT EXISTS ${EXECUTABLE_PATH}) # Strip quotes from all path variables if they exist
message(FATAL_ERROR "Executable not found: ${EXECUTABLE_PATH}") 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() endif()
message("=== Starting Windows deployment for: ${EXECUTABLE_PATH} ===") 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 if(NOT EXISTS ${EXECUTABLE_PATH})
message("Running windeployqt6 to deploy Qt dependencies...") 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( 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 RESULT_VARIABLE WINDEPLOYQT_RESULT
OUTPUT_VARIABLE WINDEPLOYQT_OUTPUT OUTPUT_VARIABLE WINDEPLOYQT_OUTPUT
ERROR_VARIABLE WINDEPLOYQT_ERROR ERROR_VARIABLE WINDEPLOYQT_ERROR
@@ -25,22 +66,56 @@ endif()
# Step 2: Find and copy runtime dependencies using GET_RUNTIME_DEPENDENCIES # Step 2: Find and copy runtime dependencies using GET_RUNTIME_DEPENDENCIES
message("Analyzing runtime dependencies for: ${EXECUTABLE_PATH}") 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 file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES ${EXECUTABLE_PATH} EXECUTABLES ${EXECUTABLE_PATH}
RESOLVED_DEPENDENCIES_VAR DLLS RESOLVED_DEPENDENCIES_VAR DLLS
PRE_EXCLUDE_REGEXES "^api-ms-" "^ext-ms-" "^AVRT" "^avrt" "^MSVCP" "^VCRUNTIME" "^ucrtbase" 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$"
POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" ".*SysWOW64/.*\\.dll" ".*Windows/.*\\.dll" ".*Microsoft.VC.*" #PRE_EXCLUDE_REGEXES "^api-ms-" "^ext-ms-" "^AVRT" "^avrt" "^MSVCP" "^VCRUNTIME" "^ucrtbase"
DIRECTORIES ${MSYS2_BIN_PATH} $ENV{PATH} 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}) foreach(DLL ${DLLS})
get_filename_component(DLL_NAME ${DLL} NAME) get_filename_component(DLL_NAME ${DLL} NAME)
message("Copying dependency: ${DLL_NAME}") get_filename_component(DLL_DIR ${DLL} DIRECTORY)
file(COPY ${DLL} DESTINATION ${OUTPUT_DIR})
# 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() endforeach()
list(LENGTH DLLS DLL_COUNT) list(LENGTH DLLS TOTAL_DLLS)
message("Successfully copied ${DLL_COUNT} runtime DLL dependencies") message("Processed ${TOTAL_DLLS} runtime dependencies, copied ${COPIED_DLLS} files")
# Step 3: Copy GStreamer plugins # Step 3: Copy GStreamer plugins
message("Copying 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/") message(WARNING "No GStreamer plugins found in ${MSYS2_BIN_PATH}/../lib/gstreamer-1.0/")
endif() 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 set(ADDITIONAL_DLLS
"libgcc_s_seh-1.dll" "libgcc_s_seh-1.dll"
"libstdc++-6.dll" "libstdc++-6.dll"
@@ -73,9 +149,45 @@ set(ADDITIONAL_DLLS
"libglib-2.0-0.dll" "libglib-2.0-0.dll"
"libintl-8.dll" "libintl-8.dll"
"libiconv-2.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}) foreach(DLL_NAME ${ADDITIONAL_DLLS})
set(DLL_PATH "${MSYS2_BIN_PATH}/${DLL_NAME}") set(DLL_PATH "${MSYS2_BIN_PATH}/${DLL_NAME}")
if(EXISTS ${DLL_PATH}) if(EXISTS ${DLL_PATH})
@@ -84,4 +196,13 @@ foreach(DLL_NAME ${ADDITIONAL_DLLS})
endif() endif()
endforeach() 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 ===") 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/MdiLightMagnify.png</file>
<file>resources/icons/IcBaselineInsertDriveFile.png</file> <file>resources/icons/IcBaselineInsertDriveFile.png</file>
<file>resources/icons/MaterialSymbolsLightHome.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> <file>qml/MapView.qml</file>
<!-- TODO: --> <!-- TODO: -->
<!-- <file>resources/dump.js</file> --> <!-- <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 // Install button placeholder
ZLabel *installLabel = new ZLabel("Install"); ZLabel *installLabel = new ZLabel("Install");
installLabel->setAlignment(Qt::AlignCenter); installLabel->setAlignment(Qt::AlignCenter);
ZLabel *downloadIpaLabel = new ZLabel("Download IPA");
downloadIpaLabel->setAlignment(Qt::AlignCenter);
installLabel->setStyleSheet("font-size: 12px; color: #007AFF; font-weight: " installLabel->setStyleSheet("font-size: 12px; color: #007AFF; font-weight: "
"bold; background-color: transparent;"); "bold; background-color: transparent;");
installLabel->setCursor(Qt::PointingHandCursor); installLabel->setCursor(Qt::PointingHandCursor);
installLabel->setFixedHeight(30); installLabel->setFixedHeight(30);
ZLabel *downloadIpaLabel = new ZLabel("Download IPA");
downloadIpaLabel->setAlignment(Qt::AlignCenter);
downloadIpaLabel->setCursor(Qt::PointingHandCursor);
connect(installLabel, &ZLabel::clicked, this, connect(installLabel, &ZLabel::clicked, this,
[this, name, bundleId, description]() { [this, name, bundleId, description]() {
onAppCardClicked(name, bundleId, description); onAppCardClicked(name, bundleId, description);
+5 -1
View File
@@ -11,7 +11,11 @@
#include <map> #include <map>
#include <string> #include <string>
#ifdef WIN32
#include "dns_sd.h"
#else
#include <dns_sd.h> #include <dns_sd.h>
#endif
class DnssdService : public QObject class DnssdService : public QObject
{ {
@@ -69,4 +73,4 @@ private:
QMap<QString, PendingDevice> m_pendingDevices; 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 #ifdef HAVE_CONFIG_H
#include <config.h> #include <config.h>
#endif #endif
#include <stdlib.h>
#define _GNU_SOURCE 1
#define __USE_GNU 1
#include <dirent.h>
#include <errno.h> #include <errno.h>
#include <getopt.h>
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h> #include <time.h>
@@ -14,11 +18,16 @@
#ifdef HAVE_UNISTD_H #ifdef HAVE_UNISTD_H
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifndef WIN32
#include <signal.h>
#endif
#include <libimobiledevice/afc.h> #include <libimobiledevice/afc.h>
#include <libimobiledevice/installation_proxy.h> #include <libimobiledevice/installation_proxy.h>
#include <libimobiledevice/libimobiledevice.h> #include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h> #include <libimobiledevice/lockdown.h>
#include <libimobiledevice/notification_proxy.h>
#include <plist/plist.h> #include <plist/plist.h>
+40 -2
View File
@@ -1,17 +1,55 @@
#include "mainwindow.h" #include "mainwindow.h"
#include <QApplication> #include <QApplication>
#include <QDebug>
#include <QDir>
#include <QStyleFactory> #include <QStyleFactory>
#include <QtGlobal>
#include <stdlib.h> // For getenv
#ifdef WIN32
#include "platform/windows/check_deps.h"
#endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QApplication a(argc, 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::setOrganizationName("iDescriptor");
// QCoreApplication::setOrganizationDomain("iDescriptor.com"); // QCoreApplication::setOrganizationDomain("iDescriptor.com");
QCoreApplication::setApplicationName("iDescriptor"); QCoreApplication::setApplicationName("iDescriptor");
// QCoreApplication::setApplicationVersion(IDESCRIPTOR_VERSION); // QCoreApplication::setApplicationVersion(IDESCRIPTOR_VERSION);
// QApplication::setStyle(QStyleFactory::create("Windows"));
QApplication::setStyle(QStyleFactory::create("macOS")); #ifndef __APPLE__
QApplication::setStyle(QStyleFactory::create("Fusion"));
#endif
MainWindow *w = MainWindow::sharedInstance(); MainWindow *w = MainWindow::sharedInstance();
w->show(); w->show();
+23
View File
@@ -29,6 +29,10 @@
#include <QMessageBox> #include <QMessageBox>
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
#ifdef WIN32
#include "platform/windows/diagnose_widget.h"
#endif
void handleCallback(const idevice_event_t *event, void *userData) void handleCallback(const idevice_event_t *event, void *userData)
{ {
printf("Device event received: "); printf("Device event received: ");
@@ -134,6 +138,25 @@ MainWindow::MainWindow(QWidget *parent)
// Welcome page (shown when no devices are connected) // Welcome page (shown when no devices are connected)
WelcomeWidget *welcomePage = new WelcomeWidget(this); 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); m_deviceManager = new DeviceManagerWidget(this);
-2
View File
@@ -21,8 +21,6 @@
</property> </property>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<layout class="QHBoxLayout" name="horizontalLayout">
</layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <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_sshChannel(nullptr), m_iproxyProcess(nullptr), m_sshConnected(false),
m_isInitialized(false), m_currentState(TerminalState::Loading) m_isInitialized(false), m_currentState(TerminalState::Loading)
{ {
setWindowTitle( setWindowTitle(QString("SSH Terminal / %1 - iDescriptor")
QString("SSH Terminal - %1").arg(m_connectionInfo.deviceName)); .arg(m_connectionInfo.deviceName));
setMinimumSize(800, 600); setMinimumSize(800, 600);
setupUI(); setupUI();
@@ -130,8 +130,11 @@ void SSHTerminalWidget::setupActionState()
menu.exec(m_terminal->mapToGlobal(pos)); menu.exec(m_terminal->mapToGlobal(pos));
} }
}); });
#ifdef WIN32
m_terminal->startTerminalEmulation();
#else
m_terminal->startTerminalTeletype(); m_terminal->startTerminalTeletype();
#endif
m_terminal->setStyleSheet("padding: 5px;"); m_terminal->setStyleSheet("padding: 5px;");
actionLayout->addWidget(m_terminal); actionLayout->addWidget(m_terminal);
@@ -459,8 +462,12 @@ void SSHTerminalWidget::checkSshData()
int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer, int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer,
sizeof(buffer), 0); sizeof(buffer), 0);
if (nbytes > 0) { if (nbytes > 0) {
#ifdef WIN32
m_terminal->receiveData(buffer, nbytes);
#else
// Write data to terminal's PTY // Write data to terminal's PTY
write(m_terminal->getPtySlaveFd(), buffer, nbytes); write(m_terminal->getPtySlaveFd(), buffer, nbytes);
#endif
} }
} }
@@ -471,7 +478,11 @@ void SSHTerminalWidget::checkSshData()
sizeof(buffer), 1); sizeof(buffer), 1);
if (nbytes > 0) { if (nbytes > 0) {
// Write stderr data to terminal's PTY // Write stderr data to terminal's PTY
#ifdef WIN32
m_terminal->receiveData(buffer, nbytes);
#else
write(m_terminal->getPtySlaveFd(), buffer, nbytes); 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 // Main layout with proper spacing and margins
m_mainLayout = new QVBoxLayout(this); m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setContentsMargins(40, 60, 40, 60); m_mainLayout->setContentsMargins(5, 5, 5, 5);
m_mainLayout->setSpacing(0); m_mainLayout->setSpacing(0);
// Add top stretch // Add top stretch
@@ -34,21 +34,22 @@ void WelcomeWidget::setupUI()
palette.color(QPalette::WindowText).lighter(140)); palette.color(QPalette::WindowText).lighter(140));
m_subtitleLabel->setPalette(palette); m_subtitleLabel->setPalette(palette);
m_mainLayout->addWidget(m_subtitleLabel); m_mainLayout->addWidget(m_subtitleLabel);
m_mainLayout->addSpacing(40); m_mainLayout->addSpacing(10);
m_imageLabel = new ResponsiveQLabel(); m_imageLabel = new ResponsiveQLabel();
m_imageLabel->setPixmap(QPixmap(":/resources/connect.png")); m_imageLabel->setPixmap(QPixmap(":/resources/connect.png"));
// Let the pixmap scale while preserving aspect ratio // Let the pixmap scale while preserving aspect ratio
m_imageLabel->setScaledContents(true); m_imageLabel->setScaledContents(true);
// Prefer centered, not full-width expansion // 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 // 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->setStyleSheet("background: transparent; border: none;");
m_imageLabel->setAlignment(Qt::AlignCenter); m_imageLabel->setAlignment(Qt::AlignCenter);
m_mainLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter); m_mainLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter);
m_mainLayout->addSpacing(32); m_mainLayout->addSpacing(10);
// Instruction text // Instruction text
m_instructionLabel = createStyledLabel( m_instructionLabel = createStyledLabel(
@@ -60,7 +61,7 @@ void WelcomeWidget::setupUI()
instructionPalette.color(QPalette::WindowText).lighter(120)); instructionPalette.color(QPalette::WindowText).lighter(120));
m_instructionLabel->setPalette(instructionPalette); m_instructionLabel->setPalette(instructionPalette);
m_mainLayout->addWidget(m_instructionLabel); m_mainLayout->addWidget(m_instructionLabel);
m_mainLayout->addSpacing(24); m_mainLayout->addSpacing(10);
// GitHub link // GitHub link
m_githubLabel = m_githubLabel =