Files
iDescriptor/src/networkdevicestoconnectwidget.cpp
T

374 lines
12 KiB
C++

/*
* 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 "networkdevicestoconnectwidget.h"
#ifdef __linux__
#include "core/services/avahi/avahi_service.h"
#else
#include "core/services/dnssd/dnssd_service.h"
#endif
#include "appcontext.h"
#include <QApplication>
#include <QFrame>
#include <QHBoxLayout>
#include <QLabel>
#include <QPalette>
#include <QPushButton>
#include <QTimer>
NetworkDeviceCard::NetworkDeviceCard(const NetworkDevice &device,
QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *cardLayout = new QVBoxLayout(this);
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();
m_connectButton = new QPushButton("Connect");
m_connectButton->setDefault(true);
connect(m_connectButton, &QPushButton::clicked, this, [this, device]() {
m_connectButton->setText("Connecting...");
m_connectButton->setEnabled(false);
AppContext::sharedInstance()->tryToConnectToNetworkDevice(device);
});
infoLayout->addWidget(m_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);
}
void NetworkDeviceCard::failed()
{
m_connectButton->setText("Failed to connect");
m_connectButton->setEnabled(false);
QTimer::singleShot(2000, this, [this]() {
m_connectButton->setText("Connect");
m_connectButton->setEnabled(true);
});
}
void NetworkDeviceCard::noPairingFile()
{
// TODO: add a button or hint to explain how to create a pairing file for
// this device
m_connectButton->setText("No pairing file");
m_connectButton->setEnabled(false);
QTimer::singleShot(5000, this, [this]() {
m_connectButton->setText("Connect");
m_connectButton->setEnabled(true);
});
}
void NetworkDeviceCard::connected()
{
m_connectButton->setText("Connected");
m_connectButton->setEnabled(false);
QTimer::singleShot(10000, this, [this]() {
m_connectButton->setText("Connect");
m_connectButton->setEnabled(true);
});
}
void NetworkDeviceCard::initStarted()
{
m_connectButton->setText("Connecting...");
m_connectButton->setEnabled(false);
}
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();
connect(AppContext::sharedInstance(),
&AppContext::noPairingFileForWirelessDevice, this,
&NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice);
connect(AppContext::sharedInstance(), &AppContext::initFailed, this,
&NetworkDevicesToConnectWidget::onDeviceInitFailed);
connect(AppContext::sharedInstance(), &AppContext::initStarted, this,
&NetworkDevicesToConnectWidget::onDeviceInitStarted);
connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this,
&NetworkDevicesToConnectWidget::onDeviceAdded);
connect(AppContext::sharedInstance(), &AppContext::deviceAlreadyExists,
this, &NetworkDevicesToConnectWidget::onDeviceAlreadyExists);
}
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(400);
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
NetworkDeviceCard *card = new NetworkDeviceCard(device);
m_deviceLayout->insertWidget(m_deviceLayout->count() - 1, card);
m_deviceCards[device.macAddress] = card;
}
void NetworkDevicesToConnectWidget::clearDeviceCards()
{
for (QWidget *card : m_deviceCards) {
if (card) {
card->deleteLater();
}
}
m_deviceCards.clear();
}
void NetworkDevicesToConnectWidget::updateDeviceList()
{
clearDeviceCards();
QList<NetworkDevice> 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 &macAddress)
{
// Find and remove the corresponding card
NetworkDeviceCard *card = m_deviceCards[macAddress];
m_deviceCards.remove(macAddress);
if (card) {
card->deleteLater();
}
// 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));
}
}
void NetworkDevicesToConnectWidget::onNoPairingFileForWirelessDevice(
const QString &macAddress)
{
NetworkDeviceCard *deviceCard = m_deviceCards[macAddress];
if (deviceCard) {
qDebug() << "Calling noPairingFile() on device card for" << macAddress;
deviceCard->noPairingFile();
}
}
// udid or mac address
void NetworkDevicesToConnectWidget::onDeviceInitFailed(const QString &uniq)
{
NetworkDeviceCard *deviceCard = m_deviceCards[uniq];
if (deviceCard) {
qDebug() << "Calling failed() on device card for" << uniq;
deviceCard->failed();
}
}
void NetworkDevicesToConnectWidget::onDeviceInitStarted(const QString &uniq)
{
NetworkDeviceCard *deviceCard = m_deviceCards[uniq];
if (deviceCard) {
qDebug() << "Calling initStarted() on device card for" << uniq;
deviceCard->initStarted();
}
}
void NetworkDevicesToConnectWidget::onDeviceAdded(
const iDescriptorDevice *device)
{
NetworkDeviceCard *deviceCard = m_deviceCards[QString::fromStdString(
device->deviceInfo.wifiMacAddress)];
if (deviceCard) {
qDebug() << "Calling connected() on device card for"
<< QString::fromStdString(device->deviceInfo.wifiMacAddress);
deviceCard->connected();
return;
}
qDebug() << "No device card found for"
<< QString::fromStdString(device->deviceInfo.wifiMacAddress);
}
void NetworkDevicesToConnectWidget::onDeviceAlreadyExists(
const iDescriptor::Uniq &uniq)
{
NetworkDeviceCard *deviceCard = m_deviceCards[QString(uniq.get())];
if (deviceCard) {
qDebug() << "Calling connected() on device card for" << uniq.get();
deviceCard->connected();
return;
}
qDebug() << "No device card found for" << uniq.get();
}