mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 11:30:22 -07:00
New module: Actions.EasyMotion (#222)
This commit is contained in:
parent
0ebd3a0534
commit
99ea4c23e8
@ -858,6 +858,13 @@
|
|||||||
Currently needs manual setting of the session start flag. This could be
|
Currently needs manual setting of the session start flag. This could be
|
||||||
automated when this moves to the core repository.
|
automated when this moves to the core repository.
|
||||||
|
|
||||||
|
* `XMonad.Actions.EasyMotion`
|
||||||
|
|
||||||
|
A new module that allows selection of visible screens using a key chord.
|
||||||
|
Inspired by [vim-easymotion](https://github.com/easymotion/vim-easymotion). See the animation
|
||||||
|
in the vim-easymotion repo to get some idea of the functionality of this
|
||||||
|
EasyMotion module.
|
||||||
|
|
||||||
* `XMonad.Layout.MultiDishes`
|
* `XMonad.Layout.MultiDishes`
|
||||||
|
|
||||||
A new layout based on Dishes, however it accepts additional configuration
|
A new layout based on Dishes, however it accepts additional configuration
|
||||||
|
382
XMonad/Actions/EasyMotion.hs
Normal file
382
XMonad/Actions/EasyMotion.hs
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : XMonad.Actions.EasyMotion
|
||||||
|
-- Copyright : (c) Matt Kingston <mattkingston@gmail.com>
|
||||||
|
-- License : BSD3-style (see LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : mattkingston@gmail.com
|
||||||
|
-- Stability : unstable
|
||||||
|
-- Portability : unportable
|
||||||
|
--
|
||||||
|
-- Provides functionality to use key chords to focus a visible window. Overlays a unique key chord
|
||||||
|
-- (a string) above each visible window and allows the user to select a window by typing that
|
||||||
|
-- chord.
|
||||||
|
-- Inspired by https://github.com/easymotion/vim-easymotion.
|
||||||
|
-- Thanks to Tom Hinton (https://github.com/larkery) for some feature inspiration and window
|
||||||
|
-- sorting code.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module XMonad.Actions.EasyMotion (
|
||||||
|
-- * Usage
|
||||||
|
-- $usage
|
||||||
|
selectWindow
|
||||||
|
, def
|
||||||
|
, EasyMotionConfig(..)
|
||||||
|
, ChordKeys(..)
|
||||||
|
, fullSize
|
||||||
|
, fixedSize
|
||||||
|
, textSize
|
||||||
|
, bar
|
||||||
|
) where
|
||||||
|
|
||||||
|
import XMonad
|
||||||
|
import XMonad.StackSet as W
|
||||||
|
import XMonad.Util.Font (releaseXMF, initXMF, Align(AlignCenter), XMonadFont(..), textExtentsXMF)
|
||||||
|
import XMonad.Util.XUtils (fi, createNewWindow, paintAndWrite, deleteWindow, showWindow)
|
||||||
|
import Control.Monad (replicateM)
|
||||||
|
import Control.Arrow ((&&&))
|
||||||
|
import Data.Maybe (isJust)
|
||||||
|
import qualified Data.Map.Strict as M (Map, map, foldr, mapWithKey)
|
||||||
|
import Data.Set (toList)
|
||||||
|
import Graphics.X11.Xlib.Extras (getWindowAttributes, getEvent)
|
||||||
|
import qualified Data.List as L (filter, partition, find, nub)
|
||||||
|
import Data.List (sortOn)
|
||||||
|
|
||||||
|
-- $usage
|
||||||
|
--
|
||||||
|
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
|
||||||
|
--
|
||||||
|
-- > import XMonad.Actions.EasyMotion (selectWindow)
|
||||||
|
--
|
||||||
|
-- To customise
|
||||||
|
--
|
||||||
|
-- > import XMonad.Actions.EasyMotion (selectWindow, EasyMotionConfig(..))
|
||||||
|
--
|
||||||
|
-- Then add a keybinding and an action to the selectWindow function. In this case M-f to focus:
|
||||||
|
--
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
--
|
||||||
|
-- Similarly, to kill a window with M-f:
|
||||||
|
--
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def) >>= (flip whenJust killWindow))
|
||||||
|
--
|
||||||
|
-- See 'EasyMotionConfig' for all configuration options. A short summary follows.
|
||||||
|
--
|
||||||
|
-- Default chord keys are s,d,f,j,k,l. To customise these and display options assign
|
||||||
|
-- different values to def:
|
||||||
|
--
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def {sKeys = AnyKeys [xK_f, xK_d]}) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
--
|
||||||
|
-- You must supply at least two different keys in the sKeys list. Keys provided earlier in the list
|
||||||
|
-- will be used preferentially- therefore, keys you would like to use more frequently should be
|
||||||
|
-- earlier in the list.
|
||||||
|
--
|
||||||
|
-- To map different sets of keys to different screens. The following configuration maps keys fdsa
|
||||||
|
-- to screen 0 and hjkl to screen 1. Keys provided earlier in the list will be used preferentially.
|
||||||
|
-- Providing the same key for multiple screens is possible but will break down in some scenarios.
|
||||||
|
--
|
||||||
|
-- > import qualified Data.Map.Strict as StrictMap (fromList)
|
||||||
|
-- > emConf :: EasyMotionConfig
|
||||||
|
-- > emConf = def { sKeys = PerScreenKeys $ StrictMap.fromList [(0, [xK_f, xK_d, xK_s, xK_a]), (1, [xK_h, xK_j, xK_k, xK_l])] }
|
||||||
|
-- > -- key bindings
|
||||||
|
-- > , ((modm, xK_f), (selectWindow emConf) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
--
|
||||||
|
-- To customise font:
|
||||||
|
--
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def {font = "xft: Sans-40"}) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
--
|
||||||
|
-- The font field provided is supplied directly to the initXMF function. The default is
|
||||||
|
-- "xft:Sans-100". Some example options:
|
||||||
|
--
|
||||||
|
-- > "xft: Sans-40"
|
||||||
|
-- > "xft: Arial-100"
|
||||||
|
-- > "xft: Cambria-80"
|
||||||
|
--
|
||||||
|
-- Customise the overlay by supplying a function to do so. The signature is @'Position' ->
|
||||||
|
-- 'Rectangle' -> 'X' 'Rectangle'@. The parameters are the height in pixels of the selection chord
|
||||||
|
-- and the rectangle of the window to be overlaid. Some are provided:
|
||||||
|
--
|
||||||
|
-- > import XMonad.Actions.EasyMotion (selectWindow, EasyMotionConfig(..), proportional, bar, fullSize)
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def { overlayF = proportional 0.3 }) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def { overlayF = bar 0.5 }) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def { overlayF = fullSize }) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
-- > , ((modm, xK_f), (selectWindow def { overlayF = fixedSize 300 350 }) >>= (flip whenJust (windows . W.focusWindow)))
|
||||||
|
|
||||||
|
-- TODO:
|
||||||
|
-- - An overlay function that creates an overlay a proportion of the width XOR height of the
|
||||||
|
-- window it's over, and with a fixed w/h proportion? E.g. overlay-height = 0.3 *
|
||||||
|
-- target-window-height; overlay-width = 0.5 * overlay-height.
|
||||||
|
-- - An overlay function that creates an overlay of a fixed w,h, aligned mid,mid, or parametrised
|
||||||
|
-- alignment?
|
||||||
|
-- - Parametrise chord generation?
|
||||||
|
-- - W.shift example; bring window from other screen to current screen? Only useful if we don't
|
||||||
|
-- show chords on current workspace.
|
||||||
|
-- - Use stringToKeysym, keysymToKeycode, keycodeToKeysym, keysymToString to take a string from
|
||||||
|
-- the user?
|
||||||
|
-- - Think a bit more about improving functionality with floating windows.
|
||||||
|
-- - currently, floating window z-order is not respected
|
||||||
|
-- - could ignore floating windows
|
||||||
|
-- - may be able to calculate the visible section of a floating window, and display the chord in
|
||||||
|
-- that space
|
||||||
|
-- - Provide an option to prepend the screen key to the easymotion keys (i.e. w,e,r)?
|
||||||
|
-- - overlay alpha
|
||||||
|
-- - Delay after selection so the user can see what they've chosen? Min-delay: 0 seconds. If
|
||||||
|
-- there's a delay, perhaps keep the other windows covered briefly to naturally draw the user's
|
||||||
|
-- attention to the window they've selected? Or briefly highlight the border of the selected
|
||||||
|
-- window?
|
||||||
|
-- - Option to cover windows that will not be selected by the current chord, such that it's
|
||||||
|
-- slightly more obvious where to maintain focus.
|
||||||
|
-- - Something unpleasant happens when the user provides only two keys (let's say f, d) for
|
||||||
|
-- chords. When they have five windows open, the following chords are generated: ddd, ddf, dfd,
|
||||||
|
-- dff, fdd. When 'f' is pressed, all chords disappear unexpectedly because we know there are no
|
||||||
|
-- other valid options. The user expects to press 'fdd'. This is an optimisation in software but
|
||||||
|
-- pretty bad for usability, as the user continues firing keys into their
|
||||||
|
-- now-unexpectedly-active window. And is of course only one concrete example of a more general
|
||||||
|
-- problem.
|
||||||
|
-- Short-term solution:
|
||||||
|
-- - Keep displaying the chord until the user has fully entered it
|
||||||
|
-- Fix:
|
||||||
|
-- - Show the shortest possible chords
|
||||||
|
|
||||||
|
-- | Associates a user window, an overlay window created by this module and a rectangle
|
||||||
|
-- circumscribing these windows
|
||||||
|
data OverlayWindow =
|
||||||
|
OverlayWindow { win :: Window -- ^ The window managed by xmonad
|
||||||
|
, attrs :: WindowAttributes -- ^ Window attributes for @win@
|
||||||
|
, overlay :: Window -- ^ Our window used to display the overlay
|
||||||
|
, rect :: Rectangle -- ^ The rectangle of @overlay@
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | An overlay window and the chord used to select it
|
||||||
|
data Overlay =
|
||||||
|
Overlay { overlayWin :: OverlayWindow -- ^ The window managed by xmonad
|
||||||
|
, chord :: [KeySym] -- ^ The chord we'll display in the overlay
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- | Maps keys to windows. AnyKeys maps keys to windows regardless which screen they're on.
|
||||||
|
-- PerScreenKeys maps keys to screens to windows. See $usage for more examples.
|
||||||
|
data ChordKeys = AnyKeys [KeySym]
|
||||||
|
| PerScreenKeys (M.Map ScreenId [KeySym])
|
||||||
|
|
||||||
|
-- | Configuration options for EasyMotion.
|
||||||
|
--
|
||||||
|
-- All colors are hex strings, e.g. "#000000"
|
||||||
|
--
|
||||||
|
-- If the number of windows for which chords are required exceeds maxChordLen, chords
|
||||||
|
-- will simply not be generated for these windows. In this way, single-key selection may be
|
||||||
|
-- preferred over the ability to select any window.
|
||||||
|
--
|
||||||
|
-- @cancelKey@, @xK_BackSpace@ and any duplicates will be removed from @sKeys@ if included.
|
||||||
|
-- See usage for examples of @sKeys@.
|
||||||
|
data EasyMotionConfig =
|
||||||
|
EMConf { txtCol :: String -- ^ Color of the text displayed
|
||||||
|
, bgCol :: String -- ^ Color of the window overlaid
|
||||||
|
, overlayF :: Position -> Rectangle -> Rectangle -- ^ Function to generate overlay rectangle
|
||||||
|
, borderCol :: String -- ^ Color of the overlay window borders
|
||||||
|
, sKeys :: ChordKeys -- ^ Keys to use for window selection
|
||||||
|
, cancelKey :: KeySym -- ^ Key to use to cancel selection
|
||||||
|
, font :: String -- ^ Font for selection characters (passed to initXMF)
|
||||||
|
, borderPx :: Int -- ^ Width of border in pixels
|
||||||
|
, maxChordLen :: Int -- ^ Maximum chord length. Use 0 for no maximum.
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Default EasyMotionConfig where
|
||||||
|
def =
|
||||||
|
EMConf { txtCol = "#ffffff"
|
||||||
|
, bgCol = "#000000"
|
||||||
|
, overlayF = proportional (0.3::Double)
|
||||||
|
, borderCol = "#ffffff"
|
||||||
|
, sKeys = AnyKeys [xK_s, xK_d, xK_f, xK_j, xK_k, xK_l]
|
||||||
|
, cancelKey = xK_q
|
||||||
|
, borderPx = 1
|
||||||
|
, maxChordLen = 0
|
||||||
|
#ifdef XFT
|
||||||
|
, font = "xft:Sans-100"
|
||||||
|
#else
|
||||||
|
, font = "-misc-fixed-*-*-*-*-200-*-*-*-*-*-*-*"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Create overlay windows of the same size as the window they select
|
||||||
|
fullSize :: Position -> Rectangle -> Rectangle
|
||||||
|
fullSize _ = id
|
||||||
|
|
||||||
|
-- | Create overlay windows a proportion of the size of the window they select
|
||||||
|
proportional :: RealFrac f => f -> Position -> Rectangle -> Rectangle
|
||||||
|
proportional f th r = Rectangle { rect_width = newW
|
||||||
|
, rect_height = newH
|
||||||
|
, rect_x = rect_x r + fi (rect_width r - newW) `div` 2
|
||||||
|
, rect_y = rect_y r + fi (rect_height r - newH) `div` 2 }
|
||||||
|
where
|
||||||
|
newH = max (fi th) (round $ f * fi (rect_height r))
|
||||||
|
newW = newH
|
||||||
|
|
||||||
|
-- | Create fixed-size overlay windows
|
||||||
|
fixedSize :: (Integral a, Integral b) => a -> b -> Position -> Rectangle -> Rectangle
|
||||||
|
fixedSize w h th r = Rectangle { rect_width = rw
|
||||||
|
, rect_height = rh
|
||||||
|
, rect_x = rect_x r + fi (rect_width r - rw) `div` 2
|
||||||
|
, rect_y = rect_y r + fi (rect_height r - rh) `div` 2 }
|
||||||
|
where
|
||||||
|
rw = max (fi w) (fi th)
|
||||||
|
rh = max (fi h) (fi th)
|
||||||
|
|
||||||
|
-- | Create overlay windows the minimum size to contain their key chord
|
||||||
|
textSize :: Position -> Rectangle -> Rectangle
|
||||||
|
textSize th r = Rectangle { rect_width = fi th
|
||||||
|
, rect_height = fi th
|
||||||
|
, rect_x = rect_x r + (fi (rect_width r) - fi th) `div` 2
|
||||||
|
, rect_y = rect_y r + (fi (rect_height r) - fi th) `div` 2 }
|
||||||
|
|
||||||
|
-- | Create overlay windows the full width of the window they select, the minimum height to contain
|
||||||
|
-- their chord, and a proportion of the distance from the top of the window they select
|
||||||
|
bar :: RealFrac f => f -> Position -> Rectangle -> Rectangle
|
||||||
|
bar f th r = Rectangle { rect_width = rect_width r
|
||||||
|
, rect_height = fi th
|
||||||
|
, rect_x = rect_x r
|
||||||
|
, rect_y = rect_y r + round (f' * (fi (rect_height r) - fi th)) }
|
||||||
|
-- clamp f in [0,1] as other values will appear to lock up xmonad as the overlay will be
|
||||||
|
-- displayed off-screen
|
||||||
|
where f' = min 0.0 $ max f 1.0
|
||||||
|
|
||||||
|
-- | Handles overlay display and window selection. Called after config has been sanitised.
|
||||||
|
handleSelectWindow :: EasyMotionConfig -> X (Maybe Window)
|
||||||
|
handleSelectWindow EMConf { sKeys = AnyKeys [] } = return Nothing
|
||||||
|
handleSelectWindow c = do
|
||||||
|
f <- initXMF $ font c
|
||||||
|
th <- (\(asc, dsc) -> asc + dsc + 2) <$> (textExtentsXMF f (concatMap keysymToString (allKeys . sKeys $ c)))
|
||||||
|
XConf { theRoot = rw, display = dpy } <- ask
|
||||||
|
XState { mapped = mappedWins, windowset = ws } <- get
|
||||||
|
-- build overlays depending on key configuration
|
||||||
|
overlays :: [Overlay] <- case sKeys c of
|
||||||
|
AnyKeys ks -> buildOverlays ks <$> sortedOverlayWindows
|
||||||
|
where
|
||||||
|
visibleWindows :: [Window]
|
||||||
|
visibleWindows = toList mappedWins
|
||||||
|
sortedOverlayWindows :: X [OverlayWindow]
|
||||||
|
sortedOverlayWindows = sortOverlayWindows <$> buildOverlayWindows dpy th visibleWindows
|
||||||
|
PerScreenKeys m ->
|
||||||
|
fmap concat
|
||||||
|
$ sequence
|
||||||
|
$ M.foldr (:) []
|
||||||
|
$ M.mapWithKey (\sid ks -> buildOverlays ks <$> sortedOverlayWindows sid) m
|
||||||
|
where
|
||||||
|
screenById :: ScreenId -> Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)
|
||||||
|
screenById sid = L.find ((== sid) . screen) (W.current ws : W.visible ws)
|
||||||
|
visibleWindowsOnScreen :: ScreenId -> [Window]
|
||||||
|
visibleWindowsOnScreen sid = L.filter (`elem` (toList mappedWins)) $ W.integrate' $ (screenById sid) >>= W.stack . W.workspace
|
||||||
|
sortedOverlayWindows :: ScreenId -> X [OverlayWindow]
|
||||||
|
sortedOverlayWindows sid = sortOverlayWindows <$> (buildOverlayWindows dpy th $ visibleWindowsOnScreen sid)
|
||||||
|
status <- io $ grabKeyboard dpy rw True grabModeAsync grabModeAsync currentTime
|
||||||
|
if (status == grabSuccess)
|
||||||
|
then do
|
||||||
|
resultWin <- handleKeyboard dpy (displayOverlay f) (cancelKey c) overlays []
|
||||||
|
io $ ungrabKeyboard dpy currentTime
|
||||||
|
mapM_ (deleteWindow . overlay . overlayWin) overlays
|
||||||
|
io $ sync dpy False
|
||||||
|
releaseXMF f
|
||||||
|
case resultWin of
|
||||||
|
-- focus the selected window
|
||||||
|
Selected o -> return . Just . win . overlayWin $ o
|
||||||
|
-- return focus correctly
|
||||||
|
_ -> whenJust (W.peek ws :: Maybe Window) (windows . W.focusWindow) >> return Nothing
|
||||||
|
else releaseXMF f >> return Nothing
|
||||||
|
where
|
||||||
|
allKeys :: ChordKeys -> [KeySym]
|
||||||
|
allKeys (AnyKeys ks) = ks
|
||||||
|
allKeys (PerScreenKeys m) = concat $ M.foldr (:) [] m
|
||||||
|
|
||||||
|
buildOverlays :: [KeySym] -> [OverlayWindow] -> [Overlay]
|
||||||
|
buildOverlays ks = appendChords (maxChordLen c) ks
|
||||||
|
|
||||||
|
buildOverlayWindows :: Display -> Position -> [Window] -> X [OverlayWindow]
|
||||||
|
buildOverlayWindows dpy th ws = sequence $ (buildOverlayWin dpy th) <$> ws
|
||||||
|
|
||||||
|
sortOverlayWindows :: [OverlayWindow] -> [OverlayWindow]
|
||||||
|
sortOverlayWindows = (sortOn ((wa_x &&& wa_y) . attrs))
|
||||||
|
|
||||||
|
makeRect :: WindowAttributes -> Rectangle
|
||||||
|
makeRect wa = Rectangle (fi (wa_x wa)) (fi (wa_y wa)) (fi (wa_width wa)) (fi (wa_height wa))
|
||||||
|
|
||||||
|
buildOverlayWin :: Display -> Position -> Window -> X OverlayWindow
|
||||||
|
buildOverlayWin dpy th w = do
|
||||||
|
wAttrs <- io $ getWindowAttributes dpy w
|
||||||
|
let r = overlayF c th $ makeRect wAttrs
|
||||||
|
o <- createNewWindow r Nothing "" True
|
||||||
|
return OverlayWindow { rect=r, overlay=o, win=w, attrs=wAttrs }
|
||||||
|
|
||||||
|
-- | Display an overlay with the provided formatting
|
||||||
|
displayOverlay :: XMonadFont -> Overlay -> X ()
|
||||||
|
displayOverlay f Overlay { overlayWin = OverlayWindow { rect = r, overlay = o }, chord = ch } = do
|
||||||
|
showWindow o
|
||||||
|
paintAndWrite o f (fi (rect_width r)) (fi (rect_height r)) (fi (borderPx c)) (bgCol c) (borderCol c) (txtCol c) (bgCol c) [AlignCenter] [concatMap keysymToString ch]
|
||||||
|
|
||||||
|
-- | Display overlay windows and chords for window selection
|
||||||
|
selectWindow :: EasyMotionConfig -> X (Maybe Window)
|
||||||
|
selectWindow conf =
|
||||||
|
handleSelectWindow conf { sKeys = sanitiseKeys (sKeys conf) }
|
||||||
|
where
|
||||||
|
-- make sure the key lists don't contain: backspace, or duplicates
|
||||||
|
sanitise :: [KeySym] -> [KeySym]
|
||||||
|
sanitise = L.nub . L.filter (`notElem` [xK_BackSpace, cancelKey conf])
|
||||||
|
sanitiseKeys :: ChordKeys -> ChordKeys
|
||||||
|
sanitiseKeys cKeys =
|
||||||
|
case cKeys of
|
||||||
|
AnyKeys ks -> AnyKeys . sanitise $ ks
|
||||||
|
PerScreenKeys m -> PerScreenKeys $ M.map sanitise m
|
||||||
|
|
||||||
|
-- | Take a list of overlays lacking chords, return a list of overlays with key chords
|
||||||
|
appendChords :: Int -> [KeySym] -> [OverlayWindow] -> [Overlay]
|
||||||
|
appendChords _ [] _ = []
|
||||||
|
appendChords maxUserSelectedLen ks overlayWins =
|
||||||
|
zipWith (\ow c -> Overlay { overlayWin=ow, chord=c }) overlayWins chords
|
||||||
|
where
|
||||||
|
chords = replicateM chordLen ks
|
||||||
|
-- the minimum necessary chord length to assign a unique chord to each visible window
|
||||||
|
minCoverLen = -((-(length overlayWins)) `div` length ks)
|
||||||
|
-- if the user has specified a max chord length we use this even if it will not cover all
|
||||||
|
-- windows, as they may prefer to focus windows with fewer keys over the ability to focus any
|
||||||
|
-- window
|
||||||
|
chordLen = if maxUserSelectedLen <= 0 then minCoverLen else min minCoverLen maxUserSelectedLen
|
||||||
|
|
||||||
|
-- | A three-state result for handling user-initiated selection cancellation, successful selection,
|
||||||
|
-- or backspace.
|
||||||
|
data HandleResult = Exit | Selected Overlay | Backspace
|
||||||
|
-- | Handle key press events for window selection.
|
||||||
|
handleKeyboard :: Display -> (Overlay -> X()) -> KeySym -> [Overlay] -> [Overlay] -> X (HandleResult)
|
||||||
|
handleKeyboard _ _ _ [] _ = return Exit
|
||||||
|
handleKeyboard dpy drawFn cancel selected deselected = do
|
||||||
|
redraw
|
||||||
|
ev <- io $ allocaXEvent $ \e -> do
|
||||||
|
maskEvent dpy (keyPressMask .|. keyReleaseMask .|. buttonPressMask) e
|
||||||
|
getEvent e
|
||||||
|
if | ev_event_type ev == keyPress -> do
|
||||||
|
s <- io $ keycodeToKeysym dpy (ev_keycode ev) 0
|
||||||
|
if | s == cancel -> return Exit
|
||||||
|
| s == xK_BackSpace -> return Backspace
|
||||||
|
| isNextOverlayKey s -> handleNextOverlayKey s
|
||||||
|
| otherwise -> handleKeyboard dpy drawFn cancel selected deselected
|
||||||
|
| ev_event_type ev == buttonPress -> do
|
||||||
|
io $ allowEvents dpy replayPointer currentTime
|
||||||
|
handleKeyboard dpy drawFn cancel selected deselected
|
||||||
|
| otherwise -> handleKeyboard dpy drawFn cancel selected deselected
|
||||||
|
where
|
||||||
|
redraw = mapM (mapM_ drawFn) [selected, deselected]
|
||||||
|
retryBackspace x =
|
||||||
|
case x of
|
||||||
|
Backspace -> redraw >> handleKeyboard dpy drawFn cancel selected deselected
|
||||||
|
_ -> return x
|
||||||
|
isNextOverlayKey keySym = isJust (L.find ((== keySym) . head .chord) selected)
|
||||||
|
handleNextOverlayKey keySym =
|
||||||
|
case fg of
|
||||||
|
[x] -> return $ Selected x
|
||||||
|
_ -> handleKeyboard dpy drawFn cancel (trim fg) (clear bg) >>= retryBackspace
|
||||||
|
where
|
||||||
|
(fg, bg) = L.partition ((== keySym) . head . chord) selected
|
||||||
|
trim = map (\o -> o { chord = tail $ chord o })
|
||||||
|
clear = map (\o -> o { chord = [] })
|
@ -184,6 +184,9 @@ edit your key bindings.
|
|||||||
Provides bindings to add and delete workspaces. Note that you may only
|
Provides bindings to add and delete workspaces. Note that you may only
|
||||||
delete a workspace that is already empty.
|
delete a workspace that is already empty.
|
||||||
|
|
||||||
|
* "XMonad.Actions.EasyMotion":
|
||||||
|
Focus a visible window using a key chord.
|
||||||
|
|
||||||
* "XMonad.Actions.FindEmptyWorkspace":
|
* "XMonad.Actions.FindEmptyWorkspace":
|
||||||
Find an empty workspace.
|
Find an empty workspace.
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ library
|
|||||||
XMonad.Actions.DynamicWorkspaceGroups
|
XMonad.Actions.DynamicWorkspaceGroups
|
||||||
XMonad.Actions.DynamicWorkspaceOrder
|
XMonad.Actions.DynamicWorkspaceOrder
|
||||||
XMonad.Actions.DynamicWorkspaces
|
XMonad.Actions.DynamicWorkspaces
|
||||||
|
XMonad.Actions.EasyMotion
|
||||||
XMonad.Actions.FindEmptyWorkspace
|
XMonad.Actions.FindEmptyWorkspace
|
||||||
XMonad.Actions.FlexibleManipulate
|
XMonad.Actions.FlexibleManipulate
|
||||||
XMonad.Actions.FlexibleResize
|
XMonad.Actions.FlexibleResize
|
||||||
|
Loading…
x
Reference in New Issue
Block a user