Rebase to flake parts #9
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# Cameras on the Lenovo Duet 3 (`lenovo-wormdingler`) — TODO
|
||||
|
||||
The front and rear cameras do **not** work on the current Mobile NixOS image.
|
||||
This file is a starting point for picking the work back up later, so we don't
|
||||
have to re-diagnose from scratch.
|
||||
|
||||
## Current state (May 2026)
|
||||
|
||||
Empirical, taken on the running device:
|
||||
|
||||
```
|
||||
$ uname -a
|
||||
Linux ideapad 6.5.0 #1-mobile-nixos SMP Tue Jan 1 00:00:00 UTC 1980 aarch64 GNU/Linux
|
||||
|
||||
$ ls /dev/video*
|
||||
/dev/video0 ← Qualcomm Venus video decoder (h.264/h.265 dec)
|
||||
/dev/video1 ← Qualcomm Venus video encoder (h.264/h.265 enc)
|
||||
|
||||
$ ls /dev/media* # nothing — no media controller graph at all
|
||||
$ lsmod | grep -i camss # nothing
|
||||
$ wpctl status | grep -A1 Sources # nothing — PipeWire has no camera sources
|
||||
```
|
||||
|
||||
Relevant kernel config bits in `/proc/config.gz` on the running image:
|
||||
|
||||
```
|
||||
CONFIG_MEDIA_CAMERA_SUPPORT=y ← media framework is on
|
||||
# CONFIG_VIDEO_QCOM_CAMSS is not set ← Qualcomm Camera Subsystem driver is OFF
|
||||
# CONFIG_VIDEO_OV5675 is not set ← every relevant sensor driver is OFF
|
||||
# CONFIG_VIDEO_OV13858 is not set
|
||||
# CONFIG_VIDEO_HI556 is not set
|
||||
# (full grep of `VIDEO_OV*` / `VIDEO_HI*` is all `not set`)
|
||||
```
|
||||
|
||||
So the *media framework* is enabled, but the *ISP driver* (CAMSS) and every
|
||||
plausible sensor driver are off, and no out-of-tree modules are shipped. The
|
||||
two `/dev/video*` nodes are the Venus codecs, not cameras — that's why
|
||||
`snapshot` reports "no camera found."
|
||||
|
||||
## Why this is non-trivial
|
||||
|
||||
1. **Kernel rebuild required.** `mobile-nixos` builds its own kernel for this
|
||||
device (see `mobile.kernel.structuredConfig` in `hardware.nix`, where we
|
||||
already enable `CIFS` and `EXFAT_FS`). Adding camera support means adding:
|
||||
- `VIDEO_QCOM_CAMSS = module;`
|
||||
- The right sensor driver(s) — and we don't currently know which ones the
|
||||
Duet 3 actually uses. The `wormdingler` Chromium-OS DT references a pair
|
||||
of OV-series sensors but the exact part numbers can vary by SKU and
|
||||
production batch.
|
||||
- Their dependencies (`I2C`, `V4L2`, `MEDIA_CONTROLLER`, etc. — most are
|
||||
already pulled in by `MEDIA_CAMERA_SUPPORT`).
|
||||
|
||||
2. **Device-tree wiring.** Even with the drivers compiled in, the device tree
|
||||
has to describe the CCI bus, the sensor I²C addresses, the regulators, the
|
||||
reset/enable GPIOs, and the CSI port mapping. Mobile NixOS' DT for
|
||||
wormdingler may or may not include these nodes — needs verification by
|
||||
reading the upstream device file at
|
||||
`${inputs.mobile-nixos}/devices/lenovo-wormdingler/`.
|
||||
|
||||
3. **libcamera is the user-space side.** CAMSS is not a "single v4l2 device"
|
||||
driver — it exposes a media-controller graph that has to be configured by
|
||||
libcamera (per-sensor IPA, format negotiation, etc.). Apps then talk to
|
||||
libcamera via the `libcamerasrc` PipeWire module or directly. So the
|
||||
user-space stack is:
|
||||
- `pkgs.libcamera` (system-wide)
|
||||
- `pkgs.pipewire` already running, but needs the libcamera module enabled
|
||||
(`services.pipewire.libcamera = …` or equivalent — check current option
|
||||
name)
|
||||
- GUI: `snapshot`, `gnome-camera`, or anything that talks to PipeWire
|
||||
video sources.
|
||||
|
||||
## Investigation checklist
|
||||
|
||||
When picking this up again, do these in order:
|
||||
|
||||
1. **Identify the actual sensors.** The cleanest way:
|
||||
- Read the upstream Mobile NixOS device file for wormdingler
|
||||
(`devices/lenovo-wormdingler/default.nix` and any `.dts` overlays).
|
||||
- Cross-check with the Chromium OS overlay at
|
||||
<https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/refs/heads/main/overlay-trogdor/>
|
||||
and the upstream Linux DTS at
|
||||
`arch/arm64/boot/dts/qcom/sc7180-trogdor-wormdingler-*.dts`.
|
||||
- As a runtime cross-check, when CAMSS is eventually loaded, `dmesg | grep -i
|
||||
-E 'cci|sensor|isp'` will print the I²C probe attempts.
|
||||
|
||||
2. **Enable kernel options** in `modules/hosts/ideapad/hardware.nix` under
|
||||
`mobile.kernel.structuredConfig`:
|
||||
|
||||
```nix
|
||||
(helpers: with helpers; {
|
||||
VIDEO_QCOM_CAMSS = module;
|
||||
# plus whatever sensors step 1 identified, e.g.:
|
||||
# VIDEO_OV5675 = module;
|
||||
# VIDEO_OV13858 = module;
|
||||
})
|
||||
```
|
||||
|
||||
Cross-build on the 14900k via the existing flow (binfmt aarch64 + push back).
|
||||
Reboot, then check:
|
||||
|
||||
```
|
||||
ls /dev/media* # expect at least /dev/media0
|
||||
sudo dmesg | grep -i -E 'camss|sensor|isp|cci' # probe history
|
||||
sudo modprobe -v qcom-camss # if not auto-loaded
|
||||
```
|
||||
|
||||
3. **Install diagnostic tools** for this round of work (do **not** keep these
|
||||
in the long-term config unless cameras actually work):
|
||||
|
||||
```nix
|
||||
environment.systemPackages = with pkgs; [
|
||||
v4l-utils # provides v4l2-ctl, media-ctl
|
||||
libcamera # provides `cam`, `qcam`
|
||||
];
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
v4l2-ctl --list-devices
|
||||
media-ctl -p # dumps the full media-controller graph
|
||||
cam -l # libcamera's view of available cameras
|
||||
```
|
||||
|
||||
4. **Wire libcamera into PipeWire.** Once `cam -l` shows at least one camera,
|
||||
enable PipeWire's libcamera module (option name may have shifted; current
|
||||
nixpkgs typically has `services.pipewire.wireplumber.extraConfig` or
|
||||
similar). Then `wpctl status` should show new Sources under "Video", and
|
||||
`snapshot` will see them.
|
||||
|
||||
5. **Re-add a camera GUI** to `configuration.nix`. `snapshot` is the simplest
|
||||
touch-first option; `gnome-camera` and `cheese` are alternatives.
|
||||
|
||||
## Why nothing else is being touched right now
|
||||
|
||||
Steps 1–4 above are speculative — there's no guarantee the Duet 3 cameras have
|
||||
working mainline-Linux sensor drivers at all. The conservative move is to
|
||||
leave the config tablet-usable without them, document the dead end, and revisit
|
||||
when there's time for a real spike.
|
||||
@@ -0,0 +1,45 @@
|
||||
{ pkgs, ... }: {
|
||||
# ─────────────────────── Power & thermal ───────────────────────
|
||||
# Snapdragon 7c (sc7180) on a tablet form factor: aim for battery life. `schedutil` is the
|
||||
# right modern cpufreq governor on ARM (responsive + power-aware); use it instead of
|
||||
# `powersave` to avoid pinning the CPU at minimum frequency under interactive load.
|
||||
powerManagement.cpuFreqGovernor = "schedutil";
|
||||
powerManagement.enable = true;
|
||||
|
||||
# ─────────────────────── logind: lid & power button ───────────────────────
|
||||
# Closing the lid suspends, even on AC — Duet 3 is a tablet, treat it like one.
|
||||
# Short press on power: suspend (matches ChromeOS/iOS); long press: poweroff.
|
||||
# The DMS bar power menu is the way to reboot / shut down explicitly.
|
||||
services.logind.settings.Login = {
|
||||
HandleLidSwitch = "suspend";
|
||||
HandleLidSwitchExternalPower = "suspend";
|
||||
HandleLidSwitchDocked = "ignore";
|
||||
HandlePowerKey = "suspend";
|
||||
HandlePowerKeyLongPress = "poweroff";
|
||||
};
|
||||
|
||||
# ─────────────────────── Idle / suspend tuning ───────────────────────
|
||||
# Allow suspend-to-RAM but disable hibernate (ARM swap-resume is unreliable, and we don't
|
||||
# have a swap device by default anyway).
|
||||
systemd.sleep.settings.Sleep = {
|
||||
AllowHibernation = "no";
|
||||
AllowHybridSleep = "no";
|
||||
AllowSuspendThenHibernate = "no";
|
||||
};
|
||||
|
||||
# upower picks the right battery percentages for low/critical out of the box; just make
|
||||
# sure the action on critical is hibernate-then-poweroff fallback (we disabled hibernate
|
||||
# so it'll go straight to poweroff). DMS reads upower for the bar widget.
|
||||
services.upower = {
|
||||
enable = true;
|
||||
criticalPowerAction = "PowerOff";
|
||||
percentageLow = 15;
|
||||
percentageCritical = 7;
|
||||
percentageAction = 3;
|
||||
};
|
||||
|
||||
# ─────────────────────── Bluetooth audio quality of life ───────────────────────
|
||||
# Duet has limited mic/speaker hw — keep wpa_supplicant power-save off so audio doesn't crackle
|
||||
# over Bluetooth when CPU is idle. (Wi-Fi + BT share the chip; aggressive power-save = stutter.)
|
||||
networking.networkmanager.wifi.powersave = false;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
# Host-only: ideapad tablet ergonomics — touchscreen calibration, IIO sensors, virtual keyboard,
|
||||
# and per-session helper daemons (tablet-mode toggle + auto-rotation via iio-sensor-proxy) for both
|
||||
# Niri and Hyprland.
|
||||
#
|
||||
# Why all of this lives at the *NixOS* layer (not the home-manager catalog under wisdom/):
|
||||
# - The hardware bits (`hardware.sensor.iio.enable`, the udev calibration matrix) are system-wide
|
||||
# and tied to this exact device, so they belong with the host module.
|
||||
# - The compositor helpers run via session-specific autostart hooks (Niri `spawn-at-startup`,
|
||||
# Hyprland `exec-once`); the wiring is gated on the matching `chiasson.desktop.<wm>.enable`,
|
||||
# so picking a different session at the greeter just leaves them dormant.
|
||||
#
|
||||
# Two compositor flavours of each daemon:
|
||||
# - Hyprland (CW transforms via `hyprctl`) — original; matches the old NixOS-New setup.
|
||||
# - Niri (CCW transforms via `niri msg output`) — needed because Niri is the V2 default.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# ─────────────────────── Hyprland helpers ───────────────────────
|
||||
ideapadTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-tablet-mode-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
||||
JQ="${pkgs.jq}/bin/jq"
|
||||
PKILL="${pkgs.procps}/bin/pkill"
|
||||
PGREP="${pkgs.procps}/bin/pgrep"
|
||||
WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"
|
||||
|
||||
monitor_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
current_transform() {
|
||||
"$HYPRCTL" -j monitors 2>/dev/null | "$JQ" -r --arg mon "$monitor_name" '
|
||||
([.[] | select(.name == $mon)][0].transform // 1 | tostring)
|
||||
' 2>/dev/null || echo "1"
|
||||
}
|
||||
|
||||
has_attached_pogo_dock() {
|
||||
"$HYPRCTL" -j devices 2>/dev/null | "$JQ" -e '
|
||||
any((([.keyboards[]?.name] + [.mice[]?.name])[]?); (ascii_downcase | test("google-inc\\.-hammer")))
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
# wvkbd is no longer auto-spawned at session start — this daemon owns its lifecycle so we
|
||||
# only have a virtual keyboard surface in memory when the pogo cover is detached.
|
||||
start_wvkbd() {
|
||||
"$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
|
||||
"$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
|
||||
}
|
||||
stop_wvkbd() {
|
||||
"$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
mode="$1"
|
||||
transform="$(current_transform)"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$tablet_scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
start_wvkbd
|
||||
else
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$keyboard_scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
stop_wvkbd
|
||||
fi
|
||||
"$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
|
||||
printf "%s\n" "$mode" > "$state_file"
|
||||
}
|
||||
|
||||
# Always reapply on startup — the cached state file may lie (e.g., session restart while
|
||||
# pogo state changed) and wvkbd needs to be (re)spawned since nothing else launches it now.
|
||||
previous_mode=""
|
||||
|
||||
while true; do
|
||||
if has_attached_pogo_dock; then
|
||||
mode="keyboard"
|
||||
else
|
||||
mode="tablet"
|
||||
fi
|
||||
|
||||
if [ "$mode" != "$previous_mode" ]; then
|
||||
apply_mode "$mode"
|
||||
previous_mode="$mode"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
ideapadAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-autorotate-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
||||
MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"
|
||||
|
||||
monitor_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
scale_for_mode() {
|
||||
mode="$(cat "$state_file" 2>/dev/null || echo keyboard)"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
printf "%s\n" "$tablet_scale"
|
||||
else
|
||||
printf "%s\n" "$keyboard_scale"
|
||||
fi
|
||||
}
|
||||
|
||||
transform_for_orientation() {
|
||||
orientation="$1"
|
||||
case "$orientation" in
|
||||
normal) printf "0\n" ;;
|
||||
bottom-up) printf "2\n" ;;
|
||||
left-up) printf "1\n" ;;
|
||||
right-up) printf "3\n" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_orientation() {
|
||||
orientation="$1"
|
||||
transform="$(transform_for_orientation "$orientation")" || return 0
|
||||
scale="$(scale_for_mode)"
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
while true; do
|
||||
"$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*normal*) apply_orientation "normal" ;;
|
||||
*bottom-up*) apply_orientation "bottom-up" ;;
|
||||
*left-up*) apply_orientation "left-up" ;;
|
||||
*right-up*) apply_orientation "right-up" ;;
|
||||
esac
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
# ─────────────────────── Niri helpers ───────────────────────
|
||||
# `niri msg output DSI-1 transform <T>` accepts: normal | 90 | 180 | 270.
|
||||
#
|
||||
# Empirical mapping for this exact device + ACCEL_MOUNT_MATRIX (lenovo-wormdingler family):
|
||||
# physical position iio orientation correct niri transform
|
||||
# ────────────────── ──────────────── ──────────────────────
|
||||
# keyboard down (LS) left-up normal
|
||||
# keyboard up (LS) right-up 180
|
||||
# keyboard right (P) bottom-up 90
|
||||
# keyboard left (P) normal 270
|
||||
# iio's "normal" is *not* the natural landscape pose here — the panel-mount matrix in the
|
||||
# mobile-nixos device file biases it toward portrait-with-keyboard-left. Don't trust the
|
||||
# textbook iio→Wayland convention; trust the table above.
|
||||
ideapadNiriTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-niri-tablet-mode-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NIRI="${pkgs.niri}/bin/niri"
|
||||
JQ="${pkgs.jq}/bin/jq"
|
||||
PKILL="${pkgs.procps}/bin/pkill"
|
||||
PGREP="${pkgs.procps}/bin/pgrep"
|
||||
WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"
|
||||
|
||||
output_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
has_attached_pogo_dock() {
|
||||
# Niri doesn't expose input devices via IPC, so we look in `/dev/input/by-id` for the
|
||||
# USB-class enumeration of the Google Hammer / Whiskers pogo keyboard. The directory
|
||||
# contains symlinks; presence of either string means the cover is attached.
|
||||
# NOTE: `find` ships in findutils, not coreutils — using the wrong package here makes
|
||||
# the pipeline fail silently (stderr is dropped) and pogo always reads as detached.
|
||||
${pkgs.findutils}/bin/find /dev/input/by-id -maxdepth 1 -type l 2>/dev/null \
|
||||
| ${pkgs.gnugrep}/bin/grep -i -E 'google.*hammer|google.*whiskers' >/dev/null
|
||||
}
|
||||
|
||||
# wvkbd is owned by this daemon: spawn on pogo detach, kill on pogo attach. The DMS bar
|
||||
# `pkill -SIGRTMIN -x wvkbd-mobintl` toggle still works while wvkbd is running (i.e., in
|
||||
# tablet mode). When the pogo cover is on, the pill is a no-op — that's intentional, the
|
||||
# physical keyboard is the input.
|
||||
start_wvkbd() {
|
||||
"$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
|
||||
"$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
|
||||
}
|
||||
stop_wvkbd() {
|
||||
"$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
mode="$1"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
"$NIRI" msg output "$output_name" scale "$tablet_scale" >/dev/null 2>&1 || true
|
||||
start_wvkbd
|
||||
else
|
||||
"$NIRI" msg output "$output_name" scale "$keyboard_scale" >/dev/null 2>&1 || true
|
||||
stop_wvkbd
|
||||
fi
|
||||
printf "%s\n" "$mode" > "$state_file"
|
||||
}
|
||||
|
||||
# Always reapply on startup — see Hyprland daemon's identical comment.
|
||||
previous_mode=""
|
||||
|
||||
while true; do
|
||||
if has_attached_pogo_dock; then
|
||||
mode="keyboard"
|
||||
else
|
||||
mode="tablet"
|
||||
fi
|
||||
|
||||
if [ "$mode" != "$previous_mode" ]; then
|
||||
apply_mode "$mode"
|
||||
previous_mode="$mode"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
ideapadNiriAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-niri-autorotate-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NIRI="${pkgs.niri}/bin/niri"
|
||||
MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"
|
||||
|
||||
output_name="DSI-1"
|
||||
|
||||
transform_for_orientation() {
|
||||
orientation="$1"
|
||||
case "$orientation" in
|
||||
normal) printf "270\n" ;;
|
||||
bottom-up) printf "90\n" ;;
|
||||
left-up) printf "normal\n" ;;
|
||||
right-up) printf "180\n" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_orientation() {
|
||||
orientation="$1"
|
||||
transform="$(transform_for_orientation "$orientation")" || return 0
|
||||
"$NIRI" msg output "$output_name" transform "$transform" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
while true; do
|
||||
"$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*normal*) apply_orientation "normal" ;;
|
||||
*bottom-up*) apply_orientation "bottom-up" ;;
|
||||
*left-up*) apply_orientation "left-up" ;;
|
||||
*right-up*) apply_orientation "right-up" ;;
|
||||
esac
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
in
|
||||
{
|
||||
# ─────────────────────── Hardware ───────────────────────
|
||||
hardware.sensor.iio.enable = true;
|
||||
|
||||
# Touchscreen calibration — solved empirically with `niri msg output DSI-1 transform normal`
|
||||
# in the natural kb-down pose. The panel's touch hardware reports raw coordinates already
|
||||
# aligned with the panel-native frame (HW(visual_top_left) = (0,0), etc.), so identity is
|
||||
# correct. `niri input.touch.map-to-output = "DSI-1"` then handles per-orientation rotation
|
||||
# on top — never re-tune this matrix per orientation; rotate the *output* instead.
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="input", ENV{ID_INPUT_TOUCHSCREEN}=="1", ENV{LIBINPUT_CALIBRATION_MATRIX}="1 0 0 0 1 0"
|
||||
'';
|
||||
|
||||
# ─────────────────────── User-facing tools ───────────────────────
|
||||
# System-wide so any user session (Niri or Hyprland) can launch wvkbd / hyprctl / niri-msg helpers.
|
||||
environment.systemPackages = [
|
||||
pkgs.wvkbd
|
||||
pkgs.iio-sensor-proxy
|
||||
ideapadTabletModeDaemon
|
||||
ideapadAutoRotateDaemon
|
||||
ideapadNiriTabletModeDaemon
|
||||
ideapadNiriAutoRotateDaemon
|
||||
];
|
||||
|
||||
# ─────────────────────── Niri session autostart ───────────────────────
|
||||
# Set on the NixOS layer; the `desktopNiri` HM module merges this into per-user `niri/config.kdl`.
|
||||
# Touch input is glued to DSI-1 so it follows whatever transform the autorotate daemon sets.
|
||||
# wvkbd is intentionally NOT spawned here — `ideapad-niri-tablet-mode-daemon` starts/kills it
|
||||
# in response to pogo-cover attach/detach so we don't keep a virtual-keyboard surface alive
|
||||
# while a physical keyboard is plugged in.
|
||||
chiasson.desktop.niri.extraSettings = lib.mkIf config.chiasson.desktop.niri.enable {
|
||||
input.touch.map-to-output = "DSI-1";
|
||||
|
||||
# wrapper-modules schema: each entry is a `command argv` list of strings (or a single string).
|
||||
spawn-at-startup = [
|
||||
[ "ideapad-niri-autorotate-daemon" ]
|
||||
[ "ideapad-niri-tablet-mode-daemon" ]
|
||||
];
|
||||
};
|
||||
|
||||
# ─────────────────────── Hyprland session autostart ───────────────────────
|
||||
# Same lifecycle policy as Niri: the tablet-mode daemon owns wvkbd, no exec-once for it.
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable {
|
||||
exec-once = lib.mkAfter [
|
||||
"${pkgs.procps}/bin/pkill -x ideapad-tablet-mode-daemon >/dev/null 2>&1 || true; ideapad-tablet-mode-daemon &"
|
||||
"${pkgs.procps}/bin/pkill -x ideapad-autorotate-daemon >/dev/null 2>&1 || true; ideapad-autorotate-daemon &"
|
||||
];
|
||||
bind = lib.mkAfter [
|
||||
"Super, K, exec, pkill -SIGRTMIN -x wvkbd-mobintl"
|
||||
];
|
||||
monitor = lib.mkAfter [
|
||||
"DSI-1,1200x2000@60.0,0x0,1.25, transform, 1"
|
||||
];
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user