packages feed

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

{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      : System.Taffybar.Widget.DiskUsage
-- Copyright   : (c) Ivan A. Malison
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Ivan A. Malison
-- Stability   : unstable
-- Portability : unportable
--
-- A label widget that displays disk usage information, backed by a shared
-- polling thread from "System.Taffybar.Information.DiskUsage".
--
-- Template variables: @$total$@, @$used$@, @$free$@, @$available$@,
-- @$usedPercent$@, @$freePercent$@, @$path$@.
--
-- Size values are auto-formatted with appropriate units (GiB, TiB, etc.).
-----------------------------------------------------------------------------

module System.Taffybar.Widget.DiskUsage
  ( DiskUsageWidgetConfig(..)
  , defaultDiskUsageWidgetConfig
  , diskUsageIconNew
  , diskUsageIconNewWith
  , diskUsageLabelNew
  , diskUsageLabelNewWith
  , diskUsageNew
  , diskUsageNewWith
  ) where

import Control.Monad (void)
import Control.Monad.IO.Class
import Data.Default (Default(..))
import qualified Data.Text as T
import qualified GI.Gtk as Gtk
import System.Taffybar.Context (TaffyIO)
import System.Taffybar.Information.DiskUsage
import System.Taffybar.Util (postGUIASync)
import System.Taffybar.Widget.Generic.ChannelWidget
import System.Taffybar.Widget.Util (buildIconLabelBox)
import Text.Printf (printf)
import Text.StringTemplate

data DiskUsageWidgetConfig = DiskUsageWidgetConfig
  { diskUsagePath         :: FilePath
  -- ^ Filesystem path to monitor (default @\/@).
  , diskUsagePollInterval :: Double
  -- ^ Polling interval in seconds (default 60).
  , diskUsageFormat       :: String
  -- ^ Label format string (default @\"$free$\"@).
  , diskUsageTooltipFormat :: Maybe String
  -- ^ Optional tooltip format string.
  , diskUsageIcon :: T.Text
  -- ^ Nerd font icon character (default U+F0A0, ).
  }

defaultDiskUsageWidgetConfig :: DiskUsageWidgetConfig
defaultDiskUsageWidgetConfig = DiskUsageWidgetConfig
  { diskUsagePath         = "/"
  , diskUsagePollInterval = 60
  , diskUsageFormat       = "$free$"
  , diskUsageTooltipFormat =
      Just "$path$: $used$ / $total$ ($usedPercent$% used)"
  , diskUsageIcon = T.pack "\xF0A0" --
  }

instance Default DiskUsageWidgetConfig where
  def = defaultDiskUsageWidgetConfig

-- | Create a disk usage label with default settings (monitors @/@, 60s poll).
diskUsageLabelNew :: TaffyIO Gtk.Widget
diskUsageLabelNew = diskUsageLabelNewWith defaultDiskUsageWidgetConfig

-- | Create a disk usage label with the given configuration.
diskUsageLabelNewWith :: DiskUsageWidgetConfig -> TaffyIO Gtk.Widget
diskUsageLabelNewWith config = do
  let path     = diskUsagePath config
      interval = diskUsagePollInterval config
  chan        <- getDiskUsageInfoChan interval path
  initialInfo <- getDiskUsageInfoState interval path

  liftIO $ do
    label <- Gtk.labelNew Nothing

    let updateLabel info = postGUIASync $ do
          let (labelText, tooltipText) = formatDiskUsage config info
          Gtk.labelSetText label labelText
          Gtk.widgetSetTooltipText label tooltipText

    void $ Gtk.onWidgetRealize label $ updateLabel initialInfo
    Gtk.widgetShowAll label
    Gtk.toWidget =<< channelWidgetNew label chan updateLabel

-- | Create a disk usage icon widget with default configuration.
diskUsageIconNew :: TaffyIO Gtk.Widget
diskUsageIconNew = diskUsageIconNewWith defaultDiskUsageWidgetConfig

-- | Create a disk usage icon widget with the provided configuration.
diskUsageIconNewWith :: DiskUsageWidgetConfig -> TaffyIO Gtk.Widget
diskUsageIconNewWith config = liftIO $ do
  label <- Gtk.labelNew (Just (diskUsageIcon config))
  Gtk.widgetShowAll label
  Gtk.toWidget label

-- | Create a combined icon+label disk usage widget with default configuration.
diskUsageNew :: TaffyIO Gtk.Widget
diskUsageNew = diskUsageNewWith defaultDiskUsageWidgetConfig

-- | Create a combined icon+label disk usage widget.
diskUsageNewWith :: DiskUsageWidgetConfig -> TaffyIO Gtk.Widget
diskUsageNewWith config = do
  iconWidget <- diskUsageIconNewWith config
  labelWidget <- diskUsageLabelNewWith config
  liftIO $ buildIconLabelBox iconWidget labelWidget

-- --------------------------------------------------------------------------
-- Formatting

formatDiskUsage :: DiskUsageWidgetConfig -> DiskUsageInfo -> (T.Text, Maybe T.Text)
formatDiskUsage config info =
  let attrs = diskUsageAttrs (diskUsagePath config) info
      labelText   = renderTpl (diskUsageFormat config) attrs
      tooltipText = renderTpl <$> diskUsageTooltipFormat config <*> pure attrs
  in (labelText, tooltipText)

renderTpl :: String -> [(String, String)] -> T.Text
renderTpl template attrs =
  T.pack $ render $ setManyAttrib attrs (newSTMP template)

diskUsageAttrs :: FilePath -> DiskUsageInfo -> [(String, String)]
diskUsageAttrs path info =
  [ ("total",       formatBytes (diskInfoTotal info))
  , ("used",        formatBytes (diskInfoUsed info))
  , ("free",        formatBytes (diskInfoFree info))
  , ("available",   formatBytes (diskInfoAvailable info))
  , ("usedPercent", printf "%.0f" (diskInfoUsedPercent info))
  , ("freePercent", printf "%.0f" (diskInfoFreePercent info))
  , ("path",        path)
  ]

-- | Format a byte count with auto-scaled units.
formatBytes :: Integer -> String
formatBytes bytes
  | gb >= 1024 = printf "%.1f TiB" (gb / 1024 :: Double)
  | gb >= 1    = printf "%.1f GiB" gb
  | mb >= 1    = printf "%.0f MiB" mb
  | otherwise  = printf "%.0f KiB" kb
  where
    kb = fromIntegral bytes / 1024        :: Double
    mb = kb / 1024
    gb = mb / 1024