implement upgrading wired connection to wireless after device is unplugged

This commit is contained in:
uncor3
2026-04-09 18:48:59 +00:00
parent 35d99a1c05
commit e631fa9ba3
13 changed files with 135 additions and 111 deletions
+3 -1
View File
@@ -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) {
+1 -1
View File
@@ -74,7 +74,7 @@ private:
signals:
void deviceAdded(std::shared_ptr<iDescriptorDevice> device);
void deviceRemoved(const QString &udid, const std::string &macAddress,
const std::string &ipAddress, bool wasWireless);
bool wasWireless);
void devicePaired(std::shared_ptr<iDescriptorDevice> device);
void devicePasswordProtected(const QString &udid);
void deviceAlreadyExists(const iDescriptor::Uniq &uniq);
+18 -31
View File
@@ -60,12 +60,19 @@ void AvahiService::stopBrowsing()
m_networkDevices.clear();
}
QList<NetworkDevice> AvahiService::getNetworkDevices() const
QMap<QString, NetworkDevice> 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<AvahiService *>(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);
}
}
+3 -2
View File
@@ -44,7 +44,8 @@ public:
void startBrowsing();
void stopBrowsing();
QList<NetworkDevice> getNetworkDevices() const;
QMap<QString, NetworkDevice> 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<NetworkDevice> m_networkDevices;
QMap<QString, NetworkDevice> m_networkDevices;
bool m_running;
};
+9 -20
View File
@@ -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<const struct sockaddr_in *>(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);
+2 -1
View File
@@ -46,7 +46,8 @@ public:
void startBrowsing();
void stopBrowsing();
QList<NetworkDevice> getNetworkDevices() const;
QMap<QString, NetworkDevice> getNetworkDevices() const;
NetworkDevice getNetworkDeviceByMac(const QString &macAddress) const;
signals:
void deviceAdded(const NetworkDevice &device);
+40 -28
View File
@@ -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)
+22 -7
View File
@@ -294,19 +294,34 @@ void fetchAppIconFromApple(
std::function<void(const QPixmap &, const QJsonObject &)> 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<std::string, std::string> 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 {
+28 -16
View File
@@ -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;
}
+6 -1
View File
@@ -40,11 +40,16 @@ public:
[this]() { m_networkProvider->startBrowsing(); });
}
QList<NetworkDevice> getNetworkDevices()
QMap<QString, NetworkDevice> getNetworkDevices()
{
return m_networkProvider->getNetworkDevices();
}
NetworkDevice getNetworkDeviceByMac(const QString &macAddress)
{
return m_networkProvider->getNetworkDeviceByMac(macAddress);
}
private:
#ifdef __linux__
AvahiService *m_networkProvider = nullptr;
+1 -1
View File
@@ -268,7 +268,7 @@ void NetworkDevicesToConnectWidget::updateDeviceList()
{
clearDeviceCards();
QList<NetworkDevice> devices =
QMap<QString, NetworkDevice> devices =
NetworkDeviceProvider::sharedInstance()->getNetworkDevices();
if (devices.isEmpty()) {
+1 -1
View File
@@ -182,7 +182,7 @@ void NetworkDevicesWidget::updateDeviceList()
{
clearDeviceCards();
QList<NetworkDevice> devices =
QMap<QString, NetworkDevice> devices =
NetworkDeviceProvider::sharedInstance()->getNetworkDevices();
if (devices.isEmpty()) {
+1 -1
View File
@@ -148,7 +148,7 @@ void SSHTerminalTool::updateDeviceList()
}
// Add wireless devices
QList<NetworkDevice> wirelessDevices =
QMap<QString, NetworkDevice> wirelessDevices =
NetworkDeviceProvider::sharedInstance()->getNetworkDevices();
for (const NetworkDevice &device : wirelessDevices) {
addWirelessDevice(device);