From 7eaed96a85ce6a3e42677cf174a102e3027a2a7d Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 2 Nov 2025 11:39:17 -0800 Subject: [PATCH] copy just enough gstreamer plugins and handle deps for Windows build - Added custom command to copy libZUpdater.dll for deployment - Updated win-deploy.cmake to copy specific GStreamer plugins - Introduced install scripts for Apple drivers and WinFsp - Improved DiagnoseWidget UI with scroll area and process indicators - Adjusted WelcomeWidget to include DiagnoseWidget on non-Apple platforms --- CMakeLists.txt | 12 ++ cmake/win-deploy.cmake | 67 +++++-- install-apple-drivers.ps1 | 68 +++++++ install-win-fsp.silent.bat | 29 +++ resources.qrc | 2 - src/mainwindow.cpp | 31 ---- src/platform/windows/diagnose_dialog.cpp | 7 +- src/platform/windows/diagnose_widget.cpp | 222 ++++++++++++++++++----- src/platform/windows/diagnose_widget.h | 10 +- src/welcomewidget.cpp | 16 +- src/ztabwidget.cpp | 9 + 11 files changed, 367 insertions(+), 106 deletions(-) create mode 100644 install-apple-drivers.ps1 create mode 100644 install-win-fsp.silent.bat diff --git a/CMakeLists.txt b/CMakeLists.txt index 46e292b..7a544bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,3 +387,15 @@ if(WIN32) endif() include(CPack) + + +# FIXME: move to win-deploy.cmake +if(WIN32) + # Ensure libZUpdater.dll is copied next to the main executable for deployment + add_custom_command(TARGET iDescriptor POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + $ + COMMENT "Copying libZUpdater.dll to executable directory" + ) +endif() diff --git a/cmake/win-deploy.cmake b/cmake/win-deploy.cmake index 9239fca..efd2ce0 100644 --- a/cmake/win-deploy.cmake +++ b/cmake/win-deploy.cmake @@ -19,7 +19,6 @@ message(" MSYS2_BIN_PATH: ${MSYS2_BIN_PATH}") 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) @@ -75,7 +74,7 @@ file(GET_RUNTIME_DEPENDENCIES 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} + DIRECTORIES ${BUILD_DIR} ${QT_BIN_PATH} ${MSYS2_BIN_PATH} "C:/lxqt/lib" $ENV{PATH} ) set(COPIED_DLLS 0) @@ -119,23 +118,52 @@ message("Processed ${TOTAL_DLLS} runtime dependencies, copied ${COPIED_DLLS} fil # Step 3: Copy GStreamer plugins message("Copying GStreamer plugins...") -file(GLOB GSTREAMER_PLUGINS "${MSYS2_BIN_PATH}/../lib/gstreamer-1.0/*.dll") +# OLD: file(GLOB GSTREAMER_PLUGINS "${MSYS2_BIN_PATH}/../lib/gstreamer-1.0/*.dll") +# Replace broad copy with targeted list (matches versioned names via glob) +set(GSTREAMER_PLUGIN_DIR "${MSYS2_BIN_PATH}/../lib/gstreamer-1.0") -if(GSTREAMER_PLUGINS) - # Create gstreamer-1.0 directory in output - file(MAKE_DIRECTORY ${OUTPUT_DIR}/gstreamer-1.0) - - foreach(PLUGIN ${GSTREAMER_PLUGINS}) - get_filename_component(PLUGIN_NAME ${PLUGIN} NAME) - message("Copying GStreamer plugin: ${PLUGIN_NAME}") - file(COPY ${PLUGIN} DESTINATION ${OUTPUT_DIR}/gstreamer-1.0) - endforeach() - - list(LENGTH GSTREAMER_PLUGINS PLUGIN_COUNT) - message("Successfully copied ${PLUGIN_COUNT} GStreamer plugins") -else() - message(WARNING "No GStreamer plugins found in ${MSYS2_BIN_PATH}/../lib/gstreamer-1.0/") -endif() +# List of plugin basenames to copy (no extension or version). Add items as needed. +set(WANTED_PLUGINS + "libgstaudioconvert" + "libgstvolume" + "libgstcoreelements" + "libgstautodetect" + "libgstdirectsound" + "libgstlibav" + "libgstapp" + "libgstlevel" + "libgstwasapi" + "libgstplayback" + "libgstaudioresample" + "libgstaudiomixer" + "libgstaudiotestsrc" + "libgstmediafoundation" + "libgstdecodebin" + "libgsttypefindfunctions" + "libgstvideoscale" + "libgstvideoconvert" + "libgstvideorate" + "libgstoverlaycomposition" +) + +file(MAKE_DIRECTORY "${OUTPUT_DIR}/gstreamer-1.0") +set(COPIED_PLUGIN_COUNT 0) +foreach(BASENAME ${WANTED_PLUGINS}) + # match any versioned filename starting with the basename + file(GLOB MATCHES "${GSTREAMER_PLUGIN_DIR}/${BASENAME}*.dll") + if(MATCHES) + foreach(PLUGIN_PATH ${MATCHES}) + get_filename_component(PLUGIN_NAME ${PLUGIN_PATH} NAME) + message("Copying GStreamer plugin: ${PLUGIN_NAME}") + file(COPY "${PLUGIN_PATH}" DESTINATION "${OUTPUT_DIR}/gstreamer-1.0") + math(EXPR COPIED_PLUGIN_COUNT "${COPIED_PLUGIN_COUNT} + 1") + endforeach() + else() + message(WARNING "Requested GStreamer plugin not found: ${BASENAME} (searched ${GSTREAMER_PLUGIN_DIR})") + endif() +endforeach() + +message("Successfully copied ${COPIED_PLUGIN_COUNT} requested GStreamer plugins") # Step 4: Manually copy the correct MSYS2 MinGW runtime DLLs. # This ensures the versions required by GStreamer/FFmpeg are used. @@ -204,5 +232,8 @@ file(COPY "${GST_LIBEXEC_PATH}/gst-ptp-helper.exe" DESTINATION "${OUTPUT_DIR}/li message("Copying executables") file(COPY C:/msys64/mingw64/bin/iproxy.exe DESTINATION ${OUTPUT_DIR}) +message("Copying required scripts") +file(COPY "${CMAKE_SOURCE_DIR}/install-apple-drivers.ps1" DESTINATION ${OUTPUT_DIR}) +file(COPY "${CMAKE_SOURCE_DIR}/install-win-fsp.silent.bat" DESTINATION ${OUTPUT_DIR}) message("=== Windows deployment completed ===") diff --git a/install-apple-drivers.ps1 b/install-apple-drivers.ps1 new file mode 100644 index 0000000..5ccdf6d --- /dev/null +++ b/install-apple-drivers.ps1 @@ -0,0 +1,68 @@ +## Taken from https://github.com/NelloKudo/Apple-Mobile-Drivers-Installer +## Apple USB and Mobile Device Ethernet drivers installer! +## Please report any issues at GitHub: https://github.com/NelloKudo/Apple-Mobile-Drivers-Installer + +## Download links for Apple USB Drivers and Apple Mobile Ethernet USB Drivers respectively. +## All of these are downloaded from Microsoft's Update Catalog, which you can browse yourself at here: https://www.catalog.update.microsoft.com/ + +$AppleDri1 = "https://catalog.s.download.windowsupdate.com/d/msdownload/update/driver/drvs/2020/11/01d96dfd-2f6f-46f7-8bc3-fd82088996d2_a31ff7000e504855b3fa124bf27b3fe5bc4d0893.cab" +$AppleDri2 = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/driver/drvs/2017/11/netaapl_7503681835e08ce761c52858949731761e1fa5a1.cab" +$AppleITunesLink = "https://www.apple.com/itunes/download/win64" + +Write-Host "" +Write-Host -ForegroundColor Cyan "Welcome to Apple USB and Mobile Device Ethernet drivers installer!!" +Write-Host "" + +## Checking if the script is being run as admin.. +if (-not ([Security.Principal.WindowsIdentity]::GetCurrent().Groups -contains 'S-1-5-32-544')) { + Write-Host -ForegroundColor Yellow "This script requires administrative privileges!" + Write-Host -ForegroundColor Yellow "Please run the script as an administrator if you want to install drivers." + pause + exit 1 +} + +## Preparing the system to actually download drivers.. +$destinationFolder = [System.IO.Path]::Combine($env:TEMP, "AppleDriTemp") +if (-not (Test-Path $destinationFolder)) { + New-Item -ItemType Directory -Path $destinationFolder | Out-Null +} + +try { + $currentPath = $PWD.Path + Write-Host -ForegroundColor Yellow "Downloading Apple iTunes and installing AppleMobileDeviceSupport64.msi.." + Write-Host -ForegroundColor Yellow "(It might take a while! Script is not frozen!)" + (New-Object System.Net.WebClient).DownloadFile($AppleITunesLink, [System.IO.Path]::Combine($destinationFolder, "iTunes64Setup.exe")) + cd "$destinationFolder" + Start-Process -FilePath "$destinationFolder\iTunes64Setup.exe" -ArgumentList "/extract" -Wait + cd "$currentPath" + Start-Process -FilePath "$destinationFolder\AppleMobileDeviceSupport64.msi" -ArgumentList "/qn" -Wait + + Write-Host -ForegroundColor Yellow "Downloading Apple USB and Mobile Device Ethernet drivers from Microsoft..." + Invoke-WebRequest -Uri $AppleDri1 -OutFile ([System.IO.Path]::Combine($destinationFolder, "AppleUSB-486.0.0.0-driv.cab")) + Invoke-WebRequest -Uri $AppleDri2 -OutFile ([System.IO.Path]::Combine($destinationFolder, "AppleNet-1.8.5.1-driv.cab")) + + Write-Host -ForegroundColor Yellow "Extracting drivers..." + & expand.exe -F:* "$destinationFolder\AppleUSB-486.0.0.0-driv.cab" "$destinationFolder" >$null 2>&1 + & expand.exe -F:* "$destinationFolder\AppleNet-1.8.5.1-driv.cab" "$destinationFolder" >$null 2>&1 + + ## Installing drivers.. + Write-Host -ForegroundColor Yellow "Installing Apple USB and Mobile Device Ethernet drivers!" + Write-Host -ForegroundColor Yellow "If any of your peripherals stop working for a few seconds that's due to Apple stuff installing." + Write-Host "" + Get-ChildItem -Path "$destinationFolder\*.inf" | ForEach-Object { + pnputil /add-driver $_.FullName /install + Write-Host "" + Write-Host -ForegroundColor Yellow "Driver installed.." + Write-Host "" + } + + ## Cleaning.. + Remove-Item -Path $destinationFolder -Recurse -Force + +} catch { + Write-Host -ForegroundColor Red "Failed to complete installation. Error: $_" +} + +Write-Host "" +Write-Host -ForegroundColor Cyan "Installation complete! Enjoy your Apple devices!!" +Write-Host -ForegroundColor Yellow "(If devices are still not working, a reboot might be needed!!)" \ No newline at end of file diff --git a/install-win-fsp.silent.bat b/install-win-fsp.silent.bat new file mode 100644 index 0000000..47ee28a --- /dev/null +++ b/install-win-fsp.silent.bat @@ -0,0 +1,29 @@ +@echo off +setlocal enabledelayedexpansion + +net session >nul 2>&1 +if %errorlevel% neq 0 ( + exit /b 1 +) + +echo Installing WinFsp... + +set "DOWNLOAD_URL=https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi" +set "CACHE_DIR=%TEMP%\winfsp-install" +set "MSI_FILE=%CACHE_DIR%\winfsp-2.1.25156.msi" + +if not exist "%CACHE_DIR%" mkdir "%CACHE_DIR%" + +curl -L -o "%MSI_FILE%" "%DOWNLOAD_URL%" +if %errorlevel% neq 0 ( + exit /b 1 +) + +msiexec /i "%MSI_FILE%" /quiet /norestart +if %errorlevel% neq 0 ( + rmdir /s /q "%CACHE_DIR%" + exit /b 1 +) + +rmdir /s /q "%CACHE_DIR%" +exit /b 0 \ No newline at end of file diff --git a/resources.qrc b/resources.qrc index dccfbe9..1e40c54 100644 --- a/resources.qrc +++ b/resources.qrc @@ -20,8 +20,6 @@ resources/icons/MdiGithub.png resources/icons/app-icon/icon.ico qml/MapView.qml - - resources/iphone.png resources/ios-wallpapers/iphone-ios4.png resources/ios-wallpapers/iphone-ios5.png diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a3c163d..5402884 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -28,10 +28,6 @@ #include #include -#ifdef WIN32 -#include "platform/windows/diagnose_widget.h" -#endif - void handleCallback(const idevice_event_t *event, void *userData) { printf("Device event received: "); @@ -79,7 +75,6 @@ void handleCallback(const idevice_event_t *event, void *userData) default: qDebug() << "Unhandled event: " << event->event; } - // return; } void handleCallbackRecovery(const irecv_device_event_t *event, void *userData) @@ -117,10 +112,7 @@ MainWindow::MainWindow(QWidget *parent) const QSize minSize(900, 600); setMinimumSize(minSize); resize(minSize); - // TODO - // setWindowIcon(QIcon(":/resources/icons/icon.png")); - // Create custom tab widget m_ZTabWidget = new ZTabWidget(this); m_ZTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false); @@ -131,31 +123,8 @@ MainWindow::MainWindow(QWidget *parent) #endif setCentralWidget(m_ZTabWidget); - // Create device manager and stacked widget for main tab m_mainStackedWidget = new QStackedWidget(); - - // 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); m_mainStackedWidget->addWidget(welcomePage); diff --git a/src/platform/windows/diagnose_dialog.cpp b/src/platform/windows/diagnose_dialog.cpp index 7c451bf..a512f10 100644 --- a/src/platform/windows/diagnose_dialog.cpp +++ b/src/platform/windows/diagnose_dialog.cpp @@ -16,10 +16,15 @@ DiagnoseDialog::DiagnoseDialog(QWidget *parent) : QDialog(parent) void DiagnoseDialog::setupUI() { QVBoxLayout *mainLayout = new QVBoxLayout(this); + QScrollArea *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); mainLayout->setContentsMargins(10, 10, 10, 10); // Add the main diagnose widget m_diagnoseWidget = new DiagnoseWidget(); + scrollArea->setWidget(m_diagnoseWidget); // Close button QHBoxLayout *buttonLayout = new QHBoxLayout(); @@ -33,7 +38,7 @@ void DiagnoseDialog::setupUI() buttonLayout->addWidget(m_closeButton); // Layout assembly - mainLayout->addWidget(m_diagnoseWidget, 1); + mainLayout->addWidget(scrollArea); mainLayout->addLayout(buttonLayout); } diff --git a/src/platform/windows/diagnose_widget.cpp b/src/platform/windows/diagnose_widget.cpp index f681c0d..32133e8 100644 --- a/src/platform/windows/diagnose_widget.cpp +++ b/src/platform/windows/diagnose_widget.cpp @@ -1,12 +1,15 @@ #include "diagnose_widget.h" +#ifdef WIN32 #include "check_deps.h" +#endif #include +#include #include #include +#include #include #include - DependencyItem::DependencyItem(const QString &name, const QString &description, QWidget *parent) : QWidget(parent), m_name(name) @@ -37,7 +40,8 @@ DependencyItem::DependencyItem(const QString &name, const QString &description, m_statusLabel->setAlignment(Qt::AlignCenter); // Right side - actions - QVBoxLayout *actionLayout = new QVBoxLayout(); + QHBoxLayout *actionLayout = new QHBoxLayout(); + actionLayout->setContentsMargins(0, 0, 0, 0); m_installButton = new QPushButton("Install"); m_installButton->setMinimumWidth(80); @@ -45,13 +49,13 @@ DependencyItem::DependencyItem(const QString &name, const QString &description, 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); + m_processIndicator = new QProcessIndicator(); + m_processIndicator->setType(QProcessIndicator::line_rotate); + m_processIndicator->setFixedSize(24, 24); + m_processIndicator->setVisible(false); + actionLayout->addWidget(m_processIndicator); actionLayout->addWidget(m_installButton); - actionLayout->addWidget(m_progressBar); actionLayout->addStretch(); layout->addLayout(infoLayout, 1); @@ -80,28 +84,47 @@ void DependencyItem::setChecking(bool checking) m_statusLabel->setText("Checking..."); m_statusLabel->setStyleSheet("color: gray;"); m_installButton->setVisible(false); - m_progressBar->setVisible(false); + m_processIndicator->setVisible(false); + m_processIndicator->stop(); + } +} + +void DependencyItem::setInstalling(bool installing) +{ + if (installing) { + m_statusLabel->setText("Installing..."); + m_statusLabel->setStyleSheet("color: gray;"); + m_installButton->setVisible(false); + m_processIndicator->setVisible(true); + m_processIndicator->start(); + } else { + m_processIndicator->stop(); + m_processIndicator->setVisible(false); } } void DependencyItem::onInstallClicked() { emit installRequested(m_name); } -DiagnoseWidget::DiagnoseWidget(QWidget *parent) : QWidget(parent) +DiagnoseWidget::DiagnoseWidget(QWidget *parent) + : QWidget(parent), m_isExpanded(false) { setupUI(); +#ifdef WIN32 // Add dependency items addDependencyItem("Apple Mobile Device Support", "Required for iOS device communication"); addDependencyItem("WinFsp", "Required for filesystem operations and mounting"); +#endif // Auto-check on startup - QTimer::singleShot(100, this, &DiagnoseWidget::checkDependencies); + QTimer::singleShot(100, this, [this]() { checkDependencies(); }); } void DiagnoseWidget::setupUI() { + setAutoFillBackground(true); m_mainLayout = new QVBoxLayout(this); m_mainLayout->setSpacing(10); @@ -115,40 +138,43 @@ void DiagnoseWidget::setupUI() m_summaryLabel = new QLabel("Checking system dependencies..."); // Check button - m_checkButton = new QPushButton("Refresh Check"); + m_checkButton = new QPushButton("Refresh Check(s)"); m_checkButton->setMaximumWidth(150); connect(m_checkButton, &QPushButton::clicked, this, - &DiagnoseWidget::checkDependencies); + [this]() { checkDependencies(false); }); - // Scroll area for dependency items - m_scrollArea = new QScrollArea(); - m_scrollArea->setWidgetResizable(true); - // m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarNever); - m_scrollArea->setMinimumHeight(200); + // Toggle button + m_toggleButton = new QPushButton("▼"); + m_toggleButton->setFixedSize(24, 24); + m_toggleButton->setCheckable(true); + connect(m_toggleButton, &QPushButton::clicked, this, + &DiagnoseWidget::onToggleExpand); m_itemsWidget = new QWidget(); + // m_itemsWidget->setSizePolicy(QSizePolicy::Expanding, + // QSizePolicy::Preferred); + m_itemsWidget->setFixedHeight(400); m_itemsLayout = new QVBoxLayout(m_itemsWidget); m_itemsLayout->setSpacing(5); m_itemsLayout->addStretch(); - - m_scrollArea->setWidget(m_itemsWidget); + m_itemsWidget->setVisible(m_isExpanded); // Layout assembly QHBoxLayout *headerLayout = new QHBoxLayout(); headerLayout->addWidget(titleLabel); - headerLayout->addStretch(); headerLayout->addWidget(m_checkButton); + headerLayout->addWidget(m_toggleButton); m_mainLayout->addLayout(headerLayout); m_mainLayout->addWidget(m_summaryLabel); - m_mainLayout->addWidget(m_scrollArea, 1); + m_mainLayout->addWidget(m_itemsWidget); } 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 + item->setProperty("name", name); connect(item, &DependencyItem::installRequested, this, &DiagnoseWidget::onInstallRequested); @@ -158,18 +184,16 @@ void DiagnoseWidget::addDependencyItem(const QString &name, m_itemsLayout->insertWidget(m_itemsLayout->count() - 1, item); } -void DiagnoseWidget::checkDependencies() +void DiagnoseWidget::checkDependencies(bool autoExpand) { 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]() { + QTimer::singleShot(500, [this, autoExpand]() { int installedCount = 0; int totalCount = m_dependencyItems.size(); @@ -177,30 +201,37 @@ void DiagnoseWidget::checkDependencies() bool installed = false; QString itemName = item->property("name").toString(); +#ifdef WIN32 if (itemName == "Apple Mobile Device Support") { installed = IsAppleMobileDeviceSupportInstalled(); } else if (itemName == "WinFsp") { installed = IsWinFspInstalled(); } +#endif 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;"); + if (m_isExpanded && autoExpand) { + onToggleExpand(); + } } else { m_summaryLabel->setText( QString("Missing dependencies (%1/%2 installed)") .arg(installedCount) .arg(totalCount)); m_summaryLabel->setStyleSheet("color: red; font-weight: bold;"); + if (!m_isExpanded && autoExpand) { + onToggleExpand(); + } } m_checkButton->setEnabled(true); @@ -209,26 +240,125 @@ void DiagnoseWidget::checkDependencies() void DiagnoseWidget::onInstallRequested(const QString &name) { - QString url; - QString message; - +#ifdef WIN32 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)); + DependencyItem *itemToInstall = nullptr; + for (DependencyItem *item : m_dependencyItems) { + if (item->property("name").toString() == name) { + itemToInstall = item; + break; + } } + + if (!itemToInstall) + return; + + itemToInstall->setInstalling(true); + + QString scriptPath = QCoreApplication::applicationDirPath() + + "/install-apple-drivers.ps1"; + + QProcess *installProcess = new QProcess(this); + connect( + installProcess, &QProcess::finished, this, + [this, installProcess, + itemToInstall](int exitCode, QProcess::ExitStatus exitStatus) { + if (exitStatus != QProcess::NormalExit || exitCode != 0) { + QString errorOutput = + installProcess->readAllStandardError(); + if (errorOutput.isEmpty()) { + errorOutput = installProcess->readAllStandardOutput(); + } + QMessageBox::warning( + this, "Installation Failed", + "Failed to launch the installation script. This " + "might be a " + "permissions issue or an internal error.\n\n" + "Details: " + + errorOutput.trimmed()); + checkDependencies(false); // Revert UI on failure + } else { + // FIXME: we need to track process completion + QMessageBox::information( + this, "Installation Started", + "The installation process has been started.\n" + "Please approve the administrator prompt (UAC) if it " + "appears.\n" + "After installation, please re-run the dependency " + "check"); + + itemToInstall->setInstalling(false); + } + installProcess->deleteLater(); + }); + + // Correctly launch powershell.exe elevated, and pass the script to it. + // The -Wait parameter is removed as it does not work with -Verb RunAs. + QString command = + QString("Start-Process -FilePath powershell.exe -Verb RunAs " + "-ArgumentList '-NoProfile -ExecutionPolicy Bypass -File " + "\"%1\"'") + .arg(scriptPath); + + QStringList args; + args << "-NoProfile" + << "-ExecutionPolicy" + << "Bypass" + << "-Command" << command; + + installProcess->start("powershell.exe", args); + return; } + + if (name == "WinFsp") { + DependencyItem *itemToInstall = nullptr; + for (DependencyItem *item : m_dependencyItems) { + if (item->property("name").toString() == name) { + itemToInstall = item; + break; + } + } + + if (!itemToInstall) + return; + + itemToInstall->setInstalling(true); + + QString scriptPath = QCoreApplication::applicationDirPath() + + "/install-win-fsp.silent.bat"; + + QProcess *installProcess = new QProcess(this); + connect( + installProcess, &QProcess::finished, this, + [this, installProcess](int exitCode, + QProcess::ExitStatus exitStatus) { + if (exitStatus != QProcess::NormalExit || exitCode != 0) { + QMessageBox::warning( + this, "Installation Failed", + "The installation script failed to run correctly. " + "This might be because the action was cancelled or an " + "error occurred.\n\nPlease try again."); + } + checkDependencies(false); + installProcess->deleteLater(); + }); + + QStringList args; + args << "-NoProfile" + << "-ExecutionPolicy" + << "Bypass" + << "-Command" + << QString("Start-Process -FilePath \"%1\" -Verb RunAs -Wait;") + .arg(scriptPath); + + installProcess->start("powershell.exe", args); + } +#endif +} + +void DiagnoseWidget::onToggleExpand() +{ + m_isExpanded = !m_isExpanded; + m_itemsWidget->setVisible(m_isExpanded); + m_toggleButton->setText(m_isExpanded ? "▲" : "▼"); } diff --git a/src/platform/windows/diagnose_widget.h b/src/platform/windows/diagnose_widget.h index dbceb96..e3ef3d1 100644 --- a/src/platform/windows/diagnose_widget.h +++ b/src/platform/windows/diagnose_widget.h @@ -10,6 +10,7 @@ #include #include +#include "../../qprocessindicator.h" class DependencyItem : public QWidget { @@ -20,6 +21,7 @@ public: QWidget *parent = nullptr); void setInstalled(bool installed); void setChecking(bool checking); + void setInstalling(bool installing); signals: void installRequested(const QString &name); @@ -33,7 +35,7 @@ private: QLabel *m_descriptionLabel; QLabel *m_statusLabel; QPushButton *m_installButton; - QProgressBar *m_progressBar; + QProcessIndicator *m_processIndicator; }; class DiagnoseWidget : public QWidget @@ -44,10 +46,11 @@ public: explicit DiagnoseWidget(QWidget *parent = nullptr); public slots: - void checkDependencies(); + void checkDependencies(bool autoExpand = true); private slots: void onInstallRequested(const QString &name); + void onToggleExpand(); private: void setupUI(); @@ -56,9 +59,10 @@ private: QVBoxLayout *m_mainLayout; QVBoxLayout *m_itemsLayout; QPushButton *m_checkButton; + QPushButton *m_toggleButton; QLabel *m_summaryLabel; - QScrollArea *m_scrollArea; QWidget *m_itemsWidget; + bool m_isExpanded; QList m_dependencyItems; }; diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 79a6a44..2842bed 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -9,13 +9,17 @@ #include #include +#ifdef WIN32 +#include "platform/windows/diagnose_widget.h" +#endif + WelcomeWidget::WelcomeWidget(QWidget *parent) : QWidget(parent) { setupUI(); } void WelcomeWidget::setupUI() { // Main layout with proper spacing and margins m_mainLayout = new QVBoxLayout(this); - m_mainLayout->setContentsMargins(5, 5, 5, 5); + m_mainLayout->setContentsMargins(0, 0, 0, 0); m_mainLayout->setSpacing(0); // Add top stretch @@ -85,11 +89,13 @@ void WelcomeWidget::setupUI() m_mainLayout->addWidget(m_githubLabel); - // Add bottom stretch - m_mainLayout->addStretch(1); + // no additional deps needed on macOS +#ifndef __APPLE__ + DiagnoseWidget *diagnoseWidget = new DiagnoseWidget(); + m_mainLayout->addWidget(diagnoseWidget); +#endif - // Set minimum size - // setMinimumSize(600, 500); + m_mainLayout->addStretch(1); } ZLabel *WelcomeWidget::createStyledLabel(const QString &text, int fontSize, diff --git a/src/ztabwidget.cpp b/src/ztabwidget.cpp index a9773e7..3f85184 100644 --- a/src/ztabwidget.cpp +++ b/src/ztabwidget.cpp @@ -9,7 +9,12 @@ ZTab::ZTab(const QString &text, QWidget *parent) : QPushButton(text, parent) { setCheckable(true); +#ifndef WIN32 setFixedHeight(50); +#else + setFixedHeight(40); +#endif + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -21,7 +26,11 @@ ZTabWidget::ZTabWidget(QWidget *parent) : QWidget(parent), m_currentIndex(0) // Create tab bar container m_tabBar = new QWidget(); +#ifndef WIN32 m_tabBar->setFixedHeight(50); +#else + m_tabBar->setFixedHeight(40); +#endif m_tabBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_tabLayout = new QHBoxLayout(m_tabBar); m_tabLayout->setSpacing(0);