diff --git a/setsysmode b/setsysmode index 231fd02..fb31d1f 100755 --- a/setsysmode +++ b/setsysmode @@ -1,132 +1,7 @@ #! /usr/bin/env python3 """Sets the system mode.""" -from argparse import ArgumentParser -from subprocess import DEVNULL, CalledProcessError, check_call, run -from sys import stderr - - -DESCRIPTION = 'Sets or toggles the system mode.' -LAPTOP_MODE_SERVICE = 'laptop-mode.service' -TABLET_MODE_SERVICE = 'tablet-mode.service' - - -def get_arguments(): - """Returns the CLI arguments.""" - - parser = ArgumentParser(description=DESCRIPTION) - parser.add_argument( - '-n', '--notify', action='store_true', - help='display an on-screen notification') - subparsers = parser.add_subparsers(dest='mode') - subparsers.add_parser('toggle', help='toggles the system mode') - subparsers.add_parser('laptop', help='switch to laptop mode') - subparsers.add_parser('tablet', help='switch to tablet mode') - subparsers.add_parser('default', help='do not disable any input devices') - return parser.parse_args() - - -def systemctl(action, unit, *, root=False): - """Runs systemctl.""" - - command = ['/usr/bin/sudo'] if root else [] - command.append('systemctl') - command.append(action) - command.append(unit) - - try: - check_call(command, stdout=DEVNULL) # Return 0 on success. - except CalledProcessError: - return False - - return True - - -def notify_send(summary, body=None): - """Sends the respective message.""" - - command = ['/usr/bin/notify-send', summary] - - if body is not None: - command.append(body) - - return run(command, stdout=DEVNULL) - - -def notify_laptop_mode(): - """Notifies about laptop mode.""" - - return notify_send('Laptop mode.', 'The system is now in laptop mode.') - - -def notify_tablet_mode(): - """Notifies about tablet mode.""" - - return notify_send('Tablet mode.', 'The system is now in tablet mode.') - - -def toggle_mode(notify): - """Toggles between laptop and tablet mode.""" - - if systemctl('status', TABLET_MODE_SERVICE): - systemctl('stop', TABLET_MODE_SERVICE, root=True) - systemctl('start', LAPTOP_MODE_SERVICE, root=True) - - if notify: - notify_tablet_mode() - else: - systemctl('stop', LAPTOP_MODE_SERVICE, root=True) - systemctl('start', TABLET_MODE_SERVICE, root=True) - - if notify: - notify_laptop_mode() - - -def default_mode(notify): - """Restores all blocked input devices.""" - - systemctl('stop', LAPTOP_MODE_SERVICE, root=True) - systemctl('stop', TABLET_MODE_SERVICE, root=True) - - if notify: - notify_send('Default mode.', 'The system is now in default mode.') - - -def laptop_mode(notify): - """Starts the laptop mode.""" - - systemctl('stop', TABLET_MODE_SERVICE, root=True) - systemctl('start', LAPTOP_MODE_SERVICE, root=True) - - if notify: - notify_laptop_mode() - - -def tablet_mode(notify): - """Starts the tablet mode.""" - - systemctl('stop', LAPTOP_MODE_SERVICE, root=True) - systemctl('start', TABLET_MODE_SERVICE, root=True) - - if notify: - notify_tablet_mode() - - -def main(): - """Runs the main program.""" - - arguments = get_arguments() - - if arguments.mode == 'toggle': - toggle_mode(arguments.notify) - elif arguments.mode == 'default': - default_mode(arguments.notify) - elif arguments.mode == 'laptop': - laptop_mode(arguments.notify) - elif arguments.mode == 'tablet': - tablet_mode(arguments.notify) - else: - print('Must specify a mode.', file=stderr, flush=True) +from tabletmode.cli import main if __name__ == '__main__': diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..1c01c02 --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +#! /usr/bin/env python + +from setuptools import setup + +setup( + name='tabletmode', + version_format='{tag}', + setup_requires=['setuptools-git-version'], + author='Richard Neumann', + author_email='mail@richard-neumann.de', + python_requires='>=3.8', + packages=['tabletmode'], + scripts=['setsysmode', 'sysmoded'], + url='https://github.com/conqp/tablet-mode', + license='GPLv3', + description='Tablet mode switch for GNOME 3.', + long_description=open('README.md').read(), + long_description_content_type="text/markdown", + keywords='tablet mode tent convertible switch' +) diff --git a/sysmoded b/sysmoded index 2a72366..347aca9 100755 --- a/sysmoded +++ b/sysmoded @@ -1,83 +1,7 @@ #! /usr/bin/env python3 """System mode daemon.""" -from argparse import ArgumentParser -from json import load -from logging import DEBUG, INFO, basicConfig, getLogger -from pathlib import Path -from subprocess import Popen - - -CONFIG_FILE = Path('/etc/tablet-mode.json') -DESCRIPTION = 'Setup system for laptop or tablet mode.' -EVTEST = '/usr/bin/evtest' -LOG_FORMAT = '[%(levelname)s] %(name)s: %(message)s' -LOGGER = getLogger(__file__) - - -def get_arguments(): - """Parses the CLI arguments.""" - - parser = ArgumentParser(description=DESCRIPTION) - parser.add_argument( - '-v', '--verbose', action='store_true', - help='turn on verbose logging') - subparsers = parser.add_subparsers(dest='mode') - subparsers.add_parser('laptop', help='enable laptop mode') - subparsers.add_parser('tablet', help='enable tablet mode') - return parser.parse_args() - - -def load_configuration(): - """Returns the configuration.""" - - try: - with CONFIG_FILE.open('r') as cfg: - return load(cfg) - except FileNotFoundError: - LOGGER.warning('Config file %s does not exist.', CONFIG_FILE) - return {} - - -def disable_device(device): - """Disables the respective device via evtest.""" - - return Popen((EVTEST, '--grab', device)) - - -def disable_devices(devices): - """Disables the given devices.""" - - subprocesses = [] - - for device in devices: - subprocess = disable_device(device) - subprocesses.append(subprocess) - - for subprocess in subprocesses: - subprocess.wait() - - -def get_devices(mode): - """Reads the device from the config file.""" - - configuration = load_configuration() - devices = configuration.get(mode) or () - - if not devices: - LOGGER.info('No devices configured to disable.') - - return devices - - -def main(): - """Runs the main program.""" - - arguments = get_arguments() - level = DEBUG if arguments.verbose else INFO - basicConfig(level=level, format=LOG_FORMAT) - devices = get_devices(arguments.mode) - disable_devices(devices) +from tabletmode.daemon import main if __name__ == '__main__': diff --git a/tabletmode/__init__.py b/tabletmode/__init__.py new file mode 100644 index 0000000..a6deee4 --- /dev/null +++ b/tabletmode/__init__.py @@ -0,0 +1 @@ +"""Tablet mode library.""" diff --git a/tabletmode/cli.py b/tabletmode/cli.py new file mode 100644 index 0000000..b4645ba --- /dev/null +++ b/tabletmode/cli.py @@ -0,0 +1,133 @@ + +"""Sets the system mode.""" + +from argparse import ArgumentParser +from subprocess import DEVNULL, CalledProcessError, check_call, run +from sys import stderr + +from tabletmode.config import load_configuration + + +DESCRIPTION = 'Sets or toggles the system mode.' +LAPTOP_MODE_SERVICE = 'laptop-mode.service' +TABLET_MODE_SERVICE = 'tablet-mode.service' + + +def get_arguments(): + """Returns the CLI arguments.""" + + parser = ArgumentParser(description=DESCRIPTION) + parser.add_argument( + '-n', '--notify', action='store_true', + help='display an on-screen notification') + subparsers = parser.add_subparsers(dest='mode') + subparsers.add_parser('toggle', help='toggles the system mode') + subparsers.add_parser('laptop', help='switch to laptop mode') + subparsers.add_parser('tablet', help='switch to tablet mode') + subparsers.add_parser('default', help='do not disable any input devices') + return parser.parse_args() + + +def systemctl(action, unit, *, root=False): + """Runs systemctl.""" + + command = ['/usr/bin/sudo'] if root else [] + command.append('systemctl') + command.append(action) + command.append(unit) + + try: + check_call(command, stdout=DEVNULL) # Return 0 on success. + except CalledProcessError: + return False + + return True + + +def notify_send(summary, body=None): + """Sends the respective message.""" + + command = ['/usr/bin/notify-send', summary] + + if body is not None: + command.append(body) + + return run(command, stdout=DEVNULL, check=False) + + +def notify_laptop_mode(): + """Notifies about laptop mode.""" + + return notify_send('Laptop mode.', 'The system is now in laptop mode.') + + +def notify_tablet_mode(): + """Notifies about tablet mode.""" + + return notify_send('Tablet mode.', 'The system is now in tablet mode.') + + +def toggle_mode(notify=False): + """Toggles between laptop and tablet mode.""" + + if systemctl('status', TABLET_MODE_SERVICE): + systemctl('stop', TABLET_MODE_SERVICE, root=True) + systemctl('start', LAPTOP_MODE_SERVICE, root=True) + + if notify: + notify_tablet_mode() + else: + systemctl('stop', LAPTOP_MODE_SERVICE, root=True) + systemctl('start', TABLET_MODE_SERVICE, root=True) + + if notify: + notify_laptop_mode() + + +def default_mode(notify=False): + """Restores all blocked input devices.""" + + systemctl('stop', LAPTOP_MODE_SERVICE, root=True) + systemctl('stop', TABLET_MODE_SERVICE, root=True) + + if notify: + notify_send('Default mode.', 'The system is now in default mode.') + + +def laptop_mode(notify=False): + """Starts the laptop mode.""" + + systemctl('stop', TABLET_MODE_SERVICE, root=True) + systemctl('start', LAPTOP_MODE_SERVICE, root=True) + + if notify: + notify_laptop_mode() + + +def tablet_mode(notify=False): + """Starts the tablet mode.""" + + systemctl('stop', LAPTOP_MODE_SERVICE, root=True) + systemctl('start', TABLET_MODE_SERVICE, root=True) + + if notify: + notify_tablet_mode() + + +def main(): + """Runs the main program.""" + + arguments = get_arguments() + configuration = load_configuration() + notify = configuration.get('notify', False) or arguments.notify + + if arguments.mode == 'toggle': + toggle_mode(notify=notify) + elif arguments.mode == 'default': + default_mode(notify=notify) + elif arguments.mode == 'laptop': + laptop_mode(notify=notify) + elif arguments.mode == 'tablet': + tablet_mode(notify=notify) + else: + print('Must specify a mode.', file=stderr, flush=True) diff --git a/tabletmode/config.py b/tabletmode/config.py new file mode 100644 index 0000000..caef038 --- /dev/null +++ b/tabletmode/config.py @@ -0,0 +1,23 @@ +"""Configuration file parsing.""" + +from json import load +from logging import getLogger +from pathlib import Path + + +__all__ = ['load_configuration'] + + +CONFIG_FILE = Path('/etc/tablet-mode.json') +LOGGER = getLogger('tabletmode') + + +def load_configuration(): + """Returns the configuration.""" + + try: + with CONFIG_FILE.open('r') as cfg: + return load(cfg) + except FileNotFoundError: + LOGGER.warning('Config file %s does not exist.', CONFIG_FILE) + return {} diff --git a/tabletmode/daemon.py b/tabletmode/daemon.py new file mode 100644 index 0000000..12786d0 --- /dev/null +++ b/tabletmode/daemon.py @@ -0,0 +1,68 @@ + +"""System mode daemon.""" + +from argparse import ArgumentParser +from logging import DEBUG, INFO, basicConfig, getLogger +from subprocess import Popen + +from tabletmode.config import load_configuration + + +DESCRIPTION = 'Setup system for laptop or tablet mode.' +EVTEST = '/usr/bin/evtest' +LOG_FORMAT = '[%(levelname)s] %(name)s: %(message)s' +LOGGER = getLogger('sysmoded') + + +def get_arguments(): + """Parses the CLI arguments.""" + + parser = ArgumentParser(description=DESCRIPTION) + parser.add_argument( + '-v', '--verbose', action='store_true', + help='turn on verbose logging') + subparsers = parser.add_subparsers(dest='mode') + subparsers.add_parser('laptop', help='enable laptop mode') + subparsers.add_parser('tablet', help='enable tablet mode') + return parser.parse_args() + + +def disable_device(device): + """Disables the respective device via evtest.""" + + return Popen((EVTEST, '--grab', device)) + + +def disable_devices(devices): + """Disables the given devices.""" + + subprocesses = [] + + for device in devices: + subprocess = disable_device(device) + subprocesses.append(subprocess) + + for subprocess in subprocesses: + subprocess.wait() + + +def get_devices(mode): + """Reads the device from the config file.""" + + configuration = load_configuration() + devices = configuration.get(mode) or () + + if not devices: + LOGGER.info('No devices configured to disable.') + + return devices + + +def main(): + """Runs the main program.""" + + arguments = get_arguments() + level = DEBUG if arguments.verbose else INFO + basicConfig(level=level, format=LOG_FORMAT) + devices = get_devices(arguments.mode) + disable_devices(devices)