packages feed

whim-0.1: Whim.hs

{-# LANGUAGE ForeignFunctionInterface, ExistentialQuantification #-}
-- whim: a window manager
-- Copyright (C) 2006 Evan Martin <martine@danga.com>

module Main (main) where

import Prelude hiding (log, lookup)
import Control.Monad.State hiding (State)
import Data.Bits ((.|.))
import qualified Data.Map
import Data.Map ((!), lookup)
import System.Cmd (system)
import System.Random

import Graphics.X11.Xlib

import Whim.Util
import qualified Whim.Xlib

magicMod :: KeyMask
magicMod = controlMask .|. mod1Mask

{-
data Config = Config {
    cMagicKey ::
  }
-}

-- | Data we track about each window.
data Client = Client {
    clWindow :: Window,
    clPos    :: Coord,
    clSize   :: Coord,
    clName   :: String
  } deriving Show

-- | A map from Window -> Client.
type ClientMap = Data.Map.Map Window Client
-- | The reason we're grabbing the mouse.
data MouseGrab = Drag | Resize

-- | Key bindings.
type KeyBindingKey = (KeyMask, KeyCode)
type KeyBindingTarget = StateM ()
type KeyBindings = Data.Map.Map KeyBindingKey KeyBindingTarget
keyConfig = [
  (xK_x, cmd "xeyes"),
  (xK_t, cmd "xvt")
  ] where cmd c = liftIO (system (c ++ " &")) >> return ()

-- | Global state.
data State = State {
    sClients     :: ClientMap,
    sMouseGrab   :: Maybe (MouseGrab, Window, Coord),
    sKeyBindings :: KeyBindings
  }
sClient :: Window -> State -> Client
sClient win state = sClients state ! win

-- | A state monad that allows IO and modifying the state.
type StateM a = StateT State IO a

-- | Wrap a Window into a Client, adding the appropriate grabs.
wrapWindow :: Display -> Window -> IO Client
wrapWindow dpy win = do
  log $ ["wrapped window", show win]

  -- Grab buttons 1 and 2 when our magic modifier is pressed.
  grabButton dpy button1 magicMod win False buttonPressMask
             grabModeAsync grabModeSync 0 0
  grabButton dpy button2 magicMod win False buttonPressMask
             grabModeAsync grabModeSync 0 0

  -- Get notifications when things happen to the window.
  selectInput dpy win (structureNotifyMask .|. propertyChangeMask .|.
                       enterWindowMask)

  -- Move it to a random location
  x <- randomRIO (500, 500) :: IO Int
  y <- randomRIO (500, 500) :: IO Int
  moveWindow dpy win (fromIntegral x) (fromIntegral y)

  mapWindow dpy win

  (x, y, w, h) <- Whim.Xlib.getWindowAttributes dpy win
  return $ Client win (x,y) (w,h) "foo"

-- | Wrap all existing Clients.
wrapInitialClients :: Display -> Window -> IO ClientMap
wrapInitialClients dpy root = do
  wins <- Whim.Xlib.queryTree dpy root
  clients <- mapM (wrapWindow dpy) wins
  return $ Data.Map.fromList (zip wins clients)

-- | Grab the keys we're interested in.
grabKeys :: Display -> Window -> IO KeyBindings
grabKeys dpy root = do
  codes <- mapM (uncurry bindKey) keyConfig
  let keys = zip (repeat magicMod) codes
  return $ Data.Map.fromList $ zip keys (map snd keyConfig)
  where
  bindKey sym f = do
    code <- keysymToKeycode dpy sym
    ungrabKey dpy code magicMod root
    grabKey   dpy code magicMod root False grabModeAsync grabModeAsync
    return code

-- | Do all of the initial setup.
initialize :: Display -> State -> IO State
initialize dpy state = do
  root <- rootWindow dpy 0
  -- Get notifications when the available window list changes.
  -- This fails if there is already a wm running.
  selectInput dpy root substructureRedirectMask
  bindings <- grabKeys dpy root
  clientmap <- wrapInitialClients dpy root
  print clientmap
  sync dpy False
  log ["initialized."]
  return $ state { sClients=clientmap, sKeyBindings=bindings }

-- | Handle a key press.
handleKey :: HandlerFunc XKeyEvent
handleKey dpy win (root, sub, time, x, y, xr, yr, mod, keycode, same) = do
  liftIO $ log ["keydown", show keycode]
  bindings <- gets sKeyBindings
  case lookup (mod, keycode) bindings of
    Nothing -> return ()
    Just cb -> cb

-- | Handle a key release.
handleKeyRelease :: HandlerFunc XKeyEvent
handleKeyRelease dpy win (root, sub, time, x, y, xr, yr, mod, keycode, same) = do
  return ()

handleButton :: HandlerFunc XButtonEvent
handleButton dpy win (root, sub, time, x, y, xr, yr, mod, button, same) = do
  liftIO $ log ["buttonpress", show root, show win,
        "(" ++ show x ++ "," ++ show y ++ ")", show button]
  let mouseMask = buttonPressMask .|.  buttonReleaseMask .|.  pointerMotionMask
  liftIO $ raiseWindow dpy win
  liftIO $ grabPointer dpy root False mouseMask
    grabModeAsync grabModeAsync 0 0 time
  modify $ \s ->
    if button == button1
      then s { sMouseGrab = Just (Drag, win, ((fromIntegral xr),(fromIntegral yr))) }
      else if button == button2
        then s { sMouseGrab = Just (Resize, win, ((fromIntegral xr),(fromIntegral yr))) }
        else s

handleMotion :: HandlerFunc XMotionEvent
handleMotion dpy win (root, sub, time, x, y, xr, yr, mod, mode, same_screen) = do
  grab <- gets sMouseGrab
  case grab of
    Nothing -> liftIO $ log ["motion but no drag?"]
    Just (grabtype, win, lastpos) -> handleGrabbedMotion grabtype win lastpos

  where
  handleGrabbedMotion grabtype win lastpos = do
    clients <- gets sClients
    -- XXX this should fail if client dies while dragging
    let client = clients ! win
    liftIO $ sync dpy False
    let curpos = (fromIntegral xr, fromIntegral yr)
    client' <- updateClient client grabtype ((curpos) -: lastpos)
    modify $ \s -> s { sClients  = Data.Map.insert win client' clients,
                       sMouseGrab = Just (grabtype, win, (curpos)) }

  updateClient client Drag delta = do
    let newpos = (clPos client) +: delta
    liftIO $ moveWindow dpy (clWindow client) (fromIntegral $ fst newpos)
                                              (fromIntegral $ snd newpos)
    return $ client {clPos=newpos}
  updateClient client Resize delta = do
    liftIO $ log ["XXX implement resize."]
    return client

handleButtonRelease :: HandlerFunc XButtonEvent
handleButtonRelease dpy win (root, sub, time, x,y, xr,yr, mod, button, same) = do
  -- Stop any existing mouse grab.
  grab <- gets sMouseGrab
  case grab of
    Just _ -> do
      liftIO $ ungrabPointer dpy time
      modify $ \s -> s { sMouseGrab=Nothing }
    Nothing -> return ()

handleMapRequest :: HandlerFunc Window
handleMapRequest dpy _ win = do
  liftIO $ log ["map", show win]
  clients <- gets sClients
  if win `Data.Map.member` clients
    then return ()
    else do client <- liftIO $ wrapWindow dpy win
            modify $ \s -> s { sClients = Data.Map.insert win client clients }

emptyHandler :: String -> Handler
emptyHandler name = Handler return (\d w a -> liftIO $ log [name])
type HandlerFunc a = Display -> Window -> a -> StateM ()
data Handler = forall a. Handler (XEventPtr -> IO a) (HandlerFunc a)

dispatchEvent :: Display -> State -> XEventPtr -> IO State
dispatchEvent dpy state xev = do
  evtype <- get_EventType xev
  win <- get_Window xev
  case Data.Map.lookup evtype dispatchTable of
    Just (Handler convertor handler) -> do
      param <- convertor xev
      execStateT (handler dpy win param) state
    Nothing -> do
      log $ ["other event:", show evtype]
      return state
  where
  dispatchTable :: Data.Map.Map EventType Handler
  dispatchTable = Data.Map.fromList [
      (buttonPress,   Handler get_ButtonEvent handleButton),
      (buttonRelease, Handler get_ButtonEvent handleButtonRelease),
      (keyPress,      Handler get_KeyEvent handleKey),
      (keyRelease,    Handler get_KeyEvent handleKeyRelease),
      (enterNotify,   emptyHandler "enter"),
      (motionNotify,  Handler get_MotionEvent handleMotion),
      (mapRequest,    Handler Whim.Xlib.get_MapRequestEvent handleMapRequest)
    ]

loopState :: a -> (a -> IO a) -> IO b
loopState state f = loop state where loop s = f s >>= loop

mainLoop :: Display -> State -> IO ()
mainLoop dpy state = do
  allocaXEvent $ \xev -> do
    log ["awaiting events."]
    loopState state $ \state -> do
      nextEvent dpy xev
      dispatchEvent dpy state xev

main :: IO ()
main = do
  dpy <- openDisplay ""
  state <- initialize dpy $ State {
             sClients     = undefined,
             sMouseGrab   = Nothing,
             sKeyBindings = undefined
           }
  print $ sClients state
  mainLoop dpy state
  putStrLn "done"