199 lines
5.8 KiB
Haskell
199 lines
5.8 KiB
Haskell
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{- |
|
|
DBus module for backlight control
|
|
-}
|
|
|
|
module DBus.IntelBacklight
|
|
( callDecBrightness
|
|
, callGetBrightness
|
|
, callIncBrightness
|
|
, callMaxBrightness
|
|
, callMinBrightness
|
|
, exportIntelBacklight
|
|
, matchSignal
|
|
) where
|
|
|
|
import Control.Monad (forM_)
|
|
|
|
import Data.Char
|
|
|
|
import Data.Int (Int16, Int32)
|
|
|
|
-- use strict IO here, the data in these files is literally 1-10 bytes
|
|
import Data.Text (pack, unpack)
|
|
import Data.Text.IO as T (readFile, writeFile)
|
|
|
|
import DBus
|
|
import DBus.Client
|
|
|
|
backlightDir :: FilePath
|
|
backlightDir = "/sys/class/backlight/intel_backlight/"
|
|
|
|
maxFile :: String
|
|
maxFile = backlightDir ++ "max_brightness"
|
|
|
|
curFile :: String
|
|
curFile = backlightDir ++ "brightness"
|
|
|
|
steps :: Brightness
|
|
steps = 16
|
|
|
|
-- TODO this is hacky but not sure if there is a cleaner way to
|
|
-- enforce type checking between these without making two new types
|
|
-- and adding Integral instances to both of them
|
|
type Brightness = Int16
|
|
type RawBrightness = Int32
|
|
|
|
maxBrightness :: Brightness
|
|
maxBrightness = 10000
|
|
|
|
readFileInt :: FilePath -> IO RawBrightness
|
|
readFileInt file = do
|
|
contents <- T.readFile file
|
|
return $ read $ takeWhile isDigit $ unpack contents
|
|
|
|
getMaxRawBrightness :: IO RawBrightness
|
|
getMaxRawBrightness = readFileInt maxFile
|
|
|
|
getRawBrightness :: IO RawBrightness
|
|
getRawBrightness = readFileInt curFile
|
|
|
|
setRawBrightness :: RawBrightness -> IO ()
|
|
setRawBrightness = T.writeFile curFile . pack . show
|
|
|
|
rawToNorm :: RawBrightness -> RawBrightness -> Brightness
|
|
rawToNorm maxRaw curRaw = fromIntegral
|
|
$ (curRaw - 1) * maxNorm `div` (maxRaw - 1)
|
|
where
|
|
maxNorm = fromIntegral maxBrightness :: Int32
|
|
|
|
normToRaw :: RawBrightness -> Brightness -> RawBrightness
|
|
normToRaw maxRaw = normalize . fromIntegral
|
|
where
|
|
normalize c = c * (maxRaw - 1) `div` maxNorm + 1
|
|
maxNorm = fromIntegral maxBrightness :: Int32
|
|
|
|
truncateNorm :: Brightness -> Brightness
|
|
truncateNorm = min maxBrightness . max 0
|
|
|
|
getBrightness :: RawBrightness -> IO Brightness
|
|
getBrightness maxRaw = rawToNorm maxRaw <$> getRawBrightness
|
|
|
|
changeBrightness :: RawBrightness -> Brightness -> IO Brightness
|
|
changeBrightness maxRaw delta = setBrightness maxRaw
|
|
=<< (+ delta) <$> getBrightness maxRaw
|
|
|
|
setBrightness :: RawBrightness -> Brightness -> IO Brightness
|
|
setBrightness maxRaw newNorm = do
|
|
let newNorm' = truncateNorm newNorm
|
|
setRawBrightness $ normToRaw maxRaw newNorm'
|
|
return newNorm'
|
|
|
|
exportIntelBacklight :: Client -> IO ()
|
|
exportIntelBacklight client = do
|
|
maxval <- getMaxRawBrightness -- assume the max value will never change
|
|
let stepsize = maxBrightness `div` steps
|
|
export client brPath defaultInterface
|
|
{ interfaceName = brInterface
|
|
, interfaceMethods =
|
|
[ autoMethod brMaxBrightness (setBrightness maxval maxBrightness)
|
|
, autoMethod brMinBrightness (setBrightness maxval 0)
|
|
, autoMethod brIncBrightness (changeBrightness maxval stepsize)
|
|
, autoMethod brDecBrightness (changeBrightness maxval (-stepsize))
|
|
, autoMethod brGetBrightness (getBrightness maxval)
|
|
]
|
|
}
|
|
|
|
brPath :: ObjectPath
|
|
brPath = "/intelbacklight"
|
|
|
|
brInterface :: InterfaceName
|
|
brInterface = "org.xmonad.Brightness"
|
|
|
|
brCurrentBrightness :: MemberName
|
|
brCurrentBrightness = "CurrentBrightness"
|
|
|
|
brGetBrightness :: MemberName
|
|
brGetBrightness = "GetBrightness"
|
|
|
|
brMaxBrightness :: MemberName
|
|
brMaxBrightness = "MaxBrightness"
|
|
|
|
brMinBrightness :: MemberName
|
|
brMinBrightness = "MinBrightness"
|
|
|
|
brIncBrightness :: MemberName
|
|
brIncBrightness = "IncBrightness"
|
|
|
|
brDecBrightness :: MemberName
|
|
brDecBrightness = "DecBrightness"
|
|
|
|
brSignal :: Signal
|
|
brSignal = signal brPath brInterface brCurrentBrightness
|
|
-- { signalDestination = Just "org.xmonad" }
|
|
|
|
brMatcher :: MatchRule
|
|
brMatcher = matchAny
|
|
{ matchPath = Just brPath
|
|
, matchInterface = Just brInterface
|
|
, matchMember = Just brCurrentBrightness
|
|
}
|
|
|
|
callBacklight :: Client -> MemberName -> IO (Maybe [Variant])
|
|
callBacklight client method = do
|
|
-- TODO this will throw a clienterror if it cannot connect at all
|
|
reply <- call client (methodCall brPath brInterface method)
|
|
{ methodCallDestination = Just "org.xmonad" }
|
|
return $ case reply of
|
|
Left _ -> Nothing
|
|
Right ret -> Just $ methodReturnBody ret
|
|
|
|
callBacklight' :: MemberName -> IO (Maybe Brightness)
|
|
callBacklight' method = do
|
|
client <- connectSession
|
|
body <- callBacklight client method
|
|
-- TODO this is a bit convoluted...I return the body in the reply of
|
|
-- the method call and feed that to the signal and then return the
|
|
-- body (the latter is not really necessary since the only things
|
|
-- that read the backlight status either use the signal or call
|
|
-- GetBrightness directly
|
|
forM_ body $ emitBrightness client
|
|
return $ body >>= signalBrightness
|
|
|
|
emitBrightness :: Client -> [Variant] -> IO ()
|
|
emitBrightness client body =
|
|
emit client $ brSignal { signalBody = body }
|
|
|
|
signalBrightness :: [Variant] -> Maybe Brightness
|
|
signalBrightness [b] = fromVariant b :: Maybe Brightness
|
|
signalBrightness _ = Nothing
|
|
|
|
callMaxBrightness :: IO (Maybe Brightness)
|
|
callMaxBrightness = callBacklight' brMaxBrightness
|
|
|
|
callMinBrightness :: IO (Maybe Brightness)
|
|
callMinBrightness = callBacklight' brMinBrightness
|
|
|
|
callIncBrightness :: IO (Maybe Brightness)
|
|
callIncBrightness = callBacklight' brIncBrightness
|
|
|
|
callDecBrightness :: IO (Maybe Brightness)
|
|
callDecBrightness = callBacklight' brDecBrightness
|
|
|
|
callGetBrightness :: IO (Maybe Brightness)
|
|
callGetBrightness = do
|
|
client <- connectSession
|
|
body <- callBacklight client brGetBrightness
|
|
return $ body >>= signalBrightness
|
|
|
|
matchSignal :: (Maybe Brightness -> IO ()) -> IO SignalHandler
|
|
matchSignal cb = do
|
|
client <- connectSession
|
|
addMatch client brMatcher $ cb . pullBrightness . signalBody
|
|
where
|
|
pullBrightness = \case
|
|
[b] -> fromVariant b :: Maybe Brightness
|
|
_ -> Nothing
|