Skip to content

prostopasta/linux-privacy-switch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

linux-privacy-switch

MIT License Ubuntu Python systemd


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.


Supported devices

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.


Quick install

git clone https://github.com/prostopasta/linux-privacy-switch.git
cd linux-privacy-switch
sudo bash install.sh

Detects your device by DMI, installs both systemd services, starts the tray.

bash tests/verify.sh   # post-install check

How it works

Physical 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

Startup sequence

On every start the daemon:

  1. Waits until system uptime β‰₯ 15 s β€” gives the ITE EC time to apply the switch position to the camera sensor before probing.
  2. 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 because camera_power sysfs always reads 0 on 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.
  3. Drains any buffered EC init-events so they aren't misread as user toggles.
  4. Enters the event loop. From here, every EC key event is a real slider movement.

Periodic health check

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.

Daemon liveness

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).


Device modes

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.


Tray indicator

Icon Meaning
🟒 Green Camera and microphone enabled
πŸ”΄ Red Camera and microphone muted
⏳ Starting Daemon is running the startup probe (~15–18 s)
⚠️ Not responding 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).


Service management

# 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

Adding a new device

# 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=XXX

Add 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 description

Reinstall to apply:

sudo bash install.sh

Scenario A β€” Physical slider (hardware kill, e.g. Legion 5)

  1. Run --detect to get DMI strings and input device name
  2. Run --monitor to identify the slider key code
  3. Add section to devices.conf with has_hw_camera_kill = true
  4. Reinstall: sudo bash install.sh
  5. Run --calibrate to find the optimal brightness threshold (see below)
  6. Test: move slider β†’ camera cuts out, mic mutes, tray icon updates

Scenario B β€” Software-only Fn key (e.g. IdeaPad 5)

  1. Run --detect / --monitor as above
  2. Add section with has_hw_camera_kill = false
  3. Reinstall: sudo bash install.sh
  4. The daemon tracks state in software only β€” no V4L2 probe is performed
  5. Test: press key β†’ mic mutes/unmutes, tray updates; camera indicator changes but the sensor stays live (this is expected)

Scenario C β€” No dedicated key

Not yet supported. Would require integrating a hotkey daemon (e.g. xbindkeys). Contributions welcome.

Pull requests with new device profiles are welcome.


Calibration

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 --calibrate

The 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.


Troubleshooting

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.


Future extensions

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 uhubctl or 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.


Related projects


License

MIT

About

Linux daemon for hardware camera+microphone privacy switch on Lenovo Legion and IdeaPad laptops. System tray indicator, ALSA/PipeWire mute, multi-device support.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors