4.7 KiB
Touchscreen Toggle — Design Spec
Per-display touchscreen enable/disable from the Control Panel UI, with state persistence across reboots.
Context
The ASUS Zenbook Duo has two ELAN touchscreens (ELAN9008 on eDP-1, ELAN9009 on eDP-2). When the keyboard is detached and both screens are active, users may want to disable touch on one screen (e.g., to avoid accidental input while using a stylus on the other). No touchscreen control exists in the project today.
Mechanism
sysfs unbind/bind — compositor-agnostic, immediate, no reboot required.
- Disable:
echo "<i2c_id>" > /sys/bus/i2c/drivers/i2c_hid_acpi/unbind - Enable:
echo "<i2c_id>" > /sys/bus/i2c/drivers/i2c_hid_acpi/bind - Requires root — routed through the daemon (which runs as root).
Backend
New file: src-tauri/src/hardware/touchscreen.rs
list_touchscreens() -> Vec<TouchscreenDevice>
Scans /sys/class/input/event* for devices with touch capabilities. Groups by parent i2c device ID to deduplicate (each ELAN device registers multiple event nodes — touch, stylus, touchpad, etc.). Returns one entry per physical touchscreen:
struct TouchscreenDevice {
name: String, // e.g., "ELAN9008:00 04F3:425B"
i2c_id: String, // e.g., "i2c-ELAN9008:00"
connector: String, // e.g., "eDP-1"
enabled: bool,
}
Display mapping: Hardcoded by device name — "ELAN9008" maps to eDP-1 (top/main), "ELAN9009" maps to eDP-2 (bottom). This matches the ACPI topology and is consistent with how the rest of the codebase handles this hardware.
set_touchscreen_enabled(i2c_id: &str, enabled: bool) -> Result<()>
Writes the i2c_id to the appropriate sysfs bind/unbind file. Called by the daemon (which has root privileges).
Daemon IPC (ipc/protocol.rs)
Add to DaemonRequest:
ListTouchscreens,
SetTouchscreenEnabled { connector: String, enabled: bool },
Add to DaemonResponse:
Touchscreens { devices: Vec<TouchscreenDevice> },
The daemon handles these requests using the hardware/touchscreen.rs functions directly (it runs as root).
New file: src-tauri/src/commands/touchscreen.rs
Two Tauri commands that route through daemon IPC (matching the pattern used by get_display_layout and apply_display_layout):
list_touchscreens— sendsListTouchscreensto daemon, returns devicesset_touchscreen_enabled(connector, enabled)— sendsSetTouchscreenEnabledto daemon, also updates settings
Register both in the invoke_handler macro in lib.rs.
Settings Persistence
Add to existing settings model (models/settings.rs):
#[serde(default)]
touchscreen_disabled: Vec<String> // connectors with touch disabled, e.g., ["eDP-2"]
Uses #[serde(default)] to avoid breaking deserialization of existing settings files.
Also update the TypeScript DuoSettings interface in types/duo.ts:
touchscreenDisabled: string[];
- Toggle off: add connector to list, save settings
- Toggle on: remove connector from list, save settings
Boot Restore
In daemon.rs, the handle_lifecycle function already restores state on Post/Thaw/Boot phases (backlight, dock mode). Add touchscreen restore there:
- Read
touchscreen_disabledfromstate.settings - Unbind each listed touchscreen
This is the correct location because the daemon runs as root (can write to sysfs) and already handles boot/resume state restoration.
Frontend
Types (types/duo.ts)
interface TouchscreenDevice {
connector: string;
name: string;
enabled: boolean;
}
API (lib/tauri.ts)
export const listTouchscreens = () =>
invoke<TouchscreenDevice[]>("list_touchscreens");
export const setTouchscreenEnabled = (connector: string, enabled: boolean) =>
invoke<void>("set_touchscreen_enabled", { connector, enabled });
Controls page (Controls.tsx)
New "Touchscreen" section with a toggle per display. Fetches state via listTouchscreens() on mount, calls setTouchscreenEnabled() on toggle. Follows existing Switch component pattern.
Display page (DisplayLayout.tsx)
Per-display touch toggle in the display details list, next to the scale selector. Only shown for displays that have a mapped touchscreen. Same data source and commands as Controls page.
Setup Scripts
The setup scripts (setup-gnome.sh, setup-kde.sh, setup-niri.sh) do not need changes — the daemon already runs as root and handles the sysfs writes directly. No new sudoers entries or polkit policies are required.
Scope Exclusions
- No auto-toggle based on keyboard attach/detach (screen is off when keyboard attached)
- No profile integration
- No compositor-specific implementations (sysfs is universal)