diff --git a/src/diagnosewidget.cpp b/src/diagnosewidget.cpp index 789425f..77f9c2f 100644 --- a/src/diagnosewidget.cpp +++ b/src/diagnosewidget.cpp @@ -20,16 +20,23 @@ #include "diagnosewidget.h" #ifdef WIN32 #include "platform/windows/check_deps.h" +#include +#include #endif #include #include +#include #include #include #include #include #include +#include +#include +#include #include #include +#include #include #include #include @@ -41,8 +48,7 @@ DependencyItem::DependencyItem(const QString &name, const QString &description, QHBoxLayout *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - // Left side - info - QVBoxLayout *infoLayout = new QVBoxLayout(); + QHBoxLayout *infoLayout = new QHBoxLayout(); m_nameLabel = new QLabel(name); QFont nameFont = m_nameLabel->font(); @@ -50,8 +56,8 @@ DependencyItem::DependencyItem(const QString &name, const QString &description, nameFont.setPointSize(nameFont.pointSize() + 1); m_nameLabel->setFont(nameFont); - m_descriptionLabel = new QLabel(description); - m_descriptionLabel->setWordWrap(true); + m_descriptionLabel = new QLabel(QString("(%1)").arg(description)); + m_descriptionLabel->setWordWrap(false); infoLayout->addWidget(m_nameLabel); infoLayout->addWidget(m_descriptionLabel); @@ -78,9 +84,9 @@ DependencyItem::DependencyItem(const QString &name, const QString &description, actionLayout->addWidget(m_processIndicator); actionLayout->addWidget(m_installButton); - actionLayout->addStretch(); - layout->addLayout(infoLayout, 1); + layout->addLayout(infoLayout); + layout->addStretch(); layout->addWidget(m_statusLabel); layout->addLayout(actionLayout); } @@ -135,6 +141,12 @@ void DependencyItem::setInstalling(bool installing) } } +void DependencyItem::setProgress(const QString &message) +{ + m_statusLabel->setText(message); + m_statusLabel->setStyleSheet("color: gray;"); +} + void DependencyItem::onInstallClicked() { emit installRequested(m_name); } DiagnoseWidget::DiagnoseWidget(QWidget *parent) @@ -143,6 +155,8 @@ DiagnoseWidget::DiagnoseWidget(QWidget *parent) setupUI(); #ifdef WIN32 + addDependencyItem("Bonjour Service", + "Required for AirPlay and network service discovery"); addDependencyItem("Apple Mobile Device Support", "Required for iOS device communication"); addDependencyItem("WinFsp", "Required for mounting your device as a drive"); @@ -240,7 +254,9 @@ void DiagnoseWidget::checkDependencies(bool autoExpand) QString itemName = item->property("name").toString(); #ifdef WIN32 - if (itemName == "Apple Mobile Device Support") { + if (itemName == "Bonjour Service") { + installed = IsBonjourServiceInstalled(); + } else if (itemName == "Apple Mobile Device Support") { installed = IsAppleMobileDeviceSupportInstalled(); } else if (itemName == "WinFsp") { installed = IsWinFspInstalled(); @@ -287,6 +303,11 @@ void DiagnoseWidget::checkDependencies(bool autoExpand) void DiagnoseWidget::onInstallRequested(const QString &name) { #ifdef WIN32 + if (name == "Bonjour Service") { + installBonjourRuntime(); + return; + } + if (name == "Apple Mobile Device Support") { DependencyItem *itemToInstall = nullptr; for (DependencyItem *item : m_dependencyItems) { @@ -634,3 +655,178 @@ void DiagnoseWidget::onToggleExpand() m_itemsWidget->updateGeometry(); adjustSize(); } + +#ifdef WIN32 +void DiagnoseWidget::installBonjourRuntime() +{ + DependencyItem *itemToInstall = nullptr; + for (DependencyItem *item : m_dependencyItems) { + if (item->property("name").toString() == "Bonjour Service") { + itemToInstall = item; + break; + } + } + + if (!itemToInstall) + return; + + itemToInstall->setInstalling(true); + itemToInstall->setProgress("Downloading..."); + + // Download Bonjour SDK + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + QNetworkRequest request( + QUrl("https://github.com/tempx-x/bonjour-sdk/raw/refs/heads/main/" + "bonjoursdksetup.exe")); + + QNetworkReply *reply = manager->get(request); + + connect(reply, &QNetworkReply::downloadProgress, this, + [itemToInstall](qint64 bytesReceived, qint64 bytesTotal) { + if (bytesTotal > 0) { + int percent = (bytesReceived * 100) / bytesTotal; + itemToInstall->setProgress( + QString("Downloading... %1%").arg(percent)); + } + }); + + connect( + reply, &QNetworkReply::finished, this, + [this, reply, manager, itemToInstall]() { + reply->deleteLater(); + manager->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + QMessageBox::critical(this, "Download Failed", + "Failed to download Bonjour SDK: " + + reply->errorString()); + checkDependencies(false); + return; + } + + itemToInstall->setProgress("Verifying..."); + + // Verify MD5 checksum + QByteArray data = reply->readAll(); + QByteArray hash = + QCryptographicHash::hash(data, QCryptographicHash::Md5); + QString actualHash = hash.toHex(); + QString expectedHash = "4ff2aae8205aec31b06743782cfcadce"; + + if (actualHash != expectedHash) { + QMessageBox::critical( + this, "Checksum Mismatch", + QString("Downloaded file checksum does not match!\n" + "Expected: %1\n" + "Got: %2") + .arg(expectedHash, actualHash)); + checkDependencies(false); + return; + } + + itemToInstall->setProgress("Extracting..."); + + // Create temp directory + QString tempDir = + QStandardPaths::writableLocation(QStandardPaths::TempLocation) + + "/bonjour_install"; + QDir().mkpath(tempDir); + + // Save the downloaded file + QString exePath = tempDir + "/bonjoursdksetup.exe"; + QFile file(exePath); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::critical(this, "Error", + "Failed to save downloaded file"); + checkDependencies(false); + return; + } + file.write(data); + file.close(); + + // Extract using libarchive + struct archive *a = archive_read_new(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + + struct archive *ext = archive_write_disk_new(); + archive_write_disk_set_options(ext, ARCHIVE_EXTRACT_TIME); + + if (archive_read_open_filename(a, exePath.toUtf8().constData(), + 10240) != ARCHIVE_OK) { + QMessageBox::critical(this, "Extraction Failed", + QString("Failed to open archive: %1") + .arg(archive_error_string(a))); + archive_read_free(a); + archive_write_free(ext); + checkDependencies(false); + return; + } + + struct archive_entry *entry; + QString msiPath; + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + QString entryName = + QString::fromUtf8(archive_entry_pathname(entry)); + + if (entryName.endsWith("Bonjour64.msi", Qt::CaseInsensitive)) { + QString fullPath = tempDir + "/" + entryName; + archive_entry_set_pathname(entry, + fullPath.toUtf8().constData()); + + if (archive_write_header(ext, entry) != ARCHIVE_OK) { + qWarning() << "Failed to write header for" << entryName; + } else { + const void *buff; + size_t size; + la_int64_t offset; + + while (archive_read_data_block(a, &buff, &size, + &offset) == ARCHIVE_OK) { + archive_write_data_block(ext, buff, size, offset); + } + } + archive_write_finish_entry(ext); + msiPath = fullPath; + break; // Only need Bonjour64.msi + } else { + archive_read_data_skip(a); + } + } + + archive_read_free(a); + archive_write_free(ext); + + if (msiPath.isEmpty()) { + QMessageBox::critical(this, "Extraction Failed", + "Could not find Bonjour64.msi in the " + "archive"); + QDir(tempDir).removeRecursively(); + checkDependencies(false); + return; + } + + itemToInstall->setProgress("Installing..."); + + // Launch the MSI via the shell (same behavior as double-click) + itemToInstall->setInstalling(false); // we can't track MSI process + + if (!QDesktopServices::openUrl(QUrl::fromLocalFile(msiPath))) { + QMessageBox::warning(this, "Installation Failed", + "Failed to launch Bonjour installer.\n\n" + "You can also run it manually from:\n" + + msiPath); + checkDependencies(false); + return; + } + + QMessageBox::information( + this, "Installation Started", + "The Bonjour installer has been launched.\n" + "Please complete the setup, then re-run the dependency check."); + + itemToInstall->setProgress("Refresh to verify installation."); + }); +} +#endif diff --git a/src/diagnosewidget.h b/src/diagnosewidget.h index f24093c..fe195a9 100644 --- a/src/diagnosewidget.h +++ b/src/diagnosewidget.h @@ -41,6 +41,7 @@ public: void setInstalled(bool installed); void setChecking(bool checking); void setInstalling(bool installing); + void setProgress(const QString &message); signals: void installRequested(const QString &name); @@ -75,6 +76,10 @@ private: void setupUI(); void addDependencyItem(const QString &name, const QString &description); +#ifdef WIN32 + void installBonjourRuntime(); +#endif + #ifdef __linux__ bool checkUdevRulesInstalled(); bool checkAvahiDaemonRunning(); diff --git a/src/platform/windows/check_deps.cpp b/src/platform/windows/check_deps.cpp index c3cc616..b2a1062 100644 --- a/src/platform/windows/check_deps.cpp +++ b/src/platform/windows/check_deps.cpp @@ -56,6 +56,17 @@ bool CheckRegistry(HKEY hKeyRoot, LPCSTR subKey, LPCSTR displayNameToFind) return false; } +bool CheckRegistryKeyExists(HKEY hKeyRoot, LPCSTR subKey) +{ + HKEY hKey; + LONG result = RegOpenKeyExA(hKeyRoot, subKey, 0, KEY_READ, &hKey); + if (result == ERROR_SUCCESS) { + RegCloseKey(hKey); + return true; + } + return false; +} + bool IsAppleMobileDeviceSupportInstalled() { if (CheckRegistry(HKEY_LOCAL_MACHINE, @@ -101,5 +112,15 @@ bool is_iDescriptorInstalled() "iDescriptor")) { return true; } + return false; +} + +bool IsBonjourServiceInstalled() +{ + if (CheckRegistryKeyExists(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Apple Inc.\\Bonjour")) { + 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 index 63d8b54..df85ea2 100644 --- a/src/platform/windows/check_deps.h +++ b/src/platform/windows/check_deps.h @@ -23,5 +23,6 @@ bool IsAppleMobileDeviceSupportInstalled(); bool IsWinFspInstalled(); bool is_iDescriptorInstalled(); +bool IsBonjourServiceInstalled(); #endif // CHECK_DEPS_H \ No newline at end of file