def toggle(program, state): ''' Toggles the running state of the given progam. If state is true, the program will be spawned. :param str program: Name of the program :param bool state: Desired state :returns: None ''' if state: try: tps.check_output(['pgrep', program], logger) except subprocess.CalledProcessError: if tps.has_program(program): logger.debug(program) subprocess.Popen([program]) else: logger.warning('{} is not installed'.format(program)) else: try: tps.check_output(['pgrep', program], logger) tps.check_call(['killall', program], logger) except subprocess.CalledProcessError: pass
def can_use_chvt(): """ Checks whether ``chvt`` can be called with ``sudo`` without a password. The ``sudo`` command has the ``-n`` option which will just make the command fail when the user does not have the appropriate permissions. The problem with ``chvt`` is that it does not have any intelligent command line argument parsing. If will return code 1 if no argument is given, the same code that ``sudo`` gives when no permission is available. Therefore I chose to use ``sudo -l` to get the whole list and see whether the full path to ``chvt`` is in there. This might break on Fedora where the ``usr``-merge has been done now. The following line is needed in a file like ``/etc/sudoers.d/chvt``:: myuser ALL = NOPASSWD: /bin/chvt You have to replace ``myuser`` which your username. Giving too broad permissions to every other user account is probably not a good idea. :rtype: bool """ command = ["sudo", "-l"] output = tps.check_output(command, logger) return b"/bin/chvt" in output
def set_subpixel_order(direction): ''' Sets the text subpixel anti-alias order. :param tps.Direction direction: New direction :returns: None ''' if tps.has_program('xfconf-query'): try: tps.check_call([ 'xfconf-query', '-c', 'xsettings', '-p', '/Xft/RGBA', '-s', direction.subpixel ], logger) except subprocess.CalledProcessError as e: logger.error(e) elif tps.has_program('gsettings'): try: schemas = tps.check_output(['gsettings', 'list-schemas'], logger).decode().split('\n') schema = 'org.gnome.settings-daemon.plugins.xsettings' if schema in schemas: tps.check_call([ 'gsettings', 'set', schema, 'rgba-order', direction.subpixel ], logger) else: logger.warning('gsettings is installed, but the "{}" schema ' 'is not available'.format(schema)) except subprocess.CalledProcessError as e: logger.error(e) else: logger.warning('neither xfconf-query nor gsettings is installed')
def get_resolution_and_shift(output): ''' Retrieves the total resolution of the virtual screen and the position of the given output within that. The X server seems to generate a huge screen which is then displayed by the physical displays. ``xrandr`` gives the size of that (virtual) screen as well as the positions of each display in that. For example, I currently have the 12.5" 1366×768 ThinkPad X220 display on the right of a 23" 1920×1080 pixel display. ``xrandr`` tells me the following:: Screen 0: … current 3286 x 1080 … LVDS1 … 1366x768+1920+0 DP2 … 1920x1080+0+0 This only shows the interesting parts. The size of the (virtual) screen is 3286×1080 and the position of the internal screen is 1366×768+1920+0. This allows to compute the transformation matrix for this. ''' xrandr_output = tps.check_output(['xrandr', '-q'], logger).strip().decode() lines = xrandr_output.split('\n') pattern_output = re.compile( r''' {} \D+ (?P<width>\d+) x (?P<height>\d+) \+ (?P<x>\d+) \+ (?P<y>\d+) '''.format(output), re.VERBOSE) pattern_screen = re.compile('current (?P<width>\d+) x (?P<height>\d+)') result = {} for line in lines: m_output = pattern_output.search(line) if m_output: result['output_width'] = int(m_output.group('width')) result['output_height'] = int(m_output.group('height')) result['output_x'] = int(m_output.group('x')) result['output_y'] = int(m_output.group('y')) m_screen = pattern_screen.search(line) if m_screen: result['screen_width'] = int(m_screen.group('width')) result['screen_height'] = int(m_screen.group('height')) if len(result) != 6: raise ScreenNotFoundException( 'The screen and output dimensions could not be gathered from ' 'xrandr. Maybe the "{}" output is not attached or enabled? Please ' 'report a bug otherwise.'.format(output)) return result
def can_use_chvt(): ''' Checks whether ``chvt`` can be called with ``sudo`` without a password. The ``sudo`` command has the ``-n`` option which will just make the command fail when the user does not have the appropriate permissions. The problem with ``chvt`` is that it does not have any intelligent command line argument parsing. If will return code 1 if no argument is given, the same code that ``sudo`` gives when no permission is available. Therefore I chose to use ``sudo -l` to get the whole list and see whether the full path to ``chvt`` is in there. This might break on Fedora where the ``usr``-merge has been done now. The following line is needed in a file like ``/etc/sudoers.d/chvt``:: myuser ALL = NOPASSWD: /bin/chvt You have to replace ``myuser`` which your username. Giving too broad permissions to every other user account is probably not a good idea. :rtype: bool ''' command = ['sudo', '-l'] output = tps.check_output(command, logger) return b'/bin/chvt' in output
def get_ethernet_con_name(): ''' Gets the lexicographically first ethernet connection name from nmcli. :returns: str :raises tps.network.MissingEthernetException: ''' if not tps.has_program('nmcli'): logger.warning('nmcli is not installed') return if get_nmcli_version() >= (0, 9, 10): command = ['nmcli', '--terse', '--fields', 'NAME,TYPE', 'con', 'show'] else: command = ['nmcli', '--terse', '--fields', 'NAME,TYPE', 'con', 'list'] lines = tps.check_output(command, logger).decode() ethernet_cons = [] for line in lines.split('\n'): if line.strip(): name, type = parse_terse_line(line) if 'ethernet' in type.lower(): ethernet_cons.append(name) if ethernet_cons: return sorted(ethernet_cons)[0] else: raise MissingEthernetException('No configured Ethernet connections.')
def get_wacom_device_ids(): ''' Gets the IDs of the built-in Wacom touch devices. This calls ``xinput`` to get the list and parses that with a regular expression. Only device names starting with ``Wacom ISD`` (default regex) are taken into account. If you have an external device, this will not be picked up. :rtype: list ''' config = tps.config.get_config() regex = config['touch']['regex'] logger.debug('Using “%s” as regex to find Wacom devices.', regex) pattern = re.compile(regex.encode()) output = tps.check_output(['xinput'], logger) lines = output.split(b'\n') ids = [] for line in lines: matcher = pattern.search(line) if matcher: ids.append(int(matcher.group(1))) return ids
def _is_docked_lsusb(regex): ''' Queries ``lsusb`` and checks whether the devices are present. ''' output = tps.check_output(['lsusb'], logger).decode().strip() match = re.search(regex, output) return bool(match)
def set_subpixel_order(direction): ''' Sets the text subpixel anti-alias order. :param tps.Direction direction: New direction :returns: None ''' if tps.has_program('xfconf-query'): try: tps.check_call(['xfconf-query', '-c', 'xsettings', '-p', '/Xft/RGBA', '-s', direction.subpixel], logger) except subprocess.CalledProcessError as e: logger.error(e) elif tps.has_program('gsettings'): try: schemas = tps.check_output( ['gsettings', 'list-schemas'], logger).decode().split('\n') schema = 'org.gnome.settings-daemon.plugins.xsettings' if schema in schemas: tps.check_call(['gsettings', 'set', schema, 'rgba-order', direction.subpixel], logger) else: logger.warning('gsettings is installed, but the "{}" schema ' 'is not available'.format(schema)) except subprocess.CalledProcessError as e: logger.error(e) else: logger.warning('neither xfconf-query nor gsettings is installed')
def get_resolution_and_shift(output): ''' Retrieves the total resolution of the virtual screen and the position of the given output within that. The X server seems to generate a huge screen which is then displayed by the physical displays. ``xrandr`` gives the size of that (virtual) screen as well as the positions of each display in that. For example, I currently have the 12.5" 1366×768 ThinkPad X220 display on the right of a 23" 1920×1080 pixel display. ``xrandr`` tells me the following:: Screen 0: … current 3286 x 1080 … LVDS1 … 1366x768+1920+0 DP2 … 1920x1080+0+0 This only shows the interesting parts. The size of the (virtual) screen is 3286×1080 and the position of the internal screen is 1366×768+1920+0. This allows to compute the transformation matrix for this. ''' xrandr_output = tps.check_output(['xrandr', '-q'], logger).strip().decode() lines = xrandr_output.split('\n') pattern_output = re.compile(r''' {} \D+ (?P<width>\d+) x (?P<height>\d+) \+ (?P<x>\d+) \+ (?P<y>\d+) '''.format(output), re.VERBOSE) pattern_screen = re.compile('current (?P<width>\d+) x (?P<height>\d+)') result = {} for line in lines: m_output = pattern_output.search(line) if m_output: result['output_width'] = int(m_output.group('width')) result['output_height'] = int(m_output.group('height')) result['output_x'] = int(m_output.group('x')) result['output_y'] = int(m_output.group('y')) m_screen = pattern_screen.search(line) if m_screen: result['screen_width'] = int(m_screen.group('width')) result['screen_height'] = int(m_screen.group('height')) if len(result) != 6: raise ScreenNotFoundException( 'The screen and output dimensions could not be gathered from ' 'xrandr. Maybe the "{}" output is not attached or enabled? Please ' 'report a bug otherwise.'.format(output)) return result
def get_graphicsl_user(): pattern = re.compile(r'\(:0(\.0)?\)') lines = tps.check_output(['who', '-u'], logger).decode().split('\n') for line in lines: m = pattern.search(line) if m: words = line.split() return words[0]
def has_device_property(device, property_): ''' Checks whether a given device supports a property. ''' command = ['xinput', '--list-props', str(device)] output = tps.check_output(command, logger).decode() regex = r'^\s+({})\s+\(\d+\):'.format(re.escape(property_)) has_property = re.search(regex, output, re.MULTILINE) is not None logger.debug('Device %i %s property “%s”', device, 'has' if has_property else 'does not have', property_) return has_property
def get_xinput_state(device): ''' Gets the device state. :param device: ``xinput`` ID of devicwe :type device: int :returns: Whether device is enabled :rtype: bool ''' output = tps.check_output(['xinput', '--list', str(device)], logger) return b'disabled' not in output
def get_xinput_state(device): """ Gets the device state. :param device: ``xinput`` ID of devicwe :type device: int :returns: Whether device is enabled :rtype: bool """ output = tps.check_output(["xinput", "--list", str(device)], logger) return b"disabled" not in output
def get_pulseaudio_sinks(): ''' Retrieves the available PulseAudio sinks on the current system and returns them in a set of strings :returns: List of sinks. If ``pactl`` is not installed, an empty list is returned instead. :rtype: list of str ''' if not tps.has_program('pactl'): logger.warning('pactl is not installed') return [] output = tps.check_output(['pactl', 'list', 'sinks'], logger).decode() sinks = re.findall('^Sink #(\d+)$', output, flags=re.MULTILINE) return sinks
def get_xinput_id(name): """ Gets the ``xinput`` ID for given device. The first parts of the name may be omitted. To get “TPPS/2 IBM TrackPoint”, it is sufficient to use “TrackPoint”. :raises InputDeviceNotFoundException: Device not found in ``xinput`` output :rtype: int """ output = tps.check_output(["xinput", "list"], logger).decode() matcher = re.search(name + r"\s*id=(\d+)", output) if matcher: return int(matcher.group(1)) raise InputDeviceNotFoundException("Input device “{}” could not be found".format(name))
def get_nmcli_version(): ''' Gets the version of nmcli, removing trailing zeroes. :returns: tuple, e.g. (0, 9, 10) for version 0.9.10.0 ''' if not tps.has_program('nmcli'): logger.warning('nmcli is not installed') return response = tps.check_output(['nmcli', '--version'], logger).decode() version_str = re.search(r'\d+(\.\d+)*', response).group(0) version_list = [int(n) for n in version_str.split('.')] while version_list[-1] == 0: version_list.pop() return tuple(version_list)
def get_xinput_id(name): ''' Gets the ``xinput`` ID for given device. The first parts of the name may be omitted. To get “TPPS/2 IBM TrackPoint”, it is sufficient to use “TrackPoint”. :raises InputDeviceNotFoundException: Device not found in ``xinput`` output :rtype: int ''' output = tps.check_output(['xinput', 'list'], logger).decode() matcher = re.search(name + r'\s*id=(\d+)', output) if matcher: return int(matcher.group(1)) raise InputDeviceNotFoundException( 'Input device “{}” could not be found'.format(name))
def get_externals(internal): ''' Gets the external screens. You have to specify the internal screen to exclude that from the listing. ;param str internal: Name of the internal screen :returns: List of external screen names :rtype: str ''' externals = [] lines = tps.check_output(['xrandr'], logger).decode().split('\n') for line in lines: if not line.startswith(internal): matcher = re.search(r'^(\S+) connected', line) if matcher: externals.append(matcher.group(1)) return externals
def get_internal(config, cache=True): ''' Matches the regular expression in the config and retrieves the actual name of the internal screen. The names of the outputs that XRandR reports may be ``LVDS1`` or ``LVDS-1``. The former happens with the Intel driver, the latter with the generic kernel modesetting driver. We do not know what the system will provide, therefore it was decided in GH-125 to use a regular expression in the configuration file. This also gives out-of-the-box support for Yoga users where the internal screen is called ``eDP1`` or ``eDP-1``. :param config: Configuration parser instance :param bool cache: Compute the value again even if it is cached ''' if cache and get_internal.cached_internal is not None: return get_internal.cached_internal if 'internal' in config['screen']: # The user has this key in his configuration. The default does not have # it any more, so this must be manual. The user could have specified # that by hand, it is perhaps not really what is wanted. logger.warning( 'You have specified the screen.internal option in your configuration file. Since version 4.8.0 this option is not used by default but screen.internal_regex (valued `%s`) is used instead. Please take a look at the new default regular expression and see whether that covers your use case already. In that case you can delete the entry from your own configuration file. This program will use your value and not try to match the regular expression.', config['screen']['internal_regex']) internal = config['screen']['internal'] else: # There is no such option, therefore we need to match the regular # expression against the output of XRandR now. output = tps.check_output(['xrandr'], logger).decode().strip() screens = get_available_screens(output) logger.debug('Screens available on this system are %s.', ', '.join(screens)) internal = filter_outputs(screens, config['screen']['internal_regex']) logger.debug('Internal screen is determined to be %s.', internal) get_internal.cached_internal = internal return internal
def get_rotation(screen): ''' Gets the current rotation of the given screen. :param str screen: Find rotation of given output :returns: Current direction :rtype: tps.Direction ''' output = tps.check_output(['xrandr', '-q', '--verbose'], logger).decode() lines = output.split('\n') for line in lines: if screen in line: matcher = re.search(r'\) (normal|left|inverted|right) \(', line) if matcher: rotation = tps.translate_direction(matcher.group(1)) logger.info('Current rotation is “{}”.'.format(rotation)) return rotation else: raise ScreenNotFoundException( 'Screen "{}" is not enabled. Do you have a screen like that in ' 'the output of "xrandr", and is it enabled? Maybe you have to ' 'adjust the option of screen.internal in the ' 'configuration.'.format(screen))
def get_internal(config, cache=True): ''' Matches the regular expression in the config and retrieves the actual name of the internal screen. The names of the outputs that XRandR reports may be ``LVDS1`` or ``LVDS-1``. The former happens with the Intel driver, the latter with the generic kernel modesetting driver. We do not know what the system will provide, therefore it was decided in GH-125 to use a regular expression in the configuration file. This also gives out-of-the-box support for Yoga users where the internal screen is called ``eDP1`` or ``eDP-1``. :param config: Configuration parser instance :param bool cache: Compute the value again even if it is cached ''' if cache and get_internal.cached_internal is not None: return get_internal.cached_internal if 'internal' in config['screen']: # The user has this key in his configuration. The default does not have # it any more, so this must be manual. The user could have specified # that by hand, it is perhaps not really what is wanted. logger.warning('You have specified the screen.internal option in your configuration file. Since version 4.8.0 this option is not used by default but screen.internal_regex (valued `%s`) is used instead. Please take a look at the new default regular expression and see whether that covers your use case already. In that case you can delete the entry from your own configuration file. This program will use your value and not try to match the regular expression.', config['screen']['internal_regex']) internal = config['screen']['internal'] else: # There is no such option, therefore we need to match the regular # expression against the output of XRandR now. output = tps.check_output(['xrandr'], logger).decode().strip() screens = get_available_screens(output) logger.debug('Screens available on this system are %s.', ', '.join(screens)) internal = filter_outputs(screens, config['screen']['internal_regex']) logger.debug('Internal screen is determined to be %s.', internal) get_internal.cached_internal = internal return internal
def has_xinput_prop(device, prop): """ Checks whether the device has the given xinput propery. """ output = tps.check_output(["xinput", "list-props", str(device)], logger) return prop in output
def has_xinput_prop(device, prop): ''' Checks whether the device has the given xinput propery. ''' output = tps.check_output(['xinput', 'list-props', str(device)], logger) return prop in output
def get_graphicsl_user(): lines = tps.check_output(['who', '-u'], logger)\ .decode().strip().split('\n') return parse_graphical_user(lines)