mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 11:30:22 -07:00
519 lines
18 KiB
Haskell
519 lines
18 KiB
Haskell
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
|
|
{-# LANGUAGE MultiParamTypeClasses, Rank2Types #-}
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- |
|
|
-- Module : XMonad.Layout.Groups.Examples
|
|
-- Copyright : Quentin Moser <moserq@gmail.com>
|
|
-- License : BSD-style (see LICENSE)
|
|
--
|
|
-- Maintainer : Quentin Moser <quentin.moser@unifr.ch>
|
|
-- Stability : unstable
|
|
-- Portability : unportable
|
|
--
|
|
-- Utility functions and example layouts for "XMonad.Layout.Groups".
|
|
--
|
|
-----------------------------------------------------------------------------
|
|
|
|
module XMonad.Layout.Groups.Examples ( -- * Usage
|
|
-- $usage
|
|
|
|
-- * Example: Wmii-like layout
|
|
-- $example1
|
|
wmiiLike
|
|
, zoomGroupIn
|
|
, zoomGroupOut
|
|
, zoomGroupReset
|
|
, toggleGroupFull
|
|
, groupToNextLayout
|
|
, groupToFullLayout
|
|
, groupToTabbedLayout
|
|
, groupToVerticalLayout
|
|
|
|
-- * Example: Row of columns
|
|
-- $example2
|
|
, rowOfColumns
|
|
, zoomColumnIn
|
|
, zoomColumnOut
|
|
, zoomColumnReset
|
|
, toggleColumnFull
|
|
, zoomWindowIn
|
|
, zoomWindowOut
|
|
, zoomWindowReset
|
|
, toggleWindowFull
|
|
|
|
-- * Example: Tiled tab groups
|
|
-- $example3
|
|
, tallTabs
|
|
, mirrorTallTabs
|
|
, fullTabs
|
|
, TiledTabsConfig(..)
|
|
, defaultTiledTabsConfig
|
|
, increaseNMasterGroups
|
|
, decreaseNMasterGroups
|
|
, shrinkMasterGroups
|
|
, expandMasterGroups
|
|
, nextOuterLayout
|
|
|
|
-- * Useful actions
|
|
-- $actions
|
|
|
|
-- ** Layout-generic actions
|
|
, swapUp
|
|
, swapDown
|
|
, swapMaster
|
|
, focusUp
|
|
, focusDown
|
|
, focusMaster
|
|
, toggleFocusFloat
|
|
|
|
-- ** 'G.Groups'-secific actions
|
|
, swapGroupUp
|
|
, swapGroupDown
|
|
, swapGroupMaster
|
|
, focusGroupUp
|
|
, focusGroupDown
|
|
, focusGroupMaster
|
|
, moveToGroupUp
|
|
, moveToGroupDown
|
|
, moveToNewGroupUp
|
|
, moveToNewGroupDown
|
|
, splitGroup
|
|
|
|
-- * Other useful stuff, re-exports
|
|
, GroupEQ
|
|
, shrinkText
|
|
, defaultTheme
|
|
) where
|
|
|
|
import XMonad hiding ((|||))
|
|
import qualified XMonad.StackSet as W
|
|
|
|
import qualified XMonad.Layout.Groups as G
|
|
|
|
import XMonad.Layout.ZoomRow
|
|
import XMonad.Layout.Tabbed
|
|
import XMonad.Layout.Named
|
|
import XMonad.Layout.Renamed
|
|
import XMonad.Layout.LayoutCombinators
|
|
import XMonad.Layout.MessageControl
|
|
import XMonad.Layout.Decoration
|
|
|
|
import XMonad.Actions.MessageFeedback
|
|
|
|
import Control.Monad (unless)
|
|
import qualified Data.Map as M
|
|
|
|
-- $usage
|
|
-- This module contains example 'G.Groups'-based layouts, and
|
|
-- 'X' actions that are useful when using them. You can either
|
|
-- import this module directly, or look at its source
|
|
-- for ideas of how "XMonad.Layout.Groups" may be used.
|
|
--
|
|
-- You can use the contents of this module by adding
|
|
--
|
|
-- > import XMonad.Layout.Groups.Examples
|
|
--
|
|
-- to the top of your @.\/.xmonad\/xmonad.hs@.
|
|
--
|
|
-- For more information on using any of the layouts, jump directly
|
|
-- to its \"Example\" section.
|
|
--
|
|
-- Whichever layout you choose to use, you will probably want to be
|
|
-- able to move focus and windows between groups in a consistent
|
|
-- manner. For this, you should take a look at the \"Useful Actions\"
|
|
-- section.
|
|
--
|
|
-- This module exports many operations with the same names as
|
|
-- 'G.ModifySpec's from "XMonad.Layout.Groups", so if you want
|
|
-- to import both, we suggest to import "XMonad.Layout.Groups"
|
|
-- qualified:
|
|
--
|
|
-- > import qualified XMonad.Layout.Groups as G
|
|
--
|
|
-- For more information on how to extend your layour hook and key bindings, see
|
|
-- "XMonad.Doc.Extending".
|
|
|
|
|
|
-- * Helper: ZoomRow of Group elements
|
|
|
|
-- | Compare two 'Group's by comparing the ids of their layouts.
|
|
data GroupEQ a = GroupEQ
|
|
deriving (Show, Read)
|
|
|
|
instance Eq a => EQF GroupEQ (G.Group l a) where
|
|
eq _ (G.G l1 _) (G.G l2 _) = G.sameID l1 l2
|
|
|
|
zoomRowG :: (Eq a, Show a, Read a, Show (l a), Read (l a))
|
|
=> ZoomRow GroupEQ (G.Group l a)
|
|
zoomRowG = zoomRowWith GroupEQ
|
|
|
|
|
|
-- * Example 1: Wmii-like layout
|
|
|
|
-- $example1
|
|
-- A layout inspired by the one used by the wmii (<http://wmii.suckless.org>).
|
|
-- Windows groups are arranged in a horizontal row, and each group can lay out
|
|
-- its windows
|
|
--
|
|
-- * by maximizing the focused one
|
|
--
|
|
-- * by tabbing them (wmii uses a stacked layout, but I'm too lazy to write it)
|
|
--
|
|
-- * by arranging them in a column.
|
|
--
|
|
-- As the groups are arranged in a 'ZoomRow', the width of each group can be increased
|
|
-- or decreased at will. Groups can also be set to use the whole screen whenever they
|
|
-- have focus.
|
|
--
|
|
-- To use this layout, add 'wmiiLike' (with a 'Shrinker' and decoration 'Theme' as
|
|
-- parameters) to your layout hook, for example:
|
|
--
|
|
-- > myLayout = wmiiLike shrinkText defaultTheme
|
|
--
|
|
-- To be able to zoom in and out of groups, change their inner layout, etc.,
|
|
-- create key bindings for the relevant actions:
|
|
--
|
|
-- > ((modMask, xK_f), toggleGroupFull)
|
|
--
|
|
-- and so on.
|
|
|
|
wmiiLike s t = G.group innerLayout zoomRowG
|
|
where column = named "Column" $ Tall 0 (3/100) (1/2)
|
|
tabs = named "Tabs" $ tabbed s t
|
|
innerLayout = renamed [CutWordsLeft 2] $ ignore NextLayout
|
|
$ ignore (JumpToLayout "") $ unEscape
|
|
$ column ||| tabs ||| Full
|
|
|
|
-- | Increase the width of the focused group
|
|
zoomGroupIn :: X ()
|
|
zoomGroupIn = sendMessage $ G.ToEnclosing $ SomeMessage $ zoomIn
|
|
|
|
-- | Decrease the size of the focused group
|
|
zoomGroupOut :: X ()
|
|
zoomGroupOut = sendMessage $ G.ToEnclosing $ SomeMessage $ zoomOut
|
|
|
|
-- | Reset the size of the focused group to the default
|
|
zoomGroupReset :: X ()
|
|
zoomGroupReset = sendMessage $ G.ToEnclosing $ SomeMessage $ zoomReset
|
|
|
|
-- | Toggle whether the currently focused group should be maximized
|
|
-- whenever it has focus.
|
|
toggleGroupFull :: X ()
|
|
toggleGroupFull = sendMessage $ G.ToEnclosing $ SomeMessage $ ZoomFullToggle
|
|
|
|
-- | Rotate the layouts in the focused group.
|
|
groupToNextLayout :: X ()
|
|
groupToNextLayout = sendMessage $ escape NextLayout
|
|
|
|
-- | Switch the focused group to the \"maximized\" layout.
|
|
groupToFullLayout :: X ()
|
|
groupToFullLayout = sendMessage $ escape $ JumpToLayout "Full"
|
|
|
|
-- | Switch the focused group to the \"tabbed\" layout.
|
|
groupToTabbedLayout :: X ()
|
|
groupToTabbedLayout = sendMessage $ escape $ JumpToLayout "Tabs"
|
|
|
|
-- | Switch the focused group to the \"column\" layout.
|
|
groupToVerticalLayout :: X ()
|
|
groupToVerticalLayout = sendMessage $ escape $ JumpToLayout "Column"
|
|
|
|
|
|
-- * Example 2: Row of columns
|
|
|
|
-- $example2
|
|
-- A layout that arranges windows in a row of columns. It uses 'ZoomRow's for
|
|
-- both, allowing you to:
|
|
--
|
|
-- * Freely change the proportion of the screen width allocated to each column
|
|
--
|
|
-- * Freely change the proportion of a column's heigth allocated to each of its windows
|
|
--
|
|
-- * Set a column to occupy the whole screen space whenever it has focus
|
|
--
|
|
-- * Set a window to occupy its whole column whenever it has focus
|
|
--
|
|
-- to use this layout, add 'rowOfColumns' to your layout hook, for example:
|
|
--
|
|
-- > myLayout = rowOfColumns
|
|
--
|
|
-- To be able to change the sizes of columns and windows, you can create key bindings
|
|
-- for the relevant actions:
|
|
--
|
|
-- > ((modMask, xK_minus), zoomWindowOut)
|
|
--
|
|
-- and so on.
|
|
|
|
rowOfColumns = G.group column zoomRowG
|
|
where column = renamed [CutWordsLeft 2, PrependWords "ZoomColumn"] $ Mirror zoomRow
|
|
|
|
-- | Increase the width of the focused column
|
|
zoomColumnIn :: X ()
|
|
zoomColumnIn = zoomGroupIn
|
|
|
|
-- | Decrease the width of the focused column
|
|
zoomColumnOut :: X ()
|
|
zoomColumnOut = zoomGroupOut
|
|
|
|
-- | Reset the width of the focused column
|
|
zoomColumnReset :: X ()
|
|
zoomColumnReset = zoomGroupReset
|
|
|
|
-- | Toggle whether the currently focused column should
|
|
-- take up all available space whenever it has focus
|
|
toggleColumnFull :: X ()
|
|
toggleColumnFull = toggleGroupFull
|
|
|
|
-- | Increase the heigth of the focused window
|
|
zoomWindowIn :: X ()
|
|
zoomWindowIn = sendMessage zoomIn
|
|
|
|
-- | Decrease the height of the focused window
|
|
zoomWindowOut :: X ()
|
|
zoomWindowOut = sendMessage zoomOut
|
|
|
|
-- | Reset the height of the focused window
|
|
zoomWindowReset :: X ()
|
|
zoomWindowReset = sendMessage zoomReset
|
|
|
|
-- | Toggle whether the currently focused window should
|
|
-- take up the whole column whenever it has focus
|
|
toggleWindowFull :: X ()
|
|
toggleWindowFull = sendMessage ZoomFullToggle
|
|
|
|
|
|
-- * Example 3: Tabbed groups in a Tall/Full layout.
|
|
|
|
-- $example3
|
|
-- A layout which arranges windows into tabbed groups, and the groups
|
|
-- themselves according to XMonad's default algorithm
|
|
-- (@'Tall' ||| 'Mirror' 'Tall' ||| 'Full'@). As their names
|
|
-- indicate, 'tallTabs' starts as 'Tall', 'mirrorTallTabs' starts
|
|
-- as 'Mirror' 'Tall' and 'fullTabs' starts as 'Full', but in any
|
|
-- case you can freely switch between the three afterwards.
|
|
--
|
|
-- You can use any of these three layouts by including it in your layout hook.
|
|
-- You will need to provide it with a 'TiledTabsConfig' containing the size
|
|
-- parameters for 'Tall' and 'Mirror' 'Tall', and the shrinker and decoration theme
|
|
-- for the tabs. If you're happy with defaults, you can use 'defaultTiledTabsConfig':
|
|
--
|
|
-- > myLayout = tallTabs defaultTiledTabsConfig
|
|
--
|
|
-- To be able to increase\/decrease the number of master groups and shrink\/expand
|
|
-- the master area, you can create key bindings for the relevant actions:
|
|
--
|
|
-- > ((modMask, xK_h), shrinkMasterGroups)
|
|
--
|
|
-- and so on.
|
|
|
|
-- | Configuration data for the "tiled tab groups" layout
|
|
data TiledTabsConfig s = TTC { vNMaster :: Int
|
|
, vRatio :: Rational
|
|
, vIncrement :: Rational
|
|
, hNMaster :: Int
|
|
, hRatio :: Rational
|
|
, hIncrement :: Rational
|
|
, tabsShrinker :: s
|
|
, tabsTheme :: Theme }
|
|
|
|
defaultTiledTabsConfig :: TiledTabsConfig DefaultShrinker
|
|
defaultTiledTabsConfig = TTC 1 0.5 (3/100) 1 0.5 (3/100) shrinkText defaultTheme
|
|
|
|
fullTabs c = G.group (_tabs c) $ Full ||| _vert c ||| _horiz c
|
|
|
|
tallTabs c = G.group (_tabs c) $ _vert c ||| _horiz c ||| Full
|
|
|
|
mirrorTallTabs c = G.group (_tabs c) $ _horiz c ||| Full ||| _vert c
|
|
|
|
_tabs c = named "Tabs" $ tabbed (tabsShrinker c) (tabsTheme c)
|
|
|
|
_vert c = named "Vertical" $ Tall (vNMaster c) (vIncrement c) (vRatio c)
|
|
|
|
_horiz c = named "Horizontal" $ Mirror $ Tall (hNMaster c) (hIncrement c) (hRatio c)
|
|
|
|
-- | Increase the number of master groups by one
|
|
increaseNMasterGroups :: X ()
|
|
increaseNMasterGroups = sendMessage $ G.ToEnclosing $ SomeMessage $ IncMasterN 1
|
|
|
|
-- | Decrease the number of master groups by one
|
|
decreaseNMasterGroups :: X ()
|
|
decreaseNMasterGroups = sendMessage $ G.ToEnclosing $ SomeMessage $ IncMasterN (-1)
|
|
|
|
-- | Shrink the master area
|
|
shrinkMasterGroups :: X ()
|
|
shrinkMasterGroups = sendMessage $ G.ToEnclosing $ SomeMessage $ Shrink
|
|
|
|
-- | Expand the master area
|
|
expandMasterGroups :: X ()
|
|
expandMasterGroups = sendMessage $ G.ToEnclosing $ SomeMessage $ Expand
|
|
|
|
-- | Rotate the available outer layout algorithms
|
|
nextOuterLayout :: X ()
|
|
nextOuterLayout = sendMessage $ G.ToEnclosing $ SomeMessage $ NextLayout
|
|
|
|
|
|
-- * Useful actions
|
|
|
|
-- $actions
|
|
-- "XMonad.Layout.Groups"-based layouts do not have the same notion
|
|
-- of window ordering as the rest of XMonad. For this reason, the usual
|
|
-- ways of reordering windows and moving focus do not work with them.
|
|
-- "XMonad.Layout.Groups" provides 'Message's that can be used to obtain
|
|
-- the right effect.
|
|
--
|
|
-- But what if you want to use both 'G.Groups' and other layouts?
|
|
-- This module provides actions that try to send 'G.GroupsMessage's, and
|
|
-- fall back to the classic way if the current layout doesn't hande them.
|
|
-- They are in the section called \"Layout-generic actions\".
|
|
--
|
|
-- The sections \"Groups-specific actions\" contains actions that don't make
|
|
-- sense for non-'G.Groups'-based layouts. These are simply wrappers around
|
|
-- the equivalent 'G.GroupsMessage's, but are included so you don't have to
|
|
-- write @sendMessage $ Modify $ ...@ everytime.
|
|
|
|
-- ** Layout-generic actions
|
|
-- #Layout-generic actions#
|
|
|
|
alt :: G.ModifySpec -> (WindowSet -> WindowSet) -> X ()
|
|
alt f g = alt2 (G.Modify f) $ windows g
|
|
|
|
alt2 :: G.GroupsMessage -> X () -> X ()
|
|
alt2 m x = do b <- send m
|
|
unless b x
|
|
|
|
-- | Swap the focused window with the previous one
|
|
swapUp :: X ()
|
|
swapUp = alt G.swapUp W.swapUp
|
|
|
|
-- | Swap the focused window with the next one
|
|
swapDown :: X ()
|
|
swapDown = alt G.swapDown W.swapDown
|
|
|
|
-- | Swap the focused window with the master window
|
|
swapMaster :: X ()
|
|
swapMaster = alt G.swapMaster W.swapMaster
|
|
|
|
-- | If the focused window is floating, focus the next floating
|
|
-- window. otherwise, focus the next non-floating one.
|
|
focusUp :: X ()
|
|
focusUp = ifFloat focusFloatUp focusNonFloatUp
|
|
|
|
-- | If the focused window is floating, focus the next floating
|
|
-- window. otherwise, focus the next non-floating one.
|
|
focusDown :: X ()
|
|
focusDown = ifFloat focusFloatDown focusNonFloatDown
|
|
|
|
-- | Move focus to the master window
|
|
focusMaster :: X ()
|
|
focusMaster = alt G.focusMaster W.shiftMaster
|
|
|
|
-- | Move focus between the floating and non-floating layers
|
|
toggleFocusFloat :: X ()
|
|
toggleFocusFloat = ifFloat focusNonFloat focusFloatUp
|
|
|
|
-- *** Floating layer helpers
|
|
|
|
getFloats :: X [Window]
|
|
getFloats = gets $ M.keys . W.floating . windowset
|
|
|
|
getWindows :: X [Window]
|
|
getWindows = gets $ W.integrate' . W.stack . W.workspace . W.current . windowset
|
|
|
|
ifFloat :: X () -> X () -> X ()
|
|
ifFloat x1 x2 = withFocused $ \w -> do floats <- getFloats
|
|
if elem w floats then x1 else x2
|
|
|
|
focusNonFloat :: X ()
|
|
focusNonFloat = alt2 G.Refocus helper
|
|
where helper = withFocused $ \w -> do
|
|
ws <- getWindows
|
|
floats <- getFloats
|
|
let (before, after) = span (/=w) ws
|
|
case filter (flip notElem floats) $ after ++ before of
|
|
[] -> return ()
|
|
w':_ -> focus w'
|
|
|
|
focusHelper :: (Bool -> Bool) -- ^ if you want to focus a floating window, 'id'.
|
|
-- if you want a non-floating one, 'not'.
|
|
-> ([Window] -> [Window]) -- ^ if you want the next window, 'id'.
|
|
-- if you want the previous one, 'reverse'.
|
|
-> X ()
|
|
focusHelper f g = withFocused $ \w -> do
|
|
ws <- getWindows
|
|
let (before, _:after) = span (/=w) ws
|
|
let toFocus = g $ after ++ before
|
|
floats <- getFloats
|
|
case filter (f . flip elem floats) toFocus of
|
|
[] -> return ()
|
|
w':_ -> focus w'
|
|
|
|
|
|
focusNonFloatUp :: X ()
|
|
focusNonFloatUp = alt2 (G.Modify G.focusUp) $ focusHelper not reverse
|
|
|
|
focusNonFloatDown :: X ()
|
|
focusNonFloatDown = alt2 (G.Modify G.focusDown) $ focusHelper not id
|
|
|
|
focusFloatUp :: X ()
|
|
focusFloatUp = focusHelper id reverse
|
|
|
|
focusFloatDown :: X ()
|
|
focusFloatDown = focusHelper id id
|
|
|
|
|
|
-- ** Groups-specific actions
|
|
|
|
wrap :: G.ModifySpec -> X ()
|
|
wrap = sendMessage . G.Modify
|
|
|
|
-- | Swap the focused group with the previous one
|
|
swapGroupUp :: X ()
|
|
swapGroupUp = wrap G.swapGroupUp
|
|
|
|
-- | Swap the focused group with the next one
|
|
swapGroupDown :: X ()
|
|
swapGroupDown = wrap G.swapGroupDown
|
|
|
|
-- | Swap the focused group with the master group
|
|
swapGroupMaster :: X ()
|
|
swapGroupMaster = wrap G.swapGroupMaster
|
|
|
|
-- | Move the focus to the previous group
|
|
focusGroupUp :: X ()
|
|
focusGroupUp = wrap G.focusGroupUp
|
|
|
|
-- | Move the focus to the next group
|
|
focusGroupDown :: X ()
|
|
focusGroupDown = wrap G.focusGroupDown
|
|
|
|
-- | Move the focus to the master group
|
|
focusGroupMaster :: X ()
|
|
focusGroupMaster = wrap G.focusGroupMaster
|
|
|
|
-- | Move the focused window to the previous group. The 'Bool' argument
|
|
-- determines what will be done if the focused window is in the very first
|
|
-- group: Wrap back to the end ('True'), or create a new group before
|
|
-- it ('False').
|
|
moveToGroupUp :: Bool -> X ()
|
|
moveToGroupUp = wrap . G.moveToGroupUp
|
|
|
|
-- | Move the focused window to the next group. The 'Bool' argument
|
|
-- determines what will be done if the focused window is in the very last
|
|
-- group: Wrap back to the beginning ('True'), or create a new group after
|
|
-- it ('False').
|
|
moveToGroupDown :: Bool -> X ()
|
|
moveToGroupDown = wrap . G.moveToGroupDown
|
|
|
|
-- | Move the focused window to a new group before the current one
|
|
moveToNewGroupUp :: X ()
|
|
moveToNewGroupUp = wrap G.moveToNewGroupUp
|
|
|
|
-- | Move the focused window to a new group after the current one
|
|
moveToNewGroupDown :: X ()
|
|
moveToNewGroupDown = wrap G.moveToNewGroupDown
|
|
|
|
-- | Split the focused group in two at the position of the focused
|
|
-- window.
|
|
splitGroup :: X ()
|
|
splitGroup = wrap G.splitGroup
|