----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.Minimize
-- Description :  Actions for minimizing and maximizing windows.
-- Copyright   :  (c) Bogdan Sinitsyn (2016)
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Bogdan Sinitsyn <bogdan.sinitsyn@gmail.com>
-- Stability   :  unstable
-- Portability :  not portable
--
-- Adds actions for minimizing and maximizing windows
--
-- This module should be used with "XMonad.Layout.Minimize". Add 'minimize' to your
-- layout modifiers as described in "XMonad.Layout.Minimize" and use actions from
-- this module
--
-- Possible keybindings:
--
-- >        , ((modm,               xK_m     ), withFocused minimizeWindow)
-- >        , ((modm .|. shiftMask, xK_m     ), withLastMinimized maximizeWindowAndFocus)
--
-----------------------------------------------------------------------------

module XMonad.Actions.Minimize
  ( -- * Usage
    -- $usage
    minimizeWindow
  , maximizeWindow
  , maximizeWindowAndFocus
  , withLastMinimized
  , withLastMinimized'
  , withFirstMinimized
  , withFirstMinimized'
  , withMinimized
  ) where

import XMonad
import XMonad.Prelude (fromMaybe, join, listToMaybe)
import qualified XMonad.StackSet as W

import qualified XMonad.Layout.BoringWindows as BW
import qualified XMonad.Util.ExtensibleState as XS
import XMonad.Util.Minimize
import XMonad.Util.WindowProperties (getProp32)

import Foreign.C.Types (CLong)
import qualified Data.List as L
import qualified Data.Map as M

-- $usage
-- Import this module with "XMonad.Layout.Minimize" and "XMonad.Layout.BoringWindows":
--
-- > import XMonad.Actions.Minimize
-- > import XMonad.Layout.Minimize
-- > import qualified XMonad.Layout.BoringWindows as BW
--
-- Then apply 'minimize' and 'boringWindows' to your layout hook and use some
-- actions from this module:
--
-- > main = xmonad def { layoutHook = minimize . BW.boringWindows $ whatever }
--
-- Example keybindings:
--
-- >        , ((modm,               xK_m     ), withFocused minimizeWindow      )
-- >        , ((modm .|. shiftMask, xK_m     ), withLastMinimized maximizeWindow)

setMinimizedState :: Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState :: Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState Window
win Int
st CLong -> [CLong] -> [CLong]
f = do
    Window -> Int -> X ()
setWMState Window
win Int
st
    forall a. (Display -> X a) -> X a
withDisplay forall a b. (a -> b) -> a -> b
$ \Display
dpy -> do
        Window
wm_state <- WorkspaceId -> X Window
getAtom WorkspaceId
"_NET_WM_STATE"
        CLong
hidden <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> WorkspaceId -> X Window
getAtom WorkspaceId
"_NET_WM_STATE_HIDDEN"
        [CLong]
wstate <- forall a. a -> Maybe a -> a
fromMaybe [] forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Window -> Window -> X (Maybe [CLong])
getProp32 Window
wm_state Window
win
        forall (m :: * -> *) a. MonadIO m => IO a -> m a
io forall a b. (a -> b) -> a -> b
$ Display -> Window -> Window -> Window -> CInt -> [CLong] -> IO ()
changeProperty32 Display
dpy Window
win Window
wm_state Window
aTOM CInt
propModeReplace (CLong -> [CLong] -> [CLong]
f CLong
hidden [CLong]
wstate)

setMinimized :: Window -> X ()
setMinimized :: Window -> X ()
setMinimized Window
win = Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState Window
win Int
iconicState (:)

setNotMinimized :: Window -> X ()
setNotMinimized :: Window -> X ()
setNotMinimized Window
win = Window -> Int -> (CLong -> [CLong] -> [CLong]) -> X ()
setMinimizedState Window
win Int
normalState forall a. Eq a => a -> [a] -> [a]
L.delete

-- It does not just set minimizedStack to newWindows because it should save
-- order in which elements were added (newer first)
modified :: (RectMap -> RectMap) -> X Bool
modified :: (RectMap -> RectMap) -> X Bool
modified RectMap -> RectMap
f = forall a (m :: * -> *).
(ExtensionClass a, Eq a, XLike m) =>
(a -> a) -> m Bool
XS.modified forall a b. (a -> b) -> a -> b
$
    \Minimized { rectMap :: Minimized -> RectMap
rectMap = RectMap
oldRectMap, minimizedStack :: Minimized -> [Window]
minimizedStack = [Window]
oldStack } ->
      let newRectMap :: RectMap
newRectMap = RectMap -> RectMap
f RectMap
oldRectMap
          newWindows :: [Window]
newWindows = forall k a. Map k a -> [k]
M.keys RectMap
newRectMap
       in Minimized { rectMap :: RectMap
rectMap = RectMap
newRectMap
                    , minimizedStack :: [Window]
minimizedStack = ([Window]
newWindows forall a. Eq a => [a] -> [a] -> [a]
L.\\ [Window]
oldStack)
                                       forall a. [a] -> [a] -> [a]
++
                                       ([Window]
oldStack forall a. Eq a => [a] -> [a] -> [a]
`L.intersect` [Window]
newWindows)
                    }


-- | Minimize a window
minimizeWindow :: Window -> X ()
minimizeWindow :: Window -> X ()
minimizeWindow Window
w = forall a. (WindowSet -> X a) -> X a
withWindowSet forall a b. (a -> b) -> a -> b
$ \WindowSet
ws ->
  X Bool -> X () -> X ()
whenX ((RectMap -> RectMap) -> X Bool
modified forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert Window
w (forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Window
w 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)) forall a b. (a -> b) -> a -> b
$ do
    Window -> X ()
setMinimized Window
w
    (WindowSet -> WindowSet) -> X ()
windows forall a b. (a -> b) -> a -> b
$ forall a i l s sd.
Ord a =>
a -> StackSet i l a s sd -> StackSet i l a s sd
W.sink Window
w
    X ()
BW.focusDown


-- | Maximize window and apply a function to maximized window and 'WindowSet'
maximizeWindowAndChangeWSet :: (Window -> WindowSet -> WindowSet) -> Window -> X ()
maximizeWindowAndChangeWSet :: (Window -> WindowSet -> WindowSet) -> Window -> X ()
maximizeWindowAndChangeWSet Window -> WindowSet -> WindowSet
f Window
w = do
  Maybe RationalRect
mrect <- forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets (forall (m :: * -> *) a. Monad m => m (m a) -> m a
join forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Window
w forall b c a. (b -> c) -> (a -> b) -> a -> c
. Minimized -> RectMap
rectMap)
  X Bool -> X () -> X ()
whenX ((RectMap -> RectMap) -> X Bool
modified forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> Map k a -> Map k a
M.delete Window
w) forall a b. (a -> b) -> a -> b
$ do
    Window -> X ()
setNotMinimized Window
w
    forall a. Message a => a -> X ()
broadcastMessage UpdateBoring
BW.UpdateBoring
    (WindowSet -> WindowSet) -> X ()
windows forall a b. (a -> b) -> a -> b
$ Window -> WindowSet -> WindowSet
f Window
w forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall a. a -> a
id (forall a i l s sd.
Ord a =>
a -> RationalRect -> StackSet i l a s sd -> StackSet i l a s sd
W.float Window
w) Maybe RationalRect
mrect

-- | Just maximize a window without focusing
maximizeWindow :: Window -> X ()
maximizeWindow :: Window -> X ()
maximizeWindow = (Window -> WindowSet -> WindowSet) -> Window -> X ()
maximizeWindowAndChangeWSet forall a b. (a -> b) -> a -> b
$ forall a b. a -> b -> a
const forall a. a -> a
id

-- | Maximize a window and then focus it
maximizeWindowAndFocus :: Window -> X ()
maximizeWindowAndFocus :: Window -> X ()
maximizeWindowAndFocus = (Window -> WindowSet -> WindowSet) -> Window -> X ()
maximizeWindowAndChangeWSet 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

-- | Perform an action with first minimized window on current workspace
--   or do nothing if there is no minimized windows on current workspace
withFirstMinimized :: (Window -> X ()) -> X ()
withFirstMinimized :: (Window -> X ()) -> X ()
withFirstMinimized Window -> X ()
action = (Maybe Window -> X ()) -> X ()
withFirstMinimized' (forall (m :: * -> *) a. Monad m => Maybe a -> (a -> m ()) -> m ()
`whenJust` Window -> X ()
action)

-- | Like withFirstMinimized but the provided action is always invoked with a
--   'Maybe Window', that will be nothing if there is no first minimized window.
withFirstMinimized' :: (Maybe Window -> X ()) -> X ()
withFirstMinimized' :: (Maybe Window -> X ()) -> X ()
withFirstMinimized' Maybe Window -> X ()
action = forall a. ([Window] -> X a) -> X a
withMinimized (Maybe Window -> X ()
action forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> Maybe a
listToMaybe forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse)

-- | Perform an action with last minimized window on current workspace
--   or do nothing if there is no minimized windows on current workspace
withLastMinimized :: (Window -> X ()) -> X ()
withLastMinimized :: (Window -> X ()) -> X ()
withLastMinimized Window -> X ()
action = (Maybe Window -> X ()) -> X ()
withLastMinimized' (forall (m :: * -> *) a. Monad m => Maybe a -> (a -> m ()) -> m ()
`whenJust` Window -> X ()
action)

-- | Like withLastMinimized but the provided action is always invoked with a
--   'Maybe Window', that will be nothing if there is no last minimized window.
withLastMinimized' :: (Maybe Window -> X ()) -> X ()
withLastMinimized' :: (Maybe Window -> X ()) -> X ()
withLastMinimized' Maybe Window -> X ()
action = forall a. ([Window] -> X a) -> X a
withMinimized (Maybe Window -> X ()
action forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> Maybe a
listToMaybe)

withMinimized :: ([Window] -> X a) -> X a
withMinimized :: forall a. ([Window] -> X a) -> X a
withMinimized [Window] -> X a
action = do
  [Window]
minimized <- forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets Minimized -> [Window]
minimizedStack
  [Window]
currentStack <- forall a. (WindowSet -> X a) -> X a
withWindowSet forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall i l a s sd. StackSet i l a s sd -> [a]
W.index
  [Window] -> X a
action forall a b. (a -> b) -> a -> b
$ [Window]
minimized forall a. Eq a => [a] -> [a] -> [a]
`L.intersect` [Window]
currentStack