module System.Taffybar.Information.EWMHDesktopInfo
( EWMHIcon(..)
, EWMHIconData
, WorkspaceId(..)
, X11Window
, allEWMHProperties
, ewmhActiveWindow
, ewmhClientList
, ewmhCurrentDesktop
, ewmhDesktopNames
, ewmhNumberOfDesktops
, ewmhStateHidden
, ewmhWMClass
, ewmhWMDesktop
, ewmhWMIcon
, ewmhWMName
, ewmhWMName2
, ewmhWMState
, ewmhWMStateHidden
, focusWindow
, getActiveWindow
, getCurrentWorkspace
, getVisibleWorkspaces
, getWindowClass
, getWindowIconsData
, getWindowMinimized
, getWindowState
, getWindowStateProperty
, getWindowTitle
, getWindows
, getWorkspace
, getWorkspaceNames
, isWindowUrgent
, parseWindowClasses
, switchOneWorkspace
, switchToWorkspace
, withDefaultCtx
, withEWMHIcons
) where
import Control.Applicative
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Data.List
import Data.List.Split
import Data.Maybe
import Data.Tuple
import Data.Word
import Foreign.ForeignPtr
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import System.Log.Logger
import System.Taffybar.Information.SafeX11 hiding (logHere)
import System.Taffybar.Information.X11DesktopInfo
import Prelude
logHere :: MonadIO m => Priority -> String -> m ()
logHere p = liftIO . logM "System.Taffybar.Information.EWMHDesktopInfo" p
newtype WorkspaceId = WorkspaceId Int deriving (Show, Read, Ord, Eq)
type PixelsWordType = Word64
type EWMHProperty = String
ewmhActiveWindow, ewmhClientList, ewmhCurrentDesktop, ewmhDesktopNames, ewmhNumberOfDesktops, ewmhStateHidden, ewmhWMDesktop, ewmhWMStateHidden, ewmhWMClass, ewmhWMState, ewmhWMIcon, ewmhWMName, ewmhWMName2 :: EWMHProperty
ewmhActiveWindow = "_NET_ACTIVE_WINDOW"
ewmhClientList = "_NET_CLIENT_LIST"
ewmhCurrentDesktop = "_NET_CURRENT_DESKTOP"
ewmhDesktopNames = "_NET_DESKTOP_NAMES"
ewmhNumberOfDesktops = "_NET_NUMBER_OF_DESKTOPS"
ewmhStateHidden = "_NET_WM_STATE_HIDDEN"
ewmhWMClass = "WM_CLASS"
ewmhWMDesktop = "_NET_WM_DESKTOP"
ewmhWMIcon = "_NET_WM_ICON"
ewmhWMName = "_NET_WM_NAME"
ewmhWMName2 = "WM_NAME"
ewmhWMState = "_NET_WM_STATE"
ewmhWMStateHidden = "_NET_WM_STATE_HIDDEN"
allEWMHProperties :: [EWMHProperty]
allEWMHProperties =
[ ewmhActiveWindow
, ewmhClientList
, ewmhCurrentDesktop
, ewmhDesktopNames
, ewmhNumberOfDesktops
, ewmhStateHidden
, ewmhWMClass
, ewmhWMDesktop
, ewmhWMIcon
, ewmhWMName
, ewmhWMName2
, ewmhWMState
, ewmhWMStateHidden
]
type EWMHIconData = (ForeignPtr PixelsWordType, Int)
data EWMHIcon = EWMHIcon
{ ewmhWidth :: Int
, ewmhHeight :: Int
, ewmhPixelsARGB :: Ptr PixelsWordType
} deriving (Show, Eq)
getWindowStateProperty :: String -> X11Window -> X11Property Bool
getWindowStateProperty property window =
not . null <$> getWindowState window [property]
getWindowState :: X11Window -> [String] -> X11Property [String]
getWindowState window request = do
let getAsLong s = fromIntegral <$> getAtom s
integers <- mapM getAsLong request
properties <- fetch getWindowProperty32 (Just window) ewmhWMState
let integerToString = zip integers request
present = intersect integers $ fromMaybe [] properties
presentStrings = map (`lookup` integerToString) present
return $ catMaybes presentStrings
getWindowMinimized :: X11Window -> X11Property Bool
getWindowMinimized = getWindowStateProperty ewmhStateHidden
getCurrentWorkspace :: X11Property WorkspaceId
getCurrentWorkspace = WorkspaceId <$> readAsInt Nothing ewmhCurrentDesktop
getVisibleWorkspaces :: X11Property [WorkspaceId]
getVisibleWorkspaces = do
vis <- getVisibleTags
allNames <- map swap <$> getWorkspaceNames
cur <- getCurrentWorkspace
return $ cur : mapMaybe (`lookup` allNames) vis
getWorkspaceNames :: X11Property [(WorkspaceId, String)]
getWorkspaceNames = go <$> readAsListOfString Nothing ewmhDesktopNames
where go = zip [WorkspaceId i | i <- [0..]]
switchToWorkspace :: WorkspaceId -> X11Property ()
switchToWorkspace (WorkspaceId idx) = do
cmd <- getAtom ewmhCurrentDesktop
sendCommandEvent cmd (fromIntegral idx)
switchOneWorkspace :: Bool -> Int -> X11Property ()
switchOneWorkspace dir end = do
cur <- getCurrentWorkspace
switchToWorkspace $ if dir then getPrev cur end else getNext cur end
getPrev :: WorkspaceId -> Int -> WorkspaceId
getPrev (WorkspaceId idx) end
| idx > 0 = WorkspaceId $ idx-1
| otherwise = WorkspaceId end
getNext :: WorkspaceId -> Int -> WorkspaceId
getNext (WorkspaceId idx) end
| idx < end = WorkspaceId $ idx+1
| otherwise = WorkspaceId 0
getWindowTitle :: X11Window -> X11Property String
getWindowTitle window = do
let w = Just window
prop <- readAsString w ewmhWMName
case prop of
"" -> readAsString w ewmhWMName2
_ -> return prop
getWindowClass :: X11Window -> X11Property String
getWindowClass window = readAsString (Just window) ewmhWMClass
parseWindowClasses :: String -> [String]
parseWindowClasses = filter (not . null) . splitOn "\NUL"
getWindowIconsData :: X11Window -> X11Property (Maybe EWMHIconData)
getWindowIconsData window = do
dpy <- getDisplay
atom <- getAtom ewmhWMIcon
lift $ rawGetWindowPropertyBytes 32 dpy atom window
withEWMHIcons :: EWMHIconData -> ([EWMHIcon] -> IO a) -> IO a
withEWMHIcons (fptr, size) action =
withForeignPtr fptr ((>>= action) . parseIcons size)
parseIcons :: Int -> Ptr PixelsWordType -> IO [EWMHIcon]
parseIcons 0 _ = return []
parseIcons totalSize arr = do
iwidth <- fromIntegral <$> peek arr
iheight <- fromIntegral <$> peekElemOff arr 1
let pixelsPtr = advancePtr arr 2
thisSize = iwidth * iheight
newArr = advancePtr pixelsPtr thisSize
thisIcon =
EWMHIcon
{ ewmhWidth = iwidth
, ewmhHeight = iheight
, ewmhPixelsARGB = pixelsPtr
}
getRes newSize
| newSize < 0 =
logHere ERROR "Attempt to recurse on negative value in parseIcons"
>> return []
| otherwise = (thisIcon :) <$> parseIcons newSize newArr
getRes $ totalSize - fromIntegral (thisSize + 2)
getActiveWindow :: X11Property (Maybe X11Window)
getActiveWindow = listToMaybe . filter (> 0) <$> readAsListOfWindow Nothing ewmhActiveWindow
getWindows :: X11Property [X11Window]
getWindows = readAsListOfWindow Nothing ewmhClientList
getWorkspace :: X11Window -> X11Property WorkspaceId
getWorkspace window = WorkspaceId <$> readAsInt (Just window) ewmhWMDesktop
focusWindow :: X11Window -> X11Property ()
focusWindow wh = do
cmd <- getAtom ewmhActiveWindow
sendWindowEvent cmd (fromIntegral wh)