taffybar-4.1.2: src/System/Taffybar/Information/Inhibitor.hs
{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.Taffybar.Information.Inhibitor
-- Copyright : (c) Ivan A. Malison
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : Ivan A. Malison
-- Stability : unstable
-- Portability : unportable
--
-- This module provides functions for managing idle/sleep inhibitors using the
-- systemd-logind DBus interface. The inhibitor is acquired by calling the
-- Inhibit method on org.freedesktop.login1.Manager, which returns a file
-- descriptor. The lock is held as long as the fd is open.
-----------------------------------------------------------------------------
module System.Taffybar.Information.Inhibitor
( -- * Types
InhibitType(..)
, InhibitorState(..)
, InhibitorContext(..)
-- * Inhibitor Management
, getInhibitorContext
, getInhibitorState
, toggleInhibitor
, getInhibitorChan
-- * Utilities
, inhibitTypeToString
, inhibitTypesFromStrings
) where
import Control.Concurrent
import Control.Concurrent.STM.TChan
import Control.Monad.IO.Class
import Control.Monad.STM (atomically)
import Control.Monad.Trans.Reader
import DBus
import DBus.Client
import Data.List (intercalate)
import qualified Data.Text as T
import System.Log.Logger
import System.Posix.IO (closeFd)
import System.Posix.Types (Fd)
import System.Taffybar.Context
-- | Types of inhibitors supported by systemd-logind
data InhibitType
= InhibitIdle
| InhibitShutdown
| InhibitSleep
| InhibitHandlePowerKey
| InhibitHandleSuspendKey
| InhibitHandleHibernateKey
| InhibitHandleLidSwitch
deriving (Eq, Show, Ord, Enum, Bounded)
-- | Current state of the inhibitor
data InhibitorState = InhibitorState
{ inhibitorActive :: Bool
, inhibitorTypes :: [InhibitType]
} deriving (Eq, Show)
-- | Context for managing an inhibitor, stored in taffybar's state
data InhibitorContext = InhibitorContext
{ inhibitorChan :: TChan InhibitorState
, inhibitorStateVar :: MVar InhibitorState
, inhibitorFdVar :: MVar (Maybe Fd)
, inhibitorConfig :: [InhibitType]
}
inhibitorLogPath :: String
inhibitorLogPath = "System.Taffybar.Information.Inhibitor"
inhibitorLog :: MonadIO m => Priority -> String -> m ()
inhibitorLog priority = liftIO . logM inhibitorLogPath priority
-- | Convert an InhibitType to its string representation for DBus
inhibitTypeToString :: InhibitType -> String
inhibitTypeToString InhibitIdle = "idle"
inhibitTypeToString InhibitShutdown = "shutdown"
inhibitTypeToString InhibitSleep = "sleep"
inhibitTypeToString InhibitHandlePowerKey = "handle-power-key"
inhibitTypeToString InhibitHandleSuspendKey = "handle-suspend-key"
inhibitTypeToString InhibitHandleHibernateKey = "handle-hibernate-key"
inhibitTypeToString InhibitHandleLidSwitch = "handle-lid-switch"
-- | Parse a string to an InhibitType
inhibitTypeFromString :: String -> Maybe InhibitType
inhibitTypeFromString "idle" = Just InhibitIdle
inhibitTypeFromString "shutdown" = Just InhibitShutdown
inhibitTypeFromString "sleep" = Just InhibitSleep
inhibitTypeFromString "handle-power-key" = Just InhibitHandlePowerKey
inhibitTypeFromString "handle-suspend-key" = Just InhibitHandleSuspendKey
inhibitTypeFromString "handle-hibernate-key" = Just InhibitHandleHibernateKey
inhibitTypeFromString "handle-lid-switch" = Just InhibitHandleLidSwitch
inhibitTypeFromString _ = Nothing
-- | Parse a list of strings to InhibitTypes, ignoring invalid ones
inhibitTypesFromStrings :: [String] -> [InhibitType]
inhibitTypesFromStrings = foldr go []
where
go s acc = case inhibitTypeFromString s of
Just t -> t : acc
Nothing -> acc
-- | Convert a list of InhibitTypes to the colon-separated format for DBus
inhibitTypesToDBusString :: [InhibitType] -> String
inhibitTypesToDBusString = intercalate ":" . map inhibitTypeToString
-- | The DBus destination for systemd-logind
login1BusName :: BusName
login1BusName = "org.freedesktop.login1"
-- | The DBus object path for the login1 manager
login1ObjectPath :: ObjectPath
login1ObjectPath = "/org/freedesktop/login1"
-- | The DBus interface for the login1 manager
login1Interface :: InterfaceName
login1Interface = "org.freedesktop.login1.Manager"
-- | Acquire an inhibitor lock via DBus
-- The Inhibit method takes (what, who, why, mode) and returns a file descriptor
acquireInhibitor :: Client -> [InhibitType] -> IO (Either MethodError Fd)
acquireInhibitor client types = do
let what = inhibitTypesToDBusString types
who = "taffybar"
why = "User requested inhibition"
mode = "block" :: String
inhibitorLog DEBUG $ "Acquiring inhibitor for: " ++ what
reply <- call client (methodCall login1ObjectPath login1Interface "Inhibit")
{ methodCallDestination = Just login1BusName
, methodCallBody = [ toVariant (T.pack what)
, toVariant (T.pack who)
, toVariant (T.pack why)
, toVariant (T.pack mode)
]
}
case reply of
Left err -> do
inhibitorLog WARNING $ "Failed to acquire inhibitor: " ++ show err
return $ Left err
Right ret -> do
case methodReturnBody ret of
[v] -> case fromVariant v :: Maybe Fd of
Just fd -> do
inhibitorLog DEBUG $ "Acquired inhibitor fd: " ++ show fd
return $ Right fd
Nothing -> do
inhibitorLog WARNING "Invalid fd type in response"
return $ Left $ methodError (methodReturnSerial ret) $
errorName_ "org.taffybar.InvalidResponse"
_ -> do
inhibitorLog WARNING "Unexpected response format"
return $ Left $ methodError (methodReturnSerial ret) $
errorName_ "org.taffybar.InvalidResponse"
-- | Release an inhibitor by closing its file descriptor
releaseInhibitor :: Fd -> IO ()
releaseInhibitor fd = do
inhibitorLog DEBUG $ "Releasing inhibitor fd: " ++ show fd
closeFd fd
-- | Get or create the InhibitorContext for the given inhibit types
getInhibitorContext :: [InhibitType] -> TaffyIO InhibitorContext
getInhibitorContext types = getStateDefault $ do
inhibitorLog DEBUG "Initializing InhibitorContext"
chan <- liftIO newBroadcastTChanIO
stateVar <- liftIO $ newMVar InhibitorState
{ inhibitorActive = False
, inhibitorTypes = types
}
fdVar <- liftIO $ newMVar Nothing
return InhibitorContext
{ inhibitorChan = chan
, inhibitorStateVar = stateVar
, inhibitorFdVar = fdVar
, inhibitorConfig = types
}
-- | Get the current inhibitor state
getInhibitorState :: [InhibitType] -> TaffyIO InhibitorState
getInhibitorState types = do
ctx <- getInhibitorContext types
liftIO $ readMVar (inhibitorStateVar ctx)
-- | Get the broadcast channel for inhibitor state changes
getInhibitorChan :: [InhibitType] -> TaffyIO (TChan InhibitorState)
getInhibitorChan types = do
ctx <- getInhibitorContext types
return $ inhibitorChan ctx
-- | Toggle the inhibitor on or off
toggleInhibitor :: [InhibitType] -> TaffyIO ()
toggleInhibitor types = do
ctx <- getInhibitorContext types
client <- asks systemDBusClient
liftIO $ modifyMVar_ (inhibitorFdVar ctx) $ \maybeFd -> do
case maybeFd of
Just fd -> do
-- Currently active, release it
releaseInhibitor fd
let newState = InhibitorState
{ inhibitorActive = False
, inhibitorTypes = types
}
_ <- swapMVar (inhibitorStateVar ctx) newState
atomically $ writeTChan (inhibitorChan ctx) newState
inhibitorLog DEBUG "Inhibitor deactivated"
return Nothing
Nothing -> do
-- Currently inactive, acquire it
result <- acquireInhibitor client types
case result of
Right fd -> do
let newState = InhibitorState
{ inhibitorActive = True
, inhibitorTypes = types
}
_ <- swapMVar (inhibitorStateVar ctx) newState
atomically $ writeTChan (inhibitorChan ctx) newState
inhibitorLog DEBUG "Inhibitor activated"
return (Just fd)
Left _ -> do
inhibitorLog WARNING "Failed to acquire inhibitor"
return Nothing