{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module System.Taffybar.Information.X11DesktopInfo
(
X11Context
, DisplayName(..)
, getX11Context
, withX11Context
, X11Property
, X11PropertyT
, eventLoop
, getDisplay
, getAtom
, X11Window
, PropertyFetcher
, fetch
, readAsInt
, readAsListOfInt
, readAsListOfString
, readAsListOfWindow
, readAsString
, isWindowUrgent
, getPrimaryOutputNumber
, getVisibleTags
, doLowerWindow
, postX11RequestSyncProp
, sendCommandEvent
, sendWindowEvent
) where
import Codec.Binary.UTF8.String as UTF8
import qualified Control.Concurrent.MVar as MV
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Data.Bits (testBit, (.|.))
import Data.Default (Default(..))
import Data.List (elemIndex)
import Data.List.Split (endBy)
import Data.Maybe (fromMaybe, listToMaybe)
import GHC.Generics (Generic)
import Graphics.X11.Xrandr (XRRScreenResources(..), XRROutputInfo(..), xrrGetOutputInfo, xrrGetScreenResources, xrrGetOutputPrimary)
import System.Taffybar.Information.SafeX11 hiding (displayName)
data X11Context = X11Context
{ X11Context -> DisplayName
ctxDisplayName :: DisplayName
, X11Context -> Display
ctxDisplay :: Display
, X11Context -> EventMask
ctxRoot :: Window
, X11Context -> MVar [(String, EventMask)]
ctxAtomCache :: MV.MVar [(String, Atom)]
}
data DisplayName = DefaultDisplay
| DisplayName String
deriving (Int -> DisplayName -> ShowS
[DisplayName] -> ShowS
DisplayName -> String
(Int -> DisplayName -> ShowS)
-> (DisplayName -> String)
-> ([DisplayName] -> ShowS)
-> Show DisplayName
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> DisplayName -> ShowS
showsPrec :: Int -> DisplayName -> ShowS
$cshow :: DisplayName -> String
show :: DisplayName -> String
$cshowList :: [DisplayName] -> ShowS
showList :: [DisplayName] -> ShowS
Show, ReadPrec [DisplayName]
ReadPrec DisplayName
Int -> ReadS DisplayName
ReadS [DisplayName]
(Int -> ReadS DisplayName)
-> ReadS [DisplayName]
-> ReadPrec DisplayName
-> ReadPrec [DisplayName]
-> Read DisplayName
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS DisplayName
readsPrec :: Int -> ReadS DisplayName
$creadList :: ReadS [DisplayName]
readList :: ReadS [DisplayName]
$creadPrec :: ReadPrec DisplayName
readPrec :: ReadPrec DisplayName
$creadListPrec :: ReadPrec [DisplayName]
readListPrec :: ReadPrec [DisplayName]
Read, DisplayName -> DisplayName -> Bool
(DisplayName -> DisplayName -> Bool)
-> (DisplayName -> DisplayName -> Bool) -> Eq DisplayName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: DisplayName -> DisplayName -> Bool
== :: DisplayName -> DisplayName -> Bool
$c/= :: DisplayName -> DisplayName -> Bool
/= :: DisplayName -> DisplayName -> Bool
Eq, Eq DisplayName
Eq DisplayName =>
(DisplayName -> DisplayName -> Ordering)
-> (DisplayName -> DisplayName -> Bool)
-> (DisplayName -> DisplayName -> Bool)
-> (DisplayName -> DisplayName -> Bool)
-> (DisplayName -> DisplayName -> Bool)
-> (DisplayName -> DisplayName -> DisplayName)
-> (DisplayName -> DisplayName -> DisplayName)
-> Ord DisplayName
DisplayName -> DisplayName -> Bool
DisplayName -> DisplayName -> Ordering
DisplayName -> DisplayName -> DisplayName
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: DisplayName -> DisplayName -> Ordering
compare :: DisplayName -> DisplayName -> Ordering
$c< :: DisplayName -> DisplayName -> Bool
< :: DisplayName -> DisplayName -> Bool
$c<= :: DisplayName -> DisplayName -> Bool
<= :: DisplayName -> DisplayName -> Bool
$c> :: DisplayName -> DisplayName -> Bool
> :: DisplayName -> DisplayName -> Bool
$c>= :: DisplayName -> DisplayName -> Bool
>= :: DisplayName -> DisplayName -> Bool
$cmax :: DisplayName -> DisplayName -> DisplayName
max :: DisplayName -> DisplayName -> DisplayName
$cmin :: DisplayName -> DisplayName -> DisplayName
min :: DisplayName -> DisplayName -> DisplayName
Ord, (forall x. DisplayName -> Rep DisplayName x)
-> (forall x. Rep DisplayName x -> DisplayName)
-> Generic DisplayName
forall x. Rep DisplayName x -> DisplayName
forall x. DisplayName -> Rep DisplayName x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. DisplayName -> Rep DisplayName x
from :: forall x. DisplayName -> Rep DisplayName x
$cto :: forall x. Rep DisplayName x -> DisplayName
to :: forall x. Rep DisplayName x -> DisplayName
Generic)
instance Default DisplayName where
def :: DisplayName
def = DisplayName
DefaultDisplay
fromDisplayName :: DisplayName -> String
fromDisplayName :: DisplayName -> String
fromDisplayName DisplayName
DefaultDisplay = String
""
fromDisplayName (DisplayName String
displayName) = String
displayName
type X11PropertyT m a = ReaderT X11Context m a
type X11Property a = X11PropertyT IO a
type X11Window = Window
type PropertyFetcher a = Display -> Atom -> X11Window -> IO (Maybe [a])
withX11Context :: DisplayName -> X11Property a -> IO a
withX11Context :: forall a. DisplayName -> X11Property a -> IO a
withX11Context DisplayName
dn X11Property a
fun = do
ctx <- DisplayName -> IO X11Context
getX11Context DisplayName
dn
res <- runReaderT fun ctx
closeDisplay (ctxDisplay ctx)
return res
getDisplay :: X11Property Display
getDisplay :: X11Property Display
getDisplay = X11Context -> Display
ctxDisplay (X11Context -> Display)
-> ReaderT X11Context IO X11Context -> X11Property Display
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ReaderT X11Context IO X11Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
doRead :: Integral a => b -> ([a] -> b)
-> PropertyFetcher a
-> Maybe X11Window
-> String
-> X11Property b
doRead :: forall a b.
Integral a =>
b
-> ([a] -> b)
-> PropertyFetcher a
-> Maybe EventMask
-> String
-> X11Property b
doRead b
b [a] -> b
transform PropertyFetcher a
windowPropFn Maybe EventMask
window String
name =
b -> ([a] -> b) -> Maybe [a] -> b
forall b a. b -> (a -> b) -> Maybe a -> b
maybe b
b [a] -> b
transform (Maybe [a] -> b)
-> ReaderT X11Context IO (Maybe [a]) -> ReaderT X11Context IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PropertyFetcher a
-> Maybe EventMask -> String -> ReaderT X11Context IO (Maybe [a])
forall a.
Integral a =>
PropertyFetcher a
-> Maybe EventMask -> String -> X11Property (Maybe [a])
fetch PropertyFetcher a
windowPropFn Maybe EventMask
window String
name
readAsInt :: Maybe X11Window
-> String
-> X11Property Int
readAsInt :: Maybe EventMask -> String -> X11Property Int
readAsInt = Int
-> ([CLong] -> Int)
-> PropertyFetcher CLong
-> Maybe EventMask
-> String
-> X11Property Int
forall a b.
Integral a =>
b
-> ([a] -> b)
-> PropertyFetcher a
-> Maybe EventMask
-> String
-> X11Property b
doRead (-Int
1) (Int -> (CLong -> Int) -> Maybe CLong -> Int
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (-Int
1) CLong -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Maybe CLong -> Int) -> ([CLong] -> Maybe CLong) -> [CLong] -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [CLong] -> Maybe CLong
forall a. [a] -> Maybe a
listToMaybe) PropertyFetcher CLong
getWindowProperty32
readAsListOfInt :: Maybe X11Window
-> String
-> X11Property [Int]
readAsListOfInt :: Maybe EventMask -> String -> X11Property [Int]
readAsListOfInt = [Int]
-> ([CLong] -> [Int])
-> PropertyFetcher CLong
-> Maybe EventMask
-> String
-> X11Property [Int]
forall a b.
Integral a =>
b
-> ([a] -> b)
-> PropertyFetcher a
-> Maybe EventMask
-> String
-> X11Property b
doRead [] ((CLong -> Int) -> [CLong] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map CLong -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral) PropertyFetcher CLong
getWindowProperty32
readAsString :: Maybe X11Window
-> String
-> X11Property String
readAsString :: Maybe EventMask -> String -> X11Property String
readAsString = String
-> ([CChar] -> String)
-> PropertyFetcher CChar
-> Maybe EventMask
-> String
-> X11Property String
forall a b.
Integral a =>
b
-> ([a] -> b)
-> PropertyFetcher a
-> Maybe EventMask
-> String
-> X11Property b
doRead String
"" ([Word8] -> String
UTF8.decode ([Word8] -> String) -> ([CChar] -> [Word8]) -> [CChar] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (CChar -> Word8) -> [CChar] -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map CChar -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral) PropertyFetcher CChar
getWindowProperty8
readAsListOfString :: Maybe X11Window
-> String
-> X11Property [String]
readAsListOfString :: Maybe EventMask -> String -> X11Property [String]
readAsListOfString = [String]
-> ([CChar] -> [String])
-> PropertyFetcher CChar
-> Maybe EventMask
-> String
-> X11Property [String]
forall a b.
Integral a =>
b
-> ([a] -> b)
-> PropertyFetcher a
-> Maybe EventMask
-> String
-> X11Property b
doRead [] [CChar] -> [String]
parse PropertyFetcher CChar
getWindowProperty8
where parse :: [CChar] -> [String]
parse = String -> String -> [String]
forall a. Eq a => [a] -> [a] -> [[a]]
endBy String
"\0" (String -> [String]) -> ([CChar] -> String) -> [CChar] -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Word8] -> String
UTF8.decode ([Word8] -> String) -> ([CChar] -> [Word8]) -> [CChar] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (CChar -> Word8) -> [CChar] -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map CChar -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral
readAsListOfWindow :: Maybe X11Window
-> String
-> X11Property [X11Window]
readAsListOfWindow :: Maybe EventMask -> String -> X11Property [EventMask]
readAsListOfWindow = [EventMask]
-> ([CLong] -> [EventMask])
-> PropertyFetcher CLong
-> Maybe EventMask
-> String
-> X11Property [EventMask]
forall a b.
Integral a =>
b
-> ([a] -> b)
-> PropertyFetcher a
-> Maybe EventMask
-> String
-> X11Property b
doRead [] ((CLong -> EventMask) -> [CLong] -> [EventMask]
forall a b. (a -> b) -> [a] -> [b]
map CLong -> EventMask
forall a b. (Integral a, Num b) => a -> b
fromIntegral) PropertyFetcher CLong
getWindowProperty32
isWindowUrgent :: X11Window -> X11Property Bool
isWindowUrgent :: EventMask -> X11Property Bool
isWindowUrgent EventMask
window = do
hints <- EventMask -> X11Property WMHints
fetchWindowHints EventMask
window
return $ testBit (wmh_flags hints) urgencyHintBit
getVisibleTags :: X11Property [String]
getVisibleTags :: X11Property [String]
getVisibleTags = Maybe EventMask -> String -> X11Property [String]
readAsListOfString Maybe EventMask
forall a. Maybe a
Nothing String
"_XMONAD_VISIBLE_WORKSPACES"
getAtom :: String -> X11Property Atom
getAtom :: String -> X11Property EventMask
getAtom String
s = do
d <- (X11Context -> Display) -> X11Property Display
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks X11Context -> Display
ctxDisplay
cacheVar <- asks ctxAtomCache
a <- lift $ lookup s <$> MV.readMVar cacheVar
let updateCacheAction = IO EventMask -> X11Property EventMask
forall (m :: * -> *) a. Monad m => m a -> ReaderT X11Context m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (IO EventMask -> X11Property EventMask)
-> IO EventMask -> X11Property EventMask
forall a b. (a -> b) -> a -> b
$ MVar [(String, EventMask)]
-> ([(String, EventMask)] -> IO ([(String, EventMask)], EventMask))
-> IO EventMask
forall a b. MVar a -> (a -> IO (a, b)) -> IO b
MV.modifyMVar MVar [(String, EventMask)]
cacheVar [(String, EventMask)] -> IO ([(String, EventMask)], EventMask)
updateCache
updateCache [(String, EventMask)]
currentCache =
do
atom <- Display -> String -> Bool -> IO EventMask
internAtom Display
d String
s Bool
False
return ((s, atom):currentCache, atom)
maybe updateCacheAction return a
eventLoop :: (Event -> IO ()) -> X11Property ()
eventLoop :: (Event -> IO ()) -> X11Property ()
eventLoop Event -> IO ()
dispatch = do
d <- (X11Context -> Display) -> X11Property Display
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks X11Context -> Display
ctxDisplay
w <- asks ctxRoot
liftIO $ do
selectInput d w $ propertyChangeMask .|. substructureNotifyMask
allocaXEvent $ \XEventPtr
e -> IO () -> IO ()
forall (f :: * -> *) a b. Applicative f => f a -> f b
forever (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
event <- Display -> XEventPtr -> IO ()
nextEvent Display
d XEventPtr
e IO () -> IO Event -> IO Event
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> XEventPtr -> IO Event
getEvent XEventPtr
e
case event of
MapNotifyEvent { ev_window :: Event -> EventMask
ev_window = EventMask
window } ->
Display -> EventMask -> EventMask -> IO ()
selectInput Display
d EventMask
window EventMask
propertyChangeMask
Event
_ -> () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
dispatch event
sendCommandEvent :: Atom -> Atom -> X11Property ()
sendCommandEvent :: EventMask -> EventMask -> X11Property ()
sendCommandEvent EventMask
cmd EventMask
arg = EventMask -> EventMask -> Maybe EventMask -> X11Property ()
sendCustomEvent EventMask
cmd EventMask
arg Maybe EventMask
forall a. Maybe a
Nothing
sendWindowEvent :: Atom -> X11Window -> X11Property ()
sendWindowEvent :: EventMask -> EventMask -> X11Property ()
sendWindowEvent EventMask
cmd EventMask
win = EventMask -> EventMask -> Maybe EventMask -> X11Property ()
sendCustomEvent EventMask
cmd EventMask
cmd (EventMask -> Maybe EventMask
forall a. a -> Maybe a
Just EventMask
win)
getX11Context :: DisplayName -> IO X11Context
getX11Context :: DisplayName -> IO X11Context
getX11Context DisplayName
ctxDisplayName = do
d <- String -> IO Display
openDisplay (String -> IO Display) -> String -> IO Display
forall a b. (a -> b) -> a -> b
$ DisplayName -> String
fromDisplayName DisplayName
ctxDisplayName
ctxRoot <- rootWindow d $ defaultScreen d
ctxAtomCache <- MV.newMVar []
return $ X11Context{ctxDisplay=d,..}
fetch :: (Integral a)
=> PropertyFetcher a
-> Maybe X11Window
-> String
-> X11Property (Maybe [a])
fetch :: forall a.
Integral a =>
PropertyFetcher a
-> Maybe EventMask -> String -> X11Property (Maybe [a])
fetch PropertyFetcher a
fetcher Maybe EventMask
window String
name = do
X11Context{..} <- ReaderT X11Context IO X11Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
atom <- getAtom name
liftIO $ fetcher ctxDisplay atom (fromMaybe ctxRoot window)
fetchWindowHints :: X11Window -> X11Property WMHints
fetchWindowHints :: EventMask -> X11Property WMHints
fetchWindowHints EventMask
window = do
d <- X11Property Display
getDisplay
liftIO $ getWMHints d window
sendCustomEvent :: Atom
-> Atom
-> Maybe X11Window
-> X11Property ()
sendCustomEvent :: EventMask -> EventMask -> Maybe EventMask -> X11Property ()
sendCustomEvent EventMask
cmd EventMask
arg Maybe EventMask
win = do
X11Context{..} <- ReaderT X11Context IO X11Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
let win' = EventMask -> Maybe EventMask -> EventMask
forall a. a -> Maybe a -> a
fromMaybe EventMask
ctxRoot Maybe EventMask
win
liftIO $ allocaXEvent $ \XEventPtr
e -> do
XEventPtr -> ScreenNumber -> IO ()
setEventType XEventPtr
e ScreenNumber
clientMessage
XEventPtr
-> EventMask
-> EventMask
-> CInt
-> EventMask
-> EventMask
-> IO ()
setClientMessageEvent XEventPtr
e EventMask
win' EventMask
cmd CInt
32 EventMask
arg EventMask
currentTime
Display -> EventMask -> Bool -> EventMask -> XEventPtr -> IO ()
sendEvent Display
ctxDisplay EventMask
ctxRoot Bool
False EventMask
structureNotifyMask XEventPtr
e
Display -> Bool -> IO ()
sync Display
ctxDisplay Bool
False
postX11RequestSyncProp :: X11Property a -> a -> X11Property a
postX11RequestSyncProp :: forall a. X11Property a -> a -> X11Property a
postX11RequestSyncProp X11Property a
prop a
a = do
c <- ReaderT X11Context IO X11Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
let action = X11Property a -> X11Context -> IO a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT X11Property a
prop X11Context
c
lift $ postX11RequestSyncDef a action
isActiveOutput :: XRRScreenResources -> RROutput -> X11Property Bool
isActiveOutput :: XRRScreenResources -> EventMask -> X11Property Bool
isActiveOutput XRRScreenResources
sres EventMask
output = do
display <- X11Property Display
getDisplay
maybeOutputInfo <- liftIO $ xrrGetOutputInfo display sres output
return $ maybe 0 xrr_oi_crtc maybeOutputInfo /= 0
getActiveOutputs :: X11Property [RROutput]
getActiveOutputs :: X11Property [EventMask]
getActiveOutputs = do
X11Context{..} <- ReaderT X11Context IO X11Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
liftIO (xrrGetScreenResources ctxDisplay ctxRoot) >>= \case
Just XRRScreenResources
sres -> (EventMask -> X11Property Bool)
-> [EventMask] -> X11Property [EventMask]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM (XRRScreenResources -> EventMask -> X11Property Bool
isActiveOutput XRRScreenResources
sres) (XRRScreenResources -> [EventMask]
xrr_sr_outputs XRRScreenResources
sres)
Maybe XRRScreenResources
Nothing -> [EventMask] -> X11Property [EventMask]
forall a. a -> ReaderT X11Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return []
getPrimaryOutputNumber :: X11Property (Maybe Int)
getPrimaryOutputNumber :: X11Property (Maybe Int)
getPrimaryOutputNumber = do
X11Context{..} <- ReaderT X11Context IO X11Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
primary <- liftIO $ xrrGetOutputPrimary ctxDisplay ctxRoot
outputs <- getActiveOutputs
return $ primary `elemIndex` outputs
doLowerWindow :: X11Window -> X11Property ()
doLowerWindow :: EventMask -> X11Property ()
doLowerWindow EventMask
window =
(X11Context -> Display) -> X11Property Display
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks X11Context -> Display
ctxDisplay X11Property Display
-> (Display -> X11Property ()) -> X11Property ()
forall a b.
ReaderT X11Context IO a
-> (a -> ReaderT X11Context IO b) -> ReaderT X11Context IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO () -> X11Property ()
forall (m :: * -> *) a. Monad m => m a -> ReaderT X11Context m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (IO () -> X11Property ())
-> (Display -> IO ()) -> Display -> X11Property ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Display -> EventMask -> IO ()) -> EventMask -> Display -> IO ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip Display -> EventMask -> IO ()
lowerWindow EventMask
window