92 lines
3.6 KiB
Haskell
92 lines
3.6 KiB
Haskell
--------------------------------------------------------------------------------
|
|
-- | Core ClientMessage module to 'achieve' concurrency in XMonad
|
|
--
|
|
-- Since XMonad is single threaded, the only way to have multiple threads that
|
|
-- listen/react to non-X events is to spawn other threads the run outside of
|
|
-- XMonad and send ClientMessages back to it to be intercepted by the event
|
|
-- hook. This module has the core plumbing to make this happen.
|
|
--
|
|
-- The clientMessages to be sent will have a defined atom (that hopefully won't
|
|
-- do anything) and be sent to the root window. It will include two 'fields',
|
|
-- the first of which will represent the 'type' of message sent (meaning the
|
|
-- type of non-X event that was intercepted) and the second containing the data
|
|
-- pertaining to said event.
|
|
|
|
-- TODO come up with a better name than 'XMsg' since it sounds vague and too
|
|
-- much like something from X even though it isn't
|
|
|
|
module XMonad.Internal.Concurrent.ClientMessage
|
|
( XMsgType(..)
|
|
, sendXMsg
|
|
, splitXMsg
|
|
) where
|
|
|
|
import Data.Char
|
|
|
|
import Graphics.X11.Types
|
|
import Graphics.X11.Xlib.Atom
|
|
import Graphics.X11.Xlib.Display
|
|
import Graphics.X11.Xlib.Event
|
|
import Graphics.X11.Xlib.Extras
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- | Data structure for the ClientMessage
|
|
--
|
|
-- These are the "types" of client messages to send; add more here as needed
|
|
|
|
-- TODO is there a way to do this in the libraries that import this one?
|
|
data XMsgType = ACPI
|
|
| Workspace
|
|
| Unknown
|
|
deriving (Eq, Show)
|
|
|
|
instance Enum XMsgType where
|
|
toEnum 0 = ACPI
|
|
toEnum 1 = Workspace
|
|
toEnum _ = errorWithoutStackTrace "ACPI.Enum.ACPIEvent.toEnum: bad argument"
|
|
|
|
fromEnum ACPI = 0
|
|
fromEnum Workspace = 1
|
|
fromEnum Unknown = 2
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- | Exported API
|
|
|
|
-- | Given a string from the data field in a ClientMessage event, return the
|
|
-- type and payload
|
|
splitXMsg :: (Integral a) => [a] -> (XMsgType, String)
|
|
splitXMsg [] = (Unknown, "")
|
|
splitXMsg (x:xs) = (xtype, tag)
|
|
where
|
|
xtype = toEnum $ fromInteger $ toInteger x
|
|
tag = map (chr . fromInteger . toInteger) $ takeWhile (/= 0) xs
|
|
|
|
-- | Emit a ClientMessage event to the X server with the given type and payloud
|
|
sendXMsg :: XMsgType -> String -> IO ()
|
|
sendXMsg xtype tag = do
|
|
dpy <- openDisplay ""
|
|
root <- rootWindow dpy $ defaultScreen dpy
|
|
allocaXEvent $ \e -> do
|
|
setEventType e clientMessage
|
|
-- Set the client message for the root window to be something hacky and
|
|
-- somewhat random that can be intercepted later in the main xmonad thread.
|
|
--
|
|
-- Use the bitmap type since this is (hopefully) not going to be used by any
|
|
-- apps I'm using (let alone the root window). If this isn't the case, the
|
|
-- logs will have some really weird messages in them. The format field is
|
|
-- set to 8 (eg one byte) which allows direct conversion between Word8 types
|
|
-- and chars/string. The last argument is a list of data, where the first
|
|
-- character represents the message type (Workspace vs ACPI) and the
|
|
-- remaining members represent the tag. Note that the data payload for the
|
|
-- message is 20 bytes, so the tag can be 19 characters long. Anything
|
|
-- longer will be clipped to 19, and anything less than 19 will be padded
|
|
-- with 0 (note this used to be random garbage before). See this function
|
|
-- for more details.
|
|
setClientMessageEvent' e root bITMAP 8 (x:t)
|
|
sendEvent dpy root False substructureNotifyMask e
|
|
flush dpy
|
|
closeDisplay dpy
|
|
where
|
|
x = fromIntegral $ fromEnum xtype
|
|
t = fmap (fromIntegral . fromEnum) tag
|