# fluxVolume

A Window Maker style system volume dockapp for the Fluxbox slit. Shows the
current output level as a tall bar with a percentage, and gives you three
large buttons: volume up, mute, volume down. Click inside the bar to jump to
a level; roll the mouse wheel anywhere on the tile to step it. An optional
fourth button mutes and unmutes the microphone.

Part of the [Flux Dock Apps Suite](https://osugisakae.com/software/flux-dock-apps/).

## Screenshot

```
┌──────────────┬────────┐
│  ┌────────┐  │   +    │
│  │        │  ├────────┤
│  │  bar   │  │  🔊    │   mute toggle — shows 🔇 while muted
│  │████████│  ├────────┤
│  └────────┘  │   −    │
│     53%      │        │
└──────────────┴────────┘
```

While muted the bar fill dims, the text reads `MUTE`, and the speaker button
lights up so the state is obvious from across the room.

With `mic_button = true` (or `--mic`) the column holds four buttons, `+`,
speaker, microphone, `−`, sharing the height equally. The microphone button
lights up with a slash through it while the mic is muted, and is drawn faint
when no microphone can be read.

## Installation

Requires Rust and a working X11 environment.

```sh
cargo build --release
cp target/release/fluxVolume ~/.local/bin/
```

The release build is stripped and LTO-optimized. No runtime dependencies
beyond libc and libX11 — plus one of the mixer command-line tools below.

### Mixer backends

fluxVolume shells out to whichever mixer tool answers, probing in this order:

| Backend | Sound server | Package (Debian / Gentoo) |
|---|---|---|
| `wpctl` | PipeWire | wireplumber / media-video/wireplumber |
| `pactl` | PulseAudio or pipewire-pulse | pulseaudio-utils / media-sound/pulseaudio |
| `amixer` | ALSA | alsa-utils / media-sound/alsa-utils |

Force one with `--backend NAME` or `backend = NAME` in the config. A forced
backend that cannot be reached shows `NO MIXER` rather than silently switching.

## Usage

```
fluxVolume [OPTIONS]
```

Add `fluxVolume` to your Fluxbox startup to embed it in the slit.

### Options

| Flag | Description |
|---|---|
| `--size SIZE` | Window size in pixels, 64 to 1024 (default 96; 64, 96 and 128 are the classic dockapp sizes) |
| `--font PATH` | Font file (default: fontconfig monospace) |
| `--theme THEME` | Color theme (`zenburn`) |
| `--backend NAME` | Mixer: `auto` (default), `wpctl`, `pactl`, `amixer` |
| `--device NAME` | Sink / control name (default per backend) |
| `--step N` | Volume step in percent (default 5) |
| `--mic` | Add a microphone mute button as a fourth row |
| `--mic-device NAME` | Source / capture control for it (default per backend) |
| `--background`, `--bg` | Run in background |
| `--verbose` / `--quiet` | More / less output on stderr |
| `--write-config [PATH]` | Write annotated config file and exit |
| `--configure` | Interactive configuration wizard |
| `--config PATH` | Load config from PATH instead of the default |
| `--dock N` | Open in slit N under NeoFlux (ignored elsewhere) |
| `--help` | Show help message |

### Mouse

| Action | Effect |
|---|---|
| Left-click `+` / `−` | Step volume by `step`, never past `max` or below 0 |
| Left-click speaker | Toggle mute |
| Left-click microphone | Toggle microphone mute (with `mic_button`) |
| Left-click in the bar | Set the level to that height |
| Scroll wheel (anywhere) | Step volume up / down |
| Middle-click | Reload config (or run `middle_click`) |
| Right-click | Open a mixer GUI if installed (or run `right_click`) |

Pressed buttons flash bright for 100 ms. The level is re-read from the mixer
every `interval` seconds (default 1) so changes made with hardware keys or
another mixer show up promptly.

## Configuration

Config file: `${XDG_CONFIG_HOME:-~/.config}/fluxVolume/config`

Generate an annotated one with `fluxVolume --write-config` (it records the
backend detected on your machine), or walk through `fluxVolume --configure`.

```ini
# Window size in pixels, 64 to 1024 (64, 96 and 128 are the classic sizes)
size = 96

# Mixer backend: auto | wpctl | pactl | amixer
backend = auto

# Sink / control name (defaults: @DEFAULT_AUDIO_SINK@, @DEFAULT_SINK@, Master)
device =

# Step in percent (1-50); highest level the buttons will set (50-200)
step = 5
max = 100

# Seconds between mixer re-reads (1-60)
interval = 1

# Per-verb overrides. {} in set_volume is the target percent. get_volume must
# print NN% or "Volume: 0.NN", plus [MUTED] / "Mute: yes" / [off] when muted.
volume_up =
volume_down =
set_volume =
mute_toggle =
get_volume =

# Microphone mute button (fourth row). mic_device defaults to
# @DEFAULT_AUDIO_SOURCE@ / @DEFAULT_SOURCE@ / Capture per backend; get_mic must
# print [MUTED] / "Mute: yes" / [off] when the mic is muted.
mic_button = false
mic_device =
mic_mute_toggle =
get_mic =

# Clicks
middle_click =
right_click =

theme = zenburn
color = 0,255,0
dim_color = 0,200,0
background = false
```

Every verb can be overridden independently, so an exotic setup (a remote
PulseAudio server, a per-application sink, a JACK bridge) only needs the
five commands written out once (seven with the microphone button).

### Keyboard

Under NeoFlux, the `focus-slit` action (default chord Super+x then s) parks
keyboard focus on a dockapp. fluxVolume then draws a ring just inside its
bevel and takes keys directly: Up, Right, `+` raise the volume; Down, Left,
`-` lower it; Return, space or `m` toggle mute; `c` toggles the microphone
when that button is on; the XF86 audio keys (including AudioMicMute) do the
same. Tab steps to the next dockapp in the slit and Escape returns focus to
the window. Other window managers never focus a dockapp, so nothing changes
there.

### Using fluxVolume alongside fluxPlayer

Both apps drive the system mixer, so they agree with each other: a step from
either tile shows up in fluxVolume's bar within a second. If you run both,
you may prefer to leave the volume buttons off fluxPlayer and let fluxVolume
own them — or keep both; nothing conflicts.

### Reloading

Middle-click the tile, or `kill -HUP <pid>`, to re-read the config and
re-detect the mixer without restarting.

## License

CC0 1.0 Universal — see `LICENSE`.
