taffybar-4.1.2: src/System/Taffybar/Information/PowerProfiles.hs
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.Taffybar.Information.PowerProfiles
-- Copyright : (c) Ivan A. Malison
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : Ivan A. Malison
-- Stability : unstable
-- Portability : unportable
--
-- This module provides information about the current power profile using
-- the power-profiles-daemon DBus API (net.hadess.PowerProfiles).
-----------------------------------------------------------------------------
module System.Taffybar.Information.PowerProfiles
( PowerProfile(..)
, PowerProfileInfo(..)
, getPowerProfileInfo
, getPowerProfileInfoFromClient
, getPowerProfileInfoChan
, getPowerProfileInfoState
, cycleProfile
, setProfile
, powerProfileToString
, stringToPowerProfile
) where
import Control.Concurrent.MVar
import Control.Concurrent.STM.TChan
import Control.Monad.IO.Class
import Control.Monad.STM (atomically)
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import DBus
import DBus.Client
import DBus.Internal.Types (Serial(..))
import qualified DBus.TH as DBus
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)
import Data.Text (Text)
import System.Log.Logger
import System.Taffybar.Context
import System.Taffybar.Util (logPrintF, maybeToEither)
-- | Power profile modes supported by power-profiles-daemon.
data PowerProfile
= PowerSaver
| Balanced
| Performance
deriving (Eq, Show, Ord, Enum, Bounded)
-- | Information about the current power profile state.
data PowerProfileInfo = PowerProfileInfo
{ currentProfile :: PowerProfile
, availableProfiles :: [PowerProfile]
, performanceDegraded :: Maybe Text
} deriving (Eq, Show)
-- | The DBus bus name for power-profiles-daemon.
powerProfilesBusName :: BusName
powerProfilesBusName = "net.hadess.PowerProfiles"
-- | The DBus object path for power-profiles-daemon.
powerProfilesObjectPath :: ObjectPath
powerProfilesObjectPath = "/net/hadess/PowerProfiles"
-- | The DBus interface name for power-profiles-daemon.
powerProfilesInterfaceName :: InterfaceName
powerProfilesInterfaceName = "net.hadess.PowerProfiles"
powerProfilesLogPath :: String
powerProfilesLogPath = "System.Taffybar.Information.PowerProfiles"
powerProfilesLogF :: (MonadIO m, Show t) => Priority -> String -> t -> m ()
powerProfilesLogF = logPrintF powerProfilesLogPath
-- | Convert a PowerProfile to its DBus string representation.
powerProfileToString :: PowerProfile -> Text
powerProfileToString PowerSaver = "power-saver"
powerProfileToString Balanced = "balanced"
powerProfileToString Performance = "performance"
-- | Parse a DBus string to a PowerProfile.
stringToPowerProfile :: Text -> Maybe PowerProfile
stringToPowerProfile "power-saver" = Just PowerSaver
stringToPowerProfile "balanced" = Just Balanced
stringToPowerProfile "performance" = Just Performance
stringToPowerProfile _ = Nothing
-- | Default info when power-profiles-daemon is unavailable.
unknownProfileInfo :: PowerProfileInfo
unknownProfileInfo = PowerProfileInfo
{ currentProfile = Balanced
, availableProfiles = []
, performanceDegraded = Nothing
}
-- XXX: Remove this once it is exposed in haskell-dbus
dummyMethodError :: MethodError
dummyMethodError = methodError (Serial 1) $ errorName_ "org.ClientTypeMismatch"
readDictMaybe :: IsVariant a => Map Text Variant -> Text -> Maybe a
readDictMaybe dict key = M.lookup key dict >>= fromVariant
getProperties
:: Client
-> IO (Either MethodError (Map Text Variant))
getProperties client = runExceptT $ do
reply <- ExceptT $ getAllProperties client $
(methodCall powerProfilesObjectPath powerProfilesInterfaceName "FakeMethod")
{ methodCallDestination = Just powerProfilesBusName }
ExceptT $ return $ maybeToEither dummyMethodError $
listToMaybe (methodReturnBody reply) >>= fromVariant
-- | Get current power profile info using the system DBus client from Context.
getPowerProfileInfo :: TaffyIO PowerProfileInfo
getPowerProfileInfo = asks systemDBusClient >>= liftIO . getPowerProfileInfoFromClient
-- | Get current power profile info from a DBus client.
getPowerProfileInfoFromClient :: Client -> IO PowerProfileInfo
getPowerProfileInfoFromClient client = do
propsResult <- getProperties client
case propsResult of
Left err -> do
powerProfilesLogF WARNING "Failed to read power profiles properties: %s" err
return unknownProfileInfo
Right props -> do
let activeProfileStr = readDictMaybe props "ActiveProfile" :: Maybe Text
profilesArray = readDictMaybe props "Profiles" :: Maybe [Map Text Variant]
degraded = readDictMaybe props "PerformanceDegraded" :: Maybe Text
-- Parse available profiles from the array of dicts
parseProfile :: Map Text Variant -> Maybe PowerProfile
parseProfile m = do
name <- readDictMaybe m "Profile"
stringToPowerProfile name
availableProfs = maybe [] (mapMaybe parseProfile) profilesArray
currentProf = fromMaybe Balanced (activeProfileStr >>= stringToPowerProfile)
return PowerProfileInfo
{ currentProfile = currentProf
, availableProfiles = availableProfs
, performanceDegraded = if degraded == Just "" then Nothing else degraded
}
-- | Set the active power profile.
setProfile :: Client -> PowerProfile -> IO (Either MethodError ())
setProfile client profile = do
let profileStr = powerProfileToString profile
result <- setProperty client
(methodCall powerProfilesObjectPath powerProfilesInterfaceName "ActiveProfile")
{ methodCallDestination = Just powerProfilesBusName }
(toVariant profileStr)
return $ case result of
Left err -> Left err
Right _ -> Right ()
-- | Cycle to the next power profile.
-- Order: power-saver -> balanced -> performance -> power-saver
cycleProfile :: Client -> PowerProfileInfo -> IO (Either MethodError ())
cycleProfile client info =
let current = currentProfile info
-- Find next profile in cycle
nextProfile = case current of
PowerSaver -> Balanced
Balanced -> Performance
Performance -> PowerSaver
in setProfile client nextProfile
-- State management for monitoring
newtype PowerProfileInfoChanVar =
PowerProfileInfoChanVar (TChan PowerProfileInfo, MVar PowerProfileInfo)
-- | Get the current power profile info state.
getPowerProfileInfoState :: TaffyIO PowerProfileInfo
getPowerProfileInfoState = do
PowerProfileInfoChanVar (_, theVar) <- getPowerProfileInfoChanVar
lift $ readMVar theVar
-- | Get a broadcast channel for power profile info updates.
getPowerProfileInfoChan :: TaffyIO (TChan PowerProfileInfo)
getPowerProfileInfoChan = do
PowerProfileInfoChanVar (chan, _) <- getPowerProfileInfoChanVar
return chan
getPowerProfileInfoChanVar :: TaffyIO PowerProfileInfoChanVar
getPowerProfileInfoChanVar =
getStateDefault $ PowerProfileInfoChanVar <$> monitorPowerProfileInfo
monitorPowerProfileInfo :: TaffyIO (TChan PowerProfileInfo, MVar PowerProfileInfo)
monitorPowerProfileInfo = do
infoVar <- lift $ newMVar unknownProfileInfo
chan <- liftIO newBroadcastTChanIO
taffyFork $ do
ctx <- ask
let updateInfo = updatePowerProfileInfo chan infoVar
signalCallback _ _ _ _ = runReaderT updateInfo ctx
_ <- registerForPowerProfilesPropertiesChanged signalCallback
updateInfo
return (chan, infoVar)
registerForPowerProfilesPropertiesChanged
:: (Signal -> String -> Map String Variant -> [String] -> IO ())
-> ReaderT Context IO SignalHandler
registerForPowerProfilesPropertiesChanged signalHandler = do
client <- asks systemDBusClient
lift $ DBus.registerForPropertiesChanged
client
matchAny { matchInterface = Just powerProfilesInterfaceName
, matchPath = Just powerProfilesObjectPath
}
signalHandler
updatePowerProfileInfo
:: TChan PowerProfileInfo
-> MVar PowerProfileInfo
-> TaffyIO ()
updatePowerProfileInfo chan var = do
info <- getPowerProfileInfo
lift $ do
_ <- swapMVar var info
atomically $ writeTChan chan info