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