Refactor and enhance service management

- Updated CMakeLists.txt to set CPACK_WIX_UPGRADE_GUID directly.
- Enhanced DependencyItem and DiagnoseWidget to manage service availability states.
- Implemented service management functions for Windows, including starting services and checking their status.
- Improved UI responsiveness in WelcomeWidget and NetworkDevicesToConnectWidget.
- Introduced SERVICE_AVAILABILITY enum to standardize service state representation.
- Cleaned up unnecessary code and comments across various files.
This commit is contained in:
uncor3
2026-04-07 11:30:27 +03:00
parent d314110a04
commit 3c5fe6787b
20 changed files with 416 additions and 428 deletions
@@ -1,54 +0,0 @@
/*
* iDescriptor: A free and open-source idevice management tool.
*
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// #include "../../iDescriptor.h"
// #include <string>
// #include <vector>
// struct JailbreakDetectionResult {
// bool is_jailbroken;
// std::vector<std::string> found_folders;
// };
// // char *possible_jailbreak_paths[] = {
// // "/Applications/Cydia.app",
// // "/Library/MobileSubstrate/MobileSubstrate.dylib",
// // "/bin/bash",
// // "/usr/sbin/sshd",
// // "/etc/apt",
// // NULL
// // };
// JailbreakDetectionResult detect_has_jailbroken_before(AfcClientHandle *afc)
// {
// // std::vector<std::string> jailbreak_folders = {".installed_palera1n",
// // ".procursus_strapped"};
// JailbreakDetectionResult result = {false, {}};
// // char **dirs = NULL;
// // size_t count = 0;
// // if (!afc_list_directory(afc, (std::string(POSSIBLE_ROOT) +
// // "bin").c_str(),
// // &dirs, &count)) {
// // free(dirs);
// // }
// // afc_dictionary_free(dirs);
// return result;
// }
@@ -1,28 +0,0 @@
/*
* iDescriptor: A free and open-source idevice management tool.
*
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// parse boot args from idevice info
// NonVolatileRAM: auto-boot: dHJ1ZQ==
// backlight-level: MTUyNg==
// boot-args:
// com.apple.System.tz0-size: MHg2MDAwMDA=
// oblit-begins:
// T2JsaXRUeXBlOiBPYmxpdGVyYXRlRGF0YVBhcnRpdGlvbi4gUmVhc29uOiB1bmtub3du
// obliteration: aGFuZGxlX21lc3NhZ2U6IE9ibGl0ZXJhdGlvbiBDb21wbGV0ZQo=
// PartitionType: GUID_partition_scheme
+3
View File
@@ -4,6 +4,9 @@ DevModeWidget::DevModeWidget(std::shared_ptr<iDescriptorDevice> device,
QWidget *parent)
: QDialog{parent}
{
#ifdef WIN32
setupWinWindow(this);
#endif
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 20, 0, 0);
mainLayout->setSpacing(0);
+157 -110
View File
@@ -23,27 +23,9 @@
#include <archive.h>
#include <archive_entry.h>
#endif
#include "iDescriptor-ui.h"
#include <QApplication>
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFrame>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProcess>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QTextStream>
#include <QTimer>
#include <QUrl>
DependencyItem::DependencyItem(const QString &name, const QString &description,
QWidget *parent)
bool optional, QWidget *parent)
: QWidget(parent), m_name(name)
{
QHBoxLayout *layout = new QHBoxLayout(this);
@@ -58,6 +40,9 @@ DependencyItem::DependencyItem(const QString &name, const QString &description,
m_nameLabel->setFont(nameFont);
m_descriptionLabel = new QLabel(QString("(%1)").arg(description));
if (optional) {
m_descriptionLabel->setText(m_descriptionLabel->text() + " (Optional)");
}
m_descriptionLabel->setWordWrap(false);
infoLayout->addWidget(m_nameLabel);
@@ -92,12 +77,17 @@ DependencyItem::DependencyItem(const QString &name, const QString &description,
layout->addLayout(actionLayout);
}
void DependencyItem::setInstalled(bool installed, bool isRequired)
void DependencyItem::setInstalled(SERVICE_AVAILABILITY availability,
bool isRequired)
{
setChecking(false);
m_availability = availability;
if (installed) {
if (m_name == "Avahi Daemon") {
const bool isInstalled = (availability != SERVICE_UNAVAILABLE);
const bool isRunning = (availability == SERVICE_AVAILABLE);
if (isRunning) {
if (m_name == "Avahi Daemon" || m_name == "Bonjour Service") {
m_statusLabel->setText("Activated");
} else {
m_statusLabel->setText("Installed");
@@ -112,6 +102,14 @@ void DependencyItem::setInstalled(bool installed, bool isRequired)
QString("QLabel { color: %1; }").arg(COLOR_GREEN.name())));
#endif
m_installButton->setVisible(false);
return;
}
// Not running or not installed
if (isInstalled) {
// Installed but not running (only meaningful for services)
m_statusLabel->setText("Installed but not running");
m_installButton->setText("Enable");
} else {
if (m_name == "Avahi Daemon") {
m_statusLabel->setText("Not activated");
@@ -124,19 +122,19 @@ void DependencyItem::setInstalled(bool installed, bool isRequired)
}
m_installButton->setText("Install");
}
#ifndef WIN32
if (isRequired) {
m_statusLabel->setStyleSheet("color: red;");
}
#else
// FIXME: if we call this multiple times, the styles will keep stacking
// and become a mess, need a better way to handle this
m_statusLabel->setStyleSheet(mergeStyles(
m_statusLabel,
QString("QLabel { color: %1; }").arg(COLOR_RED.name())));
#endif
m_installButton->setVisible(true);
}
#ifndef WIN32
if (isRequired) {
m_statusLabel->setStyleSheet("color: red;");
}
#else
// FIXME: if we call this multiple times, the styles will keep stacking
// and become a mess, need a better way to handle this
m_statusLabel->setStyleSheet(mergeStyles(
m_statusLabel, QString("QLabel { color: %1; }").arg(COLOR_RED.name())));
#endif
m_installButton->setVisible(true);
}
void DependencyItem::setChecking(bool checking)
@@ -164,13 +162,30 @@ void DependencyItem::setInstalling(bool installing)
}
}
void DependencyItem::setActivating(bool activating)
{
if (activating) {
m_statusLabel->setText("Activating...");
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::setProgress(const QString &message)
{
m_statusLabel->setText(message);
m_statusLabel->setStyleSheet("color: gray;");
}
void DependencyItem::onInstallClicked() { emit installRequested(m_name); }
void DependencyItem::onInstallClicked()
{
emit installRequested(m_name, m_availability);
}
DiagnoseWidget::DiagnoseWidget(QWidget *parent)
: QWidget(parent), m_isExpanded(false)
@@ -183,14 +198,15 @@ DiagnoseWidget::DiagnoseWidget(QWidget *parent)
"Required for AirPlay, wireless devices and network service discovery");
addDependencyItem("Apple Mobile Device Support",
"Required for iOS device communication");
addDependencyItem("WinFsp", "Required for mounting your device as a drive");
addDependencyItem("WinFsp", "Required for mounting your device as a drive",
true);
#endif
#ifdef __linux__
#ifdef ENABLE_RECOVERY_DEVICE_SUPPORT
addDependencyItem(
"UDEV rules",
"Required for recovery devices requires manual setup (optional)");
addDependencyItem("UDEV rules",
"Required for recovery devices requires manual setup",
true);
#endif
addDependencyItem(
"Avahi Daemon",
@@ -225,8 +241,10 @@ void DiagnoseWidget::setupUI()
[this]() { checkDependencies(false); });
// Toggle button
m_toggleButton = new QPushButton("");
m_toggleButton->setFixedSize(24, 24);
m_toggleButton = new ZIconWidget(
QIcon(":/resources/icons/MaterialSymbolsLightKeyboardArrowDown.png"),
"Expand/Collapse");
// m_toggleButton->setFixedSize(24, 24);
m_toggleButton->setCheckable(true);
connect(m_toggleButton, &QPushButton::clicked, this,
&DiagnoseWidget::onToggleExpand);
@@ -249,14 +267,16 @@ void DiagnoseWidget::setupUI()
}
void DiagnoseWidget::addDependencyItem(const QString &name,
const QString &description)
const QString &description,
bool optional)
{
DependencyItem *item = new DependencyItem(name, description);
DependencyItem *item = new DependencyItem(name, description, optional);
item->setProperty("name", name);
item->setProperty("optional", optional);
connect(item, &DependencyItem::installRequested, this,
&DiagnoseWidget::onInstallRequested);
m_dependencyItems.append(item);
m_dependencyItems[name] = item;
// Insert before the stretch
m_itemsLayout->insertWidget(m_itemsLayout->count() - 1, item);
@@ -274,9 +294,11 @@ void DiagnoseWidget::checkDependencies(bool autoExpand)
QTimer::singleShot(500, [this, autoExpand]() {
int installedCount = 0;
int totalCount = 0;
int optionalInstalledCount = 0;
int optionalTotalCount = 0;
for (DependencyItem *item : m_dependencyItems) {
bool installed = false;
SERVICE_AVAILABILITY installed = SERVICE_UNAVAILABLE;
QString itemName = item->property("name").toString();
#ifdef WIN32
@@ -297,27 +319,36 @@ void DiagnoseWidget::checkDependencies(bool autoExpand)
}
#endif
bool isRequired = true;
#ifdef __linux__
if (itemName == "UDEV rules") {
isRequired = false;
}
#endif
item->setInstalled(installed, isRequired);
if (isRequired) {
bool isRequired = item->property("optional").toBool() == false;
if (!isRequired) {
++optionalTotalCount;
if (installed == SERVICE_AVAILABILITY::SERVICE_AVAILABLE) {
++optionalInstalledCount;
}
} else {
++totalCount;
if (installed)
if (installed == SERVICE_AVAILABILITY::SERVICE_AVAILABLE) {
++installedCount;
}
}
item->setInstalled(installed, isRequired);
}
if (installedCount == totalCount) {
m_summaryLabel->setText(
QString(
"All required dependencies are installed/activated (%1/%2)")
.arg(installedCount)
.arg(totalCount));
if (optionalTotalCount != optionalInstalledCount) {
int optionalMissingCount =
optionalTotalCount - optionalInstalledCount;
QString optionalText = optionalMissingCount == 1
? "optional capability is"
: "optional capabilities are";
m_summaryLabel->setText(QString("%1 %2 available")
.arg(optionalMissingCount)
.arg(optionalText));
} else {
m_summaryLabel->setText(
"All required dependencies are installed");
}
m_summaryLabel->setStyleSheet(
QString("color: %1; font-weight: bold;")
.arg(COLOR_GREEN.name()));
@@ -342,23 +373,55 @@ void DiagnoseWidget::checkDependencies(bool autoExpand)
void DiagnoseWidget::onInstallRequested(const QString &name)
{
DependencyItem *itemToInstall = m_dependencyItems.value(name);
if (!itemToInstall) {
QMessageBox::warning(this, "Error",
"Dependency item not found: " + name);
return;
}
SERVICE_AVAILABILITY availability = itemToInstall->availability();
#ifdef WIN32
if (name == "Bonjour Service") {
if (availability == SERVICE_AVAILABLE_BUT_NOT_RUNNING) {
qDebug() << "Attempting to start Bonjour Service...";
itemToInstall->setActivating(true);
QTimer::singleShot(100, [this, itemToInstall, name]() {
bool success = StartBonjourService();
itemToInstall->setActivating(false);
if (!success) {
QMessageBox::warning(
this, "Activation Failed",
"Failed to start the service. Please try to start it "
"manually from the Services app (services.msc)");
}
checkDependencies(false);
});
return;
}
installBonjourRuntime();
return;
}
if (name == "Apple Mobile Device Support") {
DependencyItem *itemToInstall = nullptr;
for (DependencyItem *item : m_dependencyItems) {
if (item->property("name").toString() == name) {
itemToInstall = item;
break;
}
}
if (availability == SERVICE_AVAILABLE_BUT_NOT_RUNNING) {
qDebug() << "Attempting to start Apple Mobile Device Service...";
itemToInstall->setActivating(true);
QTimer::singleShot(100, [this, itemToInstall, name]() {
bool success = StartWinFspService();
itemToInstall->setActivating(false);
if (!itemToInstall)
if (!success) {
QMessageBox::warning(
this, "Activation Failed",
"Failed to start the service. Please try to start it "
"manually from the Services app (services.msc)");
}
checkDependencies(false);
});
return;
}
itemToInstall->setInstalling(true);
@@ -366,45 +429,31 @@ void DiagnoseWidget::onInstallRequested(const QString &name)
"/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();
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",
"This might be a "
"permissions issue or an internal error.\n\n"
"Details: " +
errorOutput.trimmed());
}
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");
checkDependencies(false);
installProcess->deleteLater();
});
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\"'")
"\"%1\"' -Wait")
.arg(scriptPath);
QStringList args;
@@ -606,7 +655,11 @@ void DiagnoseWidget::onToggleExpand()
{
m_isExpanded = !m_isExpanded;
m_itemsWidget->setVisible(m_isExpanded);
m_toggleButton->setText(m_isExpanded ? "" : "");
m_toggleButton->setIcon(
m_isExpanded
? QIcon(":/resources/icons/MaterialSymbolsLightKeyboardArrowUp.png")
: QIcon(":/resources/icons/"
"MaterialSymbolsLightKeyboardArrowDown.png"));
m_itemsWidget->updateGeometry();
adjustSize();
}
@@ -614,13 +667,7 @@ void DiagnoseWidget::onToggleExpand()
#ifdef WIN32
void DiagnoseWidget::installBonjourRuntime()
{
DependencyItem *itemToInstall = nullptr;
for (DependencyItem *item : m_dependencyItems) {
if (item->property("name").toString() == "Bonjour Service") {
itemToInstall = item;
break;
}
}
DependencyItem *itemToInstall = m_dependencyItems.value("Bonjour Service");
if (!itemToInstall)
return;
+32 -7
View File
@@ -20,16 +20,36 @@
#ifndef DIAGNOSE_WIDGET_H
#define DIAGNOSE_WIDGET_H
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "qprocessindicator.h"
#include <QApplication>
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProcess>
#include <QProgressBar>
#include <QPushButton>
#include <QRegularExpression>
#include <QScrollArea>
#include <QStandardPaths>
#include <QTextStream>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
#include <QWidget>
#include "qprocessindicator.h"
#include "service.h"
class DependencyItem : public QWidget
{
@@ -37,14 +57,17 @@ class DependencyItem : public QWidget
public:
explicit DependencyItem(const QString &name, const QString &description,
QWidget *parent = nullptr);
void setInstalled(bool installed, bool isRequired);
bool optional = false, QWidget *parent = nullptr);
void setInstalled(SERVICE_AVAILABILITY availability, bool isRequired);
void setChecking(bool checking);
void setInstalling(bool installing);
void setActivating(bool activating);
void setProgress(const QString &message);
SERVICE_AVAILABILITY availability() const { return m_availability; }
signals:
void installRequested(const QString &name);
void installRequested(const QString &name,
SERVICE_AVAILABILITY availability);
private slots:
void onInstallClicked();
@@ -56,6 +79,7 @@ private:
QLabel *m_statusLabel;
QPushButton *m_installButton;
QProcessIndicator *m_processIndicator;
SERVICE_AVAILABILITY m_availability = SERVICE_UNAVAILABLE;
};
class DiagnoseWidget : public QWidget
@@ -74,7 +98,8 @@ private slots:
private:
void setupUI();
void addDependencyItem(const QString &name, const QString &description);
void addDependencyItem(const QString &name, const QString &description,
bool optional = false);
#ifdef WIN32
void installBonjourRuntime();
@@ -88,12 +113,12 @@ private:
QVBoxLayout *m_mainLayout;
QVBoxLayout *m_itemsLayout;
QPushButton *m_checkButton;
QPushButton *m_toggleButton;
ZIconWidget *m_toggleButton;
QLabel *m_summaryLabel;
QWidget *m_itemsWidget;
bool m_isExpanded;
QList<DependencyItem *> m_dependencyItems;
QMap<QString, DependencyItem *> m_dependencyItems;
};
#endif // DIAGNOSE_WIDGET_H
+1 -1
View File
@@ -172,7 +172,7 @@ void DiskUsageWidget::setupUI()
"border: none; padding: 0; margin: 0; }");
m_galleryBar->setStyleSheet(
"QWidget#galleryBar { background-color: #9b59b6; border: 1px solid "
"#8e44ad; border-radius:0px; padding: 0; margin: 0; }");
"#b36cd1; border-radius:0px; padding: 0; margin: 0; }");
m_othersBar->setStyleSheet(
"QWidget#othersBar { background-color: #a28729; border: 1px solid "
"#c4a32d; border-radius:0px; padding: 0; margin: 0; }");
+3
View File
@@ -2,6 +2,9 @@
HowToConnectDialog::HowToConnectDialog(QWidget *parent) : QDialog{parent}
{
#ifdef WIN32
setupWinWindow(this);
#endif
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 20, 0, 0);
mainLayout->setSpacing(0);
+2 -159
View File
@@ -34,6 +34,7 @@ using u_int64_t = uint64_t;
#include <QThread>
#include <QtCore/QObject>
#include "service.h"
#include <mutex>
#include <pugixml.hpp>
#include <string>
@@ -43,8 +44,7 @@ using u_int64_t = uint64_t;
#define TOOL_NAME "iDescriptor"
#define APP_LABEL "iDescriptor"
#define APP_COPYRIGHT \
"© 2025 The iDescriptor Project contributors. See AUTHORS for details."
#define AFC2_SERVICE_NAME "com.apple.afc2"
"© 2026 The iDescriptor Project contributors. See AUTHORS for details."
#define RECOVERY_CLIENT_CONNECTION_TRIES 3
#define APPLE_VENDOR_ID 0x05ac
#define REPO_URL "https://github.com/iDescriptor/iDescriptor"
@@ -264,14 +264,6 @@ void init_idescriptor_recovery_device(uint64_t ecid,
iDescriptorInitDeviceResultRecovery &res);
#endif
struct TakeScreenshotResult {
bool success = false;
QImage img;
};
void warn(const QString &message, const QString &title = "Warning",
QWidget *parent = nullptr);
enum class AddType {
Regular,
Pairing,
@@ -282,22 +274,6 @@ enum class AddType {
std::string parse_product_type(const std::string &productType);
struct MediaEntry {
std::string name;
bool isDir;
};
struct AFCFileTree {
std::vector<MediaEntry> entries;
bool success;
std::string currentPath;
};
struct WirelessInitArgs {
const QString ip;
const QString pairing_file;
};
enum class ImageCompatibility {
Compatible, // Exact match or known compatible version
MaybeCompatible, // Major version matches but minor doesn't
@@ -411,139 +387,6 @@ inline QJsonObject getVersionedConfig(const QJsonObject &rootObj)
return QJsonObject();
}
inline void free_directory_listing(char **entries, size_t count)
{
if (!entries)
return;
for (size_t i = 0; i < count; i++) {
if (entries[i]) {
// FIXME: crashes on Windows
// free(entries[i]);
}
}
// FIXME: crashes on Windows
// free(entries);
}
inline int read_file(const char *filename, uint8_t **data, size_t *length)
{
FILE *file = fopen(filename, "rb");
if (!file) {
perror("Failed to open file");
return 0;
}
fseek(file, 0, SEEK_END);
*length = ftell(file);
fseek(file, 0, SEEK_SET);
*data = (uint8_t *)malloc(*length);
if (!*data) {
perror("Failed to allocate memory");
fclose(file);
return 0;
}
if (fread(*data, 1, *length, file) != *length) {
perror("Failed to read file");
free(*data);
fclose(file);
return 0;
}
fclose(file);
return 1;
}
struct ExportResult {
QString sourceFilePath;
QString outputFilePath;
bool success = false;
bool cancelled = false;
QString errorMessage;
qint64 bytesTransferred = 0;
};
struct ExportJobSummary {
QUuid jobId;
int totalItems = 0;
int successfulItems = 0;
int failedItems = 0;
qint64 totalBytesTransferred = 0;
QString destinationPath;
bool wasCancelled = false;
};
struct ImportResult;
template <typename ResultT> class PItem
{
public:
QString sourcePathOnDevice;
QString suggestedFileName;
QString d_udid;
std::function<void(const ResultT &)> callback;
PItem() = default;
PItem(const QString &sourcePath, const QString &fileName,
const QString &d_udid,
std::function<void(const ResultT &)> callback = nullptr)
: sourcePathOnDevice(sourcePath), suggestedFileName(fileName),
d_udid(std::move(d_udid)), callback(std::move(callback))
{
}
};
struct ExportItem : public PItem<ExportResult> {
using PItem<ExportResult>::PItem;
};
struct ImportItem : public PItem<ImportResult> {
using PItem<ImportResult>::PItem;
};
class JobBase
{
public:
QUuid jobId;
QString destinationPath;
std::optional<bool> altAfc;
std::atomic<bool> cancelRequested{false};
QUuid statusBalloonProcessId;
// device udid
QString d_udid;
virtual ~JobBase() = default;
};
template <typename ItemT> class Job : public JobBase
{
public:
QList<QString> items;
};
// Concrete aliases
using ExportJob = Job<ExportItem>;
using ImportJob = Job<ImportItem>;
struct ImportResult {
QString sourceFilePath;
QString outputFilePath;
bool success = false;
QString errorMessage;
qint64 bytesTransferred = 0;
};
struct ImportJobSummary {
QUuid jobId;
int totalItems = 0;
int successfulItems = 0;
int failedItems = 0;
qint64 totalBytesTransferred = 0;
QString destinationPath;
bool wasCancelled = false;
};
struct XmlPlistDict {
pugi::xml_node current_node;
+1 -2
View File
@@ -83,7 +83,6 @@ public:
private:
explicit IOManagerClient(QObject *parent = nullptr);
void executeExportJob(ExportJob *job);
};
#endif // IOMANAGERCLIENT_H
#endif // IOMANAGERCLIENT_H
+3 -5
View File
@@ -215,10 +215,9 @@ void NetworkDevicesToConnectWidget::setupUI()
// Scroll area
m_scrollArea = new QScrollArea();
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setMinimumHeight(400);
m_scrollArea->setMaximumHeight(400);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_scrollArea->setStyleSheet(
"QScrollArea { background: transparent; border: none; }");
/* FIXME: We need a better approach to theme awareness */
@@ -236,10 +235,9 @@ void NetworkDevicesToConnectWidget::setupUI()
m_deviceLayout->addStretch();
m_scrollArea->setWidget(m_scrollContent);
groupLayout->addWidget(m_scrollArea);
groupLayout->addWidget(m_scrollArea, 1);
mainLayout->addWidget(m_deviceGroup);
mainLayout->addStretch();
mainLayout->addWidget(m_deviceGroup, 1);
}
void NetworkDevicesToConnectWidget::createDeviceCard(
+187 -19
View File
@@ -19,6 +19,36 @@
#include "win_common.h"
#include <string>
#include <windows.h>
#include <winsvc.h>
bool IsServiceRunning(LPCSTR serviceName)
{
SC_HANDLE scm = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!scm) {
return false;
}
SC_HANDLE svc = OpenServiceA(scm, serviceName, SERVICE_QUERY_STATUS);
if (!svc) {
CloseServiceHandle(scm);
return false;
}
SERVICE_STATUS_PROCESS status;
DWORD bytesNeeded = 0;
bool isRunning = false;
if (QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO,
reinterpret_cast<LPBYTE>(&status), sizeof(status),
&bytesNeeded)) {
isRunning = (status.dwCurrentState == SERVICE_RUNNING);
}
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return isRunning;
}
bool CheckRegistry(HKEY hKeyRoot, LPCSTR subKey, LPCSTR displayNameToFind)
{
@@ -67,36 +97,56 @@ bool CheckRegistryKeyExists(HKEY hKeyRoot, LPCSTR subKey)
return false;
}
bool IsAppleMobileDeviceSupportInstalled()
SERVICE_AVAILABILITY IsAppleMobileDeviceSupportInstalled()
{
bool installed = false;
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"Apple Mobile Device Support")) {
return true;
installed = true;
} else if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\WOW6432Node\\Microsoft\\Wi"
"ndows\\CurrentVersion\\Uninstall",
"Apple Mobile Device Support")) {
installed = true;
}
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\WOW6432Node\\Microsoft\\Wi"
"ndows\\CurrentVersion\\Uninstall",
"Apple Mobile Device Support")) {
return true;
if (!installed) {
return SERVICE_UNAVAILABLE;
}
return false;
if (IsServiceRunning("Apple Mobile Device Service")) {
return SERVICE_AVAILABLE;
}
return SERVICE_AVAILABLE_BUT_NOT_RUNNING;
}
bool IsWinFspInstalled()
SERVICE_AVAILABILITY IsWinFspInstalled()
{
bool installed = false;
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"WinFsp 2025")) {
return true;
installed = true;
} else if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\WOW6432Node\\Microsoft\\Wi"
"ndows\\CurrentVersion\\Uninstall",
"WinFsp 2025")) {
installed = true;
}
if (CheckRegistry(HKEY_LOCAL_MACHINE,
"SOFTWARE\\WOW6432Node\\Microsoft\\Wi"
"ndows\\CurrentVersion\\Uninstall",
"WinFsp 2025")) {
return true;
if (!installed) {
return SERVICE_UNAVAILABLE;
}
return false;
if (IsServiceRunning("WinFsp.Launcher")) {
return SERVICE_AVAILABLE;
}
return SERVICE_AVAILABLE_BUT_NOT_RUNNING;
}
bool is_iDescriptorInstalled()
@@ -115,12 +165,130 @@ bool is_iDescriptorInstalled()
return false;
}
bool IsBonjourServiceInstalled()
bool StartServiceByName(LPCSTR serviceName, DWORD timeoutMs = 30000)
{
if (CheckRegistryKeyExists(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Apple Inc.\\Bonjour")) {
SC_HANDLE scm = OpenSCManagerA(nullptr, nullptr, SC_MANAGER_CONNECT);
if (!scm) {
return false;
}
SC_HANDLE svc = OpenServiceA(scm, serviceName,
SERVICE_START | SERVICE_QUERY_STATUS |
SERVICE_CHANGE_CONFIG);
if (!svc) {
CloseServiceHandle(scm);
return false;
}
// set startup type to Automatic
ChangeServiceConfigA(svc,
SERVICE_NO_CHANGE, // service type
SERVICE_AUTO_START, // start type
SERVICE_NO_CHANGE, // error control
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr);
SERVICE_STATUS_PROCESS status;
DWORD bytesNeeded = 0;
if (!QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO,
reinterpret_cast<LPBYTE>(&status), sizeof(status),
&bytesNeeded)) {
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return false;
}
if (status.dwCurrentState == SERVICE_RUNNING) {
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return true;
}
if (!StartServiceA(svc, 0, nullptr)) {
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return false;
}
DWORD startTick = GetTickCount();
do {
Sleep(500);
if (!QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO,
reinterpret_cast<LPBYTE>(&status),
sizeof(status), &bytesNeeded)) {
break;
}
if (status.dwCurrentState == SERVICE_RUNNING) {
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return true;
}
} while (GetTickCount() - startTick < timeoutMs &&
(status.dwCurrentState == SERVICE_START_PENDING));
CloseServiceHandle(svc);
CloseServiceHandle(scm);
return false;
}
SERVICE_AVAILABILITY IsBonjourServiceInstalled()
{
bool installed = CheckRegistryKeyExists(HKEY_LOCAL_MACHINE,
"SOFTWARE\\Apple Inc.\\Bonjour");
if (!installed) {
return SERVICE_UNAVAILABLE;
}
// Modern Bonjour service name
if (IsServiceRunning("Bonjour Service")) {
return SERVICE_AVAILABLE;
}
// Fallback for older installs
if (IsServiceRunning("mDNSResponder")) {
return SERVICE_AVAILABLE;
}
return SERVICE_AVAILABLE_BUT_NOT_RUNNING;
}
bool StartBonjourService()
{
// Modern Bonjour service name
if (IsServiceRunning("Bonjour Service")) {
return true;
}
if (StartServiceByName("Bonjour Service")) {
return true;
}
// Apparently it used to be called mDNSResponder
if (IsServiceRunning("mDNSResponder")) {
return true;
}
if (StartServiceByName("mDNSResponder")) {
return true;
}
return false;
}
bool StartAppleMobileDeviceService()
{
if (IsServiceRunning("Apple Mobile Device Service")) {
return true;
}
return StartServiceByName("Apple Mobile Device Service");
}
bool StartWinFspService()
{
if (IsServiceRunning("WinFsp.Launcher")) {
return true;
}
return StartServiceByName("WinFsp.Launcher");
}
-33
View File
@@ -1,33 +0,0 @@
#include "win_common.h"
// TODO: remove
void setupTitleBar(HWND hwnd)
{
if (!hwnd)
return;
LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
if (style) {
style &= ~WS_THICKFRAME; // disable resizing
// Keep WS_CAPTION, WS_SYSMENU, WS_MINIMIZEBOX, WS_MAXIMIZEBOX
SetWindowLongPtr(hwnd, GWL_STYLE, style);
}
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
// Clear title text and icon
SetWindowTextW(hwnd, L"");
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM) nullptr);
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM) nullptr);
// Make the title bar transparent (extend frame into client area)
MARGINS margins = {0, 0, 0, 0};
DwmExtendFrameIntoClientArea(hwnd, &margins);
// Disable the DWM caption drawing so the title bar has no background
BOOL value = TRUE;
const DWORD DWMWA_USE_IMMERSIVE_DARK_MODE_ATTR = 20;
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_ATTR, &value,
sizeof(value));
}
+9 -4
View File
@@ -28,15 +28,20 @@
#include <winternl.h>
#include <ws2tcpip.h>
#include "../../service.h"
enum WIN_BACKDROP { MICA = 2, ACRYLIC = 3, MICA_ALT = 4 };
bool IsAppleMobileDeviceSupportInstalled();
bool IsWinFspInstalled();
SERVICE_AVAILABILITY IsAppleMobileDeviceSupportInstalled();
SERVICE_AVAILABILITY IsWinFspInstalled();
bool is_iDescriptorInstalled();
bool IsBonjourServiceInstalled();
SERVICE_AVAILABILITY IsBonjourServiceInstalled();
bool StartAppleMobileDeviceService();
bool StartWinFspService();
bool StartBonjourService();
void enableAcrylic(HWND hwnd);
void setupTitleBar(HWND hwnd);
void enableMica(HWND hwnd);
inline QString getWindowsAccentColor()
+6
View File
@@ -0,0 +1,6 @@
#pragma once
enum SERVICE_AVAILABILITY {
SERVICE_AVAILABLE,
SERVICE_AVAILABLE_BUT_NOT_RUNNING,
SERVICE_UNAVAILABLE
};
+8 -3
View File
@@ -56,14 +56,19 @@ void WelcomeWidget::setupUI()
QHBoxLayout *imageAndWirelessDevicesLayout = new QHBoxLayout();
m_imageLabel = new QLabel();
m_imageLabel = new ResponsiveQLabel();
m_imageLabel->setPixmap(QPixmap(":/resources/connect.png"));
m_imageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_imageLabel->setMinimumSize(QSize(400, 0));
m_imageLabel->setMinimumWidth(200);
m_imageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_imageLabel->setStyleSheet("background: transparent; border: none;");
m_imageLabel->setAlignment(Qt::AlignCenter);
imageAndWirelessDevicesLayout->addSpacing(40);
imageAndWirelessDevicesLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter);
imageAndWirelessDevicesLayout->addSpacing(40);
QVBoxLayout *explorerWithIntructionLayout = new QVBoxLayout();
NetworkDevicesToConnectWidget *networkDevicesWidget =
new NetworkDevicesToConnectWidget();
+1 -1
View File
@@ -43,7 +43,7 @@ private:
QVBoxLayout *m_mainLayout;
ZLabel *m_titleLabel;
ZLabel *m_subtitleLabel;
QLabel *m_imageLabel;
ResponsiveQLabel *m_imageLabel;
ZLabel *m_instructionLabel;
ZLabel *m_githubLabel;
ZLabel *m_howToConnectLabel;