Files
zenbook/docs/superpowers/specs/2026-03-22-touchscreen-toggle-design.md
2026-09-26 17:37:13 +02:00

133 lines
4.7 KiB
Markdown

# 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:
```rust
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`:
```rust
ListTouchscreens,
SetTouchscreenEnabled { connector: String, enabled: bool },
```
Add to `DaemonResponse`:
```rust
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` — sends `ListTouchscreens` to daemon, returns devices
- `set_touchscreen_enabled(connector, enabled)` — sends `SetTouchscreenEnabled` to daemon, also updates settings
Register both in the `invoke_handler` macro in `lib.rs`.
## Settings Persistence
Add to existing settings model (`models/settings.rs`):
```rust
#[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`:
```typescript
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_disabled` from `state.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`)
```typescript
interface TouchscreenDevice {
connector: string;
name: string;
enabled: boolean;
}
```
### API (`lib/tauri.ts`)
```typescript
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)