/* * netsimplewifipage.cc - Implementation of the UI.Network.Wireless page * horizon-qt5, the Qt 5 user interface for * Project Horizon * * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. * This code is licensed under the AGPL 3.0 license, as noted in the * LICENSE-code file in the root directory of this repository. * * SPDX-License-Identifier: AGPL-3.0-only */ #include "netsimplewifipage.hh" #include "netdhcppage.hh" #include #include #include #ifdef HAS_INSTALL_ENV int scanResults(wpactrl_t *control, char const *s, size_t len, void *page, tain_t *) { NetworkSimpleWirelessPage *our_page = reinterpret_cast(page); return our_page->processScan(control, s, len); } #endif /* HAS_INSTALL_ENV */ NetworkSimpleWirelessPage::NetworkSimpleWirelessPage(QWidget *parent) : HorizonWizardPage(parent), control(WPACTRL_ZERO) { QVBoxLayout *layout; loadWatermark("network"); setTitle(tr("Select Your Network")); statusLabel = new QLabel(tr("Scanning for networks...")); rescanButton = new QPushButton(tr("&Rescan Networks")); connect(rescanButton, &QPushButton::clicked, [=](void) { doScan(); }); ssidListView = new QListWidget; #ifdef HAS_INSTALL_ENV exchange_item.filter = "CTRL-EVENT-SCAN-RESULTS"; exchange_item.cb = &scanResults; notify = nullptr; #endif /* HAS_INSTALL_ENV */ passphrase = new QLineEdit(this); passphrase->setEchoMode(QLineEdit::Password); passphrase->setPlaceholderText(tr("Passphrase")); passphrase->hide(); layout = new QVBoxLayout; layout->addWidget(statusLabel, 0, Qt::AlignCenter); layout->addSpacing(10); layout->addWidget(ssidListView, 0, Qt::AlignCenter); layout->addWidget(rescanButton); layout->addSpacing(10); layout->addWidget(passphrase); setLayout(layout); } NetworkSimpleWirelessPage::~NetworkSimpleWirelessPage() { #ifdef HAS_INSTALL_ENV wpactrl_end(&control); #endif /* HAS_INSTALL_ENV */ } void NetworkSimpleWirelessPage::scanDone(QString message) { rescanButton->setEnabled(true); statusLabel->setText(message); } void NetworkSimpleWirelessPage::doScan() { #ifdef HAS_INSTALL_ENV ssidListView->clear(); rescanButton->setEnabled(false); statusLabel->setText(tr("Scanning for networks...")); tain_t deadline; wparesponse_t response; std::string suppsock = "/var/run/wpa_supplicant/" + horizonWizard()->chosen_auto_iface; tain_now_g(); if(!wpactrl_start_g(&control, suppsock.c_str(), 2000)) { rescanButton->setEnabled(false); statusLabel->setText(tr("Couldn't communicate with wireless subsystem (Code %1)").arg(errno)); return; } response = wpactrl_command_g(&control, "SCAN"); if(response != WPA_OK && response != WPA_FAILBUSY) { scanDone(tr("Couldn't scan for wireless networks (Code %1)").arg(response)); return; } tain_from_millisecs(&deadline, 15000); tain_add_g(&deadline, &deadline); wpactrl_xchg_init(&exchange, &exchange_item, 1, &deadline, this); if(!wpactrl_xchg_start(&control, &exchange)) { scanDone(tr("Failed to scan for wireless networks.")); return; } if(notify != nullptr) { notify->setEnabled(false); notify->deleteLater(); notify = nullptr; } notify = new QSocketNotifier(wpactrl_fd(&control), QSocketNotifier::Read, this); connect(notify, &QSocketNotifier::activated, [=](int) { QString status; tain_now_g(); if(wpactrl_xchg_timeout_g(&control, &exchange)) { status = tr("Network scan timed out."); } else { if(wpactrl_update(&control) < 0) { status = tr("Issue communicating with wireless subsystem."); } else { int code = wpactrl_xchg_event_g(&control, &exchange); if(code == -2) { /* if the callback is what failed, we already set status */ status = statusLabel->text(); } else if(code < 0) { /* non-callback failure */ status = tr("Issue processing scanned networks (Code %1)") .arg(code); } else if(code == 0) { /* Not finished yet, so don't do anything. */ return; } else { status = tr("Scan successful."); } } } notify->setEnabled(false); notify->deleteLater(); notify = nullptr; statusLabel->setText(status); rescanButton->setEnabled(true); return; }); notify->setEnabled(true); #endif /* HAS_INSTALL_ENV */ } void NetworkSimpleWirelessPage::initializePage() { doScan(); } bool NetworkSimpleWirelessPage::isComplete() const { return (ssidListView->currentRow() != -1); } int NetworkSimpleWirelessPage::nextId() const { return HorizonWizard::Page_Network_DHCP; } #ifdef HAS_INSTALL_ENV int NetworkSimpleWirelessPage::processScan(wpactrl_t *c, const char *, size_t) { assert(c == &control); size_t bufsize = 32768; char *buf = static_cast(malloc(bufsize)); ssize_t res_size; errno = 0; res_size = wpactrl_query_g(&control, "SCAN_RESULTS", buf, bufsize); if(res_size == -1) { if(errno == EMSGSIZE) { statusLabel->setText(tr("Scan failed: Out of memory")); free(buf); return 0; } else { statusLabel->setText(tr("Scan failed (Code %1)").arg(errno)); free(buf); return 0; } } std::string raw_nets(buf, static_cast(res_size)); std::istringstream net_streams(raw_nets); /* discard the first line - it's a header */ net_streams.getline(buf, static_cast(bufsize)); /* process networks */ while(net_streams.getline(buf, static_cast(bufsize))) { std::string net_line(buf); std::string::size_type cur = 0, next = net_line.find_first_of('\t'); assert(next != std::string::npos); std::string bssid(net_line.substr(cur, next)); cur = next + 1; next = net_line.find_first_of('\t', cur); assert(next != std::string::npos); std::string freq(net_line.substr(cur, next)); cur = next + 1; next = net_line.find_first_of('\t', cur); assert(next != std::string::npos); std::string signal(net_line.substr(cur, next)); cur = next + 1; next = net_line.find_first_of('\t', cur); assert(next != std::string::npos); std::string flags(net_line.substr(cur, next)); cur = next + 1; next = net_line.find_first_of('\t', cur); assert(next == std::string::npos); std::string ssid(net_line.substr(cur, next)); QIcon icon; int strength = std::stoi(signal); if(strength < -90) { icon = QIcon::fromTheme("network-wireless-signal-none"); } else if(strength < -80) { icon = QIcon::fromTheme("network-wireless-signal-weak"); } else if(strength < -67) { icon = QIcon::fromTheme("network-wireless-signal-ok"); } else if(strength < -50) { icon = QIcon::fromTheme("network-wireless-signal-good"); } else { icon = QIcon::fromTheme("network-wireless-signal-excellent"); } QListWidgetItem *network = new QListWidgetItem(ssidListView); network->setText(QString::fromStdString(ssid)); network->setIcon(icon); network->setToolTip(tr("Frequency: %1 MHz\nBSSID: %2\nRSSI: %3") .arg(freq.c_str()).arg(bssid.c_str()) .arg(signal.c_str())); } return 1; } #endif /* HAS_INSTALL_ENV */ bool NetworkSimpleWirelessPage::validatePage() { /* What a hack! * * Independent Pages means the DHCP page is never cleaned, even when Back * is chosen. So, we have to do it from here. */ horizonWizard()->removePage(HorizonWizard::Page_Network_DHCP); horizonWizard()->setPage(HorizonWizard::Page_Network_DHCP, new NetDHCPPage); return true; }