From 99f7c0ea32aa73b3776bdf94be8ae805583a92ca Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 23 Oct 2024 11:28:13 +1000 Subject: [PATCH] database: support $XDG_CONFIG_HOME/libwacom as additional path This completes the traditional triplet of $XDG_CONFIG_HOME, /etc, and /usr/share for configuration files. Having custom .tablet files in $XDG_CONFIG_HOME makes it easier for immutable systems and also for backups that only back up the user home directory. --- .github/workflows/main.yml | 4 +++ libwacom/libwacom-database.c | 17 +++++++++- test/test_libwacom.py | 21 ++++++++++++ tools/libwacom-update-db.py | 66 +++++++++++++++++++++++++----------- tools/show-stylus.py | 7 +++- 5 files changed, 94 insertions(+), 21 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b54bbcd8..43f2c765 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -115,6 +115,9 @@ jobs: - sudo mv /usr/share/libwacom/wacom-intuos*.tablet /etc/libwacom - sudo mv /usr/share/libwacom/*.tablet /etc/libwacom - sudo mv /usr/share/libwacom/*.stylus /etc/libwacom + - sudo mv /usr/share/libwacom/*.tablet $HOME/.config/libwacom + - sudo mv /usr/share/libwacom/*.stylus $HOME/.config/libwacom + - sudo mv /usr/share/libwacom/wacom-*.tablet $HOME/.config/libwacom && sudo mv /usr/share/libwacom/huion-*.tablet /etc/libwacom # split the wacom.stylus file into to two files to check for # accumlated loading - sudo csplit data/wacom.stylus '/^\[0x822\]/' && sudo mv xx00 /etc/libwacom/first.stylus && sudo mv xx01 /usr/share/libwacom/wacom.stylus @@ -141,6 +144,7 @@ jobs: - name: list devices with database in /usr run: libwacom-list-devices --format=datafile > devicelist.default.txt - run: sudo mkdir /etc/libwacom + - run: sudo mkdir $HOME/.config/libwacom - name: split the databases between /usr/share and /etc run: ${{matrix.command}} - name: list devices with database in /etc and /usr diff --git a/libwacom/libwacom-database.c b/libwacom/libwacom-database.c index 83005ab3..028ebe77 100644 --- a/libwacom/libwacom-database.c +++ b/libwacom/libwacom-database.c @@ -1192,13 +1192,28 @@ libwacom_database_new_for_path (const char *datadir) LIBWACOM_EXPORT WacomDeviceDatabase * libwacom_database_new (void) { + WacomDeviceDatabase *db; + char *xdgdir = NULL; + char *xdg_config_home = g_strdup(g_getenv("XDG_CONFIG_HOME")); + + if (!xdg_config_home) + xdg_config_home = g_strdup_printf("%s/.config/", g_get_home_dir()); + + xdgdir = g_strdup_printf("%s/libwacom", xdg_config_home); + char *datadir[] = { + xdgdir, ETCDIR, DATADIR, NULL, }; - return database_new_for_paths(datadir); + db = database_new_for_paths(datadir); + + free(xdgdir); + free(xdg_config_home); + + return db; } LIBWACOM_EXPORT void diff --git a/test/test_libwacom.py b/test/test_libwacom.py index 5d344a0f..b8d50937 100644 --- a/test/test_libwacom.py +++ b/test/test_libwacom.py @@ -600,3 +600,24 @@ def test_nonwacom_stylus_ids(tmp_path): assert sum(s.vendor_id == 0x1234 and s.tool_id == 0x9876 for s in styli) == 1 assert sum(s.vendor_id == 0 and s.tool_id == 0xFFFFE for s in styli) == 1 assert sum(s.vendor_id == 0 and s.tool_id == 0xFFFFF for s in styli) == 1 + + +def test_load_xdg_config_home(monkeypatch, tmp_path, custom_datadir): + monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path.absolute())) + + xdg = tmp_path / "libwacom" + xdg.mkdir() + + usbid = (0x1234, 0x5678) + matches = [f"usb|{usbid[0]:04x}|{usbid[1]:04x}"] + TabletFile(name="XDGTablet", matches=matches).write_to(xdg / "uniq.tablet") + + StylusFile.default().write_to_dir(xdg) + + # This should load from system *and* XDG. system files could + # interfere with our test or may not exist but unfortunately we can't + # chroot for the test. it should be good enough this way anyway. + db = WacomDatabase() + builder = WacomBuilder.create(usbid=usbid) + device = db.new_from_builder(builder) + assert device is not None and device.name == "XDGTablet" diff --git a/tools/libwacom-update-db.py b/tools/libwacom-update-db.py index f9317de4..4e147787 100755 --- a/tools/libwacom-update-db.py +++ b/tools/libwacom-update-db.py @@ -23,11 +23,17 @@ import argparse import configparser +import os import sys import subprocess +import tempfile from pathlib import Path +def xdg_dir(): + return Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / "libwacom" + + class Tablet(object): def __init__(self, name, bus, vid, pid): self.name = name @@ -154,19 +160,20 @@ def _load(self, path): # Guess the udev directory based on path. For the case of /usr/share, the # udev directory is probably in /usr/lib so let's fallback to that. -def find_udev_base_dir(path): - for parent in path.parents: - d = Path(parent / "udev" / "rules.d") - if d.exists(): - return d.parent +def find_udev_base_dir(paths): + for path in paths: + for parent in path.parents: + d = Path(parent / "udev" / "rules.d") + if d.exists(): + return d.parent - # /usr/share but also any custom prefixes - for parent in path.parents: - d = Path(parent / "lib" / "udev" / "rules.d") - if d.exists(): - return d.parent + # /usr/share but also any custom prefixes + for parent in path.parents: + d = Path(parent / "lib" / "udev" / "rules.d") + if d.exists(): + return d.parent - raise FileNotFoundError(path) + raise FileNotFoundError(paths) # udev's behaviour is that where a file X exists in two locations, @@ -190,8 +197,8 @@ def guess_hwdb_filename(basedir): "path", nargs="?", type=Path, - default="/etc/libwacom", - help="Directory to load .tablet files from", + default=None, + help="Directory to load .tablet files from. Default: $XDG_CONFIG_HOME/libwacom and /etc/libwacom", ) # buildsystem-mode is what we use from meson, it changes the # the behavior to just generate the file and print it @@ -215,7 +222,14 @@ def guess_hwdb_filename(basedir): ) ns = parser.parse_args() - db = TabletDatabase(ns.path) + if ns.path is None: + paths = [xdg_dir(), Path("/etc/libwacom")] + else: + paths = [ns.path] + + # Reverse the list so the most important one is last and takes precedence + paths.reverse() + dbs = [TabletDatabase(p) for p in paths] hwdb = HWDBFile() # Bamboo and Intuos devices connected to the system via Wacom's @@ -230,20 +244,32 @@ def guess_hwdb_filename(basedir): wwak.has_touch = True hwdb.tablets.append(wwak) - hwdb.tablets.extend(db.tablets) + for db in dbs: + hwdb.tablets.extend(db.tablets) + if ns.buildsystem_mode: hwdb.print() else: + if os.geteuid() == 0: + print( + "WARNING: Running this command as root will not pick up .tablet files in $XDG_CONFIG_HOME/libwacom" + ) + try: - udevdir = ns.udev_base_dir or find_udev_base_dir(ns.path) + udevdir = ns.udev_base_dir or find_udev_base_dir(paths) hwdbfile = guess_hwdb_filename(udevdir) - with open(hwdbfile, "w") as fd: + + with tempfile.NamedTemporaryFile( + mode="w+", prefix=f"{hwdbfile.name}-XXXXXX", encoding="utf-8" + ) as fd: hwdb.print(fd) - print(f"New hwdb file: {hwdbfile}") + print(f"Using sudo to copy hwdb file to {hwdbfile}") + subprocess.run(["sudo", "cp", f"{fd.name}", hwdbfile.absolute()]) if not ns.skip_systemd_hwdb_update: + print("Using sudo to run systemd-hwdb update") subprocess.run( - ["systemd-hwdb", "update"], + ["sudo", "systemd-hwdb", "update"], capture_output=True, check=True, text=True, @@ -255,3 +281,5 @@ def guess_hwdb_filename(basedir): print(f"Unable to find udev base directory: {e}") except subprocess.CalledProcessError as e: print(f"hwdb update failed: {e.stderr}") + except KeyboardInterrupt: + pass diff --git a/tools/show-stylus.py b/tools/show-stylus.py index 26be13c4..79b54728 100755 --- a/tools/show-stylus.py +++ b/tools/show-stylus.py @@ -22,6 +22,7 @@ import argparse import configparser +import os import sys from pathlib import Path @@ -37,6 +38,10 @@ sys.exit(1) +def xdg_dir(): + return Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / "libwacom" + + class Ansi: clearline = "\x1b[K" @@ -133,7 +138,7 @@ def record_events(ns): def load_data_files(): lookup_paths = ( ("./data/",), - ("@DATADIR@", "@ETCDIR@"), + ("@DATADIR@", "@ETCDIR@", xdg_dir()), ("/usr/share/libwacom/", "/etc/libwacom/"), ) stylusfiles = []