packages feed

taffybar-5.2.0: src/System/Taffybar/Widget/Generic/PollingLabel.hs

{-# LANGUAGE OverloadedStrings #-}

-- | This is a simple text widget that updates its contents by calling
-- a callback at a set interval.
module System.Taffybar.Widget.Generic.PollingLabel where

import Control.Concurrent
import Control.Exception.Enclosed as E
import Control.Monad
import Control.Monad.IO.Class
import qualified Data.Text as T
import qualified GI.Gdk as Gdk
import GI.Gtk
import System.Log.Logger
import System.Taffybar.Util
import System.Taffybar.Widget.Util
import Text.Printf

-- | Create a new widget that updates itself at regular intervals.  The
-- function
--
-- > pollingLabelNew initialString cmd interval
--
-- returns a widget with initial text @initialString@. The widget forks a thread
-- to update its contents every @interval@ seconds. The command should return a
-- string with any HTML entities escaped. This is not checked by the function,
-- since Pango markup shouldn't be escaped. Proper input sanitization is up to
-- the caller.
--
-- If the IO action throws an exception, it will be swallowed and the label will
-- not update until the update interval expires.
pollingLabelNew ::
  (MonadIO m) =>
  -- | Update interval (in seconds)
  Double ->
  -- | Command to run to get the input string
  IO T.Text ->
  m GI.Gtk.Widget
pollingLabelNew interval cmd =
  pollingLabelNewWithTooltip interval $ (,Nothing) <$> cmd

-- | Like 'pollingLabelNew', but also updates tooltip text.
pollingLabelNewWithTooltip ::
  (MonadIO m) =>
  -- | Update interval (in seconds)
  Double ->
  -- | Command to run to get the input string
  IO (T.Text, Maybe T.Text) ->
  m GI.Gtk.Widget
pollingLabelNewWithTooltip interval action =
  pollingLabelWithVariableDelay $ withInterval <$> action
  where
    withInterval (a, b) = (a, b, interval)

-- | Create a polling label where each action result controls the next delay.
pollingLabelWithVariableDelay ::
  (MonadIO m) =>
  IO (T.Text, Maybe T.Text, Double) ->
  m GI.Gtk.Widget
pollingLabelWithVariableDelay =
  pollingLabelWithVariableDelayWithConfig defaultPollingLabelConfig

data PollingLabelConfig d = PollingLabelConfig
  { pollingLabelRefreshOnClick :: Bool,
    pollingLabelVariableDelayConfig :: VariableDelayConfig d
  }

defaultPollingLabelConfig :: PollingLabelConfig d
defaultPollingLabelConfig =
  PollingLabelConfig
    { pollingLabelRefreshOnClick = False,
      pollingLabelVariableDelayConfig = defaultVariableDelayConfig
    }

-- TODO: Customize the delay and message on mouse click

-- | Like 'pollingLabelWithVariableDelay', with optional click-to-refresh.
pollingLabelWithVariableDelayAndRefresh ::
  (MonadIO m) =>
  IO (T.Text, Maybe T.Text, Double) ->
  -- | Whether to refresh the label on mouse click
  Bool ->
  m GI.Gtk.Widget
pollingLabelWithVariableDelayAndRefresh action refreshOnClick =
  pollingLabelWithVariableDelayWithConfig
    ( defaultPollingLabelConfig
        { pollingLabelRefreshOnClick = refreshOnClick
        }
    )
    action

pollingLabelWithVariableDelayWithConfig ::
  (MonadIO m) =>
  PollingLabelConfig Double ->
  IO (T.Text, Maybe T.Text, Double) ->
  m GI.Gtk.Widget
pollingLabelWithVariableDelayWithConfig config action =
  liftIO $ do
    grid <- gridNew
    label <- labelNew Nothing
    ebox <- eventBoxNew
    _ <- widgetSetClassGI grid "polling-label-container"
    _ <- widgetSetClassGI label "polling-label-text"
    _ <- widgetSetClassGI ebox "polling-label"

    when (pollingLabelRefreshOnClick config) $ void $ onWidgetButtonPressEvent ebox $ onClick [Gdk.EventTypeButtonPress] $ do
      postGUIASync $ labelSetMarkup label "Refreshing..."
      forkIO $ do
        newLavelStr <-
          E.tryAny action >>= \case
            Left _ -> return "Error"
            Right (_labelStr, _, _) -> return _labelStr
        postGUIASync $ labelSetMarkup label newLavelStr

    let updateLabel (labelStr, tooltipStr, delay) = do
          postGUIASync $ do
            labelSetMarkup label labelStr
            widgetSetTooltipMarkup label tooltipStr
          logM "System.Taffybar.Widget.Generic.PollingLabel" DEBUG $
            printf "Polling label delay was %s" $
              show delay
          return delay
        updateLabelHandlingErrors =
          E.tryAny action >>= either (const $ return 1) updateLabel

    _ <- onWidgetRealize label $ do
      sampleThread <-
        foreverWithVariableDelayWithConfig
          (pollingLabelVariableDelayConfig config)
          updateLabelHandlingErrors
      void $ onWidgetUnrealize label $ killThread sampleThread

    vFillCenter label
    vFillCenter grid
    containerAdd grid label
    containerAdd ebox grid
    widgetShowAll ebox
    toWidget ebox