Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,11 @@ jobs:
run: |
python3 -m venv /tmp/mini-eq-pwg-build
/tmp/mini-eq-pwg-build/bin/python -m pip install --upgrade pip
/tmp/mini-eq-pwg-build/bin/python -m pip wheel 'pipewire-gobject>=0.3.4,<0.4' -w /tmp/mini-eq-wheelhouse
/tmp/mini-eq-pwg-build/bin/python -m pip wheel 'pipewire-gobject>=0.3.5,<0.4' -w /tmp/mini-eq-wheelhouse

python3 -m venv --system-site-packages .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install --no-index --find-links /tmp/mini-eq-wheelhouse 'pipewire-gobject>=0.3.4,<0.4'
.venv/bin/python -m pip install --no-index --find-links /tmp/mini-eq-wheelhouse 'pipewire-gobject>=0.3.5,<0.4'
.venv/bin/python -m pip install -e '.[dev]'

- name: Lint
Expand Down Expand Up @@ -272,7 +272,7 @@ jobs:
run: |
python3 -m venv --system-site-packages /tmp/mini-eq-wheel-test
/tmp/mini-eq-wheel-test/bin/python -m pip install --upgrade pip
/tmp/mini-eq-wheel-test/bin/python -m pip install --no-index --find-links /tmp/mini-eq-wheelhouse 'pipewire-gobject>=0.3.4,<0.4'
/tmp/mini-eq-wheel-test/bin/python -m pip install --no-index --find-links /tmp/mini-eq-wheelhouse 'pipewire-gobject>=0.3.5,<0.4'
/tmp/mini-eq-wheel-test/bin/python -m pip install dist/mini_eq-*.whl
/tmp/mini-eq-wheel-test/bin/mini-eq --help

Expand Down Expand Up @@ -318,7 +318,7 @@ jobs:
- name: Check pipewire-gobject GI compatibility
run: |
flatpak run --filesystem="$PWD":ro --command=python3 io.github.bhack.mini-eq \
"$PWD/tools/check_pipewire_gobject.py" --expect-version 0.3.4
"$PWD/tools/check_pipewire_gobject.py" --expect-version 0.3.5

- name: Smoke-test Flatpak runtime modules
run: |
Expand Down
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,11 @@ modules from a system-site build venv.
- Prefer existing patterns and small, targeted patches.
- Do not move logic between the large modules just to tidy them; split modules
only when the user asked for that refactor or the change needs it.
- Keep the pipewire-gobject API boundary small and app-facing. WirePlumber stays
the host session manager, not a bundled GI dependency.
- Keep the pipewire-gobject API boundary small, general-purpose, and
app-facing. Mini EQ may validate new pipewire-gobject API in a real GTK app,
but do not add Mini EQ-shaped concepts, preset/filter-chain policy, or
hardware-selection policy to pipewire-gobject. WirePlumber stays the host
session manager, not a bundled GI dependency.
- Treat the Mini EQ D-Bus control interface as a project-internal app/Shell
extension contract with version-skew tolerance. Keep `api_version = 1`
additive only: add state fields, methods, and capabilities when needed, but do
Expand Down
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Changelog

## 0.6.0 - 2026-05-09
## 0.7.0 - 2026-05-09

- Replace the direct WirePlumber Python integration with pipewire-gobject for
app-facing PipeWire registry, metadata, routing, and monitor-capture access.
- Bundle pipewire-gobject 0.3.4 in Flatpak and require
`pipewire-gobject>=0.3.4,<0.4` for PyPI installs.
- Use pipewire-gobject device route params so auto preset links follow the
detected output port when available while preserving output-node links as a
fallback.
- Bundle pipewire-gobject 0.3.5 in Flatpak and require
`pipewire-gobject>=0.3.5,<0.4` for PyPI installs.
- Keep system-wide EQ, monitor capture, output changes, and shutdown behavior
covered by a live nested-GNOME/AT-SPI runtime smoke test.
- Remove the old WirePlumber 0.4 compatibility build path and tighten release
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ also show live LUFS loudness.
- PipeWire filter-chain DSP using builtin biquad filters.
- Optional spectrum analyzer and LUFS loudness readout through a PipeWire monitor
capture stream.
- Per-output preset links for automatically using different saved presets with
headphones, speakers, HDMI, and other outputs.
- Auto preset links can follow the detected PipeWire port when available and
fall back to the selected EQ output when a port is not reported.
- Optional background mode keeps the EQ active after closing the window, with a
separate Start at Login preference and optional active-at-login routing.
- Optional GNOME Shell extension for quick panel access to routing, EQ,
analyzer status, presets, and output preset links.
analyzer status, presets, and auto preset links.
- Equalizer APO preset import from the UI or `--import-apo`, including
compatible presets exported by [AutoEq](https://autoeq.app/).

Expand Down Expand Up @@ -134,11 +134,11 @@ Install the Python package after the system packages are present:
```bash
python3 -m venv /tmp/mini-eq-pwg-build
/tmp/mini-eq-pwg-build/bin/python -m pip install --upgrade pip
/tmp/mini-eq-pwg-build/bin/python -m pip wheel 'pipewire-gobject>=0.3.4,<0.4' -w /tmp/mini-eq-wheelhouse
/tmp/mini-eq-pwg-build/bin/python -m pip wheel 'pipewire-gobject>=0.3.5,<0.4' -w /tmp/mini-eq-wheelhouse

python3 -m venv --system-site-packages ~/.local/share/mini-eq/venv
~/.local/share/mini-eq/venv/bin/python -m pip install --upgrade pip
~/.local/share/mini-eq/venv/bin/python -m pip install --no-index --find-links /tmp/mini-eq-wheelhouse 'pipewire-gobject>=0.3.4,<0.4'
~/.local/share/mini-eq/venv/bin/python -m pip install --no-index --find-links /tmp/mini-eq-wheelhouse 'pipewire-gobject>=0.3.5,<0.4'
~/.local/share/mini-eq/venv/bin/python -m pip install mini-eq
~/.local/share/mini-eq/venv/bin/mini-eq --check-deps
~/.local/share/mini-eq/venv/bin/mini-eq
Expand Down Expand Up @@ -168,7 +168,7 @@ mini-eq --install-desktop
## GNOME Shell Extension

Mini EQ also has an optional GNOME Shell extension for quick panel access to
routing, EQ, analyzer status, presets, and output preset links.
routing, EQ, analyzer status, presets, and auto preset links.

Install it from GNOME Shell Extensions:
https://extensions.gnome.org/extension/9803/mini-eq-controls/
Expand Down
9 changes: 5 additions & 4 deletions data/io.github.bhack.mini-eq.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,24 @@
</description>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.6.0/docs/screenshots/mini-eq.png</image>
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.7.0/docs/screenshots/mini-eq.png</image>
<caption>Adjust sound output with equalizer controls</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.6.0/docs/screenshots/mini-eq-dark.png</image>
<image>https://raw.githubusercontent.com/bhack/mini-eq/v0.7.0/docs/screenshots/mini-eq-dark.png</image>
<caption>Use the equalizer with dark style</caption>
</screenshot>
</screenshots>
<url type="homepage">https://github.com/bhack/mini-eq</url>
<url type="bugtracker">https://github.com/bhack/mini-eq/issues</url>
<url type="vcs-browser">https://github.com/bhack/mini-eq</url>
<releases>
<release version="0.6.0" date="2026-05-09">
<release version="0.7.0" date="2026-05-09">
<description>
<ul>
<li>Use pipewire-gobject for PipeWire registry, metadata, routing, and monitor capture.</li>
<li>Bundle pipewire-gobject 0.3.4 in Flatpak and remove the old WirePlumber 0.4 compatibility build path.</li>
<li>Add auto preset links that follow the detected output port when available.</li>
<li>Bundle pipewire-gobject 0.3.5 in Flatpak and remove the old WirePlumber 0.4 compatibility build path.</li>
<li>Improve live runtime validation for routing, monitor capture, output changes, and shutdown behavior.</li>
</ul>
</description>
Expand Down
Binary file modified docs/screenshots/mini-eq-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/mini-eq.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions extensions/gnome-shell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Current scope:
- Focus or present the Mini EQ app from the panel menu.
- Control Mini EQ system-wide routing and equalized/original audio state.
- List and load saved Mini EQ presets over the Mini EQ D-Bus control API.
- Show the current auto preset link exposed by Mini EQ.

Install for local testing:

Expand Down
8 changes: 4 additions & 4 deletions extensions/gnome-shell/mini-eq@bhack.github.io/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class MiniEqIndicator extends PanelMenu.Button {
this._statusItem.setSensitive(false);
this.menu.addMenuItem(this._statusItem);

this._outputPresetItem = new PopupMenu.PopupMenuItem(_('Output preset: None'));
this._outputPresetItem = new PopupMenu.PopupMenuItem(_('Auto preset: None'));
this._outputPresetItem.setSensitive(false);
this.menu.addMenuItem(this._outputPresetItem);

Expand Down Expand Up @@ -387,7 +387,7 @@ class MiniEqIndicator extends PanelMenu.Button {
this._presetsItem.setSensitive(false);
this._presetsItem.label.text = _('Presets');
this._statusItem.label.text = _('Mini EQ is not running');
this._outputPresetItem.label.text = _('Output preset: None');
this._outputPresetItem.label.text = _('Auto preset: None');
this._quitItem.visible = false;
this._setAnalyzerLevels([]);
this._setPresets([]);
Expand All @@ -405,8 +405,8 @@ class MiniEqIndicator extends PanelMenu.Button {

_outputPresetText(running, presetName) {
if (!running || !presetName)
return _('Output preset: None');
return _('Output preset: %s').format(presetName);
return _('Auto preset: None');
return _('Auto preset: %s').format(presetName);
}

_applyAnalyzerLevels(levels) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"uuid": "mini-eq@bhack.github.io",
"name": "Mini EQ Controls",
"description": "Control Mini EQ from the panel: open the app, toggle system-wide routing and EQ, view analyzer activity, switch saved presets, and see output preset links.",
"description": "Control Mini EQ from the panel: open the app, toggle system-wide routing and EQ, view analyzer activity, switch saved presets, and see auto preset links.",
"shell-version": ["50"],
"url": "https://github.com/bhack/mini-eq"
}
4 changes: 2 additions & 2 deletions io.github.bhack.mini-eq.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ modules:
sources:
- type: git
url: https://github.com/bhack/pipewire-gobject.git
tag: 0.3.4
commit: 82658f10338c2e9530ae575db30f15e709626cdc
tag: 0.3.5
commit: b570bc4ff0a53223416fff9a4fc05d367273789d

- python3-dependencies.yaml

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "mini-eq"
version = "0.6.0"
version = "0.7.0"
description = "Compact PipeWire system-wide parametric equalizer for Linux desktops."
readme = "README.md"
requires-python = ">=3.11"
Expand Down Expand Up @@ -40,7 +40,7 @@ classifiers = [
"Topic :: Multimedia :: Sound/Audio :: Analysis",
"Topic :: Multimedia :: Sound/Audio :: Mixers",
]
dependencies = ["numpy>=1.26", "pipewire-gobject>=0.3.4,<0.4"]
dependencies = ["numpy>=1.26", "pipewire-gobject>=0.3.5,<0.4"]

[project.urls]
Homepage = "https://github.com/bhack/mini-eq"
Expand Down
5 changes: 4 additions & 1 deletion src/mini_eq/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .instance import MiniEqAlreadyRunningError, MiniEqInstanceGuard
from .routing import SystemWideEqController
from .window import MiniEqWindow
from .window_presets import imported_apo_curve_label


class MiniEqApplication(Adw.Application):
Expand Down Expand Up @@ -91,20 +92,22 @@ def ensure_window(self, *, present: bool) -> None:
return

controller: SystemWideEqController | None = None
initial_curve_label: str | None = None

try:
controller = SystemWideEqController(self.args.output_sink)
controller.start()

if self.args.import_apo:
controller.import_apo_preset(self.args.import_apo)
initial_curve_label = imported_apo_curve_label(self.args.import_apo)
except Exception as exc:
if controller is not None:
controller.shutdown()
raise SystemExit(str(exc)) from exc

self.controller = controller
self.window = MiniEqWindow(self, self.controller, self.args.auto_route)
self.window = MiniEqWindow(self, self.controller, self.args.auto_route, initial_curve_label=initial_curve_label)
self.window.set_icon_name(APP_ICON_NAME)
self.window.present_after_setup = present
self.window.set_visible(present)
Expand Down
48 changes: 40 additions & 8 deletions src/mini_eq/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import math
import os
import re
from collections.abc import Iterable
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
Expand Down Expand Up @@ -351,6 +352,23 @@ def normalize_default_preset_name(preset_name: object) -> str | None:
return default_preset or None


def normalize_output_preset_key_candidates(output_keys: str | Iterable[str | None] | None) -> list[str]:
if output_keys is None or isinstance(output_keys, str):
raw_keys: Iterable[str | None] = (output_keys,)
else:
raw_keys = output_keys

normalized: list[str] = []
seen: set[str] = set()
for raw_key in raw_keys:
output_key = str(raw_key or "").strip()
if output_key and output_key not in seen:
normalized.append(output_key)
seen.add(output_key)

return normalized


def load_output_preset_config() -> tuple[dict[str, str], str | None]:
links_path = output_preset_links_path()
if not links_path.exists():
Expand Down Expand Up @@ -399,13 +417,23 @@ def write_output_preset_links(links: dict[str, str]) -> None:
write_output_preset_config(links)


def get_output_preset_link(sink_name: str | None) -> str | None:
output_key = str(sink_name or "").strip()
if not output_key:
def get_output_preset_link_match(output_keys: str | Iterable[str | None] | None) -> tuple[str, str] | None:
candidates = normalize_output_preset_key_candidates(output_keys)
if not candidates:
return None

links, _default_preset = load_output_preset_config()
return links.get(output_key)
for output_key in candidates:
preset_name = links.get(output_key)
if preset_name:
return output_key, preset_name

return None


def get_output_preset_link(output_keys: str | Iterable[str | None] | None) -> str | None:
match = get_output_preset_link_match(output_keys)
return match[1] if match is not None else None


def get_default_preset_name() -> str | None:
Expand All @@ -428,13 +456,17 @@ def set_output_preset_link(sink_name: str, preset_name: str) -> str:
return linked_preset


def clear_output_preset_link(sink_name: str | None) -> str | None:
output_key = str(sink_name or "").strip()
if not output_key:
def clear_output_preset_link(output_keys: str | Iterable[str | None] | None) -> str | None:
candidates = normalize_output_preset_key_candidates(output_keys)
if not candidates:
return None

links, default_preset = load_output_preset_config()
removed = links.pop(output_key, None)
removed = None
for output_key in candidates:
candidate = links.pop(output_key, None)
if removed is None and candidate:
removed = candidate
write_output_preset_config(links, default_preset)
return removed

Expand Down
8 changes: 6 additions & 2 deletions src/mini_eq/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@

Status = Literal["ok", "missing", "warning"]

PWG_REQUIRED_VERSION = "0.3.4"
PWG_REQUIRED_VERSION_PARTS = (0, 3, 4)
PWG_REQUIRED_VERSION = "0.3.5"
PWG_REQUIRED_VERSION_PARTS = (0, 3, 5)
PWG_REQUIRED_SYMBOLS = (
"Core.set_pipewire_property",
"Device.enum_all_params",
"Device.enum_params",
"Device.new",
"Param.new_props_controls",
"RouteInfo.new_from_param",
"Stream.set_pipewire_property",
)
PYGOBJECT_HINT = "Ubuntu/Debian: python3-gi; Fedora: python3-gobject; Arch: python-gobject"
Expand Down
Loading
Loading