diff --git a/plinth/modules/networks/templates/wifi_scan.html b/plinth/modules/networks/templates/wifi_scan.html index c7011e363..4fd667f2d 100644 --- a/plinth/modules/networks/templates/wifi_scan.html +++ b/plinth/modules/networks/templates/wifi_scan.html @@ -10,28 +10,52 @@

{{ title }}

-
-
-
- {% for access_point in access_points %} -
- - {% if access_point.ssid %} - - {{ access_point.ssid }} - - {% else %} - -- - {% endif %} - + {% if not device_access_points %} +

{% trans "No Wi-Fi device detected." %}

+ {% else %} +
+
+ {% for device in device_access_points %} + {% if device_access_points|length > 1 %} +

+ {% blocktrans trimmed with interface_name=device.interface_name %} + Device: {{ interface_name }} + {% endblocktrans %} +

+ {% endif %} +

+ {% trans "Last scanned: " %} + {% if device.last_scan_time %} + {{ device.last_scan_time|timesince }} + {% else %} + {% trans "never" %} + {% endif %} + {% if device.scan_requested %} + + {% endif %} +

+
+ {% for access_point in device.access_points %} +
+ + + {{ access_point.ssid_string }} + + - - {{ access_point.strength }}% - + + {{ access_point.strength }}% + +
+ {% empty %} +

+ {% trans "No Wi-Fi networks found." %} +

+ {% endfor %}
{% endfor %}
-
+ {% endif %} {% endblock %} diff --git a/plinth/modules/networks/views.py b/plinth/modules/networks/views.py index 7ebc68700..9d92442d5 100644 --- a/plinth/modules/networks/views.py +++ b/plinth/modules/networks/views.py @@ -385,11 +385,17 @@ def deactivate(request, uuid): def scan(request): """Show a list of nearby visible Wi-Fi access points.""" - access_points = network.wifi_scan() - return TemplateResponse(request, 'wifi_scan.html', { - 'title': _('Nearby Wi-Fi Networks'), - 'access_points': access_points - }) + device_access_points = network.wifi_scan() + scanning = any( + (device['scan_requested'] for device in device_access_points)) + # Refresh page in 10s if scanning, 60s otherwise + refresh_page_sec = 10 if scanning else 60 + return TemplateResponse( + request, 'wifi_scan.html', { + 'title': _('Nearby Wi-Fi Networks'), + 'device_access_points': device_access_points, + 'refresh_page_sec': refresh_page_sec + }) def add(request): diff --git a/plinth/network.py b/plinth/network.py index bbc5f5832..dfe99e466 100644 --- a/plinth/network.py +++ b/plinth/network.py @@ -4,6 +4,7 @@ Helper functions for working with network manager. """ import collections +import datetime import logging import socket import struct @@ -613,27 +614,79 @@ def delete_connection(connection_uuid): return name +def _get_access_point_as_dict(access_point, active_ap_path): + """Return Wi-Fi access point information as a dictionary.""" + ssid = access_point.get_ssid() + if not ssid: # Hidden network + return None + + try: + ssid_string = ssid.get_data().decode(encoding='utf-8') + except UnicodeError: + # XXX: Can't deal with binary SSIDs. Don't show SSIDs that are + # binary only. + return None + + is_active = (active_ap_path == access_point.get_path()) + return { + 'ssid': ssid, + 'ssid_string': ssid_string, + 'strength': access_point.get_strength(), + 'is_active': is_active + } + + def wifi_scan(): """Scan for available access points across all Wi-Fi devices.""" - access_points = [] + device_access_points = [] for device in get_nm_client().get_devices(): if device.get_device_type() != nm.DeviceType.WIFI: continue - for access_point in device.get_access_points(): - # Retrieve the bytes in SSID. Don't convert to utf-8 or - # escape it in any way as it may contain null bytes. When - # this is used in the URL it will be escaped properly and - # unescaped when taken as view function's argument. - ssid = access_point.get_ssid() - ssid_string = ssid.get_data().decode() if ssid else '' - access_points.append({ - 'interface_name': device.get_iface(), - 'ssid': ssid_string, - 'strength': access_point.get_strength() - }) + if device.get_client() is None: + # The device got deleted (see NM:show-wifi-networks.py) + return False - return access_points + # Active access point + active_ap_path = device.get_active_access_point() + + # Last scan time + last_scan = device.get_last_scan() + last_scan_time = None + scan_requested = False + if last_scan == -1: + last_scan = None + else: + boot_time = time.clock_gettime(time.CLOCK_BOOTTIME) + last_scan = boot_time - (last_scan / 1000) + last_scan_time = datetime.datetime.now() - datetime.timedelta( + seconds=last_scan) + + # Request a scan if the last scan was more than 20 seconds ago + if (not last_scan) or last_scan > 20: + device.request_scan() + scan_requested = True + + # Access points + access_points = [] + for access_point in device.get_access_points(): + ap_dict = _get_access_point_as_dict(access_point, active_ap_path) + if ap_dict: + access_points.append(ap_dict) + + access_points = sorted(access_points, + key=lambda point: -point['strength']) + + device_access_points.append({ + 'interface_name': device.get_iface(), + 'access_points': access_points, + 'last_scan': last_scan, + 'last_scan_time': last_scan_time, + 'scan_requested': scan_requested + }) + + return sorted(device_access_points, + key=lambda device: device['interface_name']) def refeed_dns():