summaryrefslogtreecommitdiffstats
path: root/lib/NoBorders.hs
blob: 09206927e430a19009e414b8a2d9cc39669f4650 (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
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances #-}
{-# LANGUAGE PatternGuards #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Layout.NoBorders
-- Copyright   :  (c) David Roundy <droundy@darcs.net>
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Spencer Janssen <spencerjanssen@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Make a given layout display without borders.  This is useful for
-- full-screen or tabbed layouts, where you don't really want to waste a
-- couple of pixels of real estate just to inform yourself that the visible
-- window has focus.
--
-----------------------------------------------------------------------------

module NoBorders (
                                -- * Usage
                                -- $usage
                                noBorders,
                                smartBorders,
                                withBorder,
                                lessBorders,
                                SetsAmbiguous(..),
                                Ambiguity(..),
                                With(..)
                 ) where

import XMonad
import XMonad.Layout.LayoutModifier
import qualified XMonad.StackSet as W
import Control.Monad
import Data.List
import qualified Data.Map as M
import Data.Function (on)

-- $usage
-- You can use this module with the following in your ~\/.xmonad\/xmonad.hs file:
--
-- > import XMonad.Layout.NoBorders
--
-- and modify the layouts to call noBorders on the layouts you want to lack
-- borders:
--
-- > layoutHook = ... ||| noBorders Full ||| ...
--
-- For more detailed instructions on editing the layoutHook see:
--
-- "XMonad.Doc.Extending#Editing_the_layout_hook"

-- todo, use an InvisibleList.
data WithBorder a = WithBorder Dimension (M.Map a Dimension) deriving ( Read, Show )

instance LayoutModifier WithBorder Window where
    unhook (WithBorder _ s) = setBorders $ M.toList s

    redoLayout (WithBorder n sold) _ _ wrs = do
        s <- mapM (\w -> winBorderWidth w >>= \bw -> return (w,bw)) ws >>= return . M.fromList
        let snew = M.mapWithKey (\w bw -> M.findWithDefault bw w sold) s
        
        setBorders $ M.toList $ M.filterWithKey (\w _ -> elem w ws) sold
        setBorders $ map (\w -> (w, n)) ws
        return (wrs, Just $ WithBorder n $ snew)
     where
        ws = map fst wrs


winBorderWidth :: Window -> X Dimension
winBorderWidth w = withDisplay $ \d -> (fromIntegral . wa_border_width) `fmap` io (getWindowAttributes d w)

-- | Removes all window borders from the specified layout.
noBorders :: LayoutClass l Window => l Window -> ModifiedLayout WithBorder l Window
noBorders = withBorder 0

-- | Forces a layout to use the specified border width. 'noBorders' is
-- equivalent to @'withBorder' 0@.
withBorder :: LayoutClass l a => Dimension -> l a -> ModifiedLayout WithBorder l a
withBorder b = ModifiedLayout $ WithBorder b M.empty

setBorders :: [(Window, Dimension)] -> X ()
setBorders ws = withDisplay $ \d -> mapM_ (\(w,bw) -> io $ setWindowBorderWidth d w bw) ws

singleton :: [a] -> Bool
singleton = null . drop 1

type SmartBorder = ConfigurableBorder Ambiguity

-- | Removes the borders from a window under one of the following conditions:
--
--  * There is only one screen and only one window. In this case it's obvious
--  that it has the focus, so no border is needed.
--
--  * A floating window covers the entire screen (e.g. mplayer).
--
smartBorders :: LayoutClass l a => l a -> ModifiedLayout SmartBorder l a
smartBorders = lessBorders Never

-- | Apply a datatype that has a SetsAmbiguous instance to provide a list of
-- windows that should not have borders.
--
-- This gives flexibility over when borders should be drawn, in particular with
-- xinerama setups: 'Ambiguity' has a number of useful 'SetsAmbiguous'
-- instances
lessBorders :: (SetsAmbiguous p, Read p, Show p, LayoutClass l a) =>
        p -> l a -> ModifiedLayout (ConfigurableBorder p) l a
lessBorders amb = ModifiedLayout (ConfigurableBorder amb M.empty)

data ConfigurableBorder p w = ConfigurableBorder p (M.Map w Dimension) deriving (Read, Show)

instance (Read p, Show p, SetsAmbiguous p) => LayoutModifier (ConfigurableBorder p) Window where
    unhook (ConfigurableBorder _ s) = do
      setBorders $ M.toList s

    redoLayout (ConfigurableBorder p sold) _ mst wrs = do
        wsh <- withWindowSet (\wset -> return (hiddens p wset mst wrs))
        s <- mapM (\w -> winBorderWidth w >>= \bw -> return (w,bw)) (ws ++ wsh) >>= return . M.fromList
        let snew = M.mapWithKey (\w bw -> M.findWithDefault bw w sold) s
        
        setBorders $ M.toList $ M.filterWithKey (\w _ -> notElem w wsh) sold
        setBorders $ map (\w -> (w, 0)) wsh
        return (wrs, Just $ ConfigurableBorder p snew)
      where
        ws = map fst wrs

-- | SetsAmbiguous allows custom actions to generate lists of windows that
-- should not have borders drawn through 'ConfigurableBorder'
--
-- To add your own (though perhaps those options would better belong as an
-- aditional constructor to 'Ambiguity'), you can add the function as such:
--
-- > data MyAmbiguity = MyAmbiguity deriving (Read, Show)
--
-- > instance SetsAmbiguous MyAmbiguity where
-- >  hiddens _ wset mst wrs = otherHiddens Screen \\ otherHiddens OnlyFloat
-- >     where otherHiddens p = hiddens p wset mst wrs
--
-- The above example is redundant, because you can have the same result with:
--
-- > layoutHook = lessBorders (Combine Difference Screen OnlyFloat) (Tall 1 0.5 0.03 ||| ... )
--
-- To get the same result as smartBorders:
--
-- > layoutHook = lessBorders (Combine Never) (Tall 1 0.5 0.03 ||| ...)
--
-- This indirect method is required to keep the Read and Show for
-- ConfigurableBorder so that xmonad can serialize state.
class SetsAmbiguous p where
    hiddens :: p -> WindowSet -> Maybe (W.Stack Window) -> [(Window, Rectangle)] -> [Window]

instance SetsAmbiguous Ambiguity where
    hiddens amb wset mst wrs
      | Combine Union a b <- amb = on union next a b
      | Combine Difference a b <- amb = on (\\) next a b
      | Combine Intersection a b <- amb = on intersect next a b
      | otherwise = tiled ms ++ floating
      where next p = hiddens p wset mst wrs
            nonzerorect (Rectangle _ _ 0 0) = False
            nonzerorect _ = True

            screens =
              [ scr | scr <- W.screens wset,
                      case amb of
                            Never -> True
                            _ -> not $ null $ integrate scr,
                      nonzerorect . screenRect $ W.screenDetail scr]
            floating = [ w |
                        (w, W.RationalRect px py wx wy) <- M.toList . W.floating $ wset,
                        px <= 0, py <= 0,
                        wx + px >= 1, wy + py >= 1]
            ms = filter (`elem` W.integrate' mst) $ map fst wrs
            tiled [w]
              | Screen <- amb = [w]
              | OnlyFloat <- amb = []
              | OtherIndicated <- amb
              , let nonF = map integrate $ W.current wset : W.visible wset
              , length (concat nonF) > length wrs
              , singleton $ filter (1==) $ map length nonF = [w]
              | singleton screens = [w]
            tiled _ = []
            integrate y = W.integrate' . W.stack $ W.workspace y

-- | In order of increasing ambiguity (less borders more frequently), where
-- subsequent constructors add additional cases where borders are not drawn
-- than their predecessors. These behaviors make most sense with with multiple
-- screens: for single screens, Never or 'smartBorders' makes more sense.
data Ambiguity = Combine With Ambiguity Ambiguity
                             -- ^ This constructor is used to combine the
                             -- borderless windows provided by the
                             -- SetsAmbiguous instances from two other
                             -- 'Ambiguity' data types.
               | OnlyFloat   -- ^ Only remove borders on floating windows that
                             -- cover the whole screen
               | Never       -- ^ Never remove borders when ambiguous:
                             -- this is the same as smartBorders
               | EmptyScreen -- ^ Focus in an empty screens does not count as
                             -- ambiguous.
               | OtherIndicated
                             -- ^ No borders on full when all other screens
                             -- have borders.
               | Screen      -- ^ Borders are never drawn on singleton screens.
                             -- With this one you really need another way such
                             -- as a statusbar to detect focus.
    deriving (Read, Show)

-- | Used to indicate to the 'SetsAmbiguous' instance for 'Ambiguity' how two
-- lists should be combined.
data With = Union        -- ^ Combine with Data.List.union
          | Difference   -- ^ Combine with Data.List.\\
          | Intersection -- ^ Combine with Data.List.intersect
        deriving (Read, Show)