taffybar-2.0.0: src/System/Taffybar/Widget/MPRIS2.hs
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.Taffybar.Widget.MPRIS2
-- Copyright : (c) Ivan A. Malison
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : Ivan A. Malison
-- Stability : unstable
-- Portability : unportable
--
-- This is a "Now Playing" widget that listens for MPRIS events on DBus. Various
-- media players implement this. This widget works with version 2 of the MPRIS
-- protocol (https://specifications.freedesktop.org/mpris-spec/latest/).
-----------------------------------------------------------------------------
module System.Taffybar.Widget.MPRIS2 ( mpris2New ) where
import qualified Control.Concurrent.MVar as MV
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import DBus
import DBus.Client
import DBus.Internal.Types
import qualified DBus.TH as DBus
import Data.Coerce
import Data.Either.Combinators
import Data.List
import qualified Data.Text as T
import qualified GI.Gtk as Gtk
import qualified Graphics.UI.Gtk as Gtk2hs
import System.Taffybar.Compat.GtkLibs
import System.Taffybar.Context
import System.Taffybar.Information.DBusClients
import System.Taffybar.Information.MPRIS2
import System.Taffybar.Information.XDG.DesktopEntry
import System.Taffybar.Util
import System.Taffybar.Widget.Util
import Text.Printf
data MPRIS2PlayerWidget = MPRIS2PlayerWidget
{ playerLabel :: Gtk.Label
, playerGrid :: Gtk.Grid
}
mpris2New :: TaffyIO Gtk2hs.Widget
mpris2New = asks dbusClient >>= \client -> lift $ fromGIWidget =<< do
grid <- Gtk.gridNew
alignCenter grid
playerWidgetsVar <- MV.newMVar []
let
newPlayerWidget :: BusName -> IO MPRIS2PlayerWidget
newPlayerWidget busName =
do
-- TODO: Size the image dynamically
pixbuf <-
maybe (loadIcon 20 "play.svg") return =<< runMaybeT
( MaybeT (rightToMaybe <$> getDesktopEntry client busName)
>>= MaybeT . getDirectoryEntryDefault
>>= MaybeT . getImageForDesktopEntry 20
)
image <- Gtk.imageNewFromPixbuf $ Just pixbuf
playerBox <- Gtk.gridNew
label <- Gtk.labelNew Nothing
Gtk.containerAdd playerBox image
Gtk.containerAdd playerBox label
alignCenter playerBox
Gtk.widgetShowAll playerBox
Gtk.containerAdd grid playerBox
Gtk.widgetHide playerBox
return MPRIS2PlayerWidget {playerLabel = label, playerGrid = playerBox}
updatePlayerWidget
children
nowPlaying@NowPlaying
{ npBusName = busName
, npStatus = status
} =
case lookup busName children of
Nothing -> do
playerWidget <- newPlayerWidget busName
setNowPlaying playerWidget
return $ (busName, playerWidget):children
Just playerWidget -> setNowPlaying playerWidget >> return children
where setNowPlaying MPRIS2PlayerWidget {playerLabel = label , playerGrid = playerBox} =
do
Gtk.labelSetMarkup label $ playingText 20 30 nowPlaying
if status == "Playing"
then
Gtk.widgetShow playerBox
else
Gtk.widgetHide playerBox
updatePlayerWidgets nowPlayings playerWidgets = do
newWidgets <- foldM updatePlayerWidget playerWidgets nowPlayings
let existingBusNames = map npBusName nowPlayings
noInfoPlayerWidgets = filter ((`notElem` existingBusNames) . fst) newWidgets
mapM_ (Gtk.widgetHide . playerGrid . snd) noInfoPlayerWidgets
return newWidgets
updatePlayerWidgetsVar nowPlayings =
MV.modifyMVar_ playerWidgetsVar (updatePlayerWidgets nowPlayings)
doUpdate = getNowPlayingInfo client >>= updatePlayerWidgetsVar
signalCallback _ _ _ _ = doUpdate
propMatcher =
matchAny
{ matchPath = Just "/org/mpris/MediaPlayer2" }
handleNameOwnerChanged _ name _ _ = do
busNames <- map (coerce . fst) <$> MV.readMVar playerWidgetsVar
when (name `elem` busNames) doUpdate
_ <- Gtk.onWidgetRealize grid $ do
updateHandler <-
DBus.registerForPropertiesChanged client propMatcher signalCallback
nameHandler <-
DBus.registerForNameOwnerChanged client matchAny handleNameOwnerChanged
void $ Gtk.onWidgetUnrealize grid $
removeMatch client updateHandler >> removeMatch client nameHandler
Gtk.widgetShow grid
doUpdate
Gtk.toWidget grid
playingText :: Int -> Int -> NowPlaying -> T.Text
playingText artistMax songMax NowPlaying { npArtists = artists, npTitle = title } = T.pack $
Gtk2hs.escapeMarkup $ printf "%s - %s"
(truncateString artistMax $ intercalate "," artists)
(truncateString songMax title)