From d4100c48a9b9ae2cf1db64ade7e6204778bf2a6a Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 1 Feb 2026 01:49:00 -0800 Subject: [PATCH] feat(ui): add NetworkDevicesToConnectWidget, update mainwindow --- src/mainwindow.cpp | 32 +-- src/networkdevicemanager.cpp | 6 +- src/networkdevicestoconnectwidget.cpp | 267 ++++++++++++++++++++++++++ src/networkdevicestoconnectwidget.h | 68 +++++++ src/toolboxwidget.cpp | 5 +- src/welcomewidget.cpp | 17 +- 6 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 src/networkdevicestoconnectwidget.cpp create mode 100644 src/networkdevicestoconnectwidget.h diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f2608a0..25f239e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,7 @@ #include "appcontext.h" #include "settingsmanager.h" // #include "devicemonitor.h" -#include "Toast.h" +// #include "Toast.h" #include "networkdevicemanager.h" #include "networkdeviceswidget.h" #include "statusballoon.h" @@ -414,7 +415,10 @@ MainWindow::MainWindow(QWidget *parent) } }); - m_deviceMonitor->start(); + /* If a device is connected before starting the app on slower machines ui + * takes a lot of time to render so delay the monitoring a bit */ + QTimer::singleShot(std::chrono::seconds(1), this, + [this]() { m_deviceMonitor->start(); }); connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [](const std::string &udid, const std::string &wifiMacAddress) { @@ -469,17 +473,17 @@ MainWindow::MainWindow(QWidget *parent) connect(AppContext::sharedInstance(), &AppContext::deviceHeartbeatFailed, this, [this](const QString &macAddress, int tries) { - Toast *toast = new Toast(this); - toast->setAttribute(Qt::WA_DeleteOnClose); - toast->setDuration(8000); // Hide after 8 seconds - toast->setTitle("Heartbeat failed"); - toast->setText( - QString("Heartbeat failed for device with MAC %1. " - "Number of failed attempts: %2") - .arg(macAddress) - .arg(tries)); - toast->setPosition(ToastPosition::BOTTOM_MIDDLE); - toast->show(); + // Toast *toast = new Toast(this); + // toast->setAttribute(Qt::WA_DeleteOnClose); + // toast->setDuration(8000); // Hide after 8 seconds + // toast->setTitle("Heartbeat failed"); + // toast->setText( + // QString("Heartbeat failed for device with MAC %1. " + // "Number of failed attempts: %2") + // .arg(macAddress) + // .arg(tries)); + // toast->setPosition(ToastPosition::BOTTOM_MIDDLE); + // toast->show(); }); // NetworkDevicesWidget *m_networkDevicesWidget = new @@ -532,7 +536,7 @@ MainWindow::~MainWindow() delete ui; m_deviceMonitor->requestInterruption(); // FIXME:QThread: Destroyed while thread '' is still running - // m_deviceMonitor->wait(); + // m_deviceMonitor->wait(); delete m_deviceMonitor; // delete m_updater; // sleep(2); diff --git a/src/networkdevicemanager.cpp b/src/networkdevicemanager.cpp index 1177fb8..7f52159 100644 --- a/src/networkdevicemanager.cpp +++ b/src/networkdevicemanager.cpp @@ -1,4 +1,5 @@ #include "networkdevicemanager.h" +#include NetworkDeviceManager *NetworkDeviceManager::sharedInstance() { @@ -23,6 +24,7 @@ NetworkDeviceManager::NetworkDeviceManager(QObject *parent) : QObject{parent} &NetworkDeviceManager::deviceRemoved); #endif - // Start scanning for network devices - m_networkProvider->startBrowsing(); + /* Helps main ui load a litte faster */ + QTimer::singleShot(std::chrono::seconds(1), this, + [this]() { m_networkProvider->startBrowsing(); }); } diff --git a/src/networkdevicestoconnectwidget.cpp b/src/networkdevicestoconnectwidget.cpp new file mode 100644 index 0000000..a08b4f3 --- /dev/null +++ b/src/networkdevicestoconnectwidget.cpp @@ -0,0 +1,267 @@ +/* + * iDescriptor: A free and open-source idevice management tool. + * + * Copyright (C) 2025 Uncore + * + * 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 . + */ + +#include "networkdevicestoconnectwidget.h" + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include "appcontext.h" +#include +#include +#include +#include +#include +#include + +NetworkDevicesToConnectWidget::NetworkDevicesToConnectWidget(QWidget *parent) + : QWidget(parent) +{ + setupUI(); + +#ifdef __linux__ + m_networkProvider = new AvahiService(this); + connect(m_networkProvider, &AvahiService::deviceAdded, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceAdded); + connect(m_networkProvider, &AvahiService::deviceRemoved, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceRemoved); +#else + m_networkProvider = new DnssdService(this); + connect(m_networkProvider, &DnssdService::deviceAdded, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceAdded); + connect(m_networkProvider, &DnssdService::deviceRemoved, this, + &NetworkDevicesToConnectWidget::onWirelessDeviceRemoved); +#endif + + // Start scanning for network devices + m_networkProvider->startBrowsing(); + + // Initial device list update + updateDeviceList(); +} + +NetworkDevicesToConnectWidget::~NetworkDevicesToConnectWidget() +{ + if (m_networkProvider) { + m_networkProvider->stopBrowsing(); + } +} + +void NetworkDevicesToConnectWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->setSpacing(10); + + // Status label + m_statusLabel = new QLabel("Scanning for network devices..."); + QFont statusFont = m_statusLabel->font(); + statusFont.setPointSize(12); + statusFont.setWeight(QFont::Medium); + m_statusLabel->setFont(statusFont); + m_statusLabel->setAlignment(Qt::AlignCenter); + mainLayout->addWidget(m_statusLabel); + + // Device group + m_deviceGroup = new QGroupBox("Network Devices"); + QFont groupFont = m_deviceGroup->font(); + groupFont.setPointSize(14); + groupFont.setWeight(QFont::Bold); + m_deviceGroup->setFont(groupFont); + + QVBoxLayout *groupLayout = new QVBoxLayout(m_deviceGroup); + groupLayout->setContentsMargins(5, 15, 5, 5); + groupLayout->setSpacing(0); + + // Scroll area + m_scrollArea = new QScrollArea(); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setMinimumHeight(200); + m_scrollArea->setMaximumHeight(400); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + /* FIXME: We need a better approach to theme awareness */ + connect(qApp, &QApplication::paletteChanged, this, [this]() { + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + }); + + // Scroll content + m_scrollContent = new QWidget(); + m_scrollContent->setContentsMargins(0, 0, 0, 0); + m_deviceLayout = new QVBoxLayout(m_scrollContent); + m_deviceLayout->setContentsMargins(5, 5, 5, 5); + m_deviceLayout->setSpacing(8); + m_deviceLayout->addStretch(); + + m_scrollArea->setWidget(m_scrollContent); + groupLayout->addWidget(m_scrollArea); + + mainLayout->addWidget(m_deviceGroup); + mainLayout->addStretch(); +} + +void NetworkDevicesToConnectWidget::createDeviceCard( + const NetworkDevice &device) +{ + // Main card frame + QWidget *card = new QWidget(); + + QVBoxLayout *cardLayout = new QVBoxLayout(card); + cardLayout->setContentsMargins(12, 10, 12, 10); + cardLayout->setSpacing(4); + + // Device name (primary) + QLabel *nameLabel = new QLabel(device.name); + nameLabel->setWordWrap(true); + QFont nameFont = nameLabel->font(); + nameFont.setPointSize(13); + nameFont.setWeight(QFont::Medium); + nameLabel->setFont(nameFont); + QPalette namePalette = nameLabel->palette(); + namePalette.setColor(QPalette::WindowText, + palette().color(QPalette::WindowText)); + nameLabel->setPalette(namePalette); + + // Device info container + QWidget *infoContainer = new QWidget(); + QHBoxLayout *infoLayout = new QHBoxLayout(infoContainer); + infoLayout->setContentsMargins(0, 0, 0, 0); + infoLayout->setSpacing(12); + + // Address info + QLabel *addressLabel = new QLabel(QString("IP: %1").arg(device.address)); + QFont addressFont = addressLabel->font(); + addressFont.setPointSize(11); + addressLabel->setFont(addressFont); + QPalette addressPalette = addressLabel->palette(); + QColor secondaryColor = palette().color(QPalette::WindowText); + secondaryColor.setAlpha(180); + addressPalette.setColor(QPalette::WindowText, secondaryColor); + addressLabel->setPalette(addressPalette); + + // Port info + QLabel *portLabel = new QLabel(QString("Port: %1").arg(device.port)); + portLabel->setFont(addressFont); + portLabel->setPalette(addressPalette); + + infoLayout->addWidget(addressLabel); + infoLayout->addWidget(portLabel); + infoLayout->addStretch(); + + QPushButton *connectButton = new QPushButton("Connect"); + connectButton->setDefault(true); + connect(connectButton, &QPushButton::clicked, this, + [this, device, connectButton]() { + connectButton->setText("Connecting..."); + connectButton->setEnabled(false); + AppContext::sharedInstance()->tryToConnectToNetworkDevice( + device.macAddress); + }); + infoLayout->addWidget(connectButton); + infoLayout->addSpacing(5); + + // Status indicator + QLabel *statusIndicator = new QLabel("●"); + QFont statusFont = statusIndicator->font(); + statusFont.setPointSize(12); + statusIndicator->setFont(statusFont); + QPalette statusPalette = statusIndicator->palette(); + statusPalette.setColor(QPalette::WindowText, + QColor(52, 199, 89)); // iOS green + statusIndicator->setPalette(statusPalette); + + infoLayout->addWidget(statusIndicator); + + cardLayout->addWidget(nameLabel); + cardLayout->addWidget(infoContainer); + + // Store the device info as property for later removal + card->setProperty("deviceName", device.name); + card->setProperty("deviceAddress", device.address); + + // Insert before the stretch + m_deviceLayout->insertWidget(m_deviceLayout->count() - 1, card); + m_deviceCards.append(card); +} + +void NetworkDevicesToConnectWidget::clearDeviceCards() +{ + for (QWidget *card : m_deviceCards) { + card->deleteLater(); + } + m_deviceCards.clear(); +} + +void NetworkDevicesToConnectWidget::updateDeviceList() +{ + clearDeviceCards(); + + QList devices = m_networkProvider->getNetworkDevices(); + + if (devices.isEmpty()) { + m_statusLabel->setText("No network devices found"); + } else { + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(devices.count())); + + for (const NetworkDevice &device : devices) { + createDeviceCard(device); + } + } +} + +void NetworkDevicesToConnectWidget::onWirelessDeviceAdded( + const NetworkDevice &device) +{ + createDeviceCard(device); + + // Update status + int deviceCount = m_deviceCards.count(); + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(deviceCount)); +} + +void NetworkDevicesToConnectWidget::onWirelessDeviceRemoved( + const QString &deviceName) +{ + // Find and remove the corresponding card + for (int i = 0; i < m_deviceCards.count(); ++i) { + QWidget *card = m_deviceCards[i]; + if (card->property("deviceName").toString() == deviceName) { + m_deviceCards.removeAt(i); + card->deleteLater(); + break; + } + } + + // Update status + int deviceCount = m_deviceCards.count(); + if (deviceCount == 0) { + m_statusLabel->setText("No network devices found"); + } else { + m_statusLabel->setText( + QString("Found %1 network device(s)").arg(deviceCount)); + } +} \ No newline at end of file diff --git a/src/networkdevicestoconnectwidget.h b/src/networkdevicestoconnectwidget.h new file mode 100644 index 0000000..0f22f2b --- /dev/null +++ b/src/networkdevicestoconnectwidget.h @@ -0,0 +1,68 @@ +/* + * iDescriptor: A free and open-source idevice management tool. + * + * Copyright (C) 2025 Uncore + * + * 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 . + */ + +#ifndef NETWORKDEVICESTOCONNECTWIDGET_H +#define NETWORKDEVICESTOCONNECTWIDGET_H + +#ifdef __linux__ +#include "core/services/avahi/avahi_service.h" +#else +#include "core/services/dnssd/dnssd_service.h" +#endif + +#include +#include +#include +#include +#include + +class NetworkDevicesToConnectWidget : public QWidget +{ + Q_OBJECT + +public: + explicit NetworkDevicesToConnectWidget(QWidget *parent = nullptr); + ~NetworkDevicesToConnectWidget(); + +private slots: + void onWirelessDeviceAdded(const NetworkDevice &device); + void onWirelessDeviceRemoved(const QString &deviceName); + +private: + void setupUI(); + void createDeviceCard(const NetworkDevice &device); + void clearDeviceCards(); + void updateDeviceList(); + + QGroupBox *m_deviceGroup = nullptr; + QScrollArea *m_scrollArea = nullptr; + QWidget *m_scrollContent = nullptr; + QVBoxLayout *m_deviceLayout = nullptr; + QLabel *m_statusLabel = nullptr; + +#ifdef __linux__ + AvahiService *m_networkProvider = nullptr; +#else + DnssdService *m_networkProvider = nullptr; +#endif + + QList m_deviceCards; +}; + +#endif // NETWORKDEVICESTOCONNECTWIDGET_H \ No newline at end of file diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 8c9d47a..a8d56f1 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -79,8 +79,9 @@ void ToolboxWidget::setupUI() m_scrollArea = new QScrollArea(); m_scrollArea->setWidgetResizable(true); m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setFrameStyle(QFrame::NoFrame); - m_scrollArea->viewport()->setAutoFillBackground(false); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + m_scrollArea->viewport()->setStyleSheet("background: transparent;"); m_contentWidget = new QWidget(); QVBoxLayout *contentLayout = new QVBoxLayout(m_contentWidget); diff --git a/src/welcomewidget.cpp b/src/welcomewidget.cpp index 57bb706..4e7fd3f 100644 --- a/src/welcomewidget.cpp +++ b/src/welcomewidget.cpp @@ -21,6 +21,7 @@ #include "diagnosewidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "networkdevicestoconnectwidget.h" #include "responsiveqlabel.h" #include #include @@ -53,6 +54,8 @@ void WelcomeWidget::setupUI() QPalette palette = m_subtitleLabel->palette(); m_mainLayout->addWidget(m_subtitleLabel); + QHBoxLayout *imageAndWirelessDevicesLayout = new QHBoxLayout(); + m_imageLabel = new ResponsiveQLabel(); m_imageLabel->setPixmap(QPixmap(":/resources/connect.png")); m_imageLabel->setScaledContents(true); @@ -61,7 +64,19 @@ void WelcomeWidget::setupUI() m_imageLabel->setStyleSheet("background: transparent; border: none;"); m_imageLabel->setAlignment(Qt::AlignCenter); - m_mainLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter); + + imageAndWirelessDevicesLayout->addStretch(1); + // m_imageLabel->setMaximumWidth(300); + imageAndWirelessDevicesLayout->addWidget(m_imageLabel, 0, Qt::AlignHCenter); + imageAndWirelessDevicesLayout->addStretch(1); + NetworkDevicesToConnectWidget *networkDevicesWidget = + new NetworkDevicesToConnectWidget(); + // FIMXE: resize original image + networkDevicesWidget->setMinimumWidth(350); + imageAndWirelessDevicesLayout->addWidget(networkDevicesWidget); + imageAndWirelessDevicesLayout->addStretch(1); + + m_mainLayout->addLayout(imageAndWirelessDevicesLayout); m_mainLayout->addSpacing(10); m_instructionLabel = createStyledLabel(