{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-}

-----------------------------------------------------------------------------
-- |
-- Module       : XMonad.Layout.MagicFocus
-- Description :  Automagically put the focused window in the master area.
-- Copyright    : (c) Peter De Wachter <pdewacht@gmail.com>
-- License      : BSD
--
-- Maintainer   : Peter De Wachter <pdewacht@gmail.com>
-- Stability    : unstable
-- Portability  : unportable
--
-- Automagically put the focused window in the master area.
-----------------------------------------------------------------------------

module XMonad.Layout.MagicFocus
    (-- * Usage
     -- $usage
     magicFocus,
     promoteWarp,
     promoteWarp',
     followOnlyIf,
     disableFollowOnWS,
     MagicFocus,
    ) where

import XMonad
import qualified XMonad.StackSet as W
import XMonad.Layout.LayoutModifier

import XMonad.Actions.UpdatePointer (updatePointer)
import XMonad.Prelude(All(..))
import qualified Data.Map as M

-- $usage
-- You can use this module with the following in your @xmonad.hs@:
--
-- > import XMonad.Layout.MagicFocus
--
-- Then edit your @layoutHook@ by adding the magicFocus layout
-- modifier:
--
-- > myLayout = magicFocus (Tall 1 (3/100) (1/2)) ||| Full ||| etc..
-- > main = xmonad def { layoutHook = myLayout,
-- >                     handleEventHook = promoteWarp }
--
-- For more detailed instructions on editing the layoutHook see
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial> and
-- "XMonad.Doc.Extending#Editing_the_layout_hook".

-- | Create a new layout which automagically puts the focused window
--   in the master area.
magicFocus :: l a -> ModifiedLayout MagicFocus l a
magicFocus :: forall (l :: * -> *) a. l a -> ModifiedLayout MagicFocus l a
magicFocus = forall (m :: * -> *) (l :: * -> *) a.
m a -> l a -> ModifiedLayout m l a
ModifiedLayout forall a. MagicFocus a
MagicFocus

data MagicFocus a = MagicFocus deriving (Int -> MagicFocus a -> ShowS
forall a. Int -> MagicFocus a -> ShowS
forall a. [MagicFocus a] -> ShowS
forall a. MagicFocus a -> WorkspaceId
forall a.
(Int -> a -> ShowS)
-> (a -> WorkspaceId) -> ([a] -> ShowS) -> Show a
showList :: [MagicFocus a] -> ShowS
$cshowList :: forall a. [MagicFocus a] -> ShowS
show :: MagicFocus a -> WorkspaceId
$cshow :: forall a. MagicFocus a -> WorkspaceId
showsPrec :: Int -> MagicFocus a -> ShowS
$cshowsPrec :: forall a. Int -> MagicFocus a -> ShowS
Show, ReadPrec [MagicFocus a]
ReadPrec (MagicFocus a)
ReadS [MagicFocus a]
forall a. ReadPrec [MagicFocus a]
forall a. ReadPrec (MagicFocus a)
forall a. Int -> ReadS (MagicFocus a)
forall a. ReadS [MagicFocus a]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [MagicFocus a]
$creadListPrec :: forall a. ReadPrec [MagicFocus a]
readPrec :: ReadPrec (MagicFocus a)
$creadPrec :: forall a. ReadPrec (MagicFocus a)
readList :: ReadS [MagicFocus a]
$creadList :: forall a. ReadS [MagicFocus a]
readsPrec :: Int -> ReadS (MagicFocus a)
$creadsPrec :: forall a. Int -> ReadS (MagicFocus a)
Read)

instance LayoutModifier MagicFocus Window where
  modifyLayout :: forall (l :: * -> *).
LayoutClass l Window =>
MagicFocus Window
-> Workspace WorkspaceId (l Window) Window
-> Rectangle
-> X ([(Window, Rectangle)], Maybe (l Window))
modifyLayout MagicFocus Window
MagicFocus (W.Workspace WorkspaceId
i l Window
l Maybe (Stack Window)
s) =
    forall (layout :: * -> *) a.
LayoutClass layout a =>
Workspace WorkspaceId (layout a) a
-> Rectangle -> X ([(a, Rectangle)], Maybe (layout a))
runLayout (forall i l a. i -> l -> Maybe (Stack a) -> Workspace i l a
W.Workspace WorkspaceId
i l Window
l (Maybe (Stack Window)
s forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Eq a => Stack a -> Stack a
shift))

shift :: (Eq a) => W.Stack a -> W.Stack a
shift :: forall a. Eq a => Stack a -> Stack a
shift (W.Stack a
f [a]
u [a]
d) = forall a. a -> [a] -> [a] -> Stack a
W.Stack a
f [] (forall a. [a] -> [a]
reverse [a]
u forall a. [a] -> [a] -> [a]
++ [a]
d)

-- | An eventHook that overrides the normal focusFollowsMouse. When the mouse
-- it moved to another window, that window is replaced as the master, and the
-- mouse is warped to inside the new master.
--
-- It prevents infinite loops when focusFollowsMouse is true (the default), and
-- MagicFocus is in use when changing focus with the mouse.
--
-- This eventHook does nothing when there are floating windows on the current
-- workspace.
promoteWarp :: Event -> X All
promoteWarp :: Event -> X All
promoteWarp = (Rational, Rational) -> (Rational, Rational) -> Event -> X All
promoteWarp' (Rational
0.5, Rational
0.5) (Rational
0.85, Rational
0.85)

-- | promoteWarp' allows you to specify an arbitrary pair of arguments to
-- pass to 'updatePointer' when the mouse enters another window.
promoteWarp' :: (Rational, Rational) -> (Rational, Rational) -> Event -> X All
promoteWarp' :: (Rational, Rational) -> (Rational, Rational) -> Event -> X All
promoteWarp' (Rational, Rational)
refPos (Rational, Rational)
ratio e :: Event
e@CrossingEvent{ev_window :: Event -> Window
ev_window = Window
w, ev_event_type :: Event -> EventType
ev_event_type = EventType
t}
    | EventType
t forall a. Eq a => a -> a -> Bool
== EventType
enterNotify Bool -> Bool -> Bool
&& Event -> NotifyMode
ev_mode   Event
e forall a. Eq a => a -> a -> Bool
== NotifyMode
notifyNormal = do
        WindowSet
ws <- forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets XState -> WindowSet
windowset
        let foc :: Maybe Window
foc = forall i l a s sd. StackSet i l a s sd -> Maybe a
W.peek WindowSet
ws
            st :: [Window]
st = forall a. Maybe (Stack a) -> [a]
W.integrate' forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall i l a. Workspace i l a -> Maybe (Stack a)
W.stack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
W.workspace forall a b. (a -> b) -> a -> b
$ forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
W.current WindowSet
ws
            wsFloats :: Map Window RationalRect
wsFloats = forall k a. (k -> a -> Bool) -> Map k a -> Map k a
M.filterWithKey (\Window
k RationalRect
_ -> Window
k forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Window]
st) forall a b. (a -> b) -> a -> b
$ forall i l a sid sd. StackSet i l a sid sd -> Map a RationalRect
W.floating WindowSet
ws
        if forall a. a -> Maybe a
Just Window
w forall a. Eq a => a -> a -> Bool
/= Maybe Window
foc Bool -> Bool -> Bool
&& forall k a. Map k a -> Bool
M.null Map Window RationalRect
wsFloats then do
            (WindowSet -> WindowSet) -> X ()
windows (forall i l a s sd. StackSet i l a s sd -> StackSet i l a s sd
W.swapMaster forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall s a i l sd.
(Eq s, Eq a, Eq i) =>
a -> StackSet i l a s sd -> StackSet i l a s sd
W.focusWindow Window
w)
            (Rational, Rational) -> (Rational, Rational) -> X ()
updatePointer (Rational, Rational)
refPos (Rational, Rational)
ratio
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> All
All Bool
False
          else forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> All
All Bool
True
promoteWarp' (Rational, Rational)
_ (Rational, Rational)
_ Event
_ = forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> All
All Bool
True

-- | Another event hook to override the focusFollowsMouse and make the pointer
-- only follow if a given condition is satisfied. This could be used to disable
-- focusFollowsMouse only for given workspaces or layouts.
-- Beware that your focusFollowsMouse setting is ignored if you use this event hook.
followOnlyIf :: X Bool -> Event -> X All
followOnlyIf :: X Bool -> Event -> X All
followOnlyIf X Bool
cond e :: Event
e@CrossingEvent{ev_window :: Event -> Window
ev_window = Window
w, ev_event_type :: Event -> EventType
ev_event_type = EventType
t}
    | EventType
t forall a. Eq a => a -> a -> Bool
== EventType
enterNotify Bool -> Bool -> Bool
&& Event -> NotifyMode
ev_mode Event
e forall a. Eq a => a -> a -> Bool
== NotifyMode
notifyNormal
    = X Bool -> X () -> X ()
whenX X Bool
cond (Window -> X ()
focus Window
w) forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> All
All Bool
False)
followOnlyIf X Bool
_ Event
_ = forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Bool -> All
All Bool
True

-- | Disables focusFollow on the given workspaces:
disableFollowOnWS :: [WorkspaceId] -> X Bool
disableFollowOnWS :: [WorkspaceId] -> X Bool
disableFollowOnWS [WorkspaceId]
wses = (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [WorkspaceId]
wses) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets (forall i l a s sd. StackSet i l a s sd -> i
W.currentTag forall b c a. (b -> c) -> (a -> b) -> a -> c
. XState -> WindowSet
windowset)