Skip to content

Squared Theming, Album Artwork, New Settings, and Bug Fixes - #1

Open
JaxonWright wants to merge 10 commits into
ErikBurdett:mainfrom
JaxonWright:feat/wavebar-customization
Open

Squared Theming, Album Artwork, New Settings, and Bug Fixes#1
JaxonWright wants to merge 10 commits into
ErikBurdett:mainfrom
JaxonWright:feat/wavebar-customization

Conversation

@JaxonWright

@JaxonWright JaxonWright commented Sep 4, 2026

Copy link
Copy Markdown

Summary

Adds a set of display and interaction customizations to WaveBar: an in-panel settings section, artist and full-title display, opt-in album art with a hardened URL allowlist, grouped playback controls, and squared waveform/slider theming.

Changes

Squared Theming

  • Waveform.qml: square waveform bars (radius: 0).
  • WavebarSlider.qml (new, vendored plugin-local copy): squared progress/volume slider derived from Omarchy's PanelSlider, imported qs.Ui so BorderSurface resolves outside the shell Ui dir, and radius: 0 on track/ticks/knob. Used by both the bar and panel so other Omarchy sliders are untouched.

Working, Safe Album Artwork

  • Service.qml: exposes a centralized, hardened trackArtUrl with isSafeTrackArt/safeTrackArt allowlist — only local file:// paths and a known set of trusted cover CDNs (Apple Music mzstatic.com, Spotify scdn.co, YouTube yimg.com/ggpht.com/googleusercontent.com, Tidal/Deezer tidal.com/dzcdn.net) are loaded. All other MPRIS-provided URLs (remote hosts, data:, special files, oversized sources) are rejected and treated as no cover.
  • BarWidget.qml: shows an 18px cover thumbnail between the waveform and the title when showCover is on (and not in vertical mode).
  • Panel.qml: shows the album art as the panel header image with a glyph fallback.
  • manifest.json: new showCover setting.

Artist and Full-Title Display Settings

  • BarWidget.qml: showArtist appends the artist after the title (matching the tooltip); showFullTitle shows the entire title without truncation and disables the scroll animation.
  • manifest.json: new showArtist and showFullTitle settings.

Grouped Playback Controls Setting

  • BarWidget.qml: groupControls places previous/play/pause/next into a single tight cluster beside the media info. Each transport button owns its own hover tooltip so grouping never blocks tooltips, and the previous-button tooltip target switches correctly when grouped.
  • manifest.json: new groupControls setting.

In-Panel Settings Section

  • Panel.qml: a cog (Settings) button beside the volume slider reveals a Settings section with toggles for every WaveBar option — show track title (plus the artist/full-title sub-toggles), show album cover, show playback controls (plus the grouped-controls toggle), and hide when paused. Toggles write back to the shell config via a setBooleanSetting helper using mutateShellConfig, so changes persist.
  • Panel content height grows while the section is open, the volume row keeps a gap for the settings button, and play/pause/next buttons use consistent icon sizing.

Bug Fixes

  • Tooltips on playback controls now appear correctly. Their text previously overflowed off the bottom of the tooltip
  • All UI elements are aligned properly and all icons are aligned as well

Docs

  • README.md: features list, inline config JSON (new settings), rewritten security section describing the opt-in art allowlist, and WavebarSlider.qml added to the qmllint validation command.
  • CHANGELOG.md: new ## Unreleased section under Media & interface and Security.

Screenshots

Click to View

Square Waveforms
image

Show Artist in Title
image

Show full title: removes the width limit and no longer marquees
image

Grouped Playback Controls
image

Fixed Tooltips: The live version of this plugin has tooltips that cover the buttons and their text overflows at the bottom
image

Main Panel: Notice squared seek and volume controls, properly aligned playback control icons, and a new settings cog
image

Main Panel with all settings enabled
image

Notes

  • Settings are string-safe-validated (String(setting(..., false)).toLowerCase() === "true") because omarchy bar set writes strings while the GUI writes booleans.
  • The panel settings section keeps dependent toggles hidden unless their parent is enabled (showArtist/showFullTitle hidden unless showTitle; groupControls hidden unless showControls).
  • Squared slider is vendored plugin-local rather than editing the shared /usr/share/omarchy/shell/Ui/PanelSlider.qml.

Square the waveform bars and provide a plugin-local WavebarSlider with
squared track, fill, tick, and knob styling so the progress and volume
sliders match, without changing other Omarchy panels that use the shared
rounded PanelSlider.
Expose a safe trackArtUrl on the media service and show the album cover in
the bar (between the waveform and the title) and as the panel header art.
Art loading is opt-in via the showCover setting and only accepts local
file:// paths plus a trusted allowlist of cover CDNs (Apple Music, Spotify,
YouTube, Tidal/Deezer); all other MPRIS-provided URLs are rejected and
treated as no cover. The allowlist lives on the service so the bar and
panel share a single check.
Add a showArtist setting that places the artist name after the track title,
matching the panel tooltip, and a showFullTitle setting that stops truncation
and horizontal scrolling so the full title (and artist) is shown. When full
title is enabled the scroll animation is disabled.
Add a groupControls setting that places previous/play/next into a single tight
group beside the media info. When disabled (default), the previous button stays
separable on the leading edge. Each transport button now owns its own hover
tooltip so grouping never blocks tooltips on the other controls.
Add a gear button next to the volume slider that reveals a Settings section in
the panel with toggles for every WaveBar option: show track title (and the
hidden artist/title toggles), show album cover, show playback controls (and the
grouped-controls toggle), and hide when paused. Toggles write back to the shell
config through a setBooleanSetting helper so changes persist, and a taller
content height is used while the section is open. The panel's sliders now use
the same squared WavebarSlider as the bar, the volume row keeps its gap for the
settings button, and the playback buttons use consistent icon sizing.
Document the new artist, full-title, album-cover, and grouped-controls options,
the add-on panel settings section, and the squared waveform/slider theming.
Describe the opt-in album-art URL allowlist in the security section and add
WavebarSlider to the qmllint validation command. Add an Unreleased changelog
section covering the media/interface and security changes.
@JaxonWright JaxonWright changed the title WaveBar customization: settings panel, artist/full-title, cover art, grouped controls, squared slider Squared Theming, Album Artwork, New Settings, and Bug Fixes Sep 4, 2026
@JaxonWright

Copy link
Copy Markdown
Author

@ErikBurdett fantastic work on this plugin. If these changes are too much, let me know. If so, I'll start maintaining my fork as its own project because I have many more ideas

Raise the fitted-content height cap while the panel settings section is open
so the last toggle (hide when paused) is not clipped at the bottom when every
option is shown.
Wrap the panel's content in a ScrollView so that when the settings section is
open every toggle stays reachable: content taller than the panel's fitted
height scrolls vertically instead of the last option overflowing the bottom
edge. The vertical scrollbar appears only when the content actually overflows.
@ErikBurdett

Copy link
Copy Markdown
Owner

@JaxonWright Yo! Thank you so much for checking out the plugin - I made it for myself and pushed it to the marketplace to learn the process, really didn't expect anyone to use it lol.

Testing your Pull Request locally and if it works it'll be merged! I do want to maintain this but updates may be slow on my end as I'm a bit busy with work most days, but please, by all means if you want to just fork and create your own version please do :)

The panel body was a bare Column with no height bound to the card, so it
drew past the card edge once the settings section was expanded. The
settings list had its own fixed-height Flickable, which bounded only the
toggles and left everything else to overflow.

That inner Flickable also wrote `ScrollBar.vertical: ScrollBar { ... }`
unqualified, but this file imports QtQuick.Controls as QQC, so the type
was unresolvable and Panel.qml failed to load outright. The previous
attempt had the same defect with a bare ScrollView. The omarchy shell
panels this was modelled on import QtQuick.Controls unqualified, which
is why the pattern looked correct when copied over.

Replace both with a single Flickable filling the key catcher so the whole
body scrolls as one list, and qualify the scrollbar as QQC.ScrollBar —
matching the sourceView ListView, which already had it right. Drop the
now-redundant settingsList wrapper Column.

Verified on a 2560x1440 screen with settings expanded: content 915px in a
947px card against 1414px available, so the panel sizes to fit and the
scrollbar stays hidden; it engages only when the content exceeds the card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rk2aBjm8DnhCip4mm5DbgH
@JaxonWright

Copy link
Copy Markdown
Author

Ok, there should no longer be any more changes required. That was a bugger to fix!

mediaButton declares `tooltipHovered: mediaHover.containsMouse`, but the
MouseArea it names had lost its `id: mediaHover`, so every load threw
"ReferenceError: mediaHover is not defined" from BarWidget.qml:117.

The same edit also retargeted the tooltip from mediaButton to root. The
bar resolves tooltip visibility through `target.tooltipHovered === true`
(Bar.qml:174) and qs.Ui/BarWidget does not define that property, so the
media tooltip's hover check silently evaluated false.

Restore the id and target mediaButton, matching prevLeft, prevButton,
playButton and nextButton: the hoverable Item owns tooltipHovered, its
MouseArea carries the matching <id>Hover id, and showTooltip receives
that Item.

The shell now loads the plugin with no warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rk2aBjm8DnhCip4mm5DbgH
@ErikBurdett

Copy link
Copy Markdown
Owner

Thanks for this — it's a genuinely good contribution, and the three commits you
pushed after the initial review fixed the things I'd found before I could even
report them.

I merged this into a local staging branch, installed it on my Omarchy machine,
and ran it. Album art works end to end, the settings toggles persist correctly
to shell.json as booleans, the squared theming looks right, and the panel
lays out properly. Replacing ScrollView with a plain Flickable was the
right call — it avoids the import problem rather than patching around it. And
retargeting the media tooltip from root to mediaButton in 20e38f5 was
better than just restoring the id: Bar.qml reads target.tooltipHovered to
decide whether to keep a tooltip up, and root has no such property, so
pointing at it would have left retention broken even with mediaHover defined.

Two things before I can merge, plus some smaller notes.


1. Required: pragma ComponentBehavior: Bound in WavebarSlider.qml

This is the only thing failing CI. Add as the first line of the file:

pragma ComponentBehavior: Bound

Why: the tick Repeater reaches root and track by id from inside a
delegate, which is unqualified access across a component boundary. Omarchy's
PanelSlider does the same, but Omarchy's copies are never linted by this
repository, so vendoring the file brought six warnings in with it. The README's
Validate section already states the convention — "WaveBar's own delegate
scopes use ComponentBehavior: Bound, so genuine unqualified-access warnings
are not exempted"
— so this just brings the vendored copy in line with the
rest of the codebase.

I've verified this one line is enough to make the whole merge green.

2. Required: make the security claims match the code

README.md in this PR says:

All other MPRIS-provided URLs — remote hosts, data:, special files, and
oversized sources
— are rejected and treated as no cover.

The remote-host and data: halves are true and the allowlist is well built —
it fails closed, which is the right default. But the last two aren't
implemented. isSafeTrackArt returns true for any string starting with /,
so these all pass:

/dev/zero            -> allowed
/proc/self/environ   -> allowed
/etc/shadow          -> allowed

and there's no size check anywhere in the path.

There's also a claim on main that this PR makes false without updating it —
README.md line 111 says WaveBar "opens no network connections". With
showCover enabled it does; I watched it fetch from i.scdn.co.

Why this one matters more than it looks: this plugin is listed in the
Omarchy plugin marketplace, and the registry records a security baseline
against a specific commit. The README is what a reviewer reads to understand
what the plugin does. A claim that overstates what the code rejects is worse
than no claim, and 1.0.2's changelog explicitly removed artwork ingestion for
security — reintroducing it is fine, but it should be a deliberate, accurately
described reversal.

The accurate version is also the stronger one, because your feature defaults to
off:

A default install opens no network connections. Enabling showCover fetches
cover art over HTTPS from an allowlist of cover CDNs; all other URLs,
including data: and unlisted hosts, are refused.

Either narrow the claim to that, or implement the rejections it currently
promises. Narrowing is fine by me.


Smaller notes, not blocking

  • The allowlist rejects some legitimate URLs. It matches on the raw
    authority string, so an uppercase host or an explicit port is refused:
    https://I.SCDN.CO/image/ab and https://i.scdn.co:8443/image/ab both fail.
    Fails closed, so it only costs you missing art. Conversely
    https://evil.com\.scdn.co/x.png passes the string check — Qt's QUrl
    should refuse the backslash so I don't think it's exploitable, but parsing
    the URL rather than string-matching it would close both.
  • Inconsistent settings parsing. Your new settings read
    String(setting(...)).toLowerCase() === "true", but showControls,
    showTitle, and hideWhenPaused still use === true, while the panel's
    toggles read all seven the string-safe way. So a string-valued showTitle
    would show as on in the panel and off in the bar. Worth unifying — your
    approach is the correct one, it just needs applying to the older three.
  • WavebarSlider.qml has no attribution. It's a near-verbatim copy of
    Omarchy's PanelSlider.qml with three radius changes. A comment at the top
    saying where it came from and that it needs re-syncing when upstream moves
    would save whoever touches it next.

Rebasing

I've pushed a few things to main since you opened this, so you'll need to
update:

  • A dedupe fix (MediaModel.js) released as 1.0.3
  • A qmllint job in CI, which is what's now checking this PR
  • CONTRIBUTING.md, SECURITY.md, and a PR template

main now requires both CI jobs to pass and requires the branch to be up to
date, so please merge or rebase main into your branch. CHANGELOG.md will
conflict
— keep your ## Unreleased section above the new ## 1.0.3 entry.
Leave version in manifest.json alone; I bump it when cutting the release.

CI needs my approval to run on a fork PR, so I'll approve each push. If the
qml job is red, run this locally to see exactly what it sees:

/usr/lib/qt6/bin/qmllint --max-warnings 0 --missing-property info \
  -I /usr/share/omarchy/shell \
  -i /usr/share/omarchy/shell/Commons/qmldir \
  -i /usr/share/omarchy/shell/Ui/qmldir \
  *.qml

Also worth a read: CONTRIBUTING.md now documents the thing that makes this
codebase awkward to test — Quickshell holds the compiled component, so a QML
edit needs omarchy-restart-shell before you can judge it, and an unresolved
QML type passes every offline check before failing at a user's login.

Happy to take the two required items in whatever order suits you. Nice work on
this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants