From 4b81d7bdf112b230cd6e2584aaf3fd8998771c81 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Fri, 24 Oct 2025 00:24:58 -0700 Subject: [PATCH] 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. --- src/.clang-format => .clang-format | 0 .github/workflows/build-windows.yml | 173 +++++++++++++++++ CMakeLists.txt | 210 +++++++++++--------- cmake/win-deploy.cmake | 149 +++++++++++++-- idescriptor.rc | 32 ++++ resources.qrc | 2 +- src/appswidget.cpp | 9 +- src/core/services/dnssd/dnssd_service.h | 6 +- src/core/services/install_ipa.cpp | 11 +- src/main.cpp | 42 +++- src/mainwindow.cpp | 23 +++ src/mainwindow.ui | 2 - src/platform/windows/check_deps.cpp | 70 +++++++ src/platform/windows/check_deps.h | 8 + src/platform/windows/diagnose_dialog.cpp | 40 ++++ src/platform/windows/diagnose_dialog.h | 28 +++ src/platform/windows/diagnose_widget.cpp | 234 +++++++++++++++++++++++ src/platform/windows/diagnose_widget.h | 66 +++++++ src/sshterminalwidget.cpp | 17 +- src/welcomewidget.cpp | 13 +- 20 files changed, 1014 insertions(+), 121 deletions(-) rename src/.clang-format => .clang-format (100%) create mode 100644 .github/workflows/build-windows.yml create mode 100644 idescriptor.rc create mode 100644 src/platform/windows/check_deps.cpp create mode 100644 src/platform/windows/check_deps.h create mode 100644 src/platform/windows/diagnose_dialog.cpp create mode 100644 src/platform/windows/diagnose_dialog.h create mode 100644 src/platform/windows/diagnose_widget.cpp create mode 100644 src/platform/windows/diagnose_widget.h diff --git a/src/.clang-format b/.clang-format similarity index 100% rename from src/.clang-format rename to .clang-format diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml new file mode 100644 index 0000000..692aec5 --- /dev/null +++ b/.github/workflows/build-windows.yml @@ -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/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 0de0185..b59201d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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=$ + -DQT_BIN_PATH=${QT_BIN_PATH} + -DMSYS2_BIN_PATH=C:/msys64/mingw64/bin + -DOUTPUT_DIR=$ + -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() \ No newline at end of file +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 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) diff --git a/cmake/win-deploy.cmake b/cmake/win-deploy.cmake index bd8684b..9239fca 100644 --- a/cmake/win-deploy.cmake +++ b/cmake/win-deploy.cmake @@ -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 ===") diff --git a/idescriptor.rc b/idescriptor.rc new file mode 100644 index 0000000..7e73436 --- /dev/null +++ b/idescriptor.rc @@ -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 \ No newline at end of file diff --git a/resources.qrc b/resources.qrc index ea2202e..c32814f 100644 --- a/resources.qrc +++ b/resources.qrc @@ -17,7 +17,7 @@ resources/icons/MdiLightMagnify.png resources/icons/IcBaselineInsertDriveFile.png resources/icons/MaterialSymbolsLightHome.png - + resources/icons/app-icon/icon.ico qml/MapView.qml diff --git a/src/appswidget.cpp b/src/appswidget.cpp index e19546f..959c3de 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -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); diff --git a/src/core/services/dnssd/dnssd_service.h b/src/core/services/dnssd/dnssd_service.h index 6f2b1a3..404e83b 100644 --- a/src/core/services/dnssd/dnssd_service.h +++ b/src/core/services/dnssd/dnssd_service.h @@ -11,7 +11,11 @@ #include #include +#ifdef WIN32 +#include "dns_sd.h" +#else #include +#endif class DnssdService : public QObject { @@ -69,4 +73,4 @@ private: QMap m_pendingDevices; }; -#endif // DNSSD_SERVICE_H \ No newline at end of file +#endif // DNSSD_SERVICE_H diff --git a/src/core/services/install_ipa.cpp b/src/core/services/install_ipa.cpp index de14dc2..74ec3af 100644 --- a/src/core/services/install_ipa.cpp +++ b/src/core/services/install_ipa.cpp @@ -1,12 +1,16 @@ #ifdef HAVE_CONFIG_H #include #endif +#include +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include #include +#include #include #include #include #include -#include #include #include #include @@ -14,11 +18,16 @@ #ifdef HAVE_UNISTD_H #include #endif +#ifndef WIN32 +#include +#endif #include #include #include #include +#include + #include diff --git a/src/main.cpp b/src/main.cpp index 92a0848..55e82ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,55 @@ #include "mainwindow.h" #include +#include +#include #include +#include +#include // 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(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eff22f1..c87bb16 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -29,6 +29,10 @@ #include #include +#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); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 692c31e..8afd362 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -21,8 +21,6 @@ - - diff --git a/src/platform/windows/check_deps.cpp b/src/platform/windows/check_deps.cpp new file mode 100644 index 0000000..0fe167f --- /dev/null +++ b/src/platform/windows/check_deps.cpp @@ -0,0 +1,70 @@ +#include +#include + +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; +} \ No newline at end of file diff --git a/src/platform/windows/check_deps.h b/src/platform/windows/check_deps.h new file mode 100644 index 0000000..383a518 --- /dev/null +++ b/src/platform/windows/check_deps.h @@ -0,0 +1,8 @@ + +#ifndef CHECK_DEPS_H +#define CHECK_DEPS_H + +bool IsAppleMobileDeviceSupportInstalled(); +bool IsWinFspInstalled(); + +#endif // CHECK_DEPS_H \ No newline at end of file diff --git a/src/platform/windows/diagnose_dialog.cpp b/src/platform/windows/diagnose_dialog.cpp new file mode 100644 index 0000000..7c451bf --- /dev/null +++ b/src/platform/windows/diagnose_dialog.cpp @@ -0,0 +1,40 @@ +#include "diagnose_dialog.h" +#include + +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(); } \ No newline at end of file diff --git a/src/platform/windows/diagnose_dialog.h b/src/platform/windows/diagnose_dialog.h new file mode 100644 index 0000000..b6ab021 --- /dev/null +++ b/src/platform/windows/diagnose_dialog.h @@ -0,0 +1,28 @@ +#ifndef DIAGNOSE_DIALOG_H +#define DIAGNOSE_DIALOG_H + +#include "diagnose_widget.h" +#include +#include +#include +#include + + +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 \ No newline at end of file diff --git a/src/platform/windows/diagnose_widget.cpp b/src/platform/windows/diagnose_widget.cpp new file mode 100644 index 0000000..f681c0d --- /dev/null +++ b/src/platform/windows/diagnose_widget.cpp @@ -0,0 +1,234 @@ +#include "diagnose_widget.h" +#include "check_deps.h" +#include +#include +#include +#include +#include + + +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)); + } + } +} diff --git a/src/platform/windows/diagnose_widget.h b/src/platform/windows/diagnose_widget.h new file mode 100644 index 0000000..dbceb96 --- /dev/null +++ b/src/platform/windows/diagnose_widget.h @@ -0,0 +1,66 @@ +#ifndef DIAGNOSE_WIDGET_H +#define DIAGNOSE_WIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include + + +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 m_dependencyItems; +}; + +#endif // DIAGNOSE_WIDGET_H \ No newline at end of file diff --git a/src/sshterminalwidget.cpp b/src/sshterminalwidget.cpp index e5afd1e..580f75a 100644 --- a/src/sshterminalwidget.cpp +++ b/src/sshterminalwidget.cpp @@ -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 } } diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 64b8083..3e9a61d 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -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 =