''' .. note:: This facade depends on `nmcli` (Network Manager command line tool). It's found in most of the popular GNU/Linux distributions. Support for other backends is not provided yet. ''' from subprocess import Popen, PIPE, call from plyer.facades import Wifi from plyer.utils import whereis_exe, deprecated try: import wifi except ModuleNotFoundError as err: raise ModuleNotFoundError( "python-wifi not installed. try:" + "`pip install --user wifi`.") from err class NMCLIWifi(Wifi): ''' .. versionadded:: 1.4.0 ''' def __init__(self, *args, **kwargs): ''' .. versionadded:: 1.4.0 ''' super().__init__(*args, **kwargs) self.names = {} @property def interfaces(self): ''' Get all the available interfaces for WiFi. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): self._enable() # fetch the devices proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() # filter devices by type interfaces = [] for line in lines: # bad escape from nmcli's side :< line = line.replace('\\:', '$$') device, dtype = line.split(':') if dtype != 'wifi': continue interfaces.append(device.replace('$$', ':')) # return wifi interfaces return interfaces def _is_enabled(self): ''' Return the status of WiFi device. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' output = Popen( ["nmcli", "radio", "wifi"], stdout=PIPE ).communicate()[0].decode('utf-8') if output.split()[0] == 'enabled': return True return False def _is_connected(self, interface=None): ''' Return whether a specified interface is connected to a WiFi network. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): self._enable() if not interface: interface = self.interfaces[0] # fetch all devices proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE,STATE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() # filter by wifi type and interface connected = False for line in lines: line = line.replace('\\:', '$$') device, dtype, state = line.split(':') device = device.replace('$$', ':') if dtype != 'wifi': continue if device != interface: continue if state == 'connected': connected = True return connected def _start_scanning(self, interface=None): ''' Start scanning for available Wi-Fi networks for the specified interface. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): self._enable() if not interface: interface = self.interfaces[0] # force rescan for fresh data call(['nmcli', 'device', 'wifi', 'rescan', 'ifname', interface]) # get properties fields = [ 'SSID', 'BSSID', 'MODE', 'CHAN', 'FREQ', 'BARS', 'RATE', 'SIGNAL', 'SECURITY' ] # fetch all networks for interface output = Popen([ 'nmcli', '--terse', '--fields', ','.join(fields), 'device', 'wifi', 'list', 'ifname', interface ], stdout=PIPE).communicate()[0].decode('utf-8') # parse output for line in output.splitlines(): line = line.replace('\\:', '$$') row = { field: value for field, value in zip(fields, line.split(':')) } row['BSSID'] = row['BSSID'].replace('$$', ':') self.names[row['SSID']] = row def _get_network_info(self, name): ''' Get all the network information by network's name (SSID). .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self.names: self._start_scanning() ret_list = {} ret_list['ssid'] = self.names[name]['SSID'] ret_list['signal'] = self.names[name]['SIGNAL'] bars = len(self.names[name]['BARS']) ret_list['quality'] = '{}/100'.format(bars / 5.0 * 100) ret_list['frequency'] = self.names[name]['FREQ'] ret_list['bitrates'] = self.names[name]['RATE'] # wpa1, wpa2, wpa1 wpa2, wep, (none), perhaps something else security = self.names[name]['SECURITY'].lower() ret_list['encrypted'] = True if 'wpa2' in security: # wpa2, wpa2+wpa1 ret_list['encryption_type'] = 'wpa2' elif 'wpa' in security: ret_list['encryption_type'] = 'wpa' elif 'wep' in security: ret_list['encryption_type'] = 'wep' elif 'none' in security: ret_list['encrypted'] = False ret_list['encryption_type'] = 'none' else: ret_list['encryption_type'] = security ret_list['channel'] = int(self.names[name]['CHAN']) ret_list['address'] = self.names[name]['BSSID'] ret_list['mode'] = self.names[name]['MODE'] return ret_list def _get_available_wifi(self): ''' Return the names of all found networks. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self.names: self._start_scanning() return list(self.names.keys()) def _connect(self, network, parameters, interface=None): ''' Connect a specific interface to a WiFi network. Expects 2 parameters: - SSID of the network - parameters: dict - password: string or None .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' self._enable() if not interface: interface = self.interfaces[0] password = parameters.get('password') command = [ 'nmcli', 'device', 'wifi', 'connect', network, 'ifname', interface ] if password: command += ['password', password] call(command) def _disconnect(self, interface=None): ''' Disconnect a specific interface from a WiFi network. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' if not self._is_enabled(): return if not interface: interface = self.interfaces[0] if self._nmcli_version() >= (1, 2, 6): call(['nmcli', 'device', 'disconnect', interface]) else: call(['nmcli', 'nm', 'enable', 'false']) def _enable(self): ''' Turn WiFi device on. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' call(['nmcli', 'radio', 'wifi', 'on']) def _disable(self): ''' Turn WiFi device off. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' call(['nmcli', 'radio', 'wifi', 'off']) def _nmcli_version(self): ''' Get nmcli version to prevent executing deprecated commands. .. versionadded:: 1.4.0 Tested with nmcli 1.2.6. ''' version = Popen(['nmcli', '-v'], stdout=PIPE) version = version.communicate()[0].decode('utf-8') while version and not version[0].isdigit(): version = version[1:] return tuple(map(int, (version.split('.')))) @deprecated class LinuxWifi(Wifi): ''' .. versionadded:: 1.2.5 ''' def __init__(self, *args, **kwargs): ''' .. versionadded:: 1.4.0 ''' super().__init__(*args, **kwargs) self.names = {} @property def interfaces(self): ''' .. versionadded:: 1.4.0 ''' proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() interfaces = [] for line in lines: device, dtype = line.split(':') if dtype != 'wifi': continue interfaces.append(device) return interfaces def _is_enabled(self): ''' Returns `True` if wifi is enabled else `False`. .. versionadded:: 1.2.5 .. versionchanged:: 1.3.2 nmcli output is properly decoded to unicode ''' enbl = Popen(["nmcli", "radio", "wifi"], stdout=PIPE, stderr=PIPE) if enbl.communicate()[0].split()[0].decode('utf-8') == "enabled": return True return False def _is_connected(self, interface=None): ''' .. versionadded:: 1.4.0 ''' if not interface: interface = self.interfaces[0] proc = Popen([ 'nmcli', '--terse', '--fields', 'DEVICE,TYPE,STATE', 'device' ], stdout=PIPE) lines = proc.communicate()[0].decode('utf-8').splitlines() connected = False for line in lines: device, dtype, state = line.split(':') if dtype != 'wifi': continue if device != interface: continue if state == 'connected': connected = True return connected def _start_scanning(self, interface=None): ''' Returns all the network information. .. versionadded:: 1.2.5 .. versionchanged:: 1.3.0 scan only if wifi is enabled ''' if not interface: interface = self.interfaces[0] if self._is_enabled(): list_ = list(wifi.Cell.all(interface)) for i in range(len(list_)): self.names[list_[i].ssid] = list_[i] else: raise Exception('Wifi not enabled.') def _get_network_info(self, name): ''' Starts scanning for available Wi-Fi networks and returns the available, devices. .. versionadded:: 1.2.5 ''' ret_list = {} ret_list['ssid'] = self.names[name].ssid ret_list['signal'] = self.names[name].signal ret_list['quality'] = self.names[name].quality ret_list['frequency'] = self.names[name].frequency ret_list['bitrates'] = self.names[name].bitrates ret_list['encrypted'] = self.names[name].encrypted ret_list['channel'] = self.names[name].channel ret_list['address'] = self.names[name].address ret_list['mode'] = self.names[name].mode if not ret_list['encrypted']: return ret_list else: ret_list['encryption_type'] = self.names[name].encryption_type return ret_list def _get_available_wifi(self): ''' Returns the name of available networks. .. versionadded:: 1.2.5 .. versionchanged:: 1.4.0 return a proper list of elements instead of dict_keys ''' return list(self.names.keys()) def _connect(self, network, parameters, interface=None): ''' Expects 2 parameters: - name/ssid of the network. - parameters: dict type - password: string or None .. versionadded:: 1.2.5 ''' if not interface: interface = self.interfaces[0] result = None try: self._enable() finally: password = parameters['password'] cell = self.names[network] result = wifi.Scheme.for_cell( interface, network, cell, password ) return result def _disconnect(self, interface=None): ''' Disconnect all the networks managed by Network manager. .. versionadded:: 1.2.5 ''' if not interface: interface = self.interfaces[0] if self._nmcli_version() >= (1, 2, 6): call(['nmcli', 'dev', 'disconnect', interface]) else: call(['nmcli', 'nm', 'enable', 'false']) def _enable(self): ''' Wifi interface power state is set to "ON". .. versionadded:: 1.3.2 ''' return call(['nmcli', 'radio', 'wifi', 'on']) def _disable(self): ''' Wifi interface power state is set to "OFF". .. versionadded:: 1.3.2 ''' return call(['nmcli', 'radio', 'wifi', 'off']) def _nmcli_version(self): ''' .. versionadded:: 1.3.2 ''' version = Popen(['nmcli', '-v'], stdout=PIPE) version = version.communicate()[0].decode('utf-8') while version and not version[0].isdigit(): version = version[1:] return tuple(map(int, (version.split('.')))) def instance(): if whereis_exe('nmcli'): return NMCLIWifi() return LinuxWifi()