Add Custom Media Source Integration via IPC Hooks#1727
Add Custom Media Source Integration via IPC Hooks#1727higorprado wants to merge 2 commits intoAvengeMedia:masterfrom
Conversation
Enable external media players (rmpc, mpv, cmus, etc.) to push metadata
to DMS through IPC hooks, allowing non-MPRIS players to appear in the
media widgets alongside native MPRIS players.
Changes:
- Add CustomMediaSource.qml singleton for external media state
- Add unified currentPlayer interface in MprisController that
transparently switches between MPRIS and custom sources
- Update media widgets (bar, dash, OSD) to use currentPlayer
- Add IPC commands: media update, setCommands, clear, status
- Add IPC documentation for the media target
External tools can integrate by calling the IPC from their hooks:
dms ipc call media setCommands '{"play":"rmpc play",...}'
dms ipc call media update '{"title":"...","artist":"...",...}'
| } | ||
|
|
||
| function _exec(cmd) { | ||
| if (cmd) Quickshell.execDetached(["sh", "-c", cmd]) |
There was a problem hiding this comment.
It's a little risky that shell commands could potentially be injected into the config, but I think it's a low risk.
quickshell/Widgets/DankAlbumArt.qml
Outdated
|
|
||
| Loader { | ||
| active: activePlayer?.playbackState === MprisPlaybackState.Playing && showAnimation | ||
| active: activePlayer?.playbackState === 1 && showAnimation |
There was a problem hiding this comment.
Why replace the enum here with numbers?
There was a problem hiding this comment.
You're right, using magic numbers is not ideal. I've replaced them with CustomMediaSource.statePlaying/stateStopped/statePaused constants. These work for both MprisPlayer and CustomMediaSource since both use the same numeric values (0, 1, 2). This keeps the code readable while maintaining compatibility with the unified media interface.
| id: root | ||
|
|
||
| property MprisPlayer activePlayer | ||
| property var activePlayer |
There was a problem hiding this comment.
I get the rationale I guess for making this a generic type, but I'm not too keen on losing the QML type here. Maybe a second property or a wrapped QtObject
There was a problem hiding this comment.
Using var is a pragmatic solution for polymorphism in QML. The activePlayer property can be either an MprisPlayer or CustomMediaSource, and QML doesn't support union types. Both types expose the same interface (playbackState, trackTitle, trackArtUrl, etc.), so a wrapper or dual properties would add complexity without practical benefit.
| readonly property bool useCustomSource: { | ||
| if (forcedSource === "custom") return CustomMediaSource.available | ||
| if (forcedSource === "mpris") return false | ||
| // Auto: use custom when it has content (track loaded), regardless of playing state |
There was a problem hiding this comment.
Why do we want to use it regardless of playing state?
There was a problem hiding this comment.
This is intentional behavior. External players like rmpc or cmus pause frequently while users work. When paused, users still expect to see their current track in the UI. If we only used the custom source when actively playing, the UI would switch away to another MPRIS player (or show nothing) every time the user paused — which would be confusing. The customHasContent check means "a track is loaded", not "track is playing".
Replace magic numbers (0, 1, 2) with CustomMediaSource constants (stateStopped, statePlaying, statePaused) for better readability and consistency across the unified media interface.
dd183e6 to
baee57f
Compare
|
Thank you for the review. Regarding the enum vs magic numbersYou're right, using magic numbers is not ideal. I've replaced them with Regarding the
|
|
|
||
| Loader { | ||
| active: activePlayer?.playbackState === MprisPlaybackState.Playing && showAnimation | ||
| active: activePlayer?.playbackState === CustomMediaSource.statePlaying && showAnimation |
There was a problem hiding this comment.
What I am wondering is why cant we re-use the MprisPlaybackState enum that is built into quickshell, instead of having a separate one or fixed numbers? We can re-purpose it for our custom media player values so it matches, same enum.
| forcedSource = "mpris" | ||
| activePlayer = source.player | ||
| } else { | ||
| forcedSource = "auto" |
There was a problem hiding this comment.
There is no path back to "auto" - selectSource seems like it's always called with an argument
| if (artist.length > 0) | ||
| return artist + (isActive ? " (Active)" : ""); | ||
| return isActive ? "Active" : "Available"; | ||
| } |
There was a problem hiding this comment.
We lose the artist data here yea?
| } | ||
|
|
||
| function _exec(cmd) { | ||
| if (cmd) Quickshell.execDetached(["sh", "-c", cmd]) |
There was a problem hiding this comment.
I feel like instead of this, we should just have a list of known players and commands like rpmc, that we can add to later. Feels a little risky to allow it this way.
Summary
Add support for external media players (rmpc, cmus, mpv, etc.) to integrate with DMS media widgets through IPC hooks, enabling non-MPRIS players to appear alongside native MPRIS players in the bar, dash, and OSD.
Problem
DMS media widgets only support MPRIS-compatible players. Many popular terminal-based music players (rmpc/MPD, cmus, moc, etc.) don't expose MPRIS interfaces, leaving users without media controls in the shell UI.
Solution
Implement a custom media source that receives metadata updates via IPC hooks and executes shell commands for playback control. The system integrates transparently with existing MPRIS infrastructure.
Architecture
CustomMediaSource.qml
A singleton that mirrors the MprisPlayer interface:
media.updateIPC commandMprisController.qml
Extended as the unified media interface:
currentPlayerproperty returns either MPRIS player or CustomMediaSourcecurrentIsPlaying,currentTrackTitle, etc.) for reliable UI updatesWidget Updates
All media widgets now use
MprisController.currentPlayer:IPC Commands
New
mediatarget for external integrations:media.update <json>media.setCommands <json>media.statusmedia.playmedia.pausemedia.playPausemedia.nextmedia.previousmedia.clearIntegration Example
External tools integrate by calling IPC from their hooks. Example with rmpc:
~/.config/rmpc/dms_hook:
~/.config/rmpc/config.ron:
The hook pattern ensures commands are always available even after DMS restarts.
Changes
Services/CustomMediaSource.qmlServices/MprisController.qmlDMSShellIPC.qmlmediaIPC targetModules/DankBar/Widgets/Media.qmlModules/DankDash/MediaPlayerTab.qmlModules/DankBar/Widgets/AudioVisualization.qmlModules/DankDash/Overview/MediaOverviewCard.qmlWidgets/DankSeekbar.qmldocs/IPC.mdmediatargetTesting
Tested with rmpc (MPD client):