summaryrefslogtreecommitdiffstats
path: root/EventLoop.hs
blob: 24680f2cb7d2ed30bb31ee09a2907499b9a6f0d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
{-# LANGUAGE MultiParamTypeClasses #-}
----------------------------------------------------------------------------
-- |
-- Module      :  Main.hs
-- Copyright   :  (c) Spencer Janssen 2007
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  sjanssen@cse.unl.edu
-- Stability   :  unstable
-- Portability :  not portable, uses mtl, X11, posix
--
-- xmonad, a minimalist, tiling window manager for X11
--
-----------------------------------------------------------------------------

module EventLoop (makeMain) where

import Data.Bits
import qualified Data.Map as M
import qualified Data.Set as S
import Control.Monad.Reader
import Control.Monad.State
import Data.Maybe (fromMaybe)

import System.Environment (getArgs)

import Graphics.X11.Xlib hiding (refreshKeyboardMapping)
import Graphics.X11.Xlib.Extras
import Graphics.X11.Xinerama    (getScreenInfo)

import XMonad
import StackSet (new, floating, member)
import qualified StackSet as W
import Operations

import System.IO

-- |
-- The main entry point
--
makeMain :: XConfig -> IO ()
makeMain xmc = do
    dpy   <- openDisplay ""
    let dflt = defaultScreen dpy

    rootw  <- rootWindow dpy dflt
    xinesc <- getScreenInfo dpy
    nbc    <- initColor dpy $ normalBorderColor xmc
    fbc    <- initColor dpy $ focusedBorderColor xmc
    hSetBuffering stdout NoBuffering
    args <- getArgs

    let layout = layoutHook xmc
        lreads = readsLayout layout
        initialWinset = new layout (workspaces xmc) $ zipWith SD xinesc gaps

        maybeRead reads' s = case reads' s of
                                [(x, "")] -> Just x
                                _         -> Nothing

        winset = fromMaybe initialWinset $ do
                    ("--resume" : s : _) <- return args
                    ws                   <- maybeRead reads s
                    return . W.ensureTags layout (workspaces xmc)
                           $ W.mapLayout (fromMaybe layout . maybeRead lreads) ws

        gaps = take (length xinesc) $ defaultGaps xmc ++ repeat (0,0,0,0)

        cf = XConf
            { display       = dpy
            , config        = xmc
            , theRoot       = rootw
            , normalBorder  = nbc
            , focusedBorder = fbc }
        st = XState
            { windowset     = initialWinset
            , mapped        = S.empty
            , waitingUnmap  = M.empty
            , dragging      = Nothing }

    xSetErrorHandler -- in C, I'm too lazy to write the binding: dons

    -- setup initial X environment
    sync dpy False
    selectInput dpy rootw $  substructureRedirectMask .|. substructureNotifyMask
                         .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask

    allocaXEvent $ \e ->
        runX cf st $ do

            grabKeys xmc
            grabButtons xmc

            io $ sync dpy False

            -- bootstrap the windowset, Operations.windows will identify all
            -- the windows in winset as new and set initial properties for
            -- those windows
            windows (const winset)

            -- scan for all top-level windows, add the unmanaged ones to the
            -- windowset
            ws <- io $ scan dpy rootw
            mapM_ manage ws

            -- main loop, for all you HOF/recursion fans out there.
            forever_ $ handle =<< io (nextEvent dpy e >> getEvent e)

    return ()
      where forever_ a = a >> forever_ a

            -- ---------------------------------------------------------------------
            -- | Event handler. Map X events onto calls into Operations.hs, which
            -- modify our internal model of the window manager state.
            --
            -- Events dwm handles that we don't:
            --
            --    [ButtonPress]    = buttonpress,
            --    [Expose]         = expose,
            --    [PropertyNotify] = propertynotify,
            --
            handle :: Event -> X ()

            -- run window manager command
            handle (KeyEvent {ev_event_type = t, ev_state = m, ev_keycode = code})
                | t == keyPress = withDisplay $ \dpy -> do
                    s  <- io $ keycodeToKeysym dpy code 0
                    mClean <- cleanMask m
                    userCode $ whenJust (M.lookup (mClean, s) (keys xmc)) id

            -- manage a new window
            handle (MapRequestEvent    {ev_window = w}) = withDisplay $ \dpy -> do
                wa <- io $ getWindowAttributes dpy w -- ignore override windows
                -- need to ignore mapping requests by managed windows not on the current workspace
                managed <- isClient w
                when (not (wa_override_redirect wa) && not managed) $ do manage w

            -- window destroyed, unmanage it
            -- window gone,      unmanage it
            handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ unmanage w

            -- We track expected unmap events in waitingUnmap.  We ignore this event unless
            -- it is synthetic or we are not expecting an unmap notification from a window.
            handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do
                e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
                if (synthetic || e == 0)
                    then unmanage w
                    else modify (\s -> s { waitingUnmap = M.adjust pred w (waitingUnmap s) })

            -- set keyboard mapping
            handle e@(MappingNotifyEvent {}) = do
                io $ refreshKeyboardMapping e
                when (ev_request e == mappingKeyboard) (grabKeys xmc)

            -- handle button release, which may finish dragging.
            handle e@(ButtonEvent {ev_event_type = t})
                | t == buttonRelease = do
                drag <- gets dragging
                case drag of
                    -- we're done dragging and have released the mouse:
                    Just (_,f) -> modify (\s -> s { dragging = Nothing }) >> f
                    Nothing    -> broadcastMessage e

            -- handle motionNotify event, which may mean we are dragging.
            handle e@(MotionEvent {ev_event_type = _t, ev_x = x, ev_y = y}) = do
                drag <- gets dragging
                case drag of
                    Just (d,_) -> d (fromIntegral x) (fromIntegral y) -- we're dragging
                    Nothing -> broadcastMessage e

            -- click on an unfocused window, makes it focused on this workspace
            handle e@(ButtonEvent {ev_window = w,ev_event_type = t,ev_button = b })
                | t == buttonPress = do
                -- If it's the root window, then it's something we
                -- grabbed in grabButtons. Otherwise, it's click-to-focus.
                isr <- isRoot w
                m <- cleanMask $ ev_state e
                if isr then userCode $ whenJust (M.lookup (m, b) $ mouseBindings xmc) ($ ev_subwindow e)
                       else focus w
                sendMessage e -- Always send button events.

            -- entered a normal window, makes this focused.
            handle e@(CrossingEvent {ev_window = w, ev_event_type = t})
                | t == enterNotify && ev_mode   e == notifyNormal
                                   && ev_detail e /= notifyInferior = focus w

            -- left a window, check if we need to focus root
            handle e@(CrossingEvent {ev_event_type = t})
                | t == leaveNotify
                = do rootw <- asks theRoot
                     when (ev_window e == rootw && not (ev_same_screen e)) $ setFocusX rootw

            -- configure a window
            handle e@(ConfigureRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
                ws <- gets windowset
                wa <- io $ getWindowAttributes dpy w

                if M.member w (floating ws)
                    || not (member w ws)
                    then do io $ configureWindow dpy w (ev_value_mask e) $ WindowChanges
                                { wc_x            = ev_x e
                                , wc_y            = ev_y e
                                , wc_width        = ev_width e
                                , wc_height       = ev_height e
                                , wc_border_width = fromIntegral (borderWidth xmc)
                                , wc_sibling      = ev_above e
                                , wc_stack_mode   = ev_detail e }
                            when (member w ws) (float w)
                    else io $ allocaXEvent $ \ev -> do
                             setEventType ev configureNotify
                             setConfigureEvent ev w w
                                 (wa_x wa) (wa_y wa) (wa_width wa)
                                 (wa_height wa) (ev_border_width e) none (wa_override_redirect wa)
                             sendEvent dpy w False 0 ev
                io $ sync dpy False

            -- configuration changes in the root may mean display settings have changed
            handle (ConfigureEvent {ev_window = w}) = whenX (isRoot w) rescreen

            -- property notify
            handle PropertyEvent { ev_event_type = t, ev_atom = a }
                | t == propertyNotify && a == wM_NAME = userCode $ logHook xmc

            handle e = broadcastMessage e -- trace (eventName e) -- ignoring


-- ---------------------------------------------------------------------
-- IO stuff. Doesn't require any X state
-- Most of these things run only on startup (bar grabkeys)

-- | scan for any new windows to manage. If they're already managed,
-- this should be idempotent.
scan :: Display -> Window -> IO [Window]
scan dpy rootw = do
    (_, _, ws) <- queryTree dpy rootw
    filterM ok ws
  -- TODO: scan for windows that are either 'IsViewable' or where WM_STATE ==
  -- Iconic
  where ok w = do wa <- getWindowAttributes dpy w
                  a  <- internAtom dpy "WM_STATE" False
                  p  <- getWindowProperty32 dpy a w
                  let ic = case p of
                            Just (3:_) -> True -- 3 for iconified
                            _          -> False
                  return $ not (wa_override_redirect wa)
                         && (wa_map_state wa == waIsViewable || ic)

-- | Grab the keys back
grabKeys :: XConfig -> X ()
grabKeys xmc = do
    XConf { display = dpy, theRoot = rootw } <- ask
    let grab kc m = io $ grabKey dpy kc m rootw True grabModeAsync grabModeAsync
    io $ ungrabKey dpy anyKey anyModifier rootw
    forM_ (M.keys $ keys xmc) $ \(mask,sym) -> do
         kc <- io $ keysymToKeycode dpy sym
         -- "If the specified KeySym is not defined for any KeyCode,
         -- XKeysymToKeycode() returns zero."
         when (kc /= '\0') $ mapM_ (grab kc . (mask .|.)) =<< extraModifiers

-- | XXX comment me
grabButtons :: XConfig -> X ()
grabButtons xmc = do
    XConf { display = dpy, theRoot = rootw } <- ask
    let grab button mask = io $ grabButton dpy button mask rootw False buttonPressMask
                                           grabModeAsync grabModeSync none none
    io $ ungrabButton dpy anyButton anyModifier rootw
    ems <- extraModifiers
    mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ mouseBindings xmc)