linux-privacy-switch makes the hardware privacy slider on Lenovo Legion / IdeaPad laptops actually work on Linux — muting the microphone and updating a tray indicator in sync with the camera.
The problem: The ITE Embedded Controller fires a key event when the slider moves, but Linux ignores it. The camera is physically cut by the EC, but the microphone stays live and no indicator updates.
The fix: A systemd daemon intercepts the /dev/input/ event, detects the true camera state via V4L2 frame capture, and applies mic mute/unmute + tray icon in sync.
| Device | DMI product_name |
Status |
|---|---|---|
| Lenovo Legion 5 15IRX10 | 83LY | ✅ Tested |
| Lenovo Legion 5 Gen 6 (AMD) | 82JU | 🔧 Needs testing |
| Lenovo IdeaPad 5 | IdeaPad 5 | 🔧 Needs testing |
Adding a new device takes ~5 minutes.
git clone https://github.com/prostopasta/linux-privacy-switch.git
cd linux-privacy-switch
sudo bash install.shDetects your device by DMI, installs both systemd services, starts the tray.
bash tests/verify.sh # post-install checkPhysical slider → ITE EC → /dev/input/eventN
→ privacy-switch daemon (root, system service)
├── V4L2 frame capture → detect true camera state
├── amixer sset Capture cap/nocap (ALSA)
├── wpctl set-mute @DEFAULT_AUDIO_SOURCE@ (PipeWire)
├── notify-send
└── heartbeat file every 10 s
→ privacy-tray (user service, after graphical-session.target)
├── tray icon ← polls state file every 800 ms
└── wpctl set-volume 50% on enable
On every start the daemon:
- Waits until system uptime ≥ 15 s — gives the ITE EC time to apply the switch position to the camera sensor before probing.
- Determines initial state:
- Hardware kill devices (
has_hw_camera_kill = true): captures one V4L2 frame. A black frame (<nonzero_threshold% nonzero bytes, default 3%) means the slider is OFF; a live frame means ON. This is necessary becausecamera_powersysfs always reads0on affected hardware — the EC controls the sensor directly. - Software-only devices (
has_hw_camera_kill = false): the sensor is always powered, so probing is skipped. The daemon reads the last saved state file.
- Hardware kill devices (
- Drains any buffered EC init-events so they aren't misread as user toggles.
- Enters the event loop. From here, every EC key event is a real slider movement.
Every 60 seconds the daemon re-probes the camera in a background thread. If the result disagrees with saved state (e.g. after a crash-and-restart), it self-corrects automatically.
The daemon writes a Unix timestamp to /var/lib/linux-privacy-switch/heartbeat every 10 s. The tray reads it: if the file is older than 30 s the icon switches to ⚠ Daemon not responding until the service recovers (Restart=always in the unit file handles this automatically).
The daemon operates in one of two modes depending on the device profile:
| Mode | has_hw_camera_kill |
V4L2 probe | State source at boot |
|---|---|---|---|
| Hardware kill | true |
Yes — black frame = OFF | V4L2 reading (authoritative) |
| Software-only | false |
No | Saved state file |
Hardware kill (e.g. Lenovo Legion slider): the ITE Embedded Controller physically disconnects the camera sensor from the bus. A V4L2 frame captured while the slider is OFF contains almost no signal (~1% nonzero bytes), making the probe reliable. The daemon runs it once at startup and repeats every 60 s.
Software-only (e.g. Fn+key on IdeaPad 5): the sensor remains powered regardless of the key state. Probing would return a live frame even when "disabled", so the daemon skips V4L2 entirely and relies on the saved state file.
Set has_hw_camera_kill appropriately in your device profile. The install.sh installer asks this as an interactive question.
| Icon | Meaning |
|---|---|
| 🟢 Green | Camera and microphone enabled |
| 🔴 Red | Camera and microphone muted |
| ⏳ Starting | Daemon is running the startup probe (~15–18 s) |
| Daemon crashed; systemd auto-restart pending |
The tray is display-only — toggle with the physical slider only. Right-click → Quit.
Launched automatically at login via linux-privacy-tray.service (systemd user service).
# Status
sudo systemctl status linux-privacy-switch
systemctl --user status linux-privacy-tray
# Restart both services at once (waits for startup probe to finish)
sudo privacy-restart
# Live logs
sudo journalctl -u linux-privacy-switch -f# 1. Find DMI strings and input device name
sudo python3 src/privacy-switch.py --detect --config config/devices.conf
# 2. Find the slider key code
sudo python3 src/privacy-switch.py --monitor
# Move the slider → you'll see device_name and code=XXXAdd a section to config/devices.conf:
[my-laptop-model]
match_sys_vendor = LENOVO
match_product_name = <product_name from --detect>
input_device_name = <device name from --detect>
key_code = <code from --monitor>
alsa_card = 0
alsa_control = Capture
has_hw_camera_kill = false # see Device modes section
sync_camera = true
sync_mic = true
description = Short descriptionReinstall to apply:
sudo bash install.sh- Run
--detectto get DMI strings and input device name - Run
--monitorto identify the slider key code - Add section to
devices.confwithhas_hw_camera_kill = true - Reinstall:
sudo bash install.sh - Run
--calibrateto find the optimal brightness threshold (see below) - Test: move slider → camera cuts out, mic mutes, tray icon updates
- Run
--detect/--monitoras above - Add section with
has_hw_camera_kill = false - Reinstall:
sudo bash install.sh - The daemon tracks state in software only — no V4L2 probe is performed
- Test: press key → mic mutes/unmutes, tray updates; camera indicator changes but the sensor stays live (this is expected)
Not yet supported. Would require integrating a hotkey daemon (e.g. xbindkeys). Contributions welcome.
Pull requests with new device profiles are welcome.
Only relevant for devices with has_hw_camera_kill = true.
The daemon detects camera state by comparing V4L2 frame brightness against a threshold. The default is 3% nonzero bytes — a safe midpoint between a typical OFF frame (~1.2%) and a typical ON frame (~5–6%).
When to calibrate: when adding a new hardware-kill device, or if the indicator flips incorrectly.
Important: If your model has no physical lens cover, cover the camera with your finger during the OFF-state measurement — some sensors output a faint residual signal even when the EC cuts power.
sudo python3 /usr/local/bin/privacy-switch --calibrateThe wizard measures brightness in both states, suggests the optimal threshold, and offers to save nonzero_threshold to your device profile. The installer also offers to run calibration on first install.
Slider direction is inverted — toggle once; the daemon self-corrects and stays in sync from then on.
Device not detected — run --detect, add a profile to devices.conf.
Tray not starting — systemctl --user restart linux-privacy-tray.
Wrong state after reboot — the daemon needs ~15–18 s to finish the startup probe. Wait a moment, then check cat /var/lib/linux-privacy-switch/state.
Mic not muting during a video call — while another app is streaming the camera, the V4L2 probe returns EBUSY and is skipped; the daemon uses the last saved state. Toggle the slider once to force a sync.
The hardware privacy switch concept is not limited to camera and microphone. The same daemon architecture can be extended to cut any signal source on toggle:
- Wi-Fi —
rfkill block wifi - Bluetooth —
rfkill block bluetooth - USB ports — cut power via
uhubctlor hub-level sysfs - Ethernet —
ip link set eth0 down
Each source would get its own sync_* flag in the device profile, making the slider a universal hardware kill switch for all radio and I/O interfaces. Pull requests adding new sync targets are welcome.
- johnfanv2/LenovoLegionLinux — fans, power, RGB for Lenovo Legion on Linux