diff --git a/README.md b/README.md index db11fd8..de5a07e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ -# Chuwi Minibook tablet mode switch +# tablet-mode -Allow users to toggle a Chuwi Minibook laptop between laptop and tablet mode. -This is based on https://github.com/conqp/tablet-mode. +Allow users to toggle a convertible laptop between laptop and tablet mode. -## Installation +## Configuration -Just run `install.sh` on your Debian system and enter the name of you local user account. -Reboot after successful installation. +The keyboard device 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 + touchpad = /dev/input/by-path/platform-i8042-serio-1-event-mouse + ## Usage -You can toggle between tablet and laptop mode by running `setsysmode toggle` or use the desktop icon provided with this package. +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. diff --git a/install.sh b/install.sh deleted file mode 100755 index 990b345..0000000 --- a/install.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -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 - -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 -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 deleted file mode 100644 index 93e4f27..0000000 --- a/laptop-mode.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Configure system for laptop mode -Conflicts=tablet-mode.service - -[Service] -ExecStart=/usr/local/bin/sysmoded laptop -StandardOutput=null - -[Install] -WantedBy=multi-user.target diff --git a/setsysmode b/setsysmode deleted file mode 100644 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 deleted file mode 100755 index 51f156a..0000000 --- a/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -#! /usr/bin/env python - -from setuptools import setup - -setup( - name='tabletmode', - use_scm_version=True, - setup_requires=['setuptools_scm'], - author='Richard Neumann', - author_email='mail@richard-neumann.de', - python_requires='>=3.8', - packages=['tabletmode'], - 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.', - long_description=open('README.md').read(), - long_description_content_type="text/markdown", - keywords='tablet mode tent convertible switch' -) diff --git a/sysmoded b/sysmoded deleted file mode 100644 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() diff --git a/tablet-mode b/tablet-mode new file mode 100755 index 0000000..d4b5c82 --- /dev/null +++ b/tablet-mode @@ -0,0 +1,48 @@ +#! /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 2c3a375..1d45f12 100644 --- a/tablet-mode.desktop +++ b/tablet-mode.desktop @@ -2,6 +2,6 @@ Comment=Toggle tablet mode Terminal=false Name=Tablet Mode -Exec=/usr/local/bin/setsysmode toggle +Exec=/usr/bin/toggle-tablet-mode Type=Application Icon=pda-symbolic diff --git a/tablet-mode.json b/tablet-mode.json deleted file mode 100644 index 14f657e..0000000 --- a/tablet-mode.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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 fc893e7..951fb4a 100644 --- a/tablet-mode.service +++ b/tablet-mode.service @@ -1,10 +1,6 @@ [Unit] -Description=Configure system for tablet mode -Conflicts=laptop-mode.service +Description=Disable keyboard and touchpad for tablet mode [Service] -ExecStart=/usr/local/bin/sysmoded tablet +ExecStart=/usr/bin/tablet-mode StandardOutput=null - -[Install] -WantedBy=multi-user.target diff --git a/tablet-mode.sudoers b/tablet-mode.sudoers index e591d1d..c49c8f5 100644 --- a/tablet-mode.sudoers +++ b/tablet-mode.sudoers @@ -1,13 +1 @@ -# 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 +%tablet ALL=(ALL) NOPASSWD: /usr/bin/systemctl start tablet-mode.service, /usr/bin/systemctl stop tablet-mode.service diff --git a/tabletmode/__init__.py b/tabletmode/__init__.py deleted file mode 100644 index a6deee4..0000000 --- a/tabletmode/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tablet mode library.""" diff --git a/tabletmode/cli.py b/tabletmode/cli.py deleted file mode 100644 index c20e898..0000000 --- a/tabletmode/cli.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Sets the system mode.""" - -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_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: - """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: str, unit: str, *, root: bool = False, - sudo: str = SUDO) -> bool: - """Runs systemctl.""" - - command = [sudo] if root else [] - command += ['systemctl', action, unit] - - try: - check_call(command, stdout=DEVNULL) # Return 0 on success. - except CalledProcessError: - return False - - return True - - -def notify_send(summary: str, body: Optional[str] = None) -> CompletedProcess: - """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() -> CompletedProcess: - """Notifies about laptop mode.""" - - return notify_send('Laptop mode.', 'The system is now in laptop 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: bool = False, *, sudo: str = SUDO) -> None: - """Restores all blocked input devices.""" - - 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, *, sudo: str = SUDO) -> None: - """Starts the laptop mode.""" - - 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, *, sudo: str = SUDO) -> None: - """Starts the tablet mode.""" - - 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, *, sudo: str = SUDO) -> None: - """Toggles between laptop and tablet mode.""" - - if systemctl('status', TABLET_MODE_SERVICE): - laptop_mode(notify=notify, sudo=sudo) - else: - tablet_mode(notify=notify, sudo=sudo) - - -def main() -> None: - """Runs the main program.""" - - 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, sudo=sudo) - elif args.mode == 'default': - default_mode(notify=notify, sudo=sudo) - elif args.mode == 'laptop': - laptop_mode(notify=notify, sudo=sudo) - elif args.mode == 'tablet': - tablet_mode(notify=notify, sudo=sudo) - else: - print('Must specify a mode.', file=stderr, flush=True) diff --git a/tabletmode/config.py b/tabletmode/config.py deleted file mode 100644 index 8c6c928..0000000 --- a/tabletmode/config.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Configuration file parsing.""" - -from json import load -from logging import getLogger -from pathlib import Path - - -__all__ = ['load_config'] - - -CONFIG_FILE = Path('/etc/tablet-mode.json') -LOGGER = getLogger('tabletmode') - - -def load_config() -> dict: - """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 deleted file mode 100644 index 0632be1..0000000 --- a/tabletmode/daemon.py +++ /dev/null @@ -1,68 +0,0 @@ -"""System mode daemon.""" - -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_config - - -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_args() -> Namespace: - """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: str) -> Popen: - """Disables the respective device via evtest.""" - - return Popen((EVTEST, '--grab', device)) - - -def disable_devices(devices: Iterable[str]) -> None: - """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: str) -> Iterable[str]: - """Reads the device from the config file.""" - - config = load_config() - devices = config.get(mode) or () - - if not devices: - LOGGER.info('No devices configured to disable.') - - return devices - - -def main(): - """Runs the main program.""" - - arguments = get_args() - level = DEBUG if arguments.verbose else INFO - basicConfig(level=level, format=LOG_FORMAT) - devices = get_devices(arguments.mode) - disable_devices(devices) diff --git a/toggle-tablet-mode b/toggle-tablet-mode new file mode 100755 index 0000000..1c535bd --- /dev/null +++ b/toggle-tablet-mode @@ -0,0 +1,10 @@ +#! /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 +