taffybar-7.3.0: src/System/Taffybar/Widget/CPUMonitor.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- |
-- Module : System.Taffybar.Widget.CPUMonitor
-- Copyright : (c) José A. Romero L.
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : José A. Romero L. <escherdragon@gmail.com>
-- Stability : unstable
-- Portability : unportable
--
-- Simple CPU monitor that uses a channel-driven graph to visualize variations
-- in the user and system CPU times in one selected core, or in all cores
-- available.
module System.Taffybar.Widget.CPUMonitor where
import Control.Monad (void)
import Control.Monad.IO.Class (liftIO)
import Data.IORef (atomicModifyIORef', newIORef)
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import qualified GI.Gdk as Gdk
import qualified GI.Gtk
import System.Taffybar.Context (TaffyIO)
import System.Taffybar.Information.CPU2
( CPULoad (..),
CPULoadSource,
acquireCPULoadFastRefresh,
cpuLoadSourceChannel,
forceCPULoadRefresh,
getCPULoadSource,
)
import System.Taffybar.Information.CPUPower
( CPUPowerInfo (..),
getCPUPowerInfoChan,
getCPUPowerInfoState,
)
import System.Taffybar.Util (postGUIASync)
import System.Taffybar.Widget.Generic.ChannelGraph
import System.Taffybar.Widget.Generic.ChannelWidget (channelWidgetNew)
import System.Taffybar.Widget.Generic.Graph
import System.Taffybar.Widget.Util (widgetSetClassGI)
import Text.Printf (printf)
-- | Creates a new CPU monitor. This is a channel-driven graph fed by CPU load
-- samples for one core (or all cores when "cpu" is selected).
cpuMonitorNew ::
-- | Configuration data for the Graph.
GraphConfig ->
-- | Polling period (in seconds).
Double ->
-- | Name of the core to watch (e.g. \"cpu\", \"cpu0\").
String ->
TaffyIO GI.Gtk.Widget
cpuMonitorNew cfg interval cpu = do
cpuMonitorNewWithHover cfg interval (min interval 0.5) cpu
-- | Create a CPU monitor that temporarily requests a faster coordinated
-- sampling cadence while the pointer is over the graph. Pointer leave and
-- widget unrealize both release the request, so the fast scheduler interval
-- does not remain active in the background.
cpuMonitorNewWithHover ::
-- | Configuration data for the graph.
GraphConfig ->
-- | Normal polling period, in seconds.
Double ->
-- | Polling period while hovered, in seconds.
Double ->
-- | Name of the core to watch (for example, @"cpu"@ or @"cpu0"@).
String ->
TaffyIO GI.Gtk.Widget
cpuMonitorNewWithHover cfg interval hoverInterval cpu = do
source <- getCPULoadSource cpu interval hoverInterval
liftIO $ do
graphWidget <- channelGraphNew cfg (cpuLoadSourceChannel source) toSample
addHoverRefresh source graphWidget
-- | Create a hover-responsive CPU graph with a live RAPL package-power label.
-- The power sampler is shared across all bars through the Taffybar context.
cpuMonitorNewWithHoverAndPower ::
-- | Configuration data for the graph.
GraphConfig ->
-- | Normal CPU polling period, in seconds.
Double ->
-- | CPU polling period while hovered, in seconds.
Double ->
-- | Package-power polling period, in seconds.
Double ->
-- | Name of the core to watch (for example, @"cpu"@ or @"cpu0"@).
String ->
TaffyIO GI.Gtk.Widget
cpuMonitorNewWithHoverAndPower cfg interval hoverInterval powerInterval cpu = do
source <- getCPULoadSource cpu interval hoverInterval
powerChan <- getCPUPowerInfoChan powerInterval
initialPower <- getCPUPowerInfoState powerInterval
liftIO $ do
graphWidget <- channelGraphNew (cfg {graphLabel = Nothing}) (cpuLoadSourceChannel source) toSample
overlay <- GI.Gtk.overlayNew
powerLabel <- GI.Gtk.labelNew Nothing
void $ widgetSetClassGI powerLabel "graph-label"
GI.Gtk.containerAdd overlay graphWidget
GI.Gtk.overlayAddOverlay overlay powerLabel
GI.Gtk.overlaySetOverlayPassThrough overlay powerLabel True
let updatePowerLabel info = postGUIASync $ do
GI.Gtk.labelSetMarkup powerLabel $ powerLabelMarkup cfg info
GI.Gtk.widgetSetTooltipText overlay $ powerTooltip info
void $ GI.Gtk.onWidgetRealize powerLabel $ updatePowerLabel initialPower
powerWidget <- channelWidgetNew overlay powerChan updatePowerLabel
GI.Gtk.toWidget powerWidget >>= addHoverRefresh source
addHoverRefresh ::
CPULoadSource ->
GI.Gtk.Widget ->
IO GI.Gtk.Widget
addHoverRefresh source graphWidget = do
eventBox <- GI.Gtk.eventBoxNew
GI.Gtk.eventBoxSetVisibleWindow eventBox False
GI.Gtk.eventBoxSetAboveChild eventBox True
GI.Gtk.containerAdd eventBox graphWidget
widget <- GI.Gtk.toWidget eventBox >>= (`widgetSetClassGI` "cpu-monitor")
releaseRef <- newIORef Nothing
let beginFastRefresh = do
currentRelease <- atomicModifyIORef' releaseRef $ \current -> (current, current)
case currentRelease of
Just _ -> pure ()
Nothing -> do
release <- acquireCPULoadFastRefresh source
previous <- atomicModifyIORef' releaseRef (Just release,)
fromMaybe (pure ()) previous
endFastRefresh = do
release <- atomicModifyIORef' releaseRef (Nothing,)
fromMaybe (pure ()) release
GI.Gtk.widgetAddEvents
widget
[ Gdk.EventMaskEnterNotifyMask,
Gdk.EventMaskLeaveNotifyMask
]
void $ GI.Gtk.onWidgetEnterNotifyEvent widget $ \_ -> beginFastRefresh >> pure False
void $ GI.Gtk.onWidgetLeaveNotifyEvent widget $ \_ -> endFastRefresh >> pure False
void $ GI.Gtk.onWidgetRealize graphWidget $ forceCPULoadRefresh source
void $ GI.Gtk.onWidgetUnrealize widget endFastRefresh
pure widget
powerLabelMarkup :: GraphConfig -> CPUPowerInfo -> T.Text
powerLabelMarkup cfg info =
T.intercalate "\n" $
filter (not . T.null) [fromMaybe "" $ graphLabel cfg, powerText]
where
powerText = maybe "--W" (T.pack . printf "%.0fW") $ cpuPackagePowerWatts info
powerTooltip :: CPUPowerInfo -> Maybe T.Text
powerTooltip info =
Just $
maybe
"CPU package power unavailable"
(T.pack . printf "CPU package power: %.1f W")
(cpuPackagePowerWatts info)
toSample :: CPULoad -> IO [Double]
toSample CPULoad {cpuTotalLoad = totalLoad, cpuSystemLoad = systemLoad} =
return [totalLoad, systemLoad]