From 8f45850b7d5e93ed5c9fa36538664b41cc5fa986 Mon Sep 17 00:00:00 2001 From: Jacob Mink <152457076+jacob-vincent-mink@users.noreply.github.com> Date: Thu, 7 May 2026 10:16:22 -0500 Subject: [PATCH] Package Dell XPS touchpad haptics (#86) * feature: Add Dell haptic touchpad control, update default intensity * fix: polish Dell haptic touchpad package Use an MIT license file for the packaged scripts and prefer the active loginctl user in the install hook so the initial intensity setup targets the live desktop session instead of an arbitrary regular account. * fix: clean up Dell haptic touchpad package metadata Drop the bundled license file and align the package metadata so the package contents still build cleanly after the maintainer update. * fix: correct Dell haptic touchpad package URL Point the package metadata at the canonical omarchy-pkgs repository so users see the source location maintained under the omacom-io project. * feature: package Dell XPS haptics as standalone service * fix: tighten Dell haptics package setup --- .../edge/dell-xps-touchpad-haptics/.SRCINFO | 27 ++ .../99-dell-xps-touchpad-haptics.rules | 2 + .../edge/dell-xps-touchpad-haptics/PKGBUILD | 32 ++ .../README.package.md | 40 ++ .../dell-xps-touchpad-haptics | 126 ++++++ .../dell-xps-touchpad-haptics-daemon | 381 ++++++++++++++++++ .../dell-xps-touchpad-haptics.install | 248 ++++++++++++ .../dell-xps-touchpad-haptics.service | 14 + 8 files changed, 870 insertions(+) create mode 100644 pkgbuilds/edge/dell-xps-touchpad-haptics/.SRCINFO create mode 100644 pkgbuilds/edge/dell-xps-touchpad-haptics/99-dell-xps-touchpad-haptics.rules create mode 100644 pkgbuilds/edge/dell-xps-touchpad-haptics/PKGBUILD create mode 100644 pkgbuilds/edge/dell-xps-touchpad-haptics/README.package.md create mode 100755 pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics create mode 100755 pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics-daemon create mode 100644 pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install create mode 100644 pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.service diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/.SRCINFO b/pkgbuilds/edge/dell-xps-touchpad-haptics/.SRCINFO new file mode 100644 index 00000000..6964772a --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/.SRCINFO @@ -0,0 +1,27 @@ +pkgbase = dell-xps-touchpad-haptics + pkgdesc = Synaptics haptic touchpad presets for Dell XPS on Omarchy + pkgver = 1.0.0 + pkgrel = 3 + url = https://github.com/omacom-io/omarchy-pkgs + install = dell-xps-touchpad-haptics.install + arch = x86_64 + license = MIT + depends = python + provides = omarchy-dell-haptic-touchpad + conflicts = omarchy-dell-haptic-touchpad + replaces = omarchy-dell-haptic-touchpad + options = !debug + source = 99-dell-xps-touchpad-haptics.rules + source = README.package.md + source = dell-xps-touchpad-haptics + source = dell-xps-touchpad-haptics-daemon + source = dell-xps-touchpad-haptics.install + source = dell-xps-touchpad-haptics.service + sha256sums = SKIP + sha256sums = SKIP + sha256sums = SKIP + sha256sums = SKIP + sha256sums = SKIP + sha256sums = SKIP + +pkgname = dell-xps-touchpad-haptics diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/99-dell-xps-touchpad-haptics.rules b/pkgbuilds/edge/dell-xps-touchpad-haptics/99-dell-xps-touchpad-haptics.rules new file mode 100644 index 00000000..c917bf17 --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/99-dell-xps-touchpad-haptics.rules @@ -0,0 +1,2 @@ +ACTION=="add", SUBSYSTEM=="pci", KERNEL=="0000:00:19.0", ATTR{power/control}="on" +ACTION=="add", SUBSYSTEM=="platform", KERNEL=="i2c_designware.0", ATTR{power/control}="on" diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/PKGBUILD b/pkgbuilds/edge/dell-xps-touchpad-haptics/PKGBUILD new file mode 100644 index 00000000..68d9967f --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/PKGBUILD @@ -0,0 +1,32 @@ +# Maintainer: Jacob Mink + +pkgname=dell-xps-touchpad-haptics +pkgver=1.0.0 +pkgrel=3 +pkgdesc="Synaptics haptic touchpad presets for Dell XPS on Omarchy" +arch=('x86_64') +url="https://github.com/omacom-io/omarchy-pkgs" +license=('MIT') +depends=('python') +conflicts=('omarchy-dell-haptic-touchpad') +provides=('omarchy-dell-haptic-touchpad') +replaces=('omarchy-dell-haptic-touchpad') +install=dell-xps-touchpad-haptics.install +options=('!debug') +source=( + '99-dell-xps-touchpad-haptics.rules' + 'README.package.md' + 'dell-xps-touchpad-haptics' + 'dell-xps-touchpad-haptics-daemon' + 'dell-xps-touchpad-haptics.install' + 'dell-xps-touchpad-haptics.service' +) +sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP') + +package() { + install -Dm644 99-dell-xps-touchpad-haptics.rules "${pkgdir}/usr/lib/udev/rules.d/99-dell-xps-touchpad-haptics.rules" + install -Dm755 dell-xps-touchpad-haptics "${pkgdir}/usr/bin/dell-xps-touchpad-haptics" + install -Dm755 dell-xps-touchpad-haptics-daemon "${pkgdir}/usr/bin/dell-xps-touchpad-haptics-daemon" + install -Dm644 dell-xps-touchpad-haptics.service "${pkgdir}/usr/lib/systemd/system/dell-xps-touchpad-haptics.service" + install -Dm644 README.package.md "${pkgdir}/usr/share/doc/${pkgname}/README.md" +} diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/README.package.md b/pkgbuilds/edge/dell-xps-touchpad-haptics/README.package.md new file mode 100644 index 00000000..13b41719 --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/README.package.md @@ -0,0 +1,40 @@ +# dell-xps-touchpad-haptics + +This package installs the Dell XPS Synaptics haptic touchpad daemon, its +systemd service, and a small CLI for switching between Omarchy's preset haptic +levels. + +What it installs: + +- `/usr/bin/dell-xps-touchpad-haptics` +- `/usr/bin/dell-xps-touchpad-haptics-daemon` +- `/usr/lib/systemd/system/dell-xps-touchpad-haptics.service` +- `/usr/lib/udev/rules.d/99-dell-xps-touchpad-haptics.rules` + +What the pacman install hook does: + +- picks a desktop user and writes `/etc/dell-xps-touchpad-haptics.env` +- creates `~/.config/omarchy/dell-haptic.conf` if needed +- enables the service and restarts it when a running systemd manager is available + +Usage: + +```bash +dell-xps-touchpad-haptics get +dell-xps-touchpad-haptics set low +dell-xps-touchpad-haptics set mid +dell-xps-touchpad-haptics set high +``` + +Preset levels: + +- `low` = `10` +- `mid` = `50` +- `high` = `100` + +Debugging: + +- level changes are logged by the service when it notices the config change and + attempts HID gain readback +- set `DELL_XPS_TOUCHPAD_HAPTICS_DEBUG=1` in `/etc/dell-xps-touchpad-haptics.env` + to log each press and release report to `journalctl -u dell-xps-touchpad-haptics.service` diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics new file mode 100755 index 00000000..19cd7df1 --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics @@ -0,0 +1,126 @@ +#!/bin/bash + +set -euo pipefail + +CONFIG_PATH="$HOME/.config/omarchy/dell-haptic.conf" +DEFAULT_INTENSITY=100 +LOW_INTENSITY=10 +MID_INTENSITY=50 +HIGH_INTENSITY=100 +SERVICE_NAME="dell-xps-touchpad-haptics.service" + +usage() { + printf 'Usage: %s get | set \n' "$(basename -- "$0")" +} + +clamp_intensity() { + local value=$1 + + if ! [[ $value =~ ^[0-9]+$ ]]; then + printf '%s\n' "$DEFAULT_INTENSITY" + return 0 + fi + + if (( value < 0 )); then + printf '0\n' + elif (( value > 100 )); then + printf '100\n' + else + printf '%s\n' "$value" + fi +} + +read_intensity() { + local line="" + local value="" + + [[ -f $CONFIG_PATH ]] || { + printf '%s\n' "$DEFAULT_INTENSITY" + return 0 + } + + while IFS= read -r line; do + line=${line#${line%%[![:space:]]*}} + line=${line%${line##*[![:space:]]}} + [[ -z $line || ${line:0:1} == "#" || $line != *=* ]] && continue + + if [[ ${line%%=*} == "INTENSITY" ]]; then + value=${line#*=} + value=${value#${value%%[![:space:]]*}} + value=${value%${value##*[![:space:]]}} + clamp_intensity "$value" + return 0 + fi + done < "$CONFIG_PATH" + + printf '%s\n' "$DEFAULT_INTENSITY" +} + +level_for_intensity() { + local intensity=$1 + + if (( intensity <= 30 )); then + printf 'low\n' + elif (( intensity < 75 )); then + printf 'mid\n' + else + printf 'high\n' + fi +} + +intensity_for_level() { + case "$1" in + low) printf '%s\n' "$LOW_INTENSITY" ;; + mid | medium) printf '%s\n' "$MID_INTENSITY" ;; + high) printf '%s\n' "$HIGH_INTENSITY" ;; + *) + echo "Haptic level must be one of: low, mid, high." >&2 + exit 1 + ;; + esac +} + +write_config() { + local intensity=$1 + + install -d -m 755 "$(dirname -- "$CONFIG_PATH")" + printf '# Dell XPS touchpad haptic intensity.\n# Presets: low=10, mid=50, high=100.\nINTENSITY=%s\n' "$intensity" >"$CONFIG_PATH" +} + +service_active() { + systemctl is-active --quiet "$SERVICE_NAME" 2>/dev/null +} + +case "${1:-}" in +get) + if (( $# != 1 )); then + usage >&2 + exit 1 + fi + + level_for_intensity "$(read_intensity)" + ;; +set) + if (( $# != 2 )); then + usage >&2 + exit 1 + fi + + previous_level=$(level_for_intensity "$(read_intensity)") + intensity=$(intensity_for_level "$2") + write_config "$intensity" + + if service_active; then + printf 'Set Dell XPS touchpad haptics from %s to %s. The running service will apply and verify the new gain in the journal shortly.\n' "$previous_level" "$(level_for_intensity "$intensity")" + else + printf 'Set Dell XPS touchpad haptics from %s to %s. Start %s to apply it.\n' "$previous_level" "$(level_for_intensity "$intensity")" "$SERVICE_NAME" + fi + ;; +--help | -h) + usage + ;; +*) + usage >&2 + exit 1 + ;; +esac diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics-daemon b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics-daemon new file mode 100755 index 00000000..74b7727c --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics-daemon @@ -0,0 +1,381 @@ +#!/usr/bin/env python3 + +"""Haptic feedback daemon for Synaptics touchpads with Manual Trigger.""" + +import fcntl +import glob +import os +import select +import signal +import struct +import sys +import time +import errno +from pathlib import Path + +VENDOR = "06CB" +REPORT_ID = 0x37 +DEFAULT_INTENSITY = 100 +DEBUG = os.environ.get("DELL_XPS_TOUCHPAD_HAPTICS_DEBUG", "").lower() not in ( + "", + "0", + "false", + "no", +) +USER_HOME = Path( + os.environ.get("DELL_XPS_TOUCHPAD_HAPTICS_HOME", str(Path.home())) +) +CONFIG_PATH = USER_HOME / ".config/omarchy/dell-haptic.conf" + +EVENT_FORMAT = "llHHi" +EVENT_SIZE = struct.calcsize(EVENT_FORMAT) +EV_KEY = 0x01 +BTN_LEFT = 272 +BTN_RIGHT = 273 +BTN_MIDDLE = 274 +BUTTON_NAMES = { + BTN_LEFT: "left", + BTN_RIGHT: "right", + BTN_MIDDLE: "middle", +} +DEVICE_GONE_ERRNOS = { + errno.ENODEV, + errno.ENXIO, + errno.EIO, +} +UNSUPPORTED_READBACK_ERRNOS = { + getattr(errno, "EREMOTEIO", 121), + errno.EINVAL, + errno.EOPNOTSUPP, +} +READBACK_UNSUPPORTED = object() + +STATE = { + "apply_requested": None, + "intensity": DEFAULT_INTENSITY, + "config_mtime_ns": None, + "readback_supported": None, + "report": struct.pack("BB", REPORT_ID, DEFAULT_INTENSITY), +} + + +class DeviceDisconnected(Exception): + pass + + +def HIDIOCSFEATURE(length): + return 0xC0000000 | (length << 16) | (ord("H") << 8) | 0x06 + + +def HIDIOCGFEATURE(length): + return 0xC0000000 | (length << 16) | (ord("H") << 8) | 0x07 + + +def clamp_intensity(value): + return max(0, min(100, value)) + + +def level_for_intensity(intensity): + if intensity <= 30: + return "low" + if intensity < 75: + return "mid" + return "high" + + +def log(message): + print(message, flush=True) + + +def log_debug(message): + if DEBUG: + log(message) + + +def load_intensity(): + try: + raw = CONFIG_PATH.read_text(encoding="utf-8") + except OSError: + return DEFAULT_INTENSITY + + for line in raw.splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + + key, value = (part.strip() for part in line.split("=", 1)) + if key != "INTENSITY": + continue + + try: + return clamp_intensity(int(value)) + except ValueError: + print( + f"Ignoring invalid intensity in {CONFIG_PATH}: {value!r}; using default {DEFAULT_INTENSITY}", + file=sys.stderr, + flush=True, + ) + return DEFAULT_INTENSITY + + return DEFAULT_INTENSITY + + +def config_mtime_ns(): + try: + return CONFIG_PATH.stat().st_mtime_ns + except OSError: + return None + + +def refresh_intensity(signum=None, _frame=None, force=False): + mtime_ns = config_mtime_ns() + previous_intensity = STATE["intensity"] + previous_mtime_ns = STATE["config_mtime_ns"] + + if not force and signum != signal.SIGHUP and mtime_ns == STATE["config_mtime_ns"]: + return + + intensity = load_intensity() + STATE["intensity"] = intensity + STATE["config_mtime_ns"] = mtime_ns + STATE["report"] = struct.pack("BB", REPORT_ID, intensity) + + if force: + reason = "startup" + elif signum == signal.SIGHUP: + reason = "sighup" + else: + reason = "config-change" + + if ( + force + or signum == signal.SIGHUP + or mtime_ns != previous_mtime_ns + or intensity != previous_intensity + ): + log( + f"Loaded haptic level={level_for_intensity(intensity)} intensity={intensity} reason={reason} config={CONFIG_PATH}" + ) + + return True + + return False + + +def request_refresh(signum, _frame): + refresh_intensity(signum=signum) + STATE["apply_requested"] = "sighup" + + +def send_haptic_report(hidraw_fd, ioctl_req, report): + try: + fcntl.ioctl(hidraw_fd, ioctl_req, report) + return True + except OSError as error: + if error.errno in DEVICE_GONE_ERRNOS: + raise DeviceDisconnected(error) from error + + print(f"Failed to send haptic report: {error}", file=sys.stderr, flush=True) + return False + + +def read_haptic_report(hidraw_fd, ioctl_req): + if STATE["readback_supported"] is False: + return READBACK_UNSUPPORTED + + report = bytearray((REPORT_ID, 0)) + + try: + fcntl.ioctl(hidraw_fd, ioctl_req, report, True) + except OSError as error: + if error.errno in DEVICE_GONE_ERRNOS: + raise DeviceDisconnected(error) from error + + if error.errno in UNSUPPORTED_READBACK_ERRNOS: + if STATE["readback_supported"] is not False: + log( + f"Haptic gain readback is unsupported on this device ({error}); continuing in write-only mode" + ) + STATE["readback_supported"] = False + return READBACK_UNSUPPORTED + + print(f"Failed to read haptic report: {error}", file=sys.stderr, flush=True) + return None + + STATE["readback_supported"] = True + return report[1] + + +def apply_current_report(hidraw_fd, ioctl_set_req, ioctl_get_req, reason): + intensity = STATE["intensity"] + + if not send_haptic_report(hidraw_fd, ioctl_set_req, STATE["report"]): + log(f"Failed to apply haptic level={level_for_intensity(intensity)} intensity={intensity} reason={reason}") + return False + + readback = read_haptic_report(hidraw_fd, ioctl_get_req) + if readback is READBACK_UNSUPPORTED: + log( + f"Applied haptic level={level_for_intensity(intensity)} intensity={intensity} reason={reason} readback=unsupported" + ) + return True + + if readback is None: + log( + f"Applied haptic level={level_for_intensity(intensity)} intensity={intensity} reason={reason} readback=unavailable" + ) + return True + + readback_level = level_for_intensity(readback) + if readback == intensity: + log( + f"Verified haptic level={readback_level} intensity={intensity} reason={reason} readback={readback}" + ) + else: + log( + f"Haptic gain mismatch expected={intensity}({level_for_intensity(intensity)}) readback={readback}({readback_level}) reason={reason}" + ) + + return True + + +def find_hidraw(): + for path in sorted(glob.glob("/sys/class/hidraw/hidraw*")): + uevent = os.path.join(path, "device", "uevent") + try: + with open(uevent, encoding="utf-8") as file: + content = file.read().upper() + if f"0000{VENDOR}" in content: + return os.path.join("/dev", os.path.basename(path)) + except OSError: + continue + return None + + +def find_touchpad_event(): + for path in sorted(glob.glob("/sys/class/input/event*/device/name")): + try: + with open(path, encoding="utf-8") as file: + name = file.read().strip().upper() + if VENDOR in name and "TOUCHPAD" in name: + event = path.split("/")[-3] + return os.path.join("/dev/input", event) + except OSError: + continue + return None + + +def open_device_pair(): + waiting_logged = False + + while True: + hidraw = find_hidraw() + event = find_touchpad_event() + + if not hidraw or not event: + if not waiting_logged: + log("Waiting for Synaptics haptic touchpad devices to appear") + waiting_logged = True + time.sleep(1) + continue + + hidraw_fd = None + event_fd = None + + try: + hidraw_fd = os.open(hidraw, os.O_RDWR) + event_fd = os.open(event, os.O_RDONLY) + return hidraw, event, hidraw_fd, event_fd + except OSError as error: + try: + os.close(hidraw_fd) + except Exception: + pass + try: + os.close(event_fd) + except Exception: + pass + + log(f"Retrying touchpad device open after {error}") + waiting_logged = True + time.sleep(1) + + +def main(): + refresh_intensity(force=True) + signal.signal(signal.SIGHUP, request_refresh) + + ioctl_set_req = HIDIOCSFEATURE(len(STATE["report"])) + ioctl_get_req = HIDIOCGFEATURE(len(STATE["report"])) + + while True: + hidraw_fd = None + event_fd = None + + try: + hidraw, event, hidraw_fd, event_fd = open_device_pair() + + log( + f"Haptic touchpad ready hidraw={hidraw} input={event} level={level_for_intensity(STATE['intensity'])} intensity={STATE['intensity']} debug={int(DEBUG)} config={CONFIG_PATH}" + ) + apply_current_report(hidraw_fd, ioctl_set_req, ioctl_get_req, "startup") + + while True: + if STATE["apply_requested"]: + apply_current_report( + hidraw_fd, + ioctl_set_req, + ioctl_get_req, + STATE["apply_requested"], + ) + STATE["apply_requested"] = None + + ready, _, _ = select.select([event_fd], [], [], 0.5) + if not ready: + if refresh_intensity(): + apply_current_report( + hidraw_fd, + ioctl_set_req, + ioctl_get_req, + "config-change", + ) + continue + + try: + data = os.read(event_fd, EVENT_SIZE) + except OSError as error: + if error.errno in DEVICE_GONE_ERRNOS: + raise DeviceDisconnected(error) from error + raise + + if len(data) < EVENT_SIZE: + continue + + _, _, ev_type, code, value = struct.unpack(EVENT_FORMAT, data) + if ev_type == EV_KEY and code in (BTN_LEFT, BTN_RIGHT, BTN_MIDDLE) and value in (0, 1): + if refresh_intensity(): + apply_current_report( + hidraw_fd, + ioctl_set_req, + ioctl_get_req, + "config-change", + ) + + if send_haptic_report(hidraw_fd, ioctl_set_req, STATE["report"]): + log_debug( + f"Applied haptic report button={BUTTON_NAMES.get(code, code)} event={'press' if value == 1 else 'release'} level={level_for_intensity(STATE['intensity'])} intensity={STATE['intensity']}" + ) + except DeviceDisconnected as error: + log(f"Touchpad device disappeared ({error}); waiting for it to return") + time.sleep(1) + except KeyboardInterrupt: + return + finally: + if event_fd is not None: + os.close(event_fd) + if hidraw_fd is not None: + os.close(hidraw_fd) + + +if __name__ == "__main__": + main() diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install new file mode 100644 index 00000000..587f96cc --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.install @@ -0,0 +1,248 @@ +_service_name="dell-xps-touchpad-haptics.service" +_default_level="high" +_env_path="/etc/dell-xps-touchpad-haptics.env" +_legacy_env_path="/etc/omarchy-dell-haptic-touchpad.env" +_legacy_override_dir="/etc/systemd/system/dell-xps-haptic-touchpad.service.d" + +_existing_home() { + local line value + + [[ -f $_env_path ]] || return 0 + + while IFS= read -r line; do + line=${line#${line%%[![:space:]]*}} + line=${line%${line##*[![:space:]]}} + [[ -z $line || ${line:0:1} == "#" || $line != *=* ]] && continue + + if [[ ${line%%=*} == "DELL_XPS_TOUCHPAD_HAPTICS_HOME" ]]; then + value=${line#*=} + value=${value#${value%%[![:space:]]*}} + value=${value%${value##*[![:space:]]}} + printf '%s\n' "$value" + return 0 + fi + done < "$_env_path" +} + +_resolve_target_user() { + if [[ -n ${OMARCHY_HAPTIC_USER:-} && ${OMARCHY_HAPTIC_USER} != "root" ]]; then + printf '%s\n' "$OMARCHY_HAPTIC_USER" + return 0 + fi + + if [[ -n ${SUDO_USER:-} && ${SUDO_USER} != "root" ]]; then + printf '%s\n' "$SUDO_USER" + return 0 + fi + + if command -v logname >/dev/null 2>&1; then + local login_name + login_name=$(logname 2>/dev/null || true) + if [[ -n $login_name && $login_name != "root" ]]; then + printf '%s\n' "$login_name" + return 0 + fi + fi + + if command -v loginctl >/dev/null 2>&1; then + local active_user + active_user=$(loginctl list-users --no-legend 2>/dev/null | awk '$2 != "root" && $4 == "active" { print $2; exit }') + if [[ -n $active_user ]]; then + printf '%s\n' "$active_user" + return 0 + fi + fi + + getent passwd | awk -F: '$3 >= 1000 && $1 != "nobody" && $7 !~ /(nologin|false)$/ { print $1; exit }' +} + +_user_home() { + getent passwd "$1" | cut -d: -f6 +} + +_debug_value() { + local line value + + [[ -f $_env_path ]] || return 0 + + while IFS= read -r line; do + line=${line#${line%%[![:space:]]*}} + line=${line%${line##*[![:space:]]}} + [[ -z $line || ${line:0:1} == "#" || $line != *=* ]] && continue + + if [[ ${line%%=*} == "DELL_XPS_TOUCHPAD_HAPTICS_DEBUG" ]]; then + value=${line#*=} + value=${value#${value%%[![:space:]]*}} + value=${value%${value##*[![:space:]]}} + printf '%s\n' "$value" + return 0 + fi + done < "$_env_path" +} + +_write_env_file() { + local home=$1 + local debug_value=$2 + local tmpfile + + tmpfile=$(mktemp) + printf 'DELL_XPS_TOUCHPAD_HAPTICS_HOME=%s\n' "$home" >"$tmpfile" + if [[ -n $debug_value ]]; then + printf 'DELL_XPS_TOUCHPAD_HAPTICS_DEBUG=%s\n' "$debug_value" >>"$tmpfile" + fi + install -D -m 644 "$tmpfile" "$_env_path" + rm -f "$tmpfile" +} + +_systemd_runtime_available() { + [[ -d /run/systemd/system ]] +} + +_udev_runtime_available() { + [[ -S /run/udev/control ]] +} + +_service_unit_present() { + [[ -f /usr/lib/systemd/system/$_service_name ]] +} + +_reload_runtime_state() { + if _systemd_runtime_available && ! systemctl daemon-reload; then + echo ":: Failed to reload the systemd manager for ${_service_name}." >&2 + return 1 + fi + + if _udev_runtime_available && ! udevadm control --reload-rules; then + echo ":: Failed to reload udev rules for ${_service_name}." >&2 + return 1 + fi +} + +_ensure_user_config() { + local user=$1 + local home=$2 + local config_dir="$home/.config/omarchy" + local config_path="$config_dir/dell-haptic.conf" + + if [[ ! -f $config_path ]] && ! env HOME="$home" USER="$user" LOGNAME="$user" \ + /usr/bin/dell-xps-touchpad-haptics set "$_default_level"; then + echo ":: Failed to create ${config_path} for user '$user'." >&2 + return 1 + fi + + if [[ -f $config_path ]]; then + chown "$user:$user" "$home/.config" 2>/dev/null || true + chown "$user:$user" "$config_dir" 2>/dev/null || true + chown "$user:$user" "$config_path" 2>/dev/null || true + fi + + return 0 +} + +_reset_trackpad() { + if command -v omarchy-restart-trackpad >/dev/null 2>&1; then + omarchy-restart-trackpad 2>/dev/null || true + fi +} + +_cleanup_legacy_state() { + rm -f "$_legacy_env_path" "$_legacy_override_dir/override.conf" + rmdir "$_legacy_override_dir" 2>/dev/null || true +} + +_enable_service() { + if ! systemctl enable "$_service_name" >/dev/null; then + echo ":: Failed to enable ${_service_name}." >&2 + return 1 + fi +} + +_start_service_if_possible() { + if ! _systemd_runtime_available; then + echo ":: ${_service_name} enabled; it will start on next boot." + return 0 + fi + + if ! systemctl restart "$_service_name" >/dev/null; then + echo ":: Failed to start ${_service_name}." >&2 + return 1 + fi +} + +_configure_package() { + local user="" + local home="" + local debug_value + local config_path="" + + _cleanup_legacy_state + + if ! _service_unit_present; then + echo ":: Required unit file for ${_service_name} was not installed." >&2 + return 1 + fi + + if ! _reload_runtime_state; then + return 1 + fi + + user=$(_resolve_target_user) + if [[ -n $user ]]; then + home=$(_user_home "$user") + fi + + if [[ -z $home || ! -d $home ]]; then + home=$(_existing_home) + fi + + if [[ -z $home || ! -d $home ]]; then + echo ":: Could not determine a usable home directory for ${_service_name}." >&2 + echo ":: Reinstall the package after logging in as your desktop user to finish setup." >&2 + return 1 + fi + + debug_value=$(_debug_value) + _write_env_file "$home" "$debug_value" + + if [[ -n $user ]]; then + if ! _ensure_user_config "$user" "$home"; then + return 1 + fi + else + config_path="$home/.config/omarchy/dell-haptic.conf" + if [[ ! -f $config_path ]]; then + echo ":: Could not seed ${config_path} because no desktop user could be identified." >&2 + return 1 + fi + fi + + if ! _enable_service; then + return 1 + fi + + _reset_trackpad + + if ! _start_service_if_possible; then + return 1 + fi + + return 0 +} + +post_install() { + _configure_package +} + +post_upgrade() { + _configure_package +} + +pre_remove() { + systemctl disable "$_service_name" 2>/dev/null || true + systemctl stop "$_service_name" 2>/dev/null || true +} + +post_remove() { + rm -f "$_env_path" + _reload_runtime_state || true +} diff --git a/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.service b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.service new file mode 100644 index 00000000..ae1ca260 --- /dev/null +++ b/pkgbuilds/edge/dell-xps-touchpad-haptics/dell-xps-touchpad-haptics.service @@ -0,0 +1,14 @@ +[Unit] +Description=Dell XPS touchpad haptics +After=systemd-udev-settle.service + +[Service] +Type=simple +SyslogIdentifier=dell-xps-touchpad-haptics +EnvironmentFile=-/etc/dell-xps-touchpad-haptics.env +ExecStart=/usr/bin/dell-xps-touchpad-haptics-daemon +Restart=on-failure +RestartSec=2 + +[Install] +WantedBy=multi-user.target