pragma Singleton // What input hardware this machine actually has. // // Exists so pages can hide controls for hardware that is not present. A // touchpad card on a desktop is not merely useless -- it is misleading, because // every switch on it appears to work: the preference is stored and Hyprland // accepts the option for a device class it has no member of. The user is left // toggling settings that will never affect anything, with nothing to say so. // // Touchpads are identified by name. libinput exposes them through the same // "mice" list as everything else that reports pointer motion, and Hyprland // passes the device name through, so an Elan or Synaptics touchpad arrives as // something like "elan-touchpad". There is no device-class field to consult. // // Read on demand rather than polled. Input devices do come and go -- a mouse is // unplugged, a receiver is moved -- so this also refreshes when Hyprland says // the device list changed, which is the only moment the answer can differ. import Quickshell import Quickshell.Io import Quickshell.Hyprland import QtQuick Singleton { id: root property var mice: [] property var keyboards: [] // True when anything that looks like a touchpad is attached. readonly property bool hasTouchpad: root.mice.some(name => name.includes("touchpad") || name.includes("trackpad")) readonly property bool hasMouse: root.mice.length > 0 function refresh(): void { if (!query.running) query.running = true; } Process { id: query running: true command: ["hyprctl", "-j", "devices"] stdout: StdioCollector { onStreamFinished: { try { const parsed = JSON.parse(this.text); root.mice = (parsed.mice ?? []).map(device => String(device.name ?? "").toLowerCase()); root.keyboards = (parsed.keyboards ?? []).map(device => String(device.name ?? "").toLowerCase()); } catch (error) { console.warn("InputDevices: could not parse hyprctl devices:", error); } } } } Connections { target: Hyprland function onRawEvent(event: var): void { if (event.name === "device" || event.name === "configreloaded") root.refresh(); } } }