2020-03-25 14:09:07 -04:00
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
|
|
2020-03-25 13:38:41 -04:00
|
|
|
module WorkspaceMon (fromList, runWorkspaceMon) where
|
|
|
|
|
|
|
|
import SendXMsg
|
|
|
|
|
|
|
|
import Data.Map as M
|
2020-03-25 12:24:40 -04:00
|
|
|
|
|
|
|
import Control.Concurrent
|
|
|
|
import Control.Monad
|
2020-03-25 14:09:07 -04:00
|
|
|
import Control.Monad.Reader
|
2020-03-25 12:24:40 -04:00
|
|
|
|
|
|
|
import Graphics.X11.Types
|
|
|
|
|
|
|
|
import Graphics.X11.Xlib.Atom
|
|
|
|
import Graphics.X11.Xlib.Display
|
|
|
|
import Graphics.X11.Xlib.Event
|
|
|
|
import Graphics.X11.Xlib.Extras
|
|
|
|
import Graphics.X11.Xlib.Misc
|
|
|
|
import Graphics.X11.Xlib.Types
|
|
|
|
|
|
|
|
import System.Directory
|
|
|
|
import System.Process (Pid)
|
|
|
|
|
2020-03-25 14:09:07 -04:00
|
|
|
-- TOOD it would be really nice if the manner we used to match windows was
|
|
|
|
-- the same as that in XMonad itself (eg with Query types)
|
2020-03-25 13:38:41 -04:00
|
|
|
type MatchTags = Map String String
|
|
|
|
|
2020-03-25 14:09:07 -04:00
|
|
|
data WConf = WConf
|
|
|
|
{ display :: Display
|
|
|
|
, matchTags :: MatchTags
|
|
|
|
}
|
|
|
|
|
|
|
|
newtype W a = W (ReaderT WConf IO a)
|
|
|
|
deriving (Functor, Monad, MonadIO, MonadReader WConf)
|
|
|
|
|
|
|
|
instance Applicative W where
|
|
|
|
pure = return
|
|
|
|
(<*>) = ap
|
|
|
|
|
|
|
|
runW :: WConf -> W a -> IO a
|
|
|
|
runW c (W a) = runReaderT a c
|
|
|
|
|
|
|
|
io :: MonadIO m => IO a -> m a
|
|
|
|
io = liftIO
|
|
|
|
|
2020-03-25 13:38:41 -04:00
|
|
|
runWorkspaceMon :: MatchTags -> IO ()
|
|
|
|
runWorkspaceMon mts = do
|
2020-03-25 12:24:40 -04:00
|
|
|
dpy <- openDisplay ""
|
|
|
|
root <- rootWindow dpy $ defaultScreen dpy
|
|
|
|
-- listen only for substructure change events (which includes MapNotify)
|
|
|
|
allocaSetWindowAttributes $ \a -> do
|
|
|
|
set_event_mask a substructureNotifyMask
|
|
|
|
changeWindowAttributes dpy root cWEventMask a
|
2020-03-25 14:09:07 -04:00
|
|
|
let c = WConf { display = dpy, matchTags = mts }
|
2020-03-25 12:24:40 -04:00
|
|
|
_ <- allocaXEvent $ \e ->
|
2020-03-25 14:09:07 -04:00
|
|
|
runW c $ forever $ handle =<< io (nextEvent dpy e >> getEvent e)
|
2020-03-25 12:24:40 -04:00
|
|
|
return ()
|
|
|
|
|
2020-03-25 14:09:07 -04:00
|
|
|
handle :: Event -> W ()
|
2020-03-25 12:24:40 -04:00
|
|
|
|
|
|
|
-- | assume this fires at least once when a new window is created (also could
|
|
|
|
-- use CreateNotify but that is really noisy)
|
2020-03-25 14:09:07 -04:00
|
|
|
handle MapNotifyEvent { ev_window = w } = do
|
|
|
|
dpy <- asks display
|
|
|
|
hint <- io $ getClassHint dpy w
|
|
|
|
mts <- asks matchTags
|
2020-03-25 13:38:41 -04:00
|
|
|
let tag = M.lookup (resClass hint) mts
|
2020-03-25 14:09:07 -04:00
|
|
|
io $ forM_ tag $ \t -> do
|
|
|
|
a <- internAtom dpy "_NET_WM_PID" False
|
|
|
|
pid <- getWindowProperty32 dpy a w
|
|
|
|
case pid of
|
|
|
|
-- ASSUMPTION windows will only have one PID at one time
|
|
|
|
Just [p] -> waitAndKill t $ fromIntegral p
|
|
|
|
_ -> return ()
|
|
|
|
|
|
|
|
handle _ = return ()
|
2020-03-25 13:38:41 -04:00
|
|
|
|
|
|
|
waitAndKill :: String -> Pid -> IO ()
|
|
|
|
waitAndKill tag pid = waitUntilExit pidDir
|
2020-03-25 12:24:40 -04:00
|
|
|
where
|
|
|
|
pidDir = "/proc/" ++ show pid
|
|
|
|
waitUntilExit d = do
|
|
|
|
-- TODO this will not work if the process is a zombie (maybe I care...)
|
|
|
|
-- ASSUMPTION on linux PIDs will always increase until they overflow, in
|
|
|
|
-- which case they will start to recycle. Barring any fork bombs, this
|
|
|
|
-- code should work because we can reasonably expect that no processes
|
|
|
|
-- will spawn with the same PID within the delay limit
|
|
|
|
res <- doesDirectoryExist d
|
2020-03-25 14:09:07 -04:00
|
|
|
if res then threadDelay 100000 >> waitUntilExit d
|
|
|
|
else sendXMsg "%%%%%" tag
|