From 8aef53652747958776bd9788c2ea6e4876b26afa Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Sat, 23 Mar 2019 15:26:21 +0100 Subject: [PATCH 01/21] Update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de5a07e..c4f58ea 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Allow users to toggle a convertible laptop between laptop and tablet mode. ## Configuration -The keyboard device to be deactivated in tablet mode must be specified in `/etc/tablet-mode.conf`: +The keyboard and touchpad devices to be deactivated in tablet mode must be specified in `/etc/tablet-mode.conf`: [Devices] keyboard = /dev/input/by-path/platform-i8042-serio-0-event-kbd From 359fecb0275cc4b397a3bf325e08b8978acd255b Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 21 May 2019 15:26:30 +0200 Subject: [PATCH 02/21] Refac. --- laptop-mode.service | 10 ++++ setsysmode | 129 ++++++++++++++++++++++++++++++++++++++++++++ sysmoded | 84 +++++++++++++++++++++++++++++ tablet-mode | 48 ----------------- tablet-mode.desktop | 2 +- tablet-mode.service | 8 ++- tablet-mode.sudoers | 14 ++++- toggle-tablet-mode | 10 ---- 8 files changed, 243 insertions(+), 62 deletions(-) create mode 100644 laptop-mode.service create mode 100755 setsysmode create mode 100755 sysmoded delete mode 100755 tablet-mode delete mode 100755 toggle-tablet-mode 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 - From 4a2ed05182797f83423e76b5534482c055c8bd80 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 21 May 2019 15:32:39 +0200 Subject: [PATCH 03/21] Fix. --- laptop-mode.service | 2 +- tablet-mode.service | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/laptop-mode.service b/laptop-mode.service index c7186e3..7f9bd26 100644 --- a/laptop-mode.service +++ b/laptop-mode.service @@ -3,7 +3,7 @@ Description=Configure system for laptop mode Conflicts=tablet-mode.service [Service] -ExecStart=/usr/bin/setsysmode laptop +ExecStart=/usr/bin/sysmoded laptop StandardOutput=null [Install] diff --git a/tablet-mode.service b/tablet-mode.service index cee7692..5ca2d1d 100644 --- a/tablet-mode.service +++ b/tablet-mode.service @@ -3,7 +3,7 @@ Description=Configure system for tablet mode Conflicts=laptop-mode.service [Service] -ExecStart=/usr/bin/setsysmode tablet +ExecStart=/usr/bin/sysmoded tablet StandardOutput=null [Install] From df32a939123564db1bbd3d2a4e55d18b695c24ad Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Sat, 1 Jun 2019 23:49:40 +0200 Subject: [PATCH 04/21] Fix. --- setsysmode | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setsysmode b/setsysmode index 2b98fae..e5272fc 100755 --- a/setsysmode +++ b/setsysmode @@ -127,3 +127,7 @@ def main(): tablet_mode(arguments.notify) else: print('Must specify a mode.', file=stderr, flush=True) + + +if __name__ == '__main__': + main() From 72733caa7b5eac768b2b31002b4ddb7f2b95542b Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Sat, 8 Jun 2019 08:51:21 +0200 Subject: [PATCH 05/21] Fix. --- setsysmode | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setsysmode b/setsysmode index e5272fc..231fd02 100755 --- a/setsysmode +++ b/setsysmode @@ -68,15 +68,15 @@ def notify_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 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', TABLET_MODE_SERVICE, root=True) - systemctl('start', LAPTOP_MODE_SERVICE, root=True) + systemctl('stop', LAPTOP_MODE_SERVICE, root=True) + systemctl('start', TABLET_MODE_SERVICE, root=True) if notify: notify_laptop_mode() From 99e8c6da9008611389037be72a33c1d7a6204459 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Mon, 2 Sep 2019 11:58:39 +0200 Subject: [PATCH 06/21] Updated readme. --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c4f58ea..955b160 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,15 @@ Allow users to toggle a convertible laptop between laptop and tablet mode. ## Configuration -The keyboard and touchpad devices to be deactivated in tablet mode must be specified in `/etc/tablet-mode.conf`: +The devices to be deactivated in either *tablet* or *laptop* mode must be specified in `/etc/tablet-mode.json`. + + { + "tablet": [ + "/dev/input/by-path/platform-i8042-serio-0-event-kbd", + "/dev/input/by-path/platform-i8042-serio-1-event-mouse" + ] + } - [Devices] - keyboard = /dev/input/by-path/platform-i8042-serio-0-event-kbd - touchpad = /dev/input/by-path/platform-i8042-serio-1-event-mouse - ## Usage You must be a member of the group `tablet` to toggle between tablet and laptop mode. From aae65e3825ca96e57aa44a7aa4df638b781f6ab6 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 3 Sep 2019 23:45:24 +0200 Subject: [PATCH 07/21] Updated README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 955b160..ee1c6f9 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,4 @@ The devices to be deactivated in either *tablet* or *laptop* mode must be specif ## Usage You must be a member of the group `tablet` to toggle between tablet and laptop mode. -You can toggle between tablet and laptop mode by running `toggle-tablet-mode` or use the desktop icon provided with this package. +You can toggle between tablet and laptop mode by running `setsysmode toggle` or use the desktop icon provided with this package. From 309bafd5d16ee91a19b789872203d16333b53402 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Thu, 26 Mar 2020 11:47:41 +0100 Subject: [PATCH 08/21] Refactored python code into library. --- setsysmode | 127 +-------------------------------------- setup.py | 20 +++++++ sysmoded | 78 +----------------------- tabletmode/__init__.py | 1 + tabletmode/cli.py | 133 +++++++++++++++++++++++++++++++++++++++++ tabletmode/config.py | 23 +++++++ tabletmode/daemon.py | 68 +++++++++++++++++++++ 7 files changed, 247 insertions(+), 203 deletions(-) create mode 100755 setup.py create mode 100644 tabletmode/__init__.py create mode 100644 tabletmode/cli.py create mode 100644 tabletmode/config.py create mode 100644 tabletmode/daemon.py 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) From ffd6978317cdffb891757afe6866aac880f59b94 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Thu, 26 Mar 2020 11:49:57 +0100 Subject: [PATCH 09/21] Updated README. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee1c6f9..4f7d1dc 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,15 @@ Allow users to toggle a convertible laptop between laptop and tablet mode. ## Configuration -The devices to be deactivated in either *tablet* or *laptop* mode must be specified in `/etc/tablet-mode.json`. +The devices to be deactivated in either *tablet* or *laptop* mode must be specified in `/etc/tablet-mode.json`. +Optionally you can specify whether desktop notifications shall be send when changing the mode using the *notify* flag. { "tablet": [ "/dev/input/by-path/platform-i8042-serio-0-event-kbd", "/dev/input/by-path/platform-i8042-serio-1-event-mouse" - ] + ], + "notify": false } ## Usage From d46be0bc0b5cf3020b97ffa3dcaa7d2367c76a24 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 28 Apr 2020 21:54:11 +0200 Subject: [PATCH 10/21] Fixed mode notification and refactored mode toggle. --- tabletmode/cli.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/tabletmode/cli.py b/tabletmode/cli.py index b4645ba..cb36a44 100644 --- a/tabletmode/cli.py +++ b/tabletmode/cli.py @@ -67,23 +67,6 @@ def notify_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.""" @@ -114,6 +97,15 @@ def tablet_mode(notify=False): notify_tablet_mode() +def toggle_mode(notify=False): + """Toggles between laptop and tablet mode.""" + + if systemctl('status', TABLET_MODE_SERVICE): + laptop_mode(notify=notify) + else: + tablet_mode(notify=notify) + + def main(): """Runs the main program.""" From 501f28e0ee6c2f5457e06cb89702872e1b7ee0d1 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 5 Jan 2021 11:36:46 +0100 Subject: [PATCH 11/21] Added type hints. --- tabletmode/cli.py | 49 +++++++++++++++++++++++--------------------- tabletmode/config.py | 4 ++-- tabletmode/daemon.py | 19 +++++++++-------- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/tabletmode/cli.py b/tabletmode/cli.py index cb36a44..41ae0cf 100644 --- a/tabletmode/cli.py +++ b/tabletmode/cli.py @@ -1,11 +1,16 @@ """Sets the system mode.""" -from argparse import ArgumentParser -from subprocess import DEVNULL, CalledProcessError, check_call, run +from argparse import ArgumentParser, Namespace +from subprocess import DEVNULL +from subprocess import CalledProcessError +from subprocess import CompletedProcess +from subprocess import check_call +from subprocess import run from sys import stderr +from typing import Optional -from tabletmode.config import load_configuration +from tabletmode.config import load_config DESCRIPTION = 'Sets or toggles the system mode.' @@ -13,7 +18,7 @@ LAPTOP_MODE_SERVICE = 'laptop-mode.service' TABLET_MODE_SERVICE = 'tablet-mode.service' -def get_arguments(): +def get_args() -> Namespace: """Returns the CLI arguments.""" parser = ArgumentParser(description=DESCRIPTION) @@ -28,13 +33,11 @@ def get_arguments(): return parser.parse_args() -def systemctl(action, unit, *, root=False): +def systemctl(action: str, unit: str, *, root: bool = False) -> bool: """Runs systemctl.""" command = ['/usr/bin/sudo'] if root else [] - command.append('systemctl') - command.append(action) - command.append(unit) + command += ['systemctl', action, unit] try: check_call(command, stdout=DEVNULL) # Return 0 on success. @@ -44,7 +47,7 @@ def systemctl(action, unit, *, root=False): return True -def notify_send(summary, body=None): +def notify_send(summary: str, body: Optional[str] = None) -> CompletedProcess: """Sends the respective message.""" command = ['/usr/bin/notify-send', summary] @@ -55,19 +58,19 @@ def notify_send(summary, body=None): return run(command, stdout=DEVNULL, check=False) -def notify_laptop_mode(): +def notify_laptop_mode() -> CompletedProcess: """Notifies about laptop mode.""" return notify_send('Laptop mode.', 'The system is now in laptop mode.') -def notify_tablet_mode(): +def notify_tablet_mode() -> CompletedProcess: """Notifies about tablet mode.""" return notify_send('Tablet mode.', 'The system is now in tablet mode.') -def default_mode(notify=False): +def default_mode(notify: bool = False) -> None: """Restores all blocked input devices.""" systemctl('stop', LAPTOP_MODE_SERVICE, root=True) @@ -77,7 +80,7 @@ def default_mode(notify=False): notify_send('Default mode.', 'The system is now in default mode.') -def laptop_mode(notify=False): +def laptop_mode(notify: bool = False) -> None: """Starts the laptop mode.""" systemctl('stop', TABLET_MODE_SERVICE, root=True) @@ -87,7 +90,7 @@ def laptop_mode(notify=False): notify_laptop_mode() -def tablet_mode(notify=False): +def tablet_mode(notify: bool = False) -> None: """Starts the tablet mode.""" systemctl('stop', LAPTOP_MODE_SERVICE, root=True) @@ -97,7 +100,7 @@ def tablet_mode(notify=False): notify_tablet_mode() -def toggle_mode(notify=False): +def toggle_mode(notify: bool = False) -> None: """Toggles between laptop and tablet mode.""" if systemctl('status', TABLET_MODE_SERVICE): @@ -106,20 +109,20 @@ def toggle_mode(notify=False): tablet_mode(notify=notify) -def main(): +def main() -> None: """Runs the main program.""" - arguments = get_arguments() - configuration = load_configuration() - notify = configuration.get('notify', False) or arguments.notify + args = get_args() + config = load_config() + notify = config.get('notify', False) or args.notify - if arguments.mode == 'toggle': + if args.mode == 'toggle': toggle_mode(notify=notify) - elif arguments.mode == 'default': + elif args.mode == 'default': default_mode(notify=notify) - elif arguments.mode == 'laptop': + elif args.mode == 'laptop': laptop_mode(notify=notify) - elif arguments.mode == 'tablet': + elif args.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 index caef038..8c6c928 100644 --- a/tabletmode/config.py +++ b/tabletmode/config.py @@ -5,14 +5,14 @@ from logging import getLogger from pathlib import Path -__all__ = ['load_configuration'] +__all__ = ['load_config'] CONFIG_FILE = Path('/etc/tablet-mode.json') LOGGER = getLogger('tabletmode') -def load_configuration(): +def load_config() -> dict: """Returns the configuration.""" try: diff --git a/tabletmode/daemon.py b/tabletmode/daemon.py index 12786d0..035c95b 100644 --- a/tabletmode/daemon.py +++ b/tabletmode/daemon.py @@ -1,11 +1,12 @@ """System mode daemon.""" -from argparse import ArgumentParser +from argparse import ArgumentParser, Namespace from logging import DEBUG, INFO, basicConfig, getLogger from subprocess import Popen +from typing import Iterable -from tabletmode.config import load_configuration +from tabletmode.config import load_config DESCRIPTION = 'Setup system for laptop or tablet mode.' @@ -14,7 +15,7 @@ LOG_FORMAT = '[%(levelname)s] %(name)s: %(message)s' LOGGER = getLogger('sysmoded') -def get_arguments(): +def get_args() -> Namespace: """Parses the CLI arguments.""" parser = ArgumentParser(description=DESCRIPTION) @@ -27,13 +28,13 @@ def get_arguments(): return parser.parse_args() -def disable_device(device): +def disable_device(device: str) -> Popen: """Disables the respective device via evtest.""" return Popen((EVTEST, '--grab', device)) -def disable_devices(devices): +def disable_devices(devices: Iterable[str]) -> None: """Disables the given devices.""" subprocesses = [] @@ -46,11 +47,11 @@ def disable_devices(devices): subprocess.wait() -def get_devices(mode): +def get_devices(mode: str) -> Iterable[str]: """Reads the device from the config file.""" - configuration = load_configuration() - devices = configuration.get(mode) or () + config = load_config() + devices = config.get(mode) or () if not devices: LOGGER.info('No devices configured to disable.') @@ -61,7 +62,7 @@ def get_devices(mode): def main(): """Runs the main program.""" - arguments = get_arguments() + arguments = get_args() level = DEBUG if arguments.verbose else INFO basicConfig(level=level, format=LOG_FORMAT) devices = get_devices(arguments.mode) From 14fd640059475632d575441a743dcfad733e6389 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 5 Jan 2021 11:37:19 +0100 Subject: [PATCH 12/21] Migrated scripts to entry points. --- setsysmode | 8 -------- setup.py | 7 ++++++- sysmoded | 8 -------- 3 files changed, 6 insertions(+), 17 deletions(-) delete mode 100755 setsysmode delete mode 100755 sysmoded diff --git a/setsysmode b/setsysmode deleted file mode 100755 index fb31d1f..0000000 --- a/setsysmode +++ /dev/null @@ -1,8 +0,0 @@ -#! /usr/bin/env python3 -"""Sets the system mode.""" - -from tabletmode.cli import main - - -if __name__ == '__main__': - main() diff --git a/setup.py b/setup.py index 1c01c02..b1c0fa4 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,12 @@ setup( author_email='mail@richard-neumann.de', python_requires='>=3.8', packages=['tabletmode'], - scripts=['setsysmode', 'sysmoded'], + entry_points={ + 'console_scripts': [ + 'setsysmode = tabletmode.cli:main', + 'sysmoded = tabletmode.daemon:main' + ], + }, url='https://github.com/conqp/tablet-mode', license='GPLv3', description='Tablet mode switch for GNOME 3.', diff --git a/sysmoded b/sysmoded deleted file mode 100755 index 347aca9..0000000 --- a/sysmoded +++ /dev/null @@ -1,8 +0,0 @@ -#! /usr/bin/env python3 -"""System mode daemon.""" - -from tabletmode.daemon import main - - -if __name__ == '__main__': - main() From cdcd82fdfd94195a79922bdb44c3cd0957cfb612 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 5 Jan 2021 11:41:43 +0100 Subject: [PATCH 13/21] Removed newline at top of files. --- tabletmode/cli.py | 1 - tabletmode/daemon.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tabletmode/cli.py b/tabletmode/cli.py index 41ae0cf..82eed77 100644 --- a/tabletmode/cli.py +++ b/tabletmode/cli.py @@ -1,4 +1,3 @@ - """Sets the system mode.""" from argparse import ArgumentParser, Namespace diff --git a/tabletmode/daemon.py b/tabletmode/daemon.py index 035c95b..0632be1 100644 --- a/tabletmode/daemon.py +++ b/tabletmode/daemon.py @@ -1,4 +1,3 @@ - """System mode daemon.""" from argparse import ArgumentParser, Namespace From 6f3f4a27647a8ccf29103a99d882991fd933e6fd Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Tue, 12 Jan 2021 15:21:40 +0100 Subject: [PATCH 14/21] Migrated to setuptools-scm. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b1c0fa4..51f156a 100755 --- a/setup.py +++ b/setup.py @@ -4,8 +4,8 @@ from setuptools import setup setup( name='tabletmode', - version_format='{tag}', - setup_requires=['setuptools-git-version'], + use_scm_version=True, + setup_requires=['setuptools_scm'], author='Richard Neumann', author_email='mail@richard-neumann.de', python_requires='>=3.8', From d24cc31b044292cd887eb53ca6e5131432c79cc4 Mon Sep 17 00:00:00 2001 From: Richard Neumann Date: Fri, 14 May 2021 21:08:01 +0200 Subject: [PATCH 15/21] Allow drop-in for sudo, such as doas --- README.md | 21 +++++++++++++-------- tabletmode/cli.py | 39 +++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 4f7d1dc..4afdc40 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,20 @@ Allow users to toggle a convertible laptop between laptop and tablet mode. ## Configuration The devices to be deactivated in either *tablet* or *laptop* mode must be specified in `/etc/tablet-mode.json`. -Optionally you can specify whether desktop notifications shall be send when changing the mode using the *notify* flag. +You can specify whether desktop notifications shall be send when changing the mode using the `notify` flag. +If specified, you can override `sudo` to provide another program that accepts commands to be run as root without a password +by the current user to elevate privileges, such as *doas*. - { - "tablet": [ - "/dev/input/by-path/platform-i8042-serio-0-event-kbd", - "/dev/input/by-path/platform-i8042-serio-1-event-mouse" - ], - "notify": false - } +```json +{ + "tablet": [ + "/dev/input/by-path/platform-i8042-serio-0-event-kbd", + "/dev/input/by-path/platform-i8042-serio-1-event-mouse" + ], + "notify": false, + "sudo": "/usr/bin/doas" +} +``` ## Usage diff --git a/tabletmode/cli.py b/tabletmode/cli.py index 82eed77..c20e898 100644 --- a/tabletmode/cli.py +++ b/tabletmode/cli.py @@ -15,6 +15,7 @@ from tabletmode.config import load_config DESCRIPTION = 'Sets or toggles the system mode.' LAPTOP_MODE_SERVICE = 'laptop-mode.service' TABLET_MODE_SERVICE = 'tablet-mode.service' +SUDO = '/usr/bin/sudo' def get_args() -> Namespace: @@ -32,10 +33,11 @@ def get_args() -> Namespace: return parser.parse_args() -def systemctl(action: str, unit: str, *, root: bool = False) -> bool: +def systemctl(action: str, unit: str, *, root: bool = False, + sudo: str = SUDO) -> bool: """Runs systemctl.""" - command = ['/usr/bin/sudo'] if root else [] + command = [sudo] if root else [] command += ['systemctl', action, unit] try: @@ -69,43 +71,43 @@ def notify_tablet_mode() -> CompletedProcess: return notify_send('Tablet mode.', 'The system is now in tablet mode.') -def default_mode(notify: bool = False) -> None: +def default_mode(notify: bool = False, *, sudo: str = SUDO) -> None: """Restores all blocked input devices.""" - systemctl('stop', LAPTOP_MODE_SERVICE, root=True) - systemctl('stop', TABLET_MODE_SERVICE, root=True) + systemctl('stop', LAPTOP_MODE_SERVICE, root=True, sudo=sudo) + systemctl('stop', TABLET_MODE_SERVICE, root=True, sudo=sudo) if notify: notify_send('Default mode.', 'The system is now in default mode.') -def laptop_mode(notify: bool = False) -> None: +def laptop_mode(notify: bool = False, *, sudo: str = SUDO) -> None: """Starts the laptop mode.""" - systemctl('stop', TABLET_MODE_SERVICE, root=True) - systemctl('start', LAPTOP_MODE_SERVICE, root=True) + systemctl('stop', TABLET_MODE_SERVICE, root=True, sudo=sudo) + systemctl('start', LAPTOP_MODE_SERVICE, root=True, sudo=sudo) if notify: notify_laptop_mode() -def tablet_mode(notify: bool = False) -> None: +def tablet_mode(notify: bool = False, *, sudo: str = SUDO) -> None: """Starts the tablet mode.""" - systemctl('stop', LAPTOP_MODE_SERVICE, root=True) - systemctl('start', TABLET_MODE_SERVICE, root=True) + systemctl('stop', LAPTOP_MODE_SERVICE, root=True, sudo=sudo) + systemctl('start', TABLET_MODE_SERVICE, root=True, sudo=sudo) if notify: notify_tablet_mode() -def toggle_mode(notify: bool = False) -> None: +def toggle_mode(notify: bool = False, *, sudo: str = SUDO) -> None: """Toggles between laptop and tablet mode.""" if systemctl('status', TABLET_MODE_SERVICE): - laptop_mode(notify=notify) + laptop_mode(notify=notify, sudo=sudo) else: - tablet_mode(notify=notify) + tablet_mode(notify=notify, sudo=sudo) def main() -> None: @@ -114,14 +116,15 @@ def main() -> None: args = get_args() config = load_config() notify = config.get('notify', False) or args.notify + sudo = config.get('sudo', SUDO) if args.mode == 'toggle': - toggle_mode(notify=notify) + toggle_mode(notify=notify, sudo=sudo) elif args.mode == 'default': - default_mode(notify=notify) + default_mode(notify=notify, sudo=sudo) elif args.mode == 'laptop': - laptop_mode(notify=notify) + laptop_mode(notify=notify, sudo=sudo) elif args.mode == 'tablet': - tablet_mode(notify=notify) + tablet_mode(notify=notify, sudo=sudo) else: print('Must specify a mode.', file=stderr, flush=True) From c1914511eeb614bd3d8bc1c1478b3807711c75f4 Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 16 Jul 2023 12:17:59 +0200 Subject: [PATCH 16/21] Add install script Add Minibook configuration Re-Add scripts --- install.sh | 27 +++++++++++++++++++++++++++ laptop-mode.service | 2 +- setsysmode | 8 ++++++++ sysmoded | 8 ++++++++ tablet-mode.desktop | 2 +- tablet-mode.json | 7 +++++++ tablet-mode.service | 2 +- 7 files changed, 53 insertions(+), 3 deletions(-) create mode 100755 install.sh create mode 100644 setsysmode create mode 100644 sysmoded create mode 100644 tablet-mode.json diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..66dc675 --- /dev/null +++ b/install.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +echo "Add tablet group..." +/usr/sbin/groupadd tablet + +read -p "Enter your local username: " user + +echo "Add user to group..." +usermod -a -G tablet "$user" + +echo "Copy files..." +cp -r tabletmode/ /usr/local/lib/python3.11/dist-packages +cp tablet-mode.service /etc/systemd/system +cp laptop-mode.service /etc/systemd/system +cp tablet-mode.json /etc/ +cp tablet-mode.desktop /home/"$user"/.local/applications +cp tablet-mode.sudoers /etc/sudoers.d/tablet-mode +cp setsysmode /usr/local/bin +chmod +x /usr/local/bin/setsysmode +cp sysmoded /usr/local/bin +chmod +x /usr/local/bin/sysmoded + +echo "Reload systemd..." +systemctl daemon-reload + +echo "Install packages..." +apt install evtest -y diff --git a/laptop-mode.service b/laptop-mode.service index 7f9bd26..93e4f27 100644 --- a/laptop-mode.service +++ b/laptop-mode.service @@ -3,7 +3,7 @@ Description=Configure system for laptop mode Conflicts=tablet-mode.service [Service] -ExecStart=/usr/bin/sysmoded laptop +ExecStart=/usr/local/bin/sysmoded laptop StandardOutput=null [Install] diff --git a/setsysmode b/setsysmode new file mode 100644 index 0000000..fb31d1f --- /dev/null +++ b/setsysmode @@ -0,0 +1,8 @@ +#! /usr/bin/env python3 +"""Sets the system mode.""" + +from tabletmode.cli import main + + +if __name__ == '__main__': + main() diff --git a/sysmoded b/sysmoded new file mode 100644 index 0000000..347aca9 --- /dev/null +++ b/sysmoded @@ -0,0 +1,8 @@ +#! /usr/bin/env python3 +"""System mode daemon.""" + +from tabletmode.daemon import main + + +if __name__ == '__main__': + main() diff --git a/tablet-mode.desktop b/tablet-mode.desktop index b077626..2c3a375 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/setsysmode toggle +Exec=/usr/local/bin/setsysmode toggle Type=Application Icon=pda-symbolic diff --git a/tablet-mode.json b/tablet-mode.json new file mode 100644 index 0000000..14f657e --- /dev/null +++ b/tablet-mode.json @@ -0,0 +1,7 @@ +{ + "tablet": [ + "/dev/input/by-path/platform-i8042-serio-0-event-kbd", + "/dev/input/by-path/pci-0000:00:14.0-usb-0:9:1.0-event-mouse" + ], + "notify": false +} diff --git a/tablet-mode.service b/tablet-mode.service index 5ca2d1d..fc893e7 100644 --- a/tablet-mode.service +++ b/tablet-mode.service @@ -3,7 +3,7 @@ Description=Configure system for tablet mode Conflicts=laptop-mode.service [Service] -ExecStart=/usr/bin/sysmoded tablet +ExecStart=/usr/local/bin/sysmoded tablet StandardOutput=null [Install] From 8b0a4c954c86bce5af43844d35c32357e78bfbfc Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 16 Jul 2023 12:21:58 +0200 Subject: [PATCH 17/21] Update README --- README.md | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4afdc40..cdc0bc5 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,11 @@ -# tablet-mode +# Chuwi Minibook tablet mode switch -Allow users to toggle a convertible laptop between laptop and tablet mode. +Allow users to toggle a Chuwi Minibook laptop between laptop and tablet mode. -## Configuration +## Installation -The devices to be deactivated in either *tablet* or *laptop* mode must be specified in `/etc/tablet-mode.json`. -You can specify whether desktop notifications shall be send when changing the mode using the `notify` flag. -If specified, you can override `sudo` to provide another program that accepts commands to be run as root without a password -by the current user to elevate privileges, such as *doas*. - -```json -{ - "tablet": [ - "/dev/input/by-path/platform-i8042-serio-0-event-kbd", - "/dev/input/by-path/platform-i8042-serio-1-event-mouse" - ], - "notify": false, - "sudo": "/usr/bin/doas" -} -``` +Just run `install.sh` and enter the name of you local user account. ## Usage -You must be a member of the group `tablet` to toggle between tablet and laptop mode. You can toggle between tablet and laptop mode by running `setsysmode toggle` or use the desktop icon provided with this package. From 902e5bebe4a68a79d708f7af74b7e9dd2ab1909b Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 16 Jul 2023 12:23:18 +0200 Subject: [PATCH 18/21] Hint about OS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cdc0bc5..a3bbed6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Allow users to toggle a Chuwi Minibook laptop between laptop and tablet mode. ## Installation -Just run `install.sh` and enter the name of you local user account. +Just run `install.sh` on you Debian system and enter the name of you local user account. ## Usage From 45b5790e79f7f79ed69471b60322d7534b2a78e1 Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 16 Jul 2023 12:29:02 +0200 Subject: [PATCH 19/21] Add hint about reboot --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a3bbed6..5cbe1cd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Allow users to toggle a Chuwi Minibook laptop between laptop and tablet mode. ## Installation -Just run `install.sh` on you Debian system and enter the name of you local user account. +Just run `install.sh` on your Debian system and enter the name of you local user account. +Reboot after successful installation. ## Usage From 5a8657d7ca681e14a6fd8682bd6fd8369ed28ada Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 16 Jul 2023 12:31:00 +0200 Subject: [PATCH 20/21] Add link to upstream repo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5cbe1cd..db11fd8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Chuwi Minibook tablet mode switch Allow users to toggle a Chuwi Minibook laptop between laptop and tablet mode. +This is based on https://github.com/conqp/tablet-mode. ## Installation From 5488f154e1e047c1f74d736d09df8e871a8b9fd6 Mon Sep 17 00:00:00 2001 From: phil Date: Sun, 16 Jul 2023 13:03:10 +0200 Subject: [PATCH 21/21] Only add group and user if they don't exist --- install.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index 66dc675..990b345 100755 --- a/install.sh +++ b/install.sh @@ -1,12 +1,20 @@ #!/usr/bin/env bash -echo "Add tablet group..." -/usr/sbin/groupadd tablet +set -eu + +GROUP=tablet + +if ! [ $(getent group "$GROUP") ]; then + echo "Add tablet group..." + /usr/sbin/groupadd tablet +fi read -p "Enter your local username: " user -echo "Add user to group..." -usermod -a -G tablet "$user" +if ! [ $(groupmems -g "$GROUP" -l | grep "$user" ) ]; then + echo "Add user to group..." + usermod -a -G tablet "$user" +fi echo "Copy files..." cp -r tabletmode/ /usr/local/lib/python3.11/dist-packages