Skip to content

Commit

Permalink
add udev rules
Browse files Browse the repository at this point in the history
Now that we support managing three different kinds of hardware, I think
it's time for us to ship our own udev rules + script, instead of asking
people to paste a random script and udev rules in each of the widget docs.
Hopefully this means things will Just Work for people installing via distro
packaging, but because of virtualenvs, people installing from source may
have to do a bit of extra fiddling.

(Is there a better way than adding another script? Like a virtualenv one
liner that will set up the virtual env and invoke qtile?)

Signed-off-by: Tycho Andersen <tycho@tycho.pizza>
  • Loading branch information
tych0 committed Feb 25, 2024
1 parent 9a935a6 commit 8594323
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 46 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ Qtile x.xx.x, released XXXX-XX-XX:
* features
- The Battery widget now supports dynamic charge control, allowing for
protecting battery life.
- To support the above (plus the other widgets that modify sysfs), qtile
now ships with its own udev rules, located at
/resources/99-qtile.rules; distro packagers will probably want to
install this rule set.
* bugfixes
- Fix groups marked with `persist=False` not being deleted when their last window is moved to another group.

Expand Down
38 changes: 38 additions & 0 deletions docs/manual/install/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,41 @@ Similar to the xsession example above, a wayland session file can be used to sta
from a login manager. To use this, you should create a `qtile-wayland.desktop
<https://github.com/qtile/qtile/blob/master/resources/qtile-wayland.desktop>`_ file in
``/usr/share/wayland-sessions``.

udev rules
==========

Qtile has widgets that support managing various kinds of hardware (LCD
backlight, keyboard backlight, battery charge thresholds) via the kernel's
exposed sysfs endpoints. However, to make this work, Qtile needs permission to
write to these files. There is a udev rules file at
``/resources/99-qtile.rules`` in the tree, which users installing from source
will want to install at ``/etc/udev/rules.d/`` on their system. By default,
this rules file changes the group of the relevant files to the ``sudo`` group,
and changes the file mode to be g+w (i.e. writable by all members of the sudo
group). The theory here is that most systems qtile is installed on will also
have the primary user in the ``sudo`` group. However, you can change this to
whatever you like with the ``--group`` argument; see the sample udev rules.

Note that this file invokes Qtile's hidden ``udev`` from udevd, so udevd will
need ``qtile`` in its ``$PATH``. For distro packaging this shouldn't be a
problem, since /usr/bin is typically in udev's path. However, for users that
installed from source, you may need to modify the udev script to be one that
sources your virtualenv and then invokes qtile (or just invoke it via its
hardcoded path if you installed it with ``--break-system-packages``), e.g.:

.. code-block:: bash
# create a wrapper script that loads the right stuff from our home directory; since
# udev will run this script as root, it has no idea about how we've installed qtile
mkdir -p $HOME/.local/bin
tee $HOME/.local/bin/qtile-udev-wrapper <<- EOF
#!/bin/sh
export PYTHONPATH=$HOME/.local/lib/python$(python3 --version | awk -F '[ .]' '{print $2 "." $3}')/site-packages
$HOME/.local/bin/qtile $@
EOF
# copy the in-tree udev rules file to the right place to make udev see it,
# and change the rules to point at our wrapper script above.
sed "s,qtile,$HOME/.local/bin/qtile-udev-wrapper,g" ./resources/99-qtile.rules | sudo tee /etc/udev/rules.d/99-qtile.rules
3 changes: 2 additions & 1 deletion libqtile/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path

from libqtile.log_utils import get_default_log, init_log
from libqtile.scripts import check, cmd_obj, migrate, run_cmd, shell, start, top
from libqtile.scripts import check, cmd_obj, migrate, run_cmd, shell, start, top, udev

try:
# Python>3.7 can get the version from importlib
Expand Down Expand Up @@ -61,6 +61,7 @@ def main():
cmd_obj.add_subcommand(subparsers, [parent_parser])
check.add_subcommand(subparsers, [parent_parser])
migrate.add_subcommand(subparsers, [parent_parser])
udev.add_subcommand(subparsers, [parent_parser])

# `qtile help` should print help
def print_help(options):
Expand Down
43 changes: 43 additions & 0 deletions libqtile/scripts/udev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import argparse
import glob
import os
import shutil
import stat

MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH


def set_file_perms(p, options):
try:
os.chmod(p, MODE)
shutil.chown(p, user=None, group=options.group)
except FileNotFoundError:
pass


def do_backlight_setup(options):
set_file_perms("/sys/class/backlight/{}/brightness".format(options.device), options)
set_file_perms("/sys/class/leds/{}/brightness".format(options.device), options)


def do_battery_setup(options):
files = glob.glob("/sys/class/power_supply/BAT*/charge_control_*_threshold")
for file in files:
set_file_perms(file, options)


def udev(options):
if options.kind == "backlight":
do_backlight_setup(options)
elif options.kind == "battery":
do_battery_setup(options)
else:
raise "Unknown udev option {}".format(options.kind)


def add_subcommand(subparsers, parents):
parser = subparsers.add_parser("udev", parents=parents, help=argparse.SUPPRESS)
parser.add_argument("kind", choices=["backlight", "battery"])
parser.add_argument("--device")
parser.add_argument("--group", default="sudo")
parser.set_defaults(func=udev)
9 changes: 3 additions & 6 deletions libqtile/widget/backlight.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,9 @@ class Backlight(base.InLoopPollText):
"""A simple widget to show the current brightness of a monitor.
If the change_command parameter is set to None, the widget will attempt to
use the interface at /sys/class to change brightness. Depending on the
setup, the user may need to be added to the video group to have permission
to write to this interface. This depends on having the correct udev rules
the brightness file; these are typically installed alongside brightness
tools such as brightnessctl (which changes the group to 'video') so
installing that is an easy way to get it working.
use the interface at /sys/class to change brightness. This depends on
having the correct udev rules, so be sure Qtile's udev rules are installed
correctly.
You can also bind keyboard shortcuts to the backlight widget with:
Expand Down
41 changes: 2 additions & 39 deletions libqtile/widget/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,45 +440,8 @@ class Battery(base.ThreadPoolText):
Key([mod, "shift"], "x", lazy.widget['battery'].charge_dynamically())
note that this functionality requires qtile to be able to write to certain
files in sysfs. The easiest way to persist this across reboots is via a
udev rule that sets g+w and ownership of the relevant files to the `sudo`
group, assuming the user qtile runs as is in that group.
This is slightly complicated, since the chage_control_{start,end}_threshold
files are not created by the device driver itself, but by the particular
ACPI module for your laptop. If we try to do the chown/chmod when the
device is added in udev, the files won't be present yet. So, we have to do
it when the ACPI module for the laptop is loaded.
For thinkpads, the udev rule looks like:
.. code-block:: bash
cat <<'EOF' | sudo tee /etc/udev/rules.d/99-qtile-battery.rules
ACTION=="add" KERNEL=="thinkpad_acpi" RUN+="/home/tycho/config/bin/qtile-battery"
EOF
and the qtile-battery script looks like:
.. code-block:: bash
#!/bin/bash -eu
GROUP=sudo
die() {
echo "$@"
exit 1
}
set_ownership() {
chgrp "$GROUP" $1 2>&1
chmod g+w $1
}
[ $# -eq 0 ] || die "Usage: $0"
set_ownership /sys/class/power_supply/BAT*/charge_control_end_threshold
set_ownership /sys/class/power_supply/BAT*/charge_control_start_threshold
files in sysfs, so make sure that qtile's udev rules are installed
correctly.
"""

background: ColorsType | None
Expand Down
26 changes: 26 additions & 0 deletions resources/99-qtile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# for controlling LCD backlight
ACTION=="add", SUBSYSTEM=="backlight", RUN+="qtile udev --group sudo backlight --device %k"

# keyboard backlight
ACTION=="add", SUBSYSTEM=="leds", RUN+="qtile udev --group sudo backlight --device %k"

# fancy battery charge control, needs to be per ACPI implementation, so we need
# to periodically check the kernel for more of these:
#
# $ ~/packages/linux/drivers/platform/x86 master git grep -l charge_control_end_threshold
# asus-wmi.c
# huawei-wmi.c
# lg-laptop.c
# msi-ec.c
# system76_acpi.c
# thinkpad_acpi.c
# toshiba_acpi.c
#
# Last checked as of 6.8-rc4.
ACTION=="add" KERNEL=="asus-wmi" RUN+="qtile udev --group sudo battery"
ACTION=="add" KERNEL=="huawei-wmi" RUN+="qtile udev --group sudo battery"
ACTION=="add" KERNEL=="lg-laptop" RUN+="qtile udev --group sudo battery"
ACTION=="add" KERNEL=="msi-ec" RUN+="qtile udev --group sudo battery"
ACTION=="add" KERNEL=="thinkpad_acpi" RUN+="qtile udev --group sudo battery"
ACTION=="add" KERNEL=="system76_acpi" RUN+="qtile udev --group sudo battery"
ACTION=="add" KERNEL=="toshiba_acpi" RUN+="qtile udev --group sudo battery"
4 changes: 4 additions & 0 deletions resources/README
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ objgraph.dot: The Qtile command graph, in graphviz dot format. Render like so:

qtile.desktop: A desktop file for qtile telling session managers about qtile
and how to run it. This file should be installed to /usr/share/xsessions

qtile-wayland.desktop: A desktop file to start qtile in wayland mode.

99-qtile.rules: udev rules for hardware that qtile can manage

0 comments on commit 8594323

Please sign in to comment.