{-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, DeriveDataTypeable, PatternGuards #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Layout.Monitor
-- Copyright : (c) Roman Cheplyaka
-- License : BSD-style (see LICENSE)
--
-- Maintainer : Roman Cheplyaka <roma@ro-che.info>
-- Stability : unstable
-- Portability : unportable
--
-- Layout modfier for displaying some window (monitor) above other windows
--
-----------------------------------------------------------------------------
module XMonad.Layout.Monitor (
-- * Usage
-- $usage
-- * Hints and issues
-- $hints
Monitor(..),
monitor,
Property(..),
MonitorMessage(..),
doHideIgnore,
manageMonitor
-- * TODO
-- $todo
) where
import XMonad
import XMonad.Layout.LayoutModifier
import XMonad.Util.WindowProperties
import XMonad.Hooks.ManageHelpers (doHideIgnore)
import XMonad.Hooks.FadeInactive (setOpacity)
import Control.Monad
-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
--
-- > import XMonad.Layout.Monitor
--
-- Define 'Monitor' record. 'monitor' can be used as a template. At least 'prop'
-- and 'rect' should be set here. Also consider setting 'persistent' to True.
--
-- Minimal example:
--
-- > myMonitor = monitor
-- > { prop = ClassName "SomeClass"
-- > , rect = Rectangle 0 0 40 20 -- rectangle 40x20 in upper left corner
-- > }
--
-- More interesting example:
--
-- > clock = monitor {
-- > -- Cairo-clock creates 2 windows with the same classname, thus also using title
-- > prop = ClassName "Cairo-clock" `And` Title "MacSlow's Cairo-Clock"
-- > -- rectangle 150x150 in lower right corner, assuming 1280x800 resolution
-- > , rect = Rectangle (1280-150) (800-150) 150 150
-- > -- avoid flickering
-- > , persistent = True
-- > -- make the window transparent
-- > , opacity = 0.6
-- > -- hide on start
-- > , visible = False
-- > -- assign it a name to be able to toggle it independently of others
-- > , name = "clock"
-- > }
--
-- Add ManageHook to de-manage monitor windows and apply opacity settings.
--
-- > manageHook = myManageHook <+> manageMonitor clock
--
-- Apply layout modifier.
--
-- > myLayouts = ModifiedLayout clock $ tall ||| Full ||| ...
--
-- After that, if there exists a window with specified properties, it will be
-- displayed on top of all /tiled/ (not floated) windows on specified
-- position.
--
-- It's also useful to add some keybinding to toggle monitor visibility:
--
-- > , ((mod1Mask, xK_u ), broadcastMessage ToggleMonitor >> refresh)
--
-- Screenshot: <http://www.haskell.org/haskellwiki/Image:Xmonad-clock.png>
data Monitor a = Monitor
{ prop :: Property -- ^ property which uniquely identifies monitor window
, rect :: Rectangle -- ^ specifies where to put monitor
, visible :: Bool -- ^ is it visible by default?
, name :: String -- ^ name of monitor (useful when we have many of them)
, persistent :: Bool -- ^ is it shown on all layouts?
, opacity :: Rational -- ^ opacity level
} deriving (Read, Show)
-- | Template for 'Monitor' record. At least 'prop' and 'rect' should be
-- redefined. Default settings: 'visible' is 'True', 'persistent' is 'False'.
monitor :: Monitor a
monitor = Monitor
{ prop = Const False
, rect = Rectangle 0 0 0 0
, visible = True
, name = ""
, persistent = False
, opacity = 1
}
-- | Messages without names affect all monitors. Messages with names affect only
-- monitors whose names match.
data MonitorMessage = ToggleMonitor | ShowMonitor | HideMonitor
| ToggleMonitorNamed String
| ShowMonitorNamed String
| HideMonitorNamed String
deriving (Read,Show,Eq,Typeable)
instance Message MonitorMessage
withMonitor :: Property -> a -> (Window -> X a) -> X a
withMonitor p a fn = do
monitorWindows <- allWithProperty p
case monitorWindows of
[] -> return a
w:_ -> fn w
instance LayoutModifier Monitor Window where
redoLayout mon _ _ rects = withMonitor (prop mon) (rects, Nothing) $ \w ->
if visible mon
then do tileWindow w (rect mon)
reveal w
return ((w,rect mon):rects, Nothing)
else do hide w
return (rects, Nothing)
handleMess mon mess
| Just ToggleMonitor <- fromMessage mess = return $ Just $ mon { visible = not $ visible mon }
| Just (ToggleMonitorNamed n) <- fromMessage mess = return $
if name mon == n then Just $ mon { visible = not $ visible mon } else Nothing
| Just ShowMonitor <- fromMessage mess = return $ Just $ mon { visible = True }
| Just (ShowMonitorNamed n) <- fromMessage mess = return $
if name mon == n then Just $ mon { visible = True } else Nothing
| Just HideMonitor <- fromMessage mess = return $ Just $ mon { visible = False }
| Just (HideMonitorNamed n) <- fromMessage mess = return $
if name mon == n then Just $ mon { visible = False } else Nothing
| Just Hide <- fromMessage mess = do unless (persistent mon) $ withMonitor (prop mon) () hide; return Nothing
| otherwise = return Nothing
-- | ManageHook which demanages monitor window and applies opacity settings.
manageMonitor :: Monitor a -> ManageHook
manageMonitor mon = propertyToQuery (prop mon) --> do
w <- ask
liftX $ setOpacity w $ opacity mon
if persistent mon then doIgnore else doHideIgnore
-- $hints
-- - This module assumes that there is only one window satisfying property exists.
--
-- - If your monitor is available on /all/ layouts, set
-- 'persistent' to 'True' to avoid unnecessary
-- flickering. You can still toggle monitor with a keybinding.
--
-- - You can use several monitors with nested modifiers. Give them names
--- to be able to toggle them independently.
--
-- - You can display monitor only on specific workspaces with
-- "XMonad.Layout.PerWorkspace".
-- $todo
-- - make Monitor remember the window it manages
--
-- - specify position relative to the screen