diff --git a/laptop-mode.service b/laptop-mode.service new file mode 100644 index 0000000..c7186e3 --- /dev/null +++ b/laptop-mode.service @@ -0,0 +1,10 @@ +[Unit] +Description=Configure system for laptop mode +Conflicts=tablet-mode.service + +[Service] +ExecStart=/usr/bin/setsysmode laptop +StandardOutput=null + +[Install] +WantedBy=multi-user.target diff --git a/setsysmode b/setsysmode new file mode 100755 index 0000000..2b98fae --- /dev/null +++ b/setsysmode @@ -0,0 +1,129 @@ +#! /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', LAPTOP_MODE_SERVICE): + systemctl('stop', LAPTOP_MODE_SERVICE, root=True) + systemctl('start', TABLET_MODE_SERVICE, root=True) + + if notify: + notify_tablet_mode() + else: + systemctl('stop', TABLET_MODE_SERVICE, root=True) + systemctl('start', LAPTOP_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) diff --git a/sysmoded b/sysmoded new file mode 100755 index 0000000..2a72366 --- /dev/null +++ b/sysmoded @@ -0,0 +1,84 @@ +#! /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) + + +if __name__ == '__main__': + main() diff --git a/tablet-mode b/tablet-mode deleted file mode 100755 index d4b5c82..0000000 --- a/tablet-mode +++ /dev/null @@ -1,48 +0,0 @@ -#! /usr/bin/env python3 -"""Grabs the respective device to discard any input from it.""" - -from configparser import ConfigParser -from contextlib import suppress -from subprocess import run -from threading import Thread - - -CONFIG_FILE = '/etc/tablet-mode.conf' -EVTEST = '/usr/bin/evtest' - - -def grab_device(device): - """Grabs the respective device via evtest.""" - - return run((EVTEST, '--grab', device)) - - -def get_devices(): - """Reads the device from the config file.""" - - parser = ConfigParser() - parser.read(CONFIG_FILE) - - with suppress(KeyError): - yield parser['Devices']['keyboard'] - - with suppress(KeyError): - yield parser['Devices']['touchpad'] - - -def disable_devices(devices): - """Disables the given devices.""" - - threads = [] - - for device in devices: - thread = Thread(target=grab_device, args=[device]) - threads.append(thread) - thread.start() - - for thread in threads: - thread.join() - - -if __name__ == '__main__': - disable_devices(get_devices()) diff --git a/tablet-mode.desktop b/tablet-mode.desktop index 1d45f12..b077626 100644 --- a/tablet-mode.desktop +++ b/tablet-mode.desktop @@ -2,6 +2,6 @@ Comment=Toggle tablet mode Terminal=false Name=Tablet Mode -Exec=/usr/bin/toggle-tablet-mode +Exec=/usr/bin/setsysmode toggle Type=Application Icon=pda-symbolic diff --git a/tablet-mode.service b/tablet-mode.service index 951fb4a..cee7692 100644 --- a/tablet-mode.service +++ b/tablet-mode.service @@ -1,6 +1,10 @@ [Unit] -Description=Disable keyboard and touchpad for tablet mode +Description=Configure system for tablet mode +Conflicts=laptop-mode.service [Service] -ExecStart=/usr/bin/tablet-mode +ExecStart=/usr/bin/setsysmode tablet StandardOutput=null + +[Install] +WantedBy=multi-user.target diff --git a/tablet-mode.sudoers b/tablet-mode.sudoers index c49c8f5..e591d1d 100644 --- a/tablet-mode.sudoers +++ b/tablet-mode.sudoers @@ -1 +1,13 @@ -%tablet ALL=(ALL) NOPASSWD: /usr/bin/systemctl start tablet-mode.service, /usr/bin/systemctl stop tablet-mode.service +# This file is part of tablet-mode. +# +# It allows users in the group "tablet" to toggle +# the system between tablet and laptop mode. +# +################################################################################ + +Cmnd_Alias START_LAPTOP_MODE = /usr/bin/systemctl start laptop-mode.service +Cmnd_Alias STOP_LAPTOP_MODE = /usr/bin/systemctl stop laptop-mode.service +Cmnd_Alias START_TABLET_MODE = /usr/bin/systemctl start tablet-mode.service +Cmnd_Alias STOP_TABLET_MODE = /usr/bin/systemctl stop tablet-mode.service + +%tablet ALL=(ALL) NOPASSWD: START_LAPTOP_MODE, STOP_LAPTOP_MODE, START_TABLET_MODE, STOP_TABLET_MODE diff --git a/toggle-tablet-mode b/toggle-tablet-mode deleted file mode 100755 index 1c535bd..0000000 --- a/toggle-tablet-mode +++ /dev/null @@ -1,10 +0,0 @@ -#! /bin/bash - -if ( systemctl status tablet-mode.service ); then - sudo systemctl stop tablet-mode.service - notify-send "Laptop mode" "The system is now in laptop mode." -else - sudo systemctl start tablet-mode.service - notify-send "Tablet mode" "The system is now in tablet mode." -fi -