From e631fa9ba3ee8872a513decde09489ac9870237b Mon Sep 17 00:00:00 2001 From: uncor3 Date: Thu, 9 Apr 2026 18:48:59 +0000 Subject: [PATCH] implement upgrading wired connection to wireless after device is unplugged --- src/appcontext.cpp | 4 +- src/appcontext.h | 2 +- src/core/services/avahi/avahi_service.cpp | 49 ++++++---------- src/core/services/avahi/avahi_service.h | 5 +- src/core/services/dnssd/dnssd_service.cpp | 29 +++------- src/core/services/dnssd/dnssd_service.h | 3 +- src/devicemenuwidget.cpp | 68 +++++++++++++---------- src/iDescriptor.h | 29 +++++++--- src/mainwindow.cpp | 44 +++++++++------ src/networkdeviceprovider.h | 7 ++- src/networkdevicestoconnectwidget.cpp | 2 +- src/networkdeviceswidget.cpp | 2 +- src/sshterminaltool.cpp | 2 +- 13 files changed, 135 insertions(+), 111 deletions(-) diff --git a/src/appcontext.cpp b/src/appcontext.cpp index f7ed217..272f9e5 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -188,7 +188,6 @@ void AppContext::removeDevice(iDescriptor::Uniq uniq, bool ask_backend) << "wasWireless:" << device->deviceInfo.isWireless; emit deviceRemoved(q_udid, device->deviceInfo.wifiMacAddress, - device->deviceInfo.ipAddress, device->deviceInfo.isWireless); emit deviceChange(); @@ -290,6 +289,9 @@ void AppContext::addRecoveryDevice(uint64_t ecid) AppContext::~AppContext() { m_devices.clear(); + for (const QString &udid : m_devices.keys()) { + core->remove_device(udid); + } #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT for (auto recoveryDevice : m_recoveryDevices) { diff --git a/src/appcontext.h b/src/appcontext.h index fc5b13c..6af45c3 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -74,7 +74,7 @@ private: signals: void deviceAdded(std::shared_ptr device); void deviceRemoved(const QString &udid, const std::string &macAddress, - const std::string &ipAddress, bool wasWireless); + bool wasWireless); void devicePaired(std::shared_ptr device); void devicePasswordProtected(const QString &udid); void deviceAlreadyExists(const iDescriptor::Uniq &uniq); diff --git a/src/core/services/avahi/avahi_service.cpp b/src/core/services/avahi/avahi_service.cpp index f1792c9..b2c47e3 100644 --- a/src/core/services/avahi/avahi_service.cpp +++ b/src/core/services/avahi/avahi_service.cpp @@ -60,12 +60,19 @@ void AvahiService::stopBrowsing() m_networkDevices.clear(); } -QList AvahiService::getNetworkDevices() const +QMap AvahiService::getNetworkDevices() const { QMutexLocker locker(&m_devicesMutex); return m_networkDevices; } +NetworkDevice +AvahiService::getNetworkDeviceByMac(const QString &macAddress) const +{ + QMutexLocker locker(&m_devicesMutex); + return m_networkDevices.value(macAddress, NetworkDevice()); +} + void AvahiService::pollAvahi() { if (m_simplePoll && m_running) { @@ -163,10 +170,7 @@ void AvahiService::browseCallback(AvahiServiceBrowser *browser, // Remove from our list { QMutexLocker locker(&service->m_devicesMutex); - service->m_networkDevices.removeIf( - [macAddress](const NetworkDevice &dev) { - return dev.macAddress == macAddress; - }); + service->m_networkDevices.remove(macAddress); } break; @@ -195,44 +199,27 @@ void AvahiService::resolveCallback( AvahiService *service = static_cast(userdata); if (event == AVAHI_RESOLVER_FOUND) { - NetworkDevice device; - device.name = QString::fromUtf8(name); - device.hostname = QString::fromUtf8(host_name); - device.port = port > 0 ? port : 22; // Default to SSH port + QString deviceName = QString::fromUtf8(name); // Convert address to string char addr_str[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(addr_str, sizeof(addr_str), address); - device.address = QString::fromUtf8(addr_str); - // Parse TXT records - for (AvahiStringList *t = txt; t; t = t->next) { - char *key = nullptr; - char *value = nullptr; - avahi_string_list_get_pair(t, &key, &value, nullptr); - if (key) { - device.txt[key] = value ? value : ""; - avahi_free(key); - } - if (value) { - avahi_free(value); - } - } - device.macAddress = device.name.split('@').first(); + NetworkDevice device( + QString::fromUtf8(name), QString::fromUtf8(addr_str), + deviceName.split('@').first(), QString::fromUtf8(host_name), + port > 0 ? port : 22); + qDebug() << "Resolved Apple device:" << device.name << "at" << device.address << ":" << device.port << "MAC:" << device.macAddress; - // Add to our list if not already present + // Add to list if not already present { QMutexLocker locker(&service->m_devicesMutex); - bool exists = std::any_of(service->m_networkDevices.begin(), - service->m_networkDevices.end(), - [&device](const NetworkDevice &existing) { - return existing == device; - }); + bool exists = service->m_networkDevices.contains(device.macAddress); if (!exists) { - service->m_networkDevices.append(device); + service->m_networkDevices[device.macAddress] = device; emit service->deviceAdded(device); } } diff --git a/src/core/services/avahi/avahi_service.h b/src/core/services/avahi/avahi_service.h index 9fe8e6e..e2c16fd 100644 --- a/src/core/services/avahi/avahi_service.h +++ b/src/core/services/avahi/avahi_service.h @@ -44,7 +44,8 @@ public: void startBrowsing(); void stopBrowsing(); - QList getNetworkDevices() const; + QMap getNetworkDevices() const; + NetworkDevice getNetworkDeviceByMac(const QString &macAddress) const; signals: void deviceAdded(const NetworkDevice &device); @@ -79,7 +80,7 @@ private: QTimer *m_pollTimer; mutable QMutex m_devicesMutex; - QList m_networkDevices; + QMap m_networkDevices; bool m_running; }; diff --git a/src/core/services/dnssd/dnssd_service.cpp b/src/core/services/dnssd/dnssd_service.cpp index 6b9ab46..b3e0b7e 100644 --- a/src/core/services/dnssd/dnssd_service.cpp +++ b/src/core/services/dnssd/dnssd_service.cpp @@ -150,10 +150,7 @@ void DNSSD_API DnssdService::browseCallback( // Remove from our list QMutexLocker locker(&service->m_devicesMutex); - service->m_networkDevices.removeIf( - [macAddress](const NetworkDevice &dev) { - return dev.macAddress == macAddress; - }); + service->m_networkDevices.remove(macAddress); service->m_pendingDevices.remove(macAddress); } } @@ -269,32 +266,28 @@ void DNSSD_API DnssdService::addrInfoCallback( auto *addr_in = reinterpret_cast(address); inet_ntop(AF_INET, &addr_in->sin_addr, ip, sizeof(ip)); - NetworkDevice device; - // Extract a better device name from hostname or use TXT records QString friendlyName = pending.hostname; if (friendlyName.endsWith(".local.")) { friendlyName = friendlyName.left(friendlyName.length() - 7); // Remove ".local." - qDebug() << "friendly name:" << friendlyName; } + QString deviceName; // Try to get device name from TXT records first if (pending.txt.contains("DvNm")) { - device.name = pending.txt["DvNm"]; + deviceName = pending.txt["DvNm"]; qDebug() << "Device name from DvNm TXT record:" << device.name; } else if (pending.txt.contains("Name")) { - device.name = pending.txt["Name"]; + deviceName = pending.txt["Name"]; qDebug() << "Device name from Name TXT record:" << device.name; } else { - // Use the cleaned hostname as fallback qDebug() << "Using hostname as device name:" << friendlyName; - device.name = friendlyName; + deviceName = friendlyName; } - device.hostname = pending.hostname; - device.address = QString::fromUtf8(ip); - device.port = pending.port > 0 ? pending.port : 22; // Default to SSH port - device.macAddress = pending.macAddress; + NetworkDevice device(deviceName, QString::fromUtf8(ip), pending.macAddress, + pending.hostname, + pending.port > 0 ? pending.port : 22); qDebug() << "Resolved IP for Apple device:" << device.name << "at" << device.address << ":" << device.port; @@ -302,11 +295,7 @@ void DNSSD_API DnssdService::addrInfoCallback( // Add to our list if not already present { QMutexLocker locker(&service->m_devicesMutex); - bool exists = std::any_of(service->m_networkDevices.begin(), - service->m_networkDevices.end(), - [&device](const NetworkDevice &existing) { - return existing == device; - }); + bool exists = m_networkDevices.contains(pending.macAddress); if (!exists) { service->m_networkDevices.append(device); emit service->deviceAdded(device); diff --git a/src/core/services/dnssd/dnssd_service.h b/src/core/services/dnssd/dnssd_service.h index fc1b1e9..7027b5d 100644 --- a/src/core/services/dnssd/dnssd_service.h +++ b/src/core/services/dnssd/dnssd_service.h @@ -46,7 +46,8 @@ public: void startBrowsing(); void stopBrowsing(); - QList getNetworkDevices() const; + QMap getNetworkDevices() const; + NetworkDevice getNetworkDeviceByMac(const QString &macAddress) const; signals: void deviceAdded(const NetworkDevice &device); diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index 001090b..f7db36c 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -97,34 +97,46 @@ void DeviceMenuWidget::init() stackedWidget->removeWidget(loadingWidget); loadingWidget->deleteLater(); - // FIXME: toast really necessary here? - // if (m_device->deviceInfo.parsedDeviceVersion.major < 13) { - // Toast *toast = new Toast(this); - // toast->setAttribute(Qt::WA_DeleteOnClose); - // toast->setDuration(8000); // Hide after 8 seconds - // toast->setTitle("Not wireless compatible"); - // toast->setText("This device is not wireless compatible."); - // toast->setPosition(ToastPosition::BOTTOM_MIDDLE); - // toast->show(); - // } else { - // if (m_device->deviceInfo.isWireless) - // return; - // bool enabled = ServiceManager::enableWirelessConnections(m_device); - // Toast *toast = new Toast(this); - // toast->setAttribute(Qt::WA_DeleteOnClose); - // toast->setDuration(8000); // Hide after 8 seconds - // toast->setPosition(ToastPosition::BOTTOM_MIDDLE); - // if (enabled) { - // toast->setTitle("Wireless connections enabled"); - // toast->setText( - // "You can now use wireless connections with this device."); - // } else { - // toast->setTitle("Failed to enable wireless connections"); - // toast->setText( - // "Could not enable wireless connections for this device."); - // } - // toast->show(); - // } + // ═══════════════════════════════════════════════════════════════════════ + // Enable wireless connections for iOS 14+ devices + // (maybe supported on 13 too, untested) + // ═══════════════════════════════════════════════════════════════════════ + if (m_device->deviceInfo.parsedDeviceVersion.major >= 14) { + auto sm = SettingsManager::sharedInstance(); + + // Don't enable if + // auto-enable wifi connections is disabled + // we've already seen this device + // device is already wireless + if (!sm->autoEnableWifiConnections() || + sm->hasSeenDevice(m_device->udid) || + m_device->deviceInfo.isWireless) + return; + + connect( + m_device->service_manager, + &CXX::ServiceManager::enable_wifi_connections_result, this, + [this, sm](bool success) { + if (success) { + QMessageBox::information( + this, "Wireless connections enabled", + "You can now connect to this device wirelessly."); + } else { + QMessageBox::warning( + this, "Failed to enable wireless connections", + "Could not enable wireless connections for this " + "device."); + } + // FIXME: this could be a problem if + // depend on this value elsewhere, but it should be fine for + // now since it's only used for showing the warning about + // auto-enabling wifi connections + sm->setHasSeenDevice(m_device->udid, true); + }, + Qt::SingleShotConnection); + + m_device->service_manager->enable_wifi_connections(); + } } void DeviceMenuWidget::switchToTab(const QString &tabName) diff --git a/src/iDescriptor.h b/src/iDescriptor.h index bc55144..f7b331e 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -294,19 +294,34 @@ void fetchAppIconFromApple( std::function callback); struct NetworkDevice { - QString name; // service name - QString hostname; // e.g., iPhone-2.local - QString address; // IPv4 or IPv6 address - uint16_t port = 22; // SSH port - std::map txt; // TXT records - QString macAddress; // MAC address if available + QString name; // service name + QString hostname; // e.g., iPhone-2.local + QString address; // IPv4 or IPv6 address + uint16_t port = 22; // SSH port + QString macAddress; // MAC address if available + + NetworkDevice() = default; + NetworkDevice(const QString &name, const QString &address, + const QString &macAddress, const QString &hostname, + uint16_t port) + : name(name), address(address), port(port), macAddress(macAddress), + hostname(hostname) + { + } + + bool isValid() const + { + return !name.isEmpty() && !address.isEmpty() && !macAddress.isEmpty() && + port > 0; + } + bool operator==(const NetworkDevice &other) const { return name == other.name && address == other.address; } }; -QPixmap load_heic(const QByteArray &data); +QImage load_heic(const QByteArray &data); // Helper struct for semantic version comparison struct AppVersion { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 83ca9a8..375e208 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -191,7 +191,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) statusbar->setStyleSheet( "QWidget#StatusBar { background-color: transparent; }"); statusLayout->addWidget(m_connectedDeviceCountLabel); - // TODO: implement downloads/uploads progress stuff StatusBalloon *statusBalloon = StatusBalloon::sharedInstance(); @@ -372,19 +371,29 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) // ═══════════════════════════════════════════════════════════════════════ // Upgrade to wireless when a "WIRED" device is removed // ═══════════════════════════════════════════════════════════════════════ - connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, - [](const QString &udid, const std::string &wifiMacAddress, - const std::string &ipAddress, bool wasWireless) { - if (wasWireless) - return; - qDebug() << "Upgrading device to wireless connection for UDID" - << udid; - // FIXME: ignore iOS 15 and lower - NetworkDevice dev; - dev.macAddress = QString::fromStdString(wifiMacAddress); - dev.address = QString::fromStdString(ipAddress); - AppContext::sharedInstance()->tryToConnectToNetworkDevice(dev); - }); + connect( + AppContext::sharedInstance(), &AppContext::deviceRemoved, this, + [](const QString &udid, const std::string &wifiMacAddress, + bool wasWireless) { + if (wasWireless) + return; + QString wifiMac = QString::fromStdString(wifiMacAddress); + NetworkDevice dev = + NetworkDeviceProvider::sharedInstance()->getNetworkDeviceByMac( + wifiMac); + if (!dev.isValid()) { + qDebug() << "No valid network device found for UDID" << udid + << "with Wi-Fi MAC" << wifiMac + << "Not trying to upgrade to wireless connection."; + return; + } + + qDebug() + << "Trying to upgrade device to wireless connection for UDID" + << udid; + // FIXME: maybe ignore iOS 15 and lower? + AppContext::sharedInstance()->tryToConnectToNetworkDevice(dev); + }); // ═══════════════════════════════════════════════════════════════════════ // Add a wireless device @@ -490,7 +499,7 @@ MainWindow::~MainWindow() void MainWindow::handleShowSleepyDeviceWarning() { - + static bool widgetAlreadyVisible = false; /* one minute cooldown to prevent spamming */ static const int kMinIntervalMs = 60 * 1000; @@ -504,9 +513,12 @@ void MainWindow::handleShowSleepyDeviceWarning() lastShown = now; - if (SettingsManager::sharedInstance()->isSleepyDeviceWarningDismissed()) { + if (SettingsManager::sharedInstance()->isSleepyDeviceWarningDismissed() || + widgetAlreadyVisible) { return; } + widgetAlreadyVisible = true; DeviceSleepWarningWidget(this).exec(); + widgetAlreadyVisible = false; } \ No newline at end of file diff --git a/src/networkdeviceprovider.h b/src/networkdeviceprovider.h index 1cfd682..c21e171 100644 --- a/src/networkdeviceprovider.h +++ b/src/networkdeviceprovider.h @@ -40,11 +40,16 @@ public: [this]() { m_networkProvider->startBrowsing(); }); } - QList getNetworkDevices() + QMap getNetworkDevices() { return m_networkProvider->getNetworkDevices(); } + NetworkDevice getNetworkDeviceByMac(const QString &macAddress) + { + return m_networkProvider->getNetworkDeviceByMac(macAddress); + } + private: #ifdef __linux__ AvahiService *m_networkProvider = nullptr; diff --git a/src/networkdevicestoconnectwidget.cpp b/src/networkdevicestoconnectwidget.cpp index 9ba5423..634cac2 100644 --- a/src/networkdevicestoconnectwidget.cpp +++ b/src/networkdevicestoconnectwidget.cpp @@ -268,7 +268,7 @@ void NetworkDevicesToConnectWidget::updateDeviceList() { clearDeviceCards(); - QList devices = + QMap devices = NetworkDeviceProvider::sharedInstance()->getNetworkDevices(); if (devices.isEmpty()) { diff --git a/src/networkdeviceswidget.cpp b/src/networkdeviceswidget.cpp index 30bc6b7..94f865d 100644 --- a/src/networkdeviceswidget.cpp +++ b/src/networkdeviceswidget.cpp @@ -182,7 +182,7 @@ void NetworkDevicesWidget::updateDeviceList() { clearDeviceCards(); - QList devices = + QMap devices = NetworkDeviceProvider::sharedInstance()->getNetworkDevices(); if (devices.isEmpty()) { diff --git a/src/sshterminaltool.cpp b/src/sshterminaltool.cpp index 7718065..84e1e50 100644 --- a/src/sshterminaltool.cpp +++ b/src/sshterminaltool.cpp @@ -148,7 +148,7 @@ void SSHTerminalTool::updateDeviceList() } // Add wireless devices - QList wirelessDevices = + QMap wirelessDevices = NetworkDeviceProvider::sharedInstance()->getNetworkDevices(); for (const NetworkDevice &device : wirelessDevices) { addWirelessDevice(device);