A fast, async, pure Rust scrolling MPRIS module for Waybar powered by zbus 5 and tokio.
- Pure Rust Async D-Bus (
zbus 5): Zero C library dependencies (libdbus-1not required). - 100% Event-Driven (Zero IPC Polling): Pure signal-driven architecture with zero background polling loops and complete silence while idle or paused.
- Adaptive Playback Calibration: Transient position calibration automatically compensates for streaming buffer delays (e.g., Spotify, browser MPRIS extensions), Bluetooth latency, and seek jitter across all media players—locking into sample-accurate sync within ~1.2s before self-disarming for zero ongoing D-Bus polling overhead.
- Dual-Tier Player Discovery: Prefers
playerctldfor recency-ordered player prioritization, with an intelligent, priority-ranked fallback (Playing>Paused>Stopped) via standard D-Bus discovery. Idle or stopped players (such asmpdor inactive browser tabs) will never shadow an actively playing player. - Real-Time Lifecycle & Cross-Player Handoff: 100% event-driven, instant UI response to player startup, exit, and playback handoff via D-Bus
NameOwnerChanged, broker-levelPropertiesChangedmatch rules, and optionalplayerctldsignals. Works completely standalone without requiringplayerctld. - Multi-Artist & Album Support: Formats collaborating/featured artists (
xesam:artistjoined with commas) and album metadata. - Sub-Second Rate-Adjusted Timers: Timer precision is decoupled from the scrolling speed, delivering responsive progress updates dynamically adjusted by playback rate.
- Pure Token-Driven Layout: 100% template-controlled output. Place brand icons, state glyphs, timers, and metadata anywhere.
- Field-Aware Inline Scrolling: Scroll individual fields independently (e.g.
{title:20:marquee} | {artist:12:bounce}). - Industry-Standard Scrolling Modes:
marquee(continuous ticker),restart(loop with hold), andbounce(pendulum back & forth). - TOML Configuration File: Persistent declarative settings at
~/.config/ScrollMPRIS/config.tomlwith automatic XDG resolution. - Freeze on Pause: Option to freeze the ticker and snap text back to the start when music is paused.
- Ultra-Lightweight Runtime: Optimized single-threaded Tokio execution with negligible CPU and memory footprint.
"custom/ScrollMPRIS": {
"return-type": "json",
"exec": "ScrollMPRIS",
"escape": true,
"on-click": "playerctl play-pause",
"on-scroll-up": "playerctl next",
"on-scroll-down": "playerctl previous"
}Tip
Both ScrollMPRIS and scrollmpris can be used interchangeably in your configuration or terminal.
Include "custom/ScrollMPRIS" in your modules-left, modules-center, or modules-right.
ScrollMPRIS automatically reads ~/.config/ScrollMPRIS/config.toml (or ~/.config/scrollmpris/config.toml).
Generate a fully commented TOML configuration file with one command:
mkdir -p ~/.config/ScrollMPRIS
ScrollMPRIS --generate-config > ~/.config/ScrollMPRIS/config.toml# General settings
speed = 50 # 0 (1000ms delay) to 100 (100ms delay)
width = 40 # Default max character width for scrolling text
scroll_mode = "marquee" # Default mode: "marquee", "restart", or "bounce"
format = "{player_icon} {status_icon} {title:20:marquee} | {artist:12:bounce} [{position}/{length}]"
format_stopped = "" # Output when stopped ("" to auto-hide from Waybar)
tooltip_format = "{player_icon} {status_icon} {title} - {artist} | {album}"
blocked = ["firefox", "chromium"] # Ignore audio from specific players
freeze_on_pause = true # Pause ticker and reset to start when paused
# Status glyphs for playback states
[icons.status]
playing = ""
paused = ""
stopped = ""
# Player-specific brand icons (fallback icon is "404")
[icons.players]
spotify = ""
vlc = ""
firefox = ""
mpv = ""
chrome = ""
edge = ""
telegramdesktop = ""
tauon = ""
"404" = ""Tip
Any CLI argument passed directly in Waybar or terminal (such as ScrollMPRIS --speed 80) dynamically overrides config.toml.
ScrollMPRIS is 100% token-driven: you control exactly where each element appears.
| Category | Token | Aliases | Description | Example Output |
|---|---|---|---|---|
| Icons | {player_icon} |
{app_icon} |
Player brand glyph | (Spotify), (VLC), (Firefox) |
{status_icon} |
{play_icon}, {state_icon} |
Playback status glyph | (Playing), (Paused) |
|
{icon} |
Combined {player_icon} {status_icon} |
|
||
| Timers | {position} |
{elapsed} |
Current playback elapsed time | 01:23 |
{remaining} |
{countdown} |
Remaining track countdown | 02:45 |
|
{length} |
{duration} |
Total track duration | 04:08 |
|
| Metadata | {title} |
Track title | Blinding Lights |
|
{artist} |
Track artist(s) (comma-joined) | The Weeknd |
||
{album} |
Track album name | After Hours |
||
{player} |
Clean player service name | spotify |
||
{status} |
Raw playback status | Playing / Paused |
Scroll individual metadata fields independently while keeping other elements static:
{title:20}: Scrolls{title}within 20 characters (using default mode);{artist}stays fixed.{title:bounce}: Scrolls{title}withbounceanimation using the globalwidth.{title:20:bounce}: Scrolls{title}within 20 characters usingbounceanimation.{title:15:marquee} | {artist:10:bounce}: Title and artist scroll independently with separate widths and animation styles!
[scroll]{title} - {artist}[/scroll]: Scrolls the combined title and artist block using globalwidth.[scroll:25]{title} - {artist}[/scroll] | {album}: Scrolls the combined block within 25 characters, leaving album fixed.[scroll:25:bounce]{title} - {artist}[/scroll]: Scrolls the combined block within 25 characters usingbounceanimation.
- If no inline modifiers are present (e.g.
format = "{player_icon} {status_icon} {title} - {artist}"), the entire formatted output scrolls up towidthcharacters.
Note
Width Priority & Behavior:
-
Override Priority: Token-specific widths (such as
{title:20}) always override the globalwidthsetting for that field. -
Combined Module Width: With multiple independent tokens (e.g.
{title:20} - {artist:20}), the total rendered length will be the sum of the active field widths plus separators ($\approx 43$ characters). -
Smart Threshold: If a song title/artist is shorter than its limit (e.g. 10 chars vs
{title:20}), it displays statically without wasting space or adding extra padding.
| Mode | Description |
|---|---|
marquee (default) |
Seamless circular continuous ticker loop with separator padding. |
restart |
Smooth left-to-right scroll, holds at the end, and snaps back to start. |
bounce |
Scrolls to the end, holds, reverses direction back to start, holds, and repeats. |
| Option | Description | Example |
|---|---|---|
-c, --config <path> |
Path to custom TOML configuration file | -c ~/my-config.toml |
--generate-config |
Output default configuration TOML to stdout and exit | ScrollMPRIS --generate-config |
-s, --speed <0-100> |
Scroll speed (0: slow=1000ms delay, 100: fast=100ms delay) | -s 50 |
-w, --width <number> |
Maximum character width for scrolling text | -w 40 |
-b, --blocked <list> |
Block specific players (comma-separated, case-insensitive) | -b edge,firefox,mpv |
--scroll <mode> |
Default scrolling behavior: marquee, restart, or bounce |
--scroll marquee |
--format <string> |
Output format template when playing or paused | --format '{player_icon} {title:20} - {artist}' |
--format-stopped <string> |
Output format template when stopped (default: "" to auto-hide) |
--format-stopped '{status_icon} No Media' |
--tooltip-format <string> |
Tooltip format (resolves all fields un-scrolled on hover) | --tooltip-format '{title} - {artist} | {album}' |
--icon-format <string> |
Override player brand icons via JSON mapping | --icon-format '{"404": "", "spotify": ""}' |
--freeze |
Pause scrolling and reset text to start when paused | --freeze |
ScrollMPRIS --format '{player_icon} {status_icon} {title:20} - {artist}'ScrollMPRIS --format '{player_icon} {title:20:marquee} - {artist} [{position}/{length}] {status_icon}'ScrollMPRIS --format '{player_icon} {title:20:bounce} - {artist} (-{remaining})'ScrollMPRIS --format '{status_icon} {title:25} - {artist}'ScrollMPRIS --format '{title:20} - {artist} [{position}]'ScrollMPRIS -s 50 --format '{player_icon} {title:15:marquee} | {artist:10:bounce} {status_icon}'Keep the Waybar module visible with custom placeholder text even when music stops:
ScrollMPRIS --format '{player_icon} {status_icon} {title:20} - {artist}' --format-stopped '{status_icon} No Media'ScrollMPRIS tags the Waybar JSON output with CSS classes matching playback status and player service name (e.g. playing, paused, stopped, spotify, firefox).
Customize your ~/.config/waybar/style.css:
#custom-ScrollMPRIS {
padding: 0 10px;
color: #cdd6f4;
background: #1e1e2e;
border-radius: 8px;
}
#custom-ScrollMPRIS.playing {
color: #a6e3a1;
}
#custom-ScrollMPRIS.paused {
color: #f9e2af;
}
#custom-ScrollMPRIS.stopped {
color: #6c7086;
}
/* Player-specific styling */
#custom-ScrollMPRIS.spotify {
color: #1db954;
}
#custom-ScrollMPRIS.firefox {
color: #ff7139;
}
#custom-ScrollMPRIS.playing.spotify {
border-bottom: 2px solid #1db954;
}yay -S scrollmpris-gitgit clone https://github.com/BEST8OY/ScrollMPRIS.git
cd ScrollMPRIS
cargo build --release
install -Dt /usr/local/bin target/release/ScrollMPRIS
ln -sf /usr/local/bin/ScrollMPRIS /usr/local/bin/scrollmprisRestart mode:
Marquee mode:
This project is licensed under the GNU General Public License v3.0 or later.

