Metadata-Version: 2.4
Name: busylight-monitor
Version: 0.1.3
Summary: Linux busylight monitor that turns red when the microphone is in use and green when idle.
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: busylight-for-humans

# Busylight Monitor

Drives a USB busylight red while you're on a call/in a meeting, and green
the rest of the time — fully automatic, no per-app configuration.

Tested against a **Plenom A/S Busylight Omega** (USB `27bb:3bcf`) — Plenom
A/S is the company behind the Kuando brand, so this also works with other
Kuando/EPOS Busylight devices, and with anything else supported by the
[`busylight-for-humans`](https://pypi.org/project/busylight-for-humans/)
Python library (Luxafor, Embrava Blynclight, MuteMe, ThingM Blink(1), and
more).

## Quickstart

For the fastest source-checkout setup path, follow [QUICKSTART.md](QUICKSTART.md).
For building an installable `.deb`, see [PACKAGING.md](PACKAGING.md).

## How it works

`busylight_monitor.py` polls `pactl list short source-outputs` every 2
seconds. An application only opens a "source-output" stream while it's
actively recording from the microphone, so this catches Zoom, Google Meet,
Microsoft Teams, Slack huddles, or anything else that uses the mic — with
no per-app setup. It then drives the light via the `busylight-for-humans`
library:

- **Red** — something currently has the mic open (you're on a call)
- **Green** — idle

The Kuando/Omega protocol needs a periodic keepalive or the device turns
itself off, so the script resends the current color every poll cycle
rather than only on state changes.

Runs as a `systemd --user` service so it starts automatically on login and
restarts itself if it ever crashes.

## Requirements

- Linux with PulseAudio or PipeWire (PipeWire's `pipewire-pulse`
  compatibility layer provides `pactl`, so either works)
- Python 3.9+
- `pactl` (`pulseaudio-utils` on Debian/Ubuntu-style systems)
- A supported USB busylight

## What's included

- `src/busylight_monitor_app/` - installable Python package with the monitor,
  CLI, and GTK frontend.
- `busylight_monitor.py` - compatibility launcher for source-checkout installs.
- `busylight-monitor.service` - a source-checkout `systemd --user` unit for
  automatic startup on login.
- `packaging/` - Debian package build script, package service, and desktop entry.
- `requirements.txt` - Python dependency list for the virtual environment.
- `scripts/check.sh` - health report for files, dependencies, audio detection,
  hardware access, udev rules, and the user service.
- `scripts/repair.sh` - common repair actions for the venv, udev rules, and
  systemd user service.
- `scripts/test_monitor_logic.py` - hardware-free tests for microphone detection.
- `QUICKSTART.md` - concise install and run instructions.
- `SETUP.md` - fuller setup notes and troubleshooting.

## Setup

### 1. Clone this repo

```bash
mkdir -p ~/applications
git clone https://repository.spitzerhome.com/david/Busylight-Linux.git ~/applications/Busylight-Linux
cd ~/applications/Busylight-Linux
```

(You can put it anywhere — just remember the path for the systemd unit in
step 5.)

### 2. Create a virtual environment and install dependencies

```bash
python3 -m venv ~/.local/share/busylight-venv
source ~/.local/share/busylight-venv/bin/activate
pip install -r requirements.txt
```

### 3. Install the udev rule (lets the light work without root)

Still inside the venv:

```bash
busylight udev-rules -o 99-busylights.rules
sudo cp 99-busylights.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
```

Unplug and replug the light so the new permission takes effect.

### 4. Sanity-check the light on its own

```bash
busylight on red
busylight on green
busylight off
```

(These block the terminal while running — that's the keepalive loop. Press
Ctrl+C to stop each one; the light turns off on exit. This is normal.)

### 5. Try the monitor by hand

```bash
~/.local/share/busylight-venv/bin/python ~/applications/Busylight-Linux/busylight_monitor.py
```

It should print `[info] connected to busylight` and `idle -> green`, with
the light solid green. Join a call and confirm it prints `ON CALL -> red`
and the light switches; end the call and confirm it goes back to green.
Ctrl+C to stop.

### 6. Run it automatically as a background service

```bash
mkdir -p ~/.config/systemd/user
```

Create `~/.config/systemd/user/busylight-monitor.service` (edit the
`ExecStart` path if you cloned this repo somewhere other than
`~/applications/Busylight-Linux`):

```ini
[Unit]
Description=Busylight call/meeting indicator (red = on a call, green = idle)
After=default.target pipewire.service pulseaudio.service
Wants=pipewire.service

[Service]
ExecStart=%h/.local/share/busylight-venv/bin/python %h/applications/Busylight-Linux/busylight_monitor.py
Restart=on-failure
RestartSec=3

[Install]
WantedBy=default.target
```

Then enable it:

```bash
systemctl --user daemon-reload
systemctl --user enable --now busylight-monitor.service
systemctl --user status busylight-monitor.service
```

Look for `active (running)` in the status output. From now on it starts
automatically on login.

## APT repository

The latest package is published to the Forgejo Debian package registry. Add it
to APT on Ubuntu/Debian-style systems with:

```bash
~/applications/Busylight-Linux/scripts/add-apt-repo.sh
```

Then install or upgrade with:

```bash
sudo apt install busylight-monitor
```

## Debian package

Build a `.deb` for easier deployment to users:

```bash
cd ~/applications/Busylight-Linux
./packaging/deb/build-deb.sh
```

Install the generated package:

```bash
sudo apt install ./dist/busylight-monitor_0.1.3_amd64.deb
```

After installing, users can open `Busylight Monitor` from their application
launcher or run:

```bash
busylight-monitor-gui
```

See [PACKAGING.md](PACKAGING.md) for build requirements, installed paths, and
package commands. The package also migrates older CLI/source-checkout install
leftovers that would conflict with the GUI package.

## Light tests

The monitor service owns the USB light while it is running. Light test commands
temporarily pause the user service, test the light, then restart the service:

```bash
busylight-monitor test-light red --seconds 5
```

Use `--no-service-pause` only when you know the monitor service is already
stopped.

## Health check and repair

Run the health check any time you want a quick report on whether the project is
set up correctly:

```bash
~/applications/Busylight-Linux/scripts/check.sh
```

It checks the project files, git remote, virtual environment, Python import,
monitor logic tests, `pactl`, current mic stream detection, busylight hardware
access, udev rules, and the `systemd --user` service. It exits nonzero if a
required check fails.

To repair the common setup pieces in one pass:

```bash
~/applications/Busylight-Linux/scripts/repair.sh --all
```

You can also repair one area at a time:

```bash
~/applications/Busylight-Linux/scripts/repair.sh --venv
~/applications/Busylight-Linux/scripts/repair.sh --udev
~/applications/Busylight-Linux/scripts/repair.sh --service
```

`--udev` uses `sudo` and you still need to unplug/replug the light after udev
rules are refreshed.

## Everyday commands

```bash
systemctl --user status busylight-monitor.service      # is it running?
journalctl --user -u busylight-monitor.service -f       # live logs
systemctl --user disable --now busylight-monitor.service  # stop it for good
systemctl --user restart busylight-monitor.service       # restart it
```

## Notes / known limitations

- It goes red whenever an app has the mic **stream open**, which for most
  call apps stays true even if you mute yourself *within* that app. It
  reflects "in a call," not "currently unmuted."
- Anything else that grabs the mic (dictation software, a voice recorder,
  a mic test) will also trigger red — it can't distinguish those from an
  actual call.
- If the color never changes, run `pactl list short source-outputs`
  yourself during a real call — if it prints nothing, your setup routes
  audio differently and the detection logic in `mic_in_use()` needs
  adjusting.
