packages feed

taffybar-4.1.2: src/System/Taffybar/Widget/Systemd.hs

{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      : System.Taffybar.Widget.Systemd
-- Copyright   : (c) Ivan A. Malison
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Ivan A. Malison
-- Stability   : unstable
-- Portability : unportable
--
-- This module provides a widget that displays the number of failed systemd
-- units. It monitors both system and user units via DBus.
--
-- The widget shows a warning indicator with the total count of failed units.
-- When there are no failures, the widget can be configured to hide.
--
-- CSS classes applied:
--
-- * @systemd-widget@ - Always applied to the container
-- * @systemd-ok@ - Applied when there are no failed units
-- * @systemd-degraded@ - Applied when there are failed units
--
-- Example CSS:
--
-- > .systemd-widget { padding: 0 5px; }
-- > .systemd-ok { color: #98c379; }
-- > .systemd-degraded { color: #e06c75; }
-----------------------------------------------------------------------------
module System.Taffybar.Widget.Systemd
  ( SystemdConfig(..)
  , defaultSystemdConfig
  , systemdIconNew
  , systemdIconNewWithConfig
  , systemdLabelNew
  , systemdLabelNewWithConfig
  , systemdNew
  , systemdNewWithConfig
  ) where

import           Control.Monad
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Reader
import           Data.Default (Default(..))
import qualified Data.Text as T
import           GI.Gtk as Gtk
import           System.Taffybar.Context
import           System.Taffybar.Information.Systemd
import           System.Taffybar.Util
import           System.Taffybar.Widget.Generic.ChannelWidget
import           System.Taffybar.Widget.Util
import           Text.StringTemplate

-- | Configuration options for the systemd widget.
data SystemdConfig = SystemdConfig
  { -- | Format string when there are failed units.
    -- Available variables: $count$, $system$, $user$
    systemdFormat :: String
    -- | Format string when there are no failures (only used if hideOnOk is False).
  , systemdFormatOk :: String
    -- | Whether to hide the widget when there are no failed units.
  , systemdHideOnOk :: Bool
    -- | Whether to monitor system units.
  , systemdMonitorSystem :: Bool
    -- | Whether to monitor user units.
  , systemdMonitorUser :: Bool
    -- | Nerd font icon character (default U+F071, warning triangle).
  , systemdIcon :: T.Text
  } deriving (Eq, Show)

-- | Default configuration for the systemd widget.
defaultSystemdConfig :: SystemdConfig
defaultSystemdConfig = SystemdConfig
  { systemdFormat = "$count$"
  , systemdFormatOk = "\x2713"        -- Check mark
  , systemdHideOnOk = True
  , systemdMonitorSystem = True
  , systemdMonitorUser = True
  , systemdIcon = T.pack "\xF071"     -- Warning triangle
  }

instance Default SystemdConfig where
  def = defaultSystemdConfig

-- | Create a systemd label widget with default configuration.
systemdLabelNew :: TaffyIO Gtk.Widget
systemdLabelNew = systemdLabelNewWithConfig defaultSystemdConfig

-- | Create a systemd label widget with custom configuration.
systemdLabelNewWithConfig :: SystemdConfig -> TaffyIO Gtk.Widget
systemdLabelNewWithConfig config = do
  chan <- getSystemdInfoChan
  ctx <- ask
  liftIO $ do
    label <- labelNew Nothing
    _ <- widgetSetClassGI label "systemd-widget"

    let updateWidget info = postGUIASync $ do
          let effectiveCount = computeEffectiveCount config info
              isOk = effectiveCount == 0
          updateSystemdLabel config label info
          updateSystemdVisibility config label isOk
          updateSystemdClasses label isOk

    -- Set initial state on realize
    void $ onWidgetRealize label $ do
      info <- runReaderT getSystemdInfoState ctx
      let effectiveCount = computeEffectiveCount config info
          isOk = effectiveCount == 0
      updateSystemdLabel config label info
      updateSystemdVisibility config label isOk
      updateSystemdClasses label isOk

    toWidget =<< channelWidgetNew label chan updateWidget

-- | Create a systemd icon widget with default configuration.
systemdIconNew :: TaffyIO Gtk.Widget
systemdIconNew = systemdIconNewWithConfig defaultSystemdConfig

-- | Create a systemd icon widget with the provided configuration.
systemdIconNewWithConfig :: SystemdConfig -> TaffyIO Gtk.Widget
systemdIconNewWithConfig config = liftIO $ do
  label <- Gtk.labelNew (Just (systemdIcon config))
  Gtk.widgetShowAll label
  Gtk.toWidget label

-- | Create a combined icon+label systemd widget with default configuration.
systemdNew :: TaffyIO Gtk.Widget
systemdNew = systemdNewWithConfig defaultSystemdConfig

-- | Create a combined icon+label systemd widget with custom configuration.
systemdNewWithConfig :: SystemdConfig -> TaffyIO Gtk.Widget
systemdNewWithConfig config = do
  iconWidget <- systemdIconNewWithConfig config
  labelWidget <- systemdLabelNewWithConfig config
  liftIO $ buildIconLabelBox iconWidget labelWidget

-- | Compute the effective count based on configuration.
computeEffectiveCount :: SystemdConfig -> SystemdInfo -> Int
computeEffectiveCount config info =
  let sysCount = if systemdMonitorSystem config then systemFailedCount info else 0
      usrCount = if systemdMonitorUser config then userFailedCount info else 0
  in sysCount + usrCount

-- | Update the label text based on the current state.
updateSystemdLabel :: SystemdConfig -> Gtk.Label -> SystemdInfo -> IO ()
updateSystemdLabel config label info = do
  let effectiveCount = computeEffectiveCount config info
      isOk = effectiveCount == 0
      formatStr = if isOk then systemdFormatOk config else systemdFormat config
      tpl = newSTMP formatStr
      tpl' = setManyAttrib
        [ ("count", show effectiveCount)
        , ("system", show (systemFailedCount info))
        , ("user", show (userFailedCount info))
        ] tpl
  labelSetMarkup label (render tpl')

-- | Update widget visibility based on configuration.
updateSystemdVisibility :: SystemdConfig -> Gtk.Label -> Bool -> IO ()
updateSystemdVisibility config label isOk =
  if isOk && systemdHideOnOk config
    then widgetHide label
    else widgetShow label

-- | Update CSS classes based on state.
updateSystemdClasses :: Gtk.Label -> Bool -> IO ()
updateSystemdClasses label isOk = do
  if isOk
    then do
      addClassIfMissing "systemd-ok" label
      removeClassIfPresent "systemd-degraded" label
    else do
      addClassIfMissing "systemd-degraded" label
      removeClassIfPresent "systemd-ok" label