mirror of
https://github.com/Horhik/dotfiles.git
synced 2024-11-28 19:11:31 +00:00
update xmonad
This commit is contained in:
parent
07d4edc7de
commit
f5cc2af653
|
@ -113,7 +113,7 @@ echo "<fc=$greenDarkerColor>—{$toggl_description(</fc>"$duration"<fc=$greenD
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo "$(toggl_timer)$(wifi)$(battery)$(volume)$(layout)$(diskspace)$(timeanddate)<fc=$greenDarkerColor>—{</fc>$(calendar)<fc=$greenDarkerColor>}——</fc>"
|
echo "$(wifi)$(battery)$(volume)$(layout)$(diskspace)$(timeanddate)<fc=$greenDarkerColor>—{</fc>$(calendar)<fc=$greenDarkerColor>}——</fc>"
|
||||||
|
|
||||||
#echo " "
|
#echo " "
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,6 @@ echo "<fc=$greenDarkerColor>—{$toggl_description(</fc>"$duration"<fc=$greenD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
echo "$(toggl_timer)$(wifi)$(battery)$(volume)$(layout)$(diskspace)$(timeanddate)<fc=$greenDarkerColor>—{</fc>$(calendar)<fc=$greenDarkerColor>}——</fc>"
|
echo "$(wifi)$(battery)$(volume)$(layout)$(diskspace)$(timeanddate)<fc=$greenDarkerColor>—{</fc>$(calendar)<fc=$greenDarkerColor>}——</fc>"
|
||||||
|
|
||||||
#echo " "
|
#echo " "
|
||||||
|
|
|
@ -17,7 +17,7 @@ Config {
|
||||||
, sepChar = "%" -- delineator between plugin names and straight text
|
, sepChar = "%" -- delineator between plugin names and straight text
|
||||||
, alignSep = "][" -- separator between left-right alignment
|
, alignSep = "][" -- separator between left-right alignment
|
||||||
|
|
||||||
, template = "<fc=#83a598>——{</fc>%StdinReader%<fc=#83a598>}—————————————————————————————————————————————————————————————————————————————————————————————————————————————</fc>][%/home/horhik/.config/xmobar/bin/xmobarstatus%"
|
, template = "<fc=#83a598>——{</fc>%StdinReader%<fc=#83a598>}—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————</fc>][%/home/horhik/.config/xmobar/bin/xmobarstatus%"
|
||||||
|
|
||||||
-- general behavior
|
-- general behavior
|
||||||
, lowerOnStart = False -- send to bottom of window stack on start
|
, lowerOnStart = False -- send to bottom of window stack on start
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
-- layout
|
-- layout
|
||||||
, sepChar = "%" -- delineator between plugin names and straight text
|
, sepChar = "%" -- delineator between plugin names and straight text
|
||||||
, alignSep = "][" -- separator between left-right alignment
|
, alignSep = "][" -- separator between left-right alignment
|
||||||
, template = "<fc=#a89984>--{</fc>%StdinReader%<fc=#a89984>}—————————————————————————————————————————————————————————————————————————————————————————————————————————————————</fc>][%/home/horhik/.config/xmobar/bin/xmobarstatus2%"
|
, template = "<fc=#a89984>--{</fc>%StdinReader%<fc=#a89984>}—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————</fc>][%/home/horhik/.config/xmobar/bin/xmobarstatus2%"
|
||||||
|
|
||||||
-- general behavior
|
-- general behavior
|
||||||
, lowerOnStart = False -- send to bottom of window stack on start
|
, lowerOnStart = False -- send to bottom of window stack on start
|
||||||
|
|
972
home/xmonad/.xmonad/lib/GridSelect/Extras.hs
Normal file
972
home/xmonad/.xmonad/lib/GridSelect/Extras.hs
Normal file
|
@ -0,0 +1,972 @@
|
||||||
|
{-# LANGUAGE ScopedTypeVariables, TupleSections, GeneralizedNewtypeDeriving, TypeSynonymInstances, FlexibleInstances #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : GridSelect.Extras
|
||||||
|
-- Copyright : Clemens Fruhwirth <clemens@endorphin.org>, Max Magorsch <max@magorsch.de>
|
||||||
|
-- License : BSD-style (see LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : Max Magorsch <max@magorsch.de>
|
||||||
|
-- Stability : unstable
|
||||||
|
-- Portability : unportable
|
||||||
|
--
|
||||||
|
-- GridSelect.Extras adds a method to [XMonad.Actions.GridSelect]
|
||||||
|
-- (http://hackage.haskell.org/package/xmonad-contrib-0.15/docs/XMonad-Actions-GridSelect.html)
|
||||||
|
-- that displays a message at the top of the screen while using the normal GridSelect.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module GridSelect.Extras
|
||||||
|
(
|
||||||
|
-- * Usage
|
||||||
|
-- $usage
|
||||||
|
runSelectedActionWithMessageAndIcon
|
||||||
|
,
|
||||||
|
|
||||||
|
-- ** Screenshots
|
||||||
|
-- $screenshots
|
||||||
|
|
||||||
|
-- ** Customizing
|
||||||
|
-- $customizing
|
||||||
|
|
||||||
|
-- * Configuration
|
||||||
|
GSConfig(..)
|
||||||
|
, def
|
||||||
|
, buildDefaultGSConfig
|
||||||
|
, defaultNavigation
|
||||||
|
)
|
||||||
|
where
|
||||||
|
import Data.Maybe
|
||||||
|
import Data.Bits
|
||||||
|
import Data.Char
|
||||||
|
import qualified Data.Foldable
|
||||||
|
import Data.Ord ( comparing )
|
||||||
|
import Control.Applicative
|
||||||
|
import Control.Monad.State
|
||||||
|
import Control.Arrow
|
||||||
|
import Data.List as L
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import XMonad hiding ( liftX )
|
||||||
|
import XMonad.Util.Font
|
||||||
|
import XMonad.Prompt ( mkUnmanagedWindow )
|
||||||
|
import XMonad.StackSet as W
|
||||||
|
import XMonad.Layout.Decoration
|
||||||
|
import XMonad.Util.Image
|
||||||
|
import XMonad.Util.NamedWindows
|
||||||
|
import XMonad.Util.XUtils
|
||||||
|
import XMonad.Actions.WindowBringer ( bringWindow )
|
||||||
|
import Text.Printf
|
||||||
|
import System.Random ( mkStdGen
|
||||||
|
, genRange
|
||||||
|
, next
|
||||||
|
)
|
||||||
|
import Data.Word ( Word8 )
|
||||||
|
|
||||||
|
-- $usage
|
||||||
|
--
|
||||||
|
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
|
||||||
|
--
|
||||||
|
-- > import qualified GridSelect.Extras
|
||||||
|
--
|
||||||
|
-- Then add a keybinding, e.g.
|
||||||
|
--
|
||||||
|
-- > , ((modm, xK_g), gridselect gsconfig message actions)
|
||||||
|
-- > where
|
||||||
|
-- > gridselect = GridSelect.Extras.runSelectedActionWithMessage
|
||||||
|
-- > gsconfig = GridSelect.Extras.def
|
||||||
|
-- > message = "Please choose an option:"
|
||||||
|
-- > actions = [ ("Option #1", unsafeSpawn "notify-send 'Option #1'"),
|
||||||
|
-- > ("Option #2", unsafeSpawn "notify-send 'Option #2'") ]
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- This module also supports displaying an icon in the message window. To do so, just use 'runSelectedActionWithMessageAndIcon' like so:
|
||||||
|
--
|
||||||
|
-- > , ((modm, xK_g), gridselect gsconfig message icon actions)
|
||||||
|
-- > where
|
||||||
|
-- > gridselect = GridSelect.Extras.runSelectedActionWithMessageAndIcon
|
||||||
|
-- > icon = [[True, False, False],
|
||||||
|
-- > [False, True, False],
|
||||||
|
-- > [True, False, True ]]
|
||||||
|
-- > -- ...
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- $screenshots
|
||||||
|
--
|
||||||
|
-- Selecting an action:
|
||||||
|
--
|
||||||
|
-- <<https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/gridselect-extras.png>>
|
||||||
|
--
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- $customizing
|
||||||
|
--
|
||||||
|
-- It is possible to customize GridSelect.Extras the same way GridSelect is customized. Please refer to the [GridSelect Documentation](http://hackage.haskell.org/package/xmonad-contrib-0.15/docs/XMonad-Actions-GridSelect.html) for further information.
|
||||||
|
|
||||||
|
-- | The 'Default' instance gives a basic configuration for 'gridselect', with
|
||||||
|
-- the colorizer chosen based on the type.
|
||||||
|
--
|
||||||
|
-- If you want to replace the 'gs_colorizer' field, use 'buildDefaultGSConfig'
|
||||||
|
-- instead of 'def' to avoid ambiguous type variables.
|
||||||
|
data GSConfig a = GSConfig {
|
||||||
|
gs_cellheight :: Integer,
|
||||||
|
gs_cellwidth :: Integer,
|
||||||
|
gs_cellpadding :: Integer,
|
||||||
|
gs_colorizer :: a -> Bool -> X (String, String),
|
||||||
|
gs_font :: String,
|
||||||
|
gs_navigate :: TwoD a (Maybe a),
|
||||||
|
gs_rearranger :: Rearranger a,
|
||||||
|
gs_originFractX :: Double,
|
||||||
|
gs_originFractY :: Double,
|
||||||
|
gs_bordercolor :: String
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | That is 'fromClassName' if you are selecting a 'Window', or
|
||||||
|
-- 'defaultColorizer' if you are selecting a 'String'. The catch-all instance
|
||||||
|
-- @HasColorizer a@ uses the 'focusedBorderColor' and 'normalBorderColor'
|
||||||
|
-- colors.
|
||||||
|
class HasColorizer a where
|
||||||
|
defaultColorizer :: a -> Bool -> X (String, String)
|
||||||
|
|
||||||
|
instance {-# OVERLAPPING #-} HasColorizer Window where
|
||||||
|
defaultColorizer = fromClassName
|
||||||
|
|
||||||
|
instance {-# OVERLAPPING #-} HasColorizer String where
|
||||||
|
defaultColorizer = stringColorizer
|
||||||
|
|
||||||
|
instance {-# OVERLAPPING #-} HasColorizer a where
|
||||||
|
defaultColorizer _ isFg =
|
||||||
|
let getColor = if isFg then focusedBorderColor else normalBorderColor
|
||||||
|
in asks $ (, "black") . getColor . config
|
||||||
|
|
||||||
|
instance {-# OVERLAPPING #-} HasColorizer a => Default (GSConfig a) where
|
||||||
|
def = buildDefaultGSConfig defaultColorizer
|
||||||
|
|
||||||
|
{-# DEPRECATED defaultGSConfig "Use def (from Data.Default, and re-exported from XMonad.Actions.GridSelect) instead." #-}
|
||||||
|
defaultGSConfig :: HasColorizer a => GSConfig a
|
||||||
|
defaultGSConfig = def
|
||||||
|
|
||||||
|
type TwoDPosition = (Integer, Integer)
|
||||||
|
|
||||||
|
type TwoDElementMap a = [(TwoDPosition,(String,a))]
|
||||||
|
|
||||||
|
data TwoDState a = TwoDState { td_curpos :: TwoDPosition
|
||||||
|
, td_availSlots :: [TwoDPosition]
|
||||||
|
, td_elements :: [(String,a)]
|
||||||
|
, td_gsconfig :: GSConfig a
|
||||||
|
, td_font :: XMonadFont
|
||||||
|
, td_paneX :: Integer
|
||||||
|
, td_paneY :: Integer
|
||||||
|
, td_drawingWin :: Window
|
||||||
|
, td_searchString :: String
|
||||||
|
, td_elementmap :: TwoDElementMap a
|
||||||
|
}
|
||||||
|
|
||||||
|
generateElementmap :: TwoDState a -> X (TwoDElementMap a)
|
||||||
|
generateElementmap s = do
|
||||||
|
rearrangedElements <- rearranger searchString sortedElements
|
||||||
|
return $ zip positions rearrangedElements
|
||||||
|
where
|
||||||
|
TwoDState { td_availSlots = positions, td_gsconfig = gsconfig, td_searchString = searchString }
|
||||||
|
= s
|
||||||
|
GSConfig { gs_rearranger = rearranger } = gsconfig
|
||||||
|
-- Filter out any elements that don't contain the searchString (case insensitive)
|
||||||
|
filteredElements =
|
||||||
|
L.filter ((searchString `isInfixOfI`) . fst) (td_elements s)
|
||||||
|
-- Sorts the elementmap
|
||||||
|
sortedElements = orderElementmap searchString filteredElements
|
||||||
|
-- Case Insensitive version of isInfixOf
|
||||||
|
needle `isInfixOfI` haystack = upper needle `isInfixOf` upper haystack
|
||||||
|
upper = map toUpper
|
||||||
|
|
||||||
|
|
||||||
|
-- | We enforce an ordering such that we will always get the same result. If the
|
||||||
|
-- elements position changes from call to call of gridselect, then the shown
|
||||||
|
-- positions will also change when you search for the same string. This is
|
||||||
|
-- especially the case when using gridselect for showing and switching between
|
||||||
|
-- workspaces, as workspaces are usually shown in order of last visited. The
|
||||||
|
-- chosen ordering is "how deep in the haystack the needle is" (number of
|
||||||
|
-- characters from the beginning of the string and the needle).
|
||||||
|
orderElementmap :: String -> [(String, a)] -> [(String, a)]
|
||||||
|
orderElementmap searchString elements = if not $ null searchString
|
||||||
|
then sortedElements
|
||||||
|
else elements
|
||||||
|
where
|
||||||
|
upper = map toUpper
|
||||||
|
-- Calculates a (score, element) tuple where the score is the depth of the (case insensitive) needle.
|
||||||
|
calcScore element =
|
||||||
|
( length $ takeWhile (not . isPrefixOf (upper searchString))
|
||||||
|
(tails . upper . fst $ element)
|
||||||
|
, element
|
||||||
|
)
|
||||||
|
-- Use the score and then the string as the parameters for comparing, making
|
||||||
|
-- it consistent even when two strings that score the same, as it will then be
|
||||||
|
-- sorted by the strings, making it consistent.
|
||||||
|
compareScore = comparing (\(score, (str, _)) -> (score, str))
|
||||||
|
sortedElements = map snd . sortBy compareScore $ map calcScore elements
|
||||||
|
|
||||||
|
|
||||||
|
newtype TwoD a b = TwoD { unTwoD :: StateT (TwoDState a) X b }
|
||||||
|
deriving (Monad,Functor,MonadState (TwoDState a))
|
||||||
|
|
||||||
|
instance Applicative (TwoD a) where
|
||||||
|
(<*>) = ap
|
||||||
|
pure = return
|
||||||
|
|
||||||
|
liftX :: X a1 -> TwoD a a1
|
||||||
|
liftX = TwoD . lift
|
||||||
|
|
||||||
|
evalTwoD :: TwoD a1 a -> TwoDState a1 -> X a
|
||||||
|
evalTwoD m s = flip evalStateT s $ unTwoD m
|
||||||
|
|
||||||
|
diamondLayer :: (Enum a, Num a, Eq a) => a -> [(a, a)]
|
||||||
|
diamondLayer 0 = [(0, 0)]
|
||||||
|
diamondLayer n =
|
||||||
|
-- tr = top right
|
||||||
|
-- r = ur ++ 90 degree clock-wise rotation of ur
|
||||||
|
let tr = [ (x, n - x) | x <- [0 .. n - 1] ]
|
||||||
|
r = tr ++ map (\(x, y) -> (y, -x)) tr
|
||||||
|
in r ++ map (negate *** negate) r
|
||||||
|
|
||||||
|
diamond :: (Enum a, Num a, Eq a) => [(a, a)]
|
||||||
|
diamond = concatMap diamondLayer [0 ..]
|
||||||
|
|
||||||
|
diamondRestrict
|
||||||
|
:: Integer -> Integer -> Integer -> Integer -> [(Integer, Integer)]
|
||||||
|
diamondRestrict x y originX originY =
|
||||||
|
L.filter (\(x', y') -> abs x' <= x && abs y' <= y)
|
||||||
|
. map (\(x', y') -> (x' + fromInteger originX, y' + fromInteger originY))
|
||||||
|
. take 1000
|
||||||
|
$ diamond
|
||||||
|
|
||||||
|
findInElementMap :: (Eq a) => a -> [(a, b)] -> Maybe (a, b)
|
||||||
|
findInElementMap pos = find ((== pos) . fst)
|
||||||
|
|
||||||
|
drawWinBox
|
||||||
|
:: Window
|
||||||
|
-> XMonadFont
|
||||||
|
-> (String, String)
|
||||||
|
-> String
|
||||||
|
-> Integer
|
||||||
|
-> Integer
|
||||||
|
-> String
|
||||||
|
-> Integer
|
||||||
|
-> Integer
|
||||||
|
-> Integer
|
||||||
|
-> X ()
|
||||||
|
drawWinBox win font (fg, bg) bc ch cw text x y cp = withDisplay $ \dpy -> do
|
||||||
|
gc <- liftIO $ createGC dpy win
|
||||||
|
bordergc <- liftIO $ createGC dpy win
|
||||||
|
liftIO $ do
|
||||||
|
Just fgcolor <- initColor dpy fg
|
||||||
|
Just bgcolor <- initColor dpy bg
|
||||||
|
Just bordercolor <- initColor dpy bc
|
||||||
|
setForeground dpy gc fgcolor
|
||||||
|
setBackground dpy gc bgcolor
|
||||||
|
setForeground dpy bordergc bordercolor
|
||||||
|
fillRectangle dpy
|
||||||
|
win
|
||||||
|
gc
|
||||||
|
(fromInteger x)
|
||||||
|
(fromInteger y)
|
||||||
|
(fromInteger cw)
|
||||||
|
(fromInteger ch)
|
||||||
|
drawRectangle dpy
|
||||||
|
win
|
||||||
|
bordergc
|
||||||
|
(fromInteger x)
|
||||||
|
(fromInteger y)
|
||||||
|
(fromInteger cw)
|
||||||
|
(fromInteger ch)
|
||||||
|
stext <- shrinkWhile
|
||||||
|
(shrinkIt shrinkText)
|
||||||
|
(\n -> do
|
||||||
|
size <- liftIO $ textWidthXMF dpy font n
|
||||||
|
return $ size > fromInteger (cw - (2 * cp))
|
||||||
|
)
|
||||||
|
text
|
||||||
|
-- calculate the offset to vertically centre the text based on the ascender and descender
|
||||||
|
(asc, desc) <- liftIO $ textExtentsXMF font stext
|
||||||
|
let offset = ((ch - fromIntegral (asc + desc)) `div` 2) + fromIntegral asc
|
||||||
|
printStringXMF dpy
|
||||||
|
win
|
||||||
|
font
|
||||||
|
gc
|
||||||
|
bg
|
||||||
|
fg
|
||||||
|
(fromInteger (x + cp))
|
||||||
|
(fromInteger (y + offset))
|
||||||
|
stext
|
||||||
|
liftIO $ freeGC dpy gc
|
||||||
|
liftIO $ freeGC dpy bordergc
|
||||||
|
|
||||||
|
updateAllElements :: TwoD a ()
|
||||||
|
updateAllElements = do
|
||||||
|
s <- get
|
||||||
|
updateElements (td_elementmap s)
|
||||||
|
|
||||||
|
grayoutElements :: Int -> TwoD a ()
|
||||||
|
grayoutElements skip = do
|
||||||
|
s <- get
|
||||||
|
updateElementsWithColorizer grayOnly $ drop skip (td_elementmap s)
|
||||||
|
where grayOnly _ _ = return ("#808080", "#808080")
|
||||||
|
|
||||||
|
updateElements :: TwoDElementMap a -> TwoD a ()
|
||||||
|
updateElements elementmap = do
|
||||||
|
s <- get
|
||||||
|
updateElementsWithColorizer (gs_colorizer (td_gsconfig s)) elementmap
|
||||||
|
|
||||||
|
updateElementsWithColorizer
|
||||||
|
:: (a -> Bool -> X (String, String)) -> TwoDElementMap a -> TwoD a ()
|
||||||
|
updateElementsWithColorizer colorizer elementmap = do
|
||||||
|
TwoDState { td_curpos = curpos, td_drawingWin = win, td_gsconfig = gsconfig, td_font = font, td_paneX = paneX, td_paneY = paneY } <-
|
||||||
|
get
|
||||||
|
let cellwidth = gs_cellwidth gsconfig
|
||||||
|
cellheight = gs_cellheight gsconfig
|
||||||
|
paneX' = div (paneX - cellwidth) 2
|
||||||
|
paneY' = div (paneY - cellheight) 2
|
||||||
|
updateElement (pos@(x, y), (text, element)) = liftX $ do
|
||||||
|
colors <- colorizer element (pos == curpos)
|
||||||
|
drawWinBox win
|
||||||
|
font
|
||||||
|
colors
|
||||||
|
(gs_bordercolor gsconfig)
|
||||||
|
cellheight
|
||||||
|
cellwidth
|
||||||
|
text
|
||||||
|
(paneX' + x * cellwidth)
|
||||||
|
(paneY' + y * cellheight)
|
||||||
|
(gs_cellpadding gsconfig)
|
||||||
|
mapM_ updateElement elementmap
|
||||||
|
|
||||||
|
stdHandle :: Event -> TwoD a (Maybe a) -> TwoD a (Maybe a)
|
||||||
|
stdHandle ButtonEvent { ev_event_type = t, ev_x = x, ev_y = y } contEventloop
|
||||||
|
| t == buttonRelease = do
|
||||||
|
s@TwoDState { td_paneX = px, td_paneY = py, td_gsconfig = (GSConfig ch cw _ _ _ _ _ _ _ _) } <-
|
||||||
|
get
|
||||||
|
let gridX = (fi x - (px - cw) `div` 2) `div` cw
|
||||||
|
gridY = (fi y - (py - ch) `div` 2) `div` ch
|
||||||
|
case lookup (gridX, gridY) (td_elementmap s) of
|
||||||
|
Just (_, el) -> return (Just el)
|
||||||
|
Nothing -> contEventloop
|
||||||
|
| otherwise = contEventloop
|
||||||
|
|
||||||
|
stdHandle ExposeEvent{} contEventloop = updateAllElements2 >> contEventloop
|
||||||
|
|
||||||
|
stdHandle _ contEventloop = contEventloop
|
||||||
|
|
||||||
|
-- | Embeds a key handler into the X event handler that dispatches key
|
||||||
|
-- events to the key handler, while non-key event go to the standard
|
||||||
|
-- handler.
|
||||||
|
makeXEventhandler
|
||||||
|
:: ((KeySym, String, KeyMask) -> TwoD a (Maybe a)) -> TwoD a (Maybe a)
|
||||||
|
makeXEventhandler keyhandler =
|
||||||
|
fix
|
||||||
|
$ \me -> join $ liftX $ withDisplay $ \d -> liftIO $ allocaXEvent $ \e -> do
|
||||||
|
maskEvent d (exposureMask .|. keyPressMask .|. buttonReleaseMask) e
|
||||||
|
ev <- getEvent e
|
||||||
|
if ev_event_type ev == keyPress
|
||||||
|
then do
|
||||||
|
(ks, s) <- lookupString $ asKeyEvent e
|
||||||
|
return $ do
|
||||||
|
mask <- liftX $ cleanMask (ev_state ev)
|
||||||
|
keyhandler (fromMaybe xK_VoidSymbol ks, s, mask)
|
||||||
|
else return $ stdHandle ev me
|
||||||
|
|
||||||
|
-- | When the map contains (KeySym,KeyMask) tuple for the given event,
|
||||||
|
-- the associated action in the map associated shadows the default key
|
||||||
|
-- handler
|
||||||
|
shadowWithKeymap
|
||||||
|
:: M.Map (KeyMask, KeySym) a
|
||||||
|
-> ((KeySym, String, KeyMask) -> a)
|
||||||
|
-> (KeySym, String, KeyMask)
|
||||||
|
-> a
|
||||||
|
shadowWithKeymap keymap dflt keyEvent@(ks, _, m') =
|
||||||
|
fromMaybe (dflt keyEvent) (M.lookup (m', ks) keymap)
|
||||||
|
|
||||||
|
-- Helper functions to use for key handler functions
|
||||||
|
|
||||||
|
-- | Closes gridselect returning the element under the cursor
|
||||||
|
select :: TwoD a (Maybe a)
|
||||||
|
select = do
|
||||||
|
s <- get
|
||||||
|
return $ snd . snd <$> findInElementMap (td_curpos s) (td_elementmap s)
|
||||||
|
|
||||||
|
-- | Closes gridselect returning no element.
|
||||||
|
cancel :: TwoD a (Maybe a)
|
||||||
|
cancel = return Nothing
|
||||||
|
|
||||||
|
-- | Sets the absolute position of the cursor.
|
||||||
|
setPos :: (Integer, Integer) -> TwoD a ()
|
||||||
|
setPos newPos = do
|
||||||
|
s <- get
|
||||||
|
let elmap = td_elementmap s
|
||||||
|
newSelectedEl = findInElementMap newPos (td_elementmap s)
|
||||||
|
oldPos = td_curpos s
|
||||||
|
when (isJust newSelectedEl && newPos /= oldPos) $ do
|
||||||
|
put s { td_curpos = newPos }
|
||||||
|
updateElements2 (catMaybes [findInElementMap oldPos elmap, newSelectedEl])
|
||||||
|
|
||||||
|
-- | Moves the cursor by the offsets specified
|
||||||
|
move :: (Integer, Integer) -> TwoD a ()
|
||||||
|
move (dx, dy) = do
|
||||||
|
s <- get
|
||||||
|
let (x, y) = td_curpos s
|
||||||
|
newPos = (x + dx, y + dy)
|
||||||
|
setPos newPos
|
||||||
|
|
||||||
|
moveNext :: TwoD a ()
|
||||||
|
moveNext = do
|
||||||
|
position <- gets td_curpos
|
||||||
|
elems <- gets td_elementmap
|
||||||
|
let n = length elems
|
||||||
|
m = case findIndex (\p -> fst p == position) elems of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just k | k == n - 1 -> Just 0
|
||||||
|
| otherwise -> Just (k + 1)
|
||||||
|
whenJust m $ \i -> setPos (fst $ elems !! i)
|
||||||
|
|
||||||
|
movePrev :: TwoD a ()
|
||||||
|
movePrev = do
|
||||||
|
position <- gets td_curpos
|
||||||
|
elems <- gets td_elementmap
|
||||||
|
let n = length elems
|
||||||
|
m = case findIndex (\p -> fst p == position) elems of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just 0 -> Just (n - 1)
|
||||||
|
Just k -> Just (k - 1)
|
||||||
|
whenJust m $ \i -> setPos (fst $ elems !! i)
|
||||||
|
|
||||||
|
-- | Apply a transformation function the current search string
|
||||||
|
transformSearchString :: (String -> String) -> TwoD a ()
|
||||||
|
transformSearchString f = do
|
||||||
|
s <- get
|
||||||
|
let oldSearchString = td_searchString s
|
||||||
|
newSearchString = f oldSearchString
|
||||||
|
when (newSearchString /= oldSearchString) $ do
|
||||||
|
-- FIXME curpos might end up outside new bounds
|
||||||
|
let s' = s { td_searchString = newSearchString }
|
||||||
|
m <- liftX $ generateElementmap s'
|
||||||
|
let s'' = s' { td_elementmap = m }
|
||||||
|
oldLen = length $ td_elementmap s
|
||||||
|
newLen = length $ td_elementmap s''
|
||||||
|
-- All the elements in the previous element map should be
|
||||||
|
-- grayed out, except for those which will be covered by
|
||||||
|
-- elements in the new element map.
|
||||||
|
when (newLen < oldLen) $ grayoutElements newLen
|
||||||
|
put s''
|
||||||
|
updateAllElements
|
||||||
|
|
||||||
|
-- | By default gridselect used the defaultNavigation action, which
|
||||||
|
-- binds left,right,up,down and vi-style h,l,j,k navigation. Return
|
||||||
|
-- quits gridselect, returning the selected element, while Escape
|
||||||
|
-- cancels the selection. Slash enters the substring search mode. In
|
||||||
|
-- substring search mode, every string-associated keystroke is
|
||||||
|
-- added to a search string, which narrows down the object
|
||||||
|
-- selection. Substring search mode comes back to regular navigation
|
||||||
|
-- via Return, while Escape cancels the search. If you want that
|
||||||
|
-- navigation style, add 'defaultNavigation' as 'gs_navigate' to your
|
||||||
|
-- 'GSConfig' object. This is done by 'buildDefaultGSConfig' automatically.
|
||||||
|
defaultNavigation :: TwoD a (Maybe a)
|
||||||
|
defaultNavigation = makeXEventhandler
|
||||||
|
$ shadowWithKeymap navKeyMap navDefaultHandler
|
||||||
|
where
|
||||||
|
navKeyMap = M.fromList
|
||||||
|
[ ((0, xK_Escape) , cancel)
|
||||||
|
, ((0, xK_Return) , select)
|
||||||
|
, ((0, xK_slash) , substringSearch defaultNavigation)
|
||||||
|
, ((0, xK_Left) , move (-1, 0) >> defaultNavigation)
|
||||||
|
, ((0, xK_h) , move (-1, 0) >> defaultNavigation)
|
||||||
|
, ((0, xK_Right) , move (1, 0) >> defaultNavigation)
|
||||||
|
, ((0, xK_l) , move (1, 0) >> defaultNavigation)
|
||||||
|
, ((0, xK_Down) , move (0, 1) >> defaultNavigation)
|
||||||
|
, ((0, xK_j) , move (0, 1) >> defaultNavigation)
|
||||||
|
, ((0, xK_Up) , move (0, -1) >> defaultNavigation)
|
||||||
|
, ((0, xK_k) , move (0, -1) >> defaultNavigation)
|
||||||
|
, ((0, xK_Tab) , moveNext >> defaultNavigation)
|
||||||
|
, ((0, xK_n) , moveNext >> defaultNavigation)
|
||||||
|
, ((shiftMask, xK_Tab), movePrev >> defaultNavigation)
|
||||||
|
, ((0, xK_p) , movePrev >> defaultNavigation)
|
||||||
|
]
|
||||||
|
-- The navigation handler ignores unknown key symbols, therefore we const
|
||||||
|
navDefaultHandler = const defaultNavigation
|
||||||
|
|
||||||
|
-- | This navigation style combines navigation and search into one mode at the cost of losing vi style
|
||||||
|
-- navigation. With this style, there is no substring search submode,
|
||||||
|
-- but every typed character is added to the substring search.
|
||||||
|
navNSearch :: TwoD a (Maybe a)
|
||||||
|
navNSearch = makeXEventhandler
|
||||||
|
$ shadowWithKeymap navNSearchKeyMap navNSearchDefaultHandler
|
||||||
|
where
|
||||||
|
navNSearchKeyMap = M.fromList
|
||||||
|
[ ((0, xK_Escape) , cancel)
|
||||||
|
, ((0, xK_Return) , select)
|
||||||
|
, ((0, xK_Left) , move (-1, 0) >> navNSearch)
|
||||||
|
, ((0, xK_Right) , move (1, 0) >> navNSearch)
|
||||||
|
, ((0, xK_Down) , move (0, 1) >> navNSearch)
|
||||||
|
, ((0, xK_Up) , move (0, -1) >> navNSearch)
|
||||||
|
, ((0, xK_Tab) , moveNext >> navNSearch)
|
||||||
|
, ((shiftMask, xK_Tab), movePrev >> navNSearch)
|
||||||
|
, ( (0, xK_BackSpace)
|
||||||
|
, transformSearchString (\s -> if s == "" then "" else init s)
|
||||||
|
>> navNSearch
|
||||||
|
)
|
||||||
|
]
|
||||||
|
-- The navigation handler ignores unknown key symbols, therefore we const
|
||||||
|
navNSearchDefaultHandler (_, s, _) = do
|
||||||
|
transformSearchString (++ s)
|
||||||
|
navNSearch
|
||||||
|
|
||||||
|
-- | Navigation submode used for substring search. It returns to the
|
||||||
|
-- first argument navigation style when the user hits Return.
|
||||||
|
substringSearch :: TwoD a (Maybe a) -> TwoD a (Maybe a)
|
||||||
|
substringSearch returnNavigation = fix $ \me ->
|
||||||
|
let searchKeyMap = M.fromList
|
||||||
|
[ ((0, xK_Escape), transformSearchString (const "") >> returnNavigation)
|
||||||
|
, ((0, xK_Return), returnNavigation)
|
||||||
|
, ( (0, xK_BackSpace)
|
||||||
|
, transformSearchString (\s -> if s == "" then "" else init s) >> me
|
||||||
|
)
|
||||||
|
]
|
||||||
|
searchDefaultHandler (_, s, _) = do
|
||||||
|
transformSearchString (++ s)
|
||||||
|
me
|
||||||
|
in makeXEventhandler $ shadowWithKeymap searchKeyMap searchDefaultHandler
|
||||||
|
|
||||||
|
|
||||||
|
-- FIXME probably move that into Utils?
|
||||||
|
-- Conversion scheme as in http://en.wikipedia.org/wiki/HSV_color_space
|
||||||
|
hsv2rgb :: Fractional a => (Integer, a, a) -> (a, a, a)
|
||||||
|
hsv2rgb (h, s, v) =
|
||||||
|
let hi = div h 60 `mod` 6 :: Integer
|
||||||
|
f = ((fromInteger h / 60) - fromInteger hi) :: Fractional a => a
|
||||||
|
q = v * (1 - f)
|
||||||
|
p = v * (1 - s)
|
||||||
|
t = v * (1 - (1 - f) * s)
|
||||||
|
in case hi of
|
||||||
|
0 -> (v, t, p)
|
||||||
|
1 -> (q, v, p)
|
||||||
|
2 -> (p, v, t)
|
||||||
|
3 -> (p, q, v)
|
||||||
|
4 -> (t, p, v)
|
||||||
|
5 -> (v, p, q)
|
||||||
|
_ -> error "The world is ending. x mod a >= a."
|
||||||
|
|
||||||
|
-- | Default colorizer for Strings
|
||||||
|
stringColorizer :: String -> Bool -> X (String, String)
|
||||||
|
stringColorizer s active =
|
||||||
|
let seed x = toInteger (sum $ map ((* x) . fromEnum) s) :: Integer
|
||||||
|
(r, g, b) = hsv2rgb
|
||||||
|
( seed 83 `mod` 360
|
||||||
|
, fromInteger (seed 191 `mod` 1000) / 2500 + 0.4
|
||||||
|
, fromInteger (seed 121 `mod` 1000) / 2500 + 0.4
|
||||||
|
)
|
||||||
|
in if active
|
||||||
|
then return ("#faff69", "black")
|
||||||
|
else return
|
||||||
|
( "#" ++ concatMap
|
||||||
|
(twodigitHex . (round :: Double -> Word8) . (* 256))
|
||||||
|
[r, g, b]
|
||||||
|
, "white"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- | Colorize a window depending on it's className.
|
||||||
|
fromClassName :: Window -> Bool -> X (String, String)
|
||||||
|
fromClassName w active = runQuery className w >>= flip defaultColorizer active
|
||||||
|
|
||||||
|
twodigitHex :: Word8 -> String
|
||||||
|
twodigitHex = printf "%02x"
|
||||||
|
|
||||||
|
-- | A colorizer that picks a color inside a range,
|
||||||
|
-- and depending on the window's class.
|
||||||
|
colorRangeFromClassName
|
||||||
|
:: (Word8, Word8, Word8) -- ^ Beginning of the color range
|
||||||
|
-> (Word8, Word8, Word8) -- ^ End of the color range
|
||||||
|
-> (Word8, Word8, Word8) -- ^ Background of the active window
|
||||||
|
-> (Word8, Word8, Word8) -- ^ Inactive text color
|
||||||
|
-> (Word8, Word8, Word8) -- ^ Active text color
|
||||||
|
-> Window
|
||||||
|
-> Bool
|
||||||
|
-> X (String, String)
|
||||||
|
colorRangeFromClassName startC endC activeC inactiveT activeT w active = do
|
||||||
|
classname <- runQuery className w
|
||||||
|
if active
|
||||||
|
then return (rgbToHex activeC, rgbToHex activeT)
|
||||||
|
else return
|
||||||
|
(rgbToHex $ mix startC endC $ stringToRatio classname, rgbToHex inactiveT)
|
||||||
|
where
|
||||||
|
rgbToHex :: (Word8, Word8, Word8) -> String
|
||||||
|
rgbToHex (r, g, b) = '#' : twodigitHex r ++ twodigitHex g ++ twodigitHex b
|
||||||
|
|
||||||
|
-- | Creates a mix of two colors according to a ratio
|
||||||
|
-- (1 -> first color, 0 -> second color).
|
||||||
|
mix
|
||||||
|
:: (Word8, Word8, Word8)
|
||||||
|
-> (Word8, Word8, Word8)
|
||||||
|
-> Double
|
||||||
|
-> (Word8, Word8, Word8)
|
||||||
|
mix (r1, g1, b1) (r2, g2, b2) r = (mix' r1 r2, mix' g1 g2, mix' b1 b2)
|
||||||
|
where mix' a b = truncate $ (fi a * r) + (fi b * (1 - r))
|
||||||
|
|
||||||
|
-- | Generates a Double from a string, trying to
|
||||||
|
-- achieve a random distribution.
|
||||||
|
-- We create a random seed from the sum of all characters
|
||||||
|
-- in the string, and use it to generate a ratio between 0 and 1
|
||||||
|
stringToRatio :: String -> Double
|
||||||
|
stringToRatio "" = 0
|
||||||
|
stringToRatio s =
|
||||||
|
let gen = mkStdGen $ sum $ map fromEnum s
|
||||||
|
range = (\(a, b) -> b - a) $ genRange gen
|
||||||
|
randomInt = foldr1 combine $ replicate 20 next
|
||||||
|
combine f1 f2 g = let (_, g') = f1 g in f2 g'
|
||||||
|
in fi (fst $ randomInt gen) / fi range
|
||||||
|
|
||||||
|
-- | Brings up a 2D grid of elements in the center of the screen, and one can
|
||||||
|
-- select an element with cursors keys. The selected element is returned.
|
||||||
|
gridselect :: GSConfig a -> [(String, a)] -> X (Maybe a)
|
||||||
|
gridselect _ [] = return Nothing
|
||||||
|
gridselect gsconfig elements = withDisplay $ \dpy -> do
|
||||||
|
rootw <- asks theRoot
|
||||||
|
scr <- gets $ screenRect . W.screenDetail . W.current . windowset
|
||||||
|
win <- liftIO $ mkUnmanagedWindow dpy
|
||||||
|
(defaultScreenOfDisplay dpy)
|
||||||
|
rootw
|
||||||
|
(rect_x scr)
|
||||||
|
(rect_y scr)
|
||||||
|
(rect_width scr)
|
||||||
|
(rect_height scr)
|
||||||
|
liftIO $ mapWindow dpy win
|
||||||
|
liftIO $ selectInput dpy
|
||||||
|
win
|
||||||
|
(exposureMask .|. keyPressMask .|. buttonReleaseMask)
|
||||||
|
status <- io
|
||||||
|
$ grabKeyboard dpy win True grabModeAsync grabModeAsync currentTime
|
||||||
|
io $ grabPointer dpy
|
||||||
|
win
|
||||||
|
True
|
||||||
|
buttonReleaseMask
|
||||||
|
grabModeAsync
|
||||||
|
grabModeAsync
|
||||||
|
none
|
||||||
|
none
|
||||||
|
currentTime
|
||||||
|
font <- initXMF (gs_font gsconfig)
|
||||||
|
let screenWidth = toInteger $ rect_width scr
|
||||||
|
screenHeight = toInteger $ rect_height scr
|
||||||
|
selectedElement <- if status == grabSuccess
|
||||||
|
then do
|
||||||
|
let
|
||||||
|
restriction ss cs =
|
||||||
|
(fromInteger ss / fromInteger (cs gsconfig) - 1) / 2 :: Double
|
||||||
|
restrictX = floor $ restriction screenWidth gs_cellwidth
|
||||||
|
restrictY = floor $ restriction screenHeight gs_cellheight
|
||||||
|
originPosX =
|
||||||
|
floor
|
||||||
|
$ (gs_originFractX gsconfig - (1 / 2))
|
||||||
|
* 2
|
||||||
|
* fromIntegral restrictX
|
||||||
|
originPosY =
|
||||||
|
floor
|
||||||
|
$ (gs_originFractY gsconfig - (1 / 2))
|
||||||
|
* 2
|
||||||
|
* fromIntegral restrictY
|
||||||
|
coords = diamondRestrict restrictX restrictY originPosX originPosY
|
||||||
|
s = TwoDState
|
||||||
|
{ td_curpos = head coords
|
||||||
|
, td_availSlots = coords
|
||||||
|
, td_elements = elements
|
||||||
|
, td_gsconfig = gsconfig
|
||||||
|
, td_font = font
|
||||||
|
, td_paneX = screenWidth
|
||||||
|
, td_paneY = screenHeight
|
||||||
|
, td_drawingWin = win
|
||||||
|
, td_searchString = ""
|
||||||
|
, td_elementmap = []
|
||||||
|
}
|
||||||
|
m <- generateElementmap s
|
||||||
|
evalTwoD (updateAllElements >> gs_navigate gsconfig)
|
||||||
|
(s { td_elementmap = m })
|
||||||
|
else return Nothing
|
||||||
|
liftIO $ do
|
||||||
|
unmapWindow dpy win
|
||||||
|
destroyWindow dpy win
|
||||||
|
ungrabPointer dpy currentTime
|
||||||
|
sync dpy False
|
||||||
|
releaseXMF font
|
||||||
|
return selectedElement
|
||||||
|
|
||||||
|
-- | Like `gridSelect' but with the current windows and their titles as elements
|
||||||
|
gridselectWindow :: GSConfig Window -> X (Maybe Window)
|
||||||
|
gridselectWindow gsconf = windowMap >>= gridselect gsconf
|
||||||
|
|
||||||
|
-- | Brings up a 2D grid of windows in the center of the screen, and one can
|
||||||
|
-- select a window with cursors keys. The selected window is then passed to
|
||||||
|
-- a callback function.
|
||||||
|
withSelectedWindow :: (Window -> X ()) -> GSConfig Window -> X ()
|
||||||
|
withSelectedWindow callback conf = do
|
||||||
|
mbWindow <- gridselectWindow conf
|
||||||
|
Data.Foldable.forM_ mbWindow callback
|
||||||
|
-- case mbWindow of
|
||||||
|
-- Just w -> callback w
|
||||||
|
-- Nothing -> return ()
|
||||||
|
|
||||||
|
windowMap :: X [(String, Window)]
|
||||||
|
windowMap = do
|
||||||
|
ws <- gets windowset
|
||||||
|
mapM keyValuePair (W.allWindows ws)
|
||||||
|
where keyValuePair w = (, w) `fmap` decorateName' w
|
||||||
|
|
||||||
|
decorateName' :: Window -> X String
|
||||||
|
decorateName' w = show <$> getName w
|
||||||
|
|
||||||
|
-- | Builds a default gs config from a colorizer function.
|
||||||
|
buildDefaultGSConfig :: (a -> Bool -> X (String, String)) -> GSConfig a
|
||||||
|
buildDefaultGSConfig col = GSConfig 50
|
||||||
|
130
|
||||||
|
10
|
||||||
|
col
|
||||||
|
"xft:Sans-8"
|
||||||
|
defaultNavigation
|
||||||
|
noRearranger
|
||||||
|
(1 / 2)
|
||||||
|
(1 / 2)
|
||||||
|
"white"
|
||||||
|
|
||||||
|
-- | Brings selected window to the current workspace.
|
||||||
|
bringSelected :: GSConfig Window -> X ()
|
||||||
|
bringSelected = withSelectedWindow $ \w -> do
|
||||||
|
windows (bringWindow w)
|
||||||
|
XMonad.focus w
|
||||||
|
windows W.shiftMaster
|
||||||
|
|
||||||
|
-- | Switches to selected window's workspace and focuses that window.
|
||||||
|
goToSelected :: GSConfig Window -> X ()
|
||||||
|
goToSelected = withSelectedWindow $ windows . W.focusWindow
|
||||||
|
|
||||||
|
-- | Select an application to spawn from a given list
|
||||||
|
spawnSelected :: GSConfig String -> [String] -> X ()
|
||||||
|
spawnSelected conf lst = gridselect conf (zip lst lst) >>= flip whenJust spawn
|
||||||
|
|
||||||
|
-- | Select an action and run it in the X monad
|
||||||
|
runSelectedAction :: GSConfig (X ()) -> [(String, X ())] -> X ()
|
||||||
|
runSelectedAction conf actions = do
|
||||||
|
selectedActionM <- gridselect conf actions
|
||||||
|
fromMaybe (return ()) selectedActionM
|
||||||
|
-- case selectedActionM of
|
||||||
|
-- Just selectedAction -> selectedAction
|
||||||
|
-- Nothing -> return ()
|
||||||
|
|
||||||
|
-- | Select a workspace and view it using the given function
|
||||||
|
-- (normally 'W.view' or 'W.greedyView')
|
||||||
|
--
|
||||||
|
-- Another option is to shift the current window to the selected workspace:
|
||||||
|
--
|
||||||
|
-- > gridselectWorkspace (\ws -> W.greedyView ws . W.shift ws)
|
||||||
|
gridselectWorkspace
|
||||||
|
:: GSConfig WorkspaceId -> (WorkspaceId -> WindowSet -> WindowSet) -> X ()
|
||||||
|
gridselectWorkspace conf viewFunc =
|
||||||
|
gridselectWorkspace' conf (windows . viewFunc)
|
||||||
|
|
||||||
|
-- | Select a workspace and run an arbitrary action on it.
|
||||||
|
gridselectWorkspace' :: GSConfig WorkspaceId -> (WorkspaceId -> X ()) -> X ()
|
||||||
|
gridselectWorkspace' conf func = withWindowSet $ \ws -> do
|
||||||
|
let wss =
|
||||||
|
map W.tag $ W.hidden ws ++ map W.workspace (W.current ws : W.visible ws)
|
||||||
|
gridselect conf (zip wss wss) >>= flip whenJust func
|
||||||
|
|
||||||
|
-- $rearrangers
|
||||||
|
--
|
||||||
|
-- Rearrangers allow for arbitrary post-filter rearranging of the grid
|
||||||
|
-- elements.
|
||||||
|
--
|
||||||
|
-- For example, to be able to switch to a new dynamic workspace by typing
|
||||||
|
-- in its name, you can use the following keybinding action:
|
||||||
|
--
|
||||||
|
-- > import XMonad.Actions.DynamicWorkspaces (addWorkspace)
|
||||||
|
-- >
|
||||||
|
-- > gridselectWorkspace' defaultGSConfig
|
||||||
|
-- > { gs_navigate = navNSearch
|
||||||
|
-- > , gs_rearranger = searchStringRearrangerGenerator id
|
||||||
|
-- > }
|
||||||
|
-- > addWorkspace
|
||||||
|
|
||||||
|
-- | A function taking the search string and a list of elements, and
|
||||||
|
-- returning a potentially rearranged list of elements.
|
||||||
|
type Rearranger a = String -> [(String, a)] -> X [(String, a)]
|
||||||
|
|
||||||
|
-- | A rearranger that leaves the elements unmodified.
|
||||||
|
noRearranger :: Rearranger a
|
||||||
|
noRearranger _ = return
|
||||||
|
|
||||||
|
-- | A generator for rearrangers that append a single element based on the
|
||||||
|
-- search string, if doing so would not be redundant (empty string or value
|
||||||
|
-- already present).
|
||||||
|
searchStringRearrangerGenerator :: (String -> a) -> Rearranger a
|
||||||
|
searchStringRearrangerGenerator f =
|
||||||
|
let r "" xs = return xs
|
||||||
|
r s xs | s `elem` map fst xs = return xs
|
||||||
|
| otherwise = return $ xs ++ [(s, f s)]
|
||||||
|
in r
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------------------------------------------------------
|
||||||
|
-- custom part --
|
||||||
|
----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
-- | A custom colorizer that colors depending on the title of the grid column
|
||||||
|
myCustomColorizer :: String -> a -> Bool -> X (String, String)
|
||||||
|
myCustomColorizer text _ p
|
||||||
|
| p = pure ("#f44336", "#1a1a1a")
|
||||||
|
| otherwise = if "MIT" `isInfixOf` text
|
||||||
|
then pure ("#4caf50", "#1a1a1a")
|
||||||
|
else if "BIG" `isInfixOf` text
|
||||||
|
then pure ("#2196f3", "#1a1a1a")
|
||||||
|
else pure ("#1a1a1a", "gray")
|
||||||
|
|
||||||
|
|
||||||
|
-- | Select an action and run it in the X monad. Furthermore display a message on top of the screen.
|
||||||
|
runSelectedActionWithMessageAndIcon
|
||||||
|
:: GSConfig (X ()) -> String -> [[Bool]] -> [(String, X ())] -> X ()
|
||||||
|
runSelectedActionWithMessageAndIcon conf message icon actions = do
|
||||||
|
selectedActionM <- gridselectWithMessageAndIcon conf message icon actions
|
||||||
|
fromMaybe (return ()) selectedActionM
|
||||||
|
-- case selectedActionM of
|
||||||
|
-- Just selectedAction -> selectedAction
|
||||||
|
-- Nothing -> return ()
|
||||||
|
|
||||||
|
|
||||||
|
-- | Brings up a 2D grid of elements in the center of the screen, and one can
|
||||||
|
-- select an element with cursors keys. The selected element is returned.
|
||||||
|
gridselectWithMessageAndIcon
|
||||||
|
:: GSConfig a -> String -> [[Bool]] -> [(String, a)] -> X (Maybe a)
|
||||||
|
gridselectWithMessageAndIcon _ _ _ [] = return Nothing
|
||||||
|
gridselectWithMessageAndIcon gsconfig message icon elements =
|
||||||
|
withDisplay $ \dpy -> do
|
||||||
|
rootw <- asks theRoot
|
||||||
|
scr <- gets $ screenRect . W.screenDetail . W.current . windowset
|
||||||
|
win <- liftIO $ mkUnmanagedWindow dpy
|
||||||
|
(defaultScreenOfDisplay dpy)
|
||||||
|
rootw
|
||||||
|
(rect_x scr)
|
||||||
|
(rect_y scr)
|
||||||
|
(rect_width scr)
|
||||||
|
(rect_height scr)
|
||||||
|
liftIO $ mapWindow dpy win
|
||||||
|
|
||||||
|
message_win <- createNewWindow (Rectangle 450 50 1000 60) Nothing "" True
|
||||||
|
liftIO $ mapWindow dpy message_win
|
||||||
|
fs <- initXMF "xft:Inconsolata:size=14"
|
||||||
|
paintTextAndIcons message_win
|
||||||
|
fs
|
||||||
|
1000
|
||||||
|
60
|
||||||
|
1
|
||||||
|
"#1a1a1a"
|
||||||
|
"gray"
|
||||||
|
"gray"
|
||||||
|
"#1a1a1a"
|
||||||
|
[AlignCenter]
|
||||||
|
[message]
|
||||||
|
[CenterLeft 10]
|
||||||
|
[icon]
|
||||||
|
|
||||||
|
liftIO $ selectInput dpy
|
||||||
|
win
|
||||||
|
(exposureMask .|. keyPressMask .|. buttonReleaseMask)
|
||||||
|
status <- io
|
||||||
|
$ grabKeyboard dpy win True grabModeAsync grabModeAsync currentTime
|
||||||
|
io $ grabPointer dpy
|
||||||
|
win
|
||||||
|
True
|
||||||
|
buttonReleaseMask
|
||||||
|
grabModeAsync
|
||||||
|
grabModeAsync
|
||||||
|
none
|
||||||
|
none
|
||||||
|
currentTime
|
||||||
|
font <- initXMF (gs_font gsconfig)
|
||||||
|
let screenWidth = toInteger $ rect_width scr
|
||||||
|
screenHeight = toInteger $ rect_height scr
|
||||||
|
selectedElement <- if status == grabSuccess
|
||||||
|
then do
|
||||||
|
let
|
||||||
|
restriction ss cs =
|
||||||
|
(fromInteger ss / fromInteger (cs gsconfig) - 1) / 2 :: Double
|
||||||
|
restrictX = floor $ restriction screenWidth gs_cellwidth
|
||||||
|
restrictY = floor $ restriction screenHeight gs_cellheight
|
||||||
|
originPosX =
|
||||||
|
floor
|
||||||
|
$ (gs_originFractX gsconfig - (1 / 2))
|
||||||
|
* 2
|
||||||
|
* fromIntegral restrictX
|
||||||
|
originPosY =
|
||||||
|
floor
|
||||||
|
$ (gs_originFractY gsconfig - (1 / 2))
|
||||||
|
* 2
|
||||||
|
* fromIntegral restrictY
|
||||||
|
coords = diamondRestrict restrictX restrictY originPosX originPosY
|
||||||
|
s = TwoDState
|
||||||
|
{ td_curpos = head coords
|
||||||
|
, td_availSlots = coords
|
||||||
|
, td_elements = elements
|
||||||
|
, td_gsconfig = gsconfig
|
||||||
|
, td_font = font
|
||||||
|
, td_paneX = screenWidth
|
||||||
|
, td_paneY = screenHeight
|
||||||
|
, td_drawingWin = win
|
||||||
|
, td_searchString = ""
|
||||||
|
, td_elementmap = []
|
||||||
|
}
|
||||||
|
m <- generateElementmap s
|
||||||
|
evalTwoD (updateAllElements2 >> gs_navigate gsconfig)
|
||||||
|
(s { td_elementmap = m })
|
||||||
|
else return Nothing
|
||||||
|
liftIO $ do
|
||||||
|
-- unmapWindow dpy message_win
|
||||||
|
destroyWindow dpy message_win
|
||||||
|
|
||||||
|
unmapWindow dpy win
|
||||||
|
destroyWindow dpy win
|
||||||
|
ungrabPointer dpy currentTime
|
||||||
|
sync dpy False
|
||||||
|
releaseXMF font
|
||||||
|
return selectedElement
|
||||||
|
|
||||||
|
|
||||||
|
updateAllElements2 :: TwoD a ()
|
||||||
|
updateAllElements2 = do
|
||||||
|
s <- get
|
||||||
|
updateElements2 (td_elementmap s)
|
||||||
|
|
||||||
|
|
||||||
|
updateElements2 :: TwoDElementMap a -> TwoD a ()
|
||||||
|
updateElements2 elementmap = do
|
||||||
|
s <- get
|
||||||
|
updateElementsWithColorizer2 myCustomColorizer elementmap
|
||||||
|
|
||||||
|
|
||||||
|
updateElementsWithColorizer2
|
||||||
|
:: (String -> a -> Bool -> X (String, String))
|
||||||
|
-> TwoDElementMap a
|
||||||
|
-> TwoD a ()
|
||||||
|
updateElementsWithColorizer2 colorizer elementmap = do
|
||||||
|
TwoDState { td_curpos = curpos, td_drawingWin = win, td_gsconfig = gsconfig, td_font = font, td_paneX = paneX, td_paneY = paneY } <-
|
||||||
|
get
|
||||||
|
let cellwidth = gs_cellwidth gsconfig
|
||||||
|
cellheight = gs_cellheight gsconfig
|
||||||
|
paneX' = div (paneX - cellwidth) 2
|
||||||
|
paneY' = div (paneY - cellheight) 2
|
||||||
|
updateElement (pos@(x, y), (text, element)) = liftX $ do
|
||||||
|
-- colors <- colorizer element (pos == curpos)
|
||||||
|
colors <- colorizer text element (pos == curpos)
|
||||||
|
drawWinBox win
|
||||||
|
font
|
||||||
|
colors
|
||||||
|
(gs_bordercolor gsconfig)
|
||||||
|
cellheight
|
||||||
|
cellwidth
|
||||||
|
text
|
||||||
|
(paneX' + x * cellwidth)
|
||||||
|
(paneY' + y * cellheight)
|
||||||
|
(gs_cellpadding gsconfig)
|
||||||
|
mapM_ updateElement elementmap
|
398
home/xmonad/.xmonad/lib/TaskMonad.hs
Normal file
398
home/xmonad/.xmonad/lib/TaskMonad.hs
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
{-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : TaskMonad
|
||||||
|
-- Copyright : Max magorsch <max@magorsch.de>
|
||||||
|
-- License : BSD-style (see LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : Max Magorsch <max@magorsch.de>
|
||||||
|
-- Stability : unstable
|
||||||
|
-- Portability : unportable
|
||||||
|
--
|
||||||
|
-- TaskMonad bundles a number of tools that can be used to directly interact
|
||||||
|
-- with taskwarrior from within xmonad. Furthermore, workflows following the
|
||||||
|
-- Getting Things Done principles are implemented.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module TaskMonad
|
||||||
|
(
|
||||||
|
-- * Installation
|
||||||
|
|
||||||
|
-- ** Install with Cabal
|
||||||
|
-- $installWithCabal
|
||||||
|
|
||||||
|
-- ** Install without Cabal
|
||||||
|
-- $installWithoutCabal
|
||||||
|
|
||||||
|
-- * Usage
|
||||||
|
-- $usage
|
||||||
|
|
||||||
|
-- * Step 1: Capture
|
||||||
|
-- $capture
|
||||||
|
taskwarriorPrompt
|
||||||
|
,
|
||||||
|
|
||||||
|
-- * Step 2 & 3: Clarify & Organize
|
||||||
|
-- $organize
|
||||||
|
processInbox
|
||||||
|
,
|
||||||
|
|
||||||
|
-- * Step 4: Reflect
|
||||||
|
-- $reflect
|
||||||
|
togglePriority
|
||||||
|
,
|
||||||
|
|
||||||
|
-- * Step 5: Engage
|
||||||
|
-- $engage
|
||||||
|
taskSelect
|
||||||
|
, dueSelect
|
||||||
|
, tagSelect
|
||||||
|
, projectSelect
|
||||||
|
,
|
||||||
|
|
||||||
|
-- * Scratchpad
|
||||||
|
-- $scratchpad
|
||||||
|
taskwarriorScratchpads
|
||||||
|
, taskwarriorScratchpad
|
||||||
|
|
||||||
|
-- * All Components
|
||||||
|
-- $components
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import System.IO
|
||||||
|
import Control.Monad ( filterM )
|
||||||
|
|
||||||
|
import XMonad hiding ( liftX )
|
||||||
|
import XMonad.Util.Font
|
||||||
|
import qualified XMonad.StackSet as W
|
||||||
|
import XMonad.Layout.Decoration
|
||||||
|
import XMonad.Prompt
|
||||||
|
import XMonad.Prompt.Input
|
||||||
|
import XMonad.Util.Image
|
||||||
|
import XMonad.Util.NamedWindows
|
||||||
|
import XMonad.Util.XUtils
|
||||||
|
import XMonad.Util.NamedScratchpad
|
||||||
|
import XMonad.Util.Run
|
||||||
|
import XMonad.Actions.GridSelect
|
||||||
|
|
||||||
|
import qualified GridSelect.Extras
|
||||||
|
|
||||||
|
import TaskMonad.Prompt
|
||||||
|
import TaskMonad.ScratchPad
|
||||||
|
import TaskMonad.Utils
|
||||||
|
import TaskMonad.GridSelect
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- $installWithCabal
|
||||||
|
--
|
||||||
|
-- To install TaskMonad from hackage just execute:
|
||||||
|
--
|
||||||
|
-- > cabal update
|
||||||
|
-- > cabal install TaskMonad
|
||||||
|
--
|
||||||
|
-- Afterwards import TaskMonad in your `xmonad.hs`
|
||||||
|
--
|
||||||
|
-- > import TaskMonad
|
||||||
|
--
|
||||||
|
|
||||||
|
-- $installWithoutCabal
|
||||||
|
--
|
||||||
|
-- To install Taskmonad without using cabal just download and copy the source code into your `~/.xmonad/-- lib/` folder. The folder structure should afterwards look like this:
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- > .xmonad
|
||||||
|
-- > |-- lib
|
||||||
|
-- > | |-- Taskmonad.hs
|
||||||
|
-- > | |-- Taskmonad
|
||||||
|
-- > | | |-- GridSelect.hs
|
||||||
|
-- > | | |-- Prompt.hs
|
||||||
|
-- > | | |-- ScratchPad.hs
|
||||||
|
-- > | | `-- Utils.hs
|
||||||
|
-- > | |-- GridSelect
|
||||||
|
-- > | | `-- Extras.hs
|
||||||
|
-- > | `-- ...
|
||||||
|
-- > |-- xmonad.hs
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- Afterwards import TaskMonad in your `xmonad.hs`
|
||||||
|
--
|
||||||
|
-- > import TaskMonad
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
-- $usage
|
||||||
|
-- To get started, add a manage hook for the taskwarrior scratchpad:
|
||||||
|
--
|
||||||
|
-- > import TaskMonad
|
||||||
|
-- >
|
||||||
|
-- > -- ...
|
||||||
|
-- >
|
||||||
|
-- > ... , manageHook = namedScratchpadManageHook taskwarriorScratchpads
|
||||||
|
--
|
||||||
|
-- After that you can bind the taskwarrior prompt to a key to get started:
|
||||||
|
--
|
||||||
|
-- > ... , ("M-p", taskwarriorPrompt [(\x -> x == "processInbox", processInbox)])
|
||||||
|
--
|
||||||
|
-- You can also bind any other TaskMonad action to a key. For example:
|
||||||
|
--
|
||||||
|
-- > ... , ("M-S-p", taskwarriorScratchpad) -- Opens the taskwarrior scratchpad
|
||||||
|
-- >
|
||||||
|
-- > ... , ("M-C-p", taskSelect "status:pending") -- Displays all pending tasks
|
||||||
|
-- >
|
||||||
|
-- > ... , ("M-C-S-p", tagSelect) -- Displays all tags
|
||||||
|
--
|
||||||
|
-- In general you can customize the tools ad libitum. A good way to get started is to implement custom actions for the taskwarrior prompt. Please refer to 'taskwarriorPrompt' for further information.
|
||||||
|
|
||||||
|
|
||||||
|
-- $capture
|
||||||
|
--
|
||||||
|
-- You can easily capture tasks, ideas or notes using the 'taskwarriorPrompt' like this:
|
||||||
|
--
|
||||||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/capture.png >>
|
||||||
|
--
|
||||||
|
|
||||||
|
-- $organize
|
||||||
|
-- You can clarify and organize your tasks using 'processInbox'.
|
||||||
|
-- It implements the typical Getting Things Done workflow using GridSelects:
|
||||||
|
--
|
||||||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/workflow.png >>
|
||||||
|
--
|
||||||
|
|
||||||
|
-- $reflect
|
||||||
|
-- You can implement your own custom daily- and weeklyreview routines.
|
||||||
|
-- For example you can use 'togglePriority' to adjust the priority of tasks
|
||||||
|
-- during the daily- / weeklyreview like this:
|
||||||
|
--
|
||||||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-gridselect.png >>
|
||||||
|
|
||||||
|
-- $engage
|
||||||
|
-- To decide which task to do next, you can use a collection of gridselects.
|
||||||
|
-- You can use 'tagSelect', 'projectSelect', 'dueSelect' to display a gridselect
|
||||||
|
-- to filter the tasks by tag, project or due date. However you can also display
|
||||||
|
-- all pending tasks using 'taskSelect' like this:
|
||||||
|
--
|
||||||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/engage.png >>
|
||||||
|
|
||||||
|
-- $scratchpad
|
||||||
|
-- The taskwarrior scratchpad is used to display taskwarrior reports that
|
||||||
|
-- have been invoked using the taskwarrior prompt. However, you can use the
|
||||||
|
-- scratchpad at your convenience. Just add a manage hook:
|
||||||
|
--
|
||||||
|
-- > ... , manageHook = namedScratchpadManageHook taskwarriorScratchpads
|
||||||
|
--
|
||||||
|
-- Afterwards you can bind a key to 'taskwarriorScratchpad'. The Scratchpad will look like this
|
||||||
|
--
|
||||||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-scratchpad.png >>
|
||||||
|
|
||||||
|
-- $components
|
||||||
|
-- * 'TaskMonad'
|
||||||
|
--
|
||||||
|
-- * 'TaskMonad.Prompt'
|
||||||
|
-- * 'TaskMonad.Scratchpad'
|
||||||
|
-- * 'TaskMonad.GridSelect'
|
||||||
|
-- * 'TaskMonad.Utils'
|
||||||
|
--
|
||||||
|
-- * 'GridSelect.Extras'
|
||||||
|
|
||||||
|
|
||||||
|
-- | Opens a set of gridselects used to process the inbox using the Getting Things Done workflow
|
||||||
|
processInbox :: X ()
|
||||||
|
processInbox = io (getTaskwarriorTaskList "+INBOX" ["id", "description"])
|
||||||
|
>>= \ts -> startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Recursively processes a given list of tasks using the typical GTD workflow.
|
||||||
|
startInboxProcessing :: [[String]] -> X ()
|
||||||
|
startInboxProcessing [] = dummyAction
|
||||||
|
startInboxProcessing (t : ts) = twgs
|
||||||
|
("Actionable? " ++ t !! 1, [("YES", actionable), ("NO", not_actionable)])
|
||||||
|
where
|
||||||
|
not_actionable = twgs
|
||||||
|
( "Next Steps: " ++ t !! 1
|
||||||
|
, [ ("[Back]" , startInboxProcessing (t : ts))
|
||||||
|
, ("Reference" , moveToFile t ts)
|
||||||
|
, ("Project Support", moveToFile t ts)
|
||||||
|
, ("Someday/Later" , somedayLater t ts)
|
||||||
|
, ("Trash" , trash t ts)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
actionable = twgs
|
||||||
|
( "Does it take multiple steps?"
|
||||||
|
, [ ("[Back]", startInboxProcessing (t : ts))
|
||||||
|
, ("YES" , makeProject t ts)
|
||||||
|
, ("NO" , single_step)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
single_step = twgs
|
||||||
|
( "Does it take less than 2 minutes?"
|
||||||
|
, [("[Back]", actionable), ("YES", do_it), ("NO", more_than_2_min)]
|
||||||
|
)
|
||||||
|
|
||||||
|
do_it =
|
||||||
|
twgs ("Do it now!", [("[Back]", single_step), ("Finished", done t ts)])
|
||||||
|
|
||||||
|
more_than_2_min = twgs
|
||||||
|
( "Next Steps: " ++ t !! 1
|
||||||
|
, [ ("[Back]" , single_step)
|
||||||
|
, ("Move to calendar", calendar t ts)
|
||||||
|
, ("Waiting For" , waitingFor t ts)
|
||||||
|
, ("Edit Task" , edit_task)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
edit_task = twgs
|
||||||
|
( "How to process task: " ++ head t
|
||||||
|
, [ ("[Back]" , more_than_2_min)
|
||||||
|
, ("[Finish]" , startInboxProcessing ts)
|
||||||
|
, ("Free Editing" , editTaskAction t ts)
|
||||||
|
, ("Set Tags" , editTaskAction t ts)
|
||||||
|
, ("Set Description", editTaskAction t ts)
|
||||||
|
, ("Set Due Date" , editTaskAction t ts)
|
||||||
|
, ("Set Project" , editTaskAction t ts)
|
||||||
|
, ("Set Context" , editTaskAction t ts)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Sends a message to the notification daemon
|
||||||
|
notify :: MonadIO m => String -> m ()
|
||||||
|
notify message = unsafeSpawn $ "notify-send '" ++ message ++ "'"
|
||||||
|
|
||||||
|
|
||||||
|
-- | Opens a GridSelect with a custom message and the taskwarrior icon
|
||||||
|
twgs :: (String, [(String, X ())]) -> X ()
|
||||||
|
twgs (message, actions) = GridSelect.Extras.runSelectedActionWithMessageAndIcon
|
||||||
|
defaultTWGSExtraConfig
|
||||||
|
message
|
||||||
|
twicon
|
||||||
|
actions
|
||||||
|
|
||||||
|
|
||||||
|
-- | Deletes the current task. Afterwards the remaining tasks will be processed.
|
||||||
|
trash
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
trash t ts = do
|
||||||
|
runTmuxCommand $ "echo 'yes' | task " ++ head t ++ " delete"
|
||||||
|
notify $ "Deleted Task:\n\n " ++ (t !! 1)
|
||||||
|
startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Sets a task to done. Afterwards the remaining tasks will be processed.
|
||||||
|
done
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
done t ts = do
|
||||||
|
runTmuxCommand $ "echo 'yes' | task " ++ head t ++ " done"
|
||||||
|
notify $ "Done Task:\n\n " ++ (t !! 1)
|
||||||
|
startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Changes the tag of the current task from INBOX to SOMEDAY
|
||||||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||||||
|
somedayLater
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
somedayLater t ts = do
|
||||||
|
runTmuxCommand $ "task " ++ head t ++ " modify -INBOX +SOMEDAY"
|
||||||
|
notify $ "Changed Task:\n\n " ++ (t !! 1) ++ "\n\n to Somday/Maybe"
|
||||||
|
startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Changes the tag of the current task from INBOX to WAITINGFOR
|
||||||
|
-- Afterwards the remaining tasks will be processed.
|
||||||
|
waitingFor
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
waitingFor t ts = do
|
||||||
|
runTmuxCommand $ "task " ++ head t ++ " modify -INBOX +WAITINGFOR"
|
||||||
|
notify $ "Changed Task:\n\n " ++ (t !! 1) ++ "\n\n to Waiting For"
|
||||||
|
startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Moves the information of the current task to a markdown file.
|
||||||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||||||
|
moveToFile
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
moveToFile t ts = do
|
||||||
|
runTmuxCommand
|
||||||
|
$ "task information "
|
||||||
|
++ head t
|
||||||
|
++ " >> ~/reference/inbox.md && echo 'yes' | task "
|
||||||
|
++ head t
|
||||||
|
++ " done"
|
||||||
|
notify
|
||||||
|
$ "Moved the task:\n\n "
|
||||||
|
++ (t !! 1)
|
||||||
|
++ "\n\n to the file: \n\n ~/reference/inbox.md.\n \n Please consider editing the file."
|
||||||
|
startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Opens a customPrompt to create an appointment using
|
||||||
|
-- [gcalcli](https://github.com/insanum/gcalcli)
|
||||||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||||||
|
calendar
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
calendar t ts = customPrompt "gcalcli add" [] (addAndContinue t ts)
|
||||||
|
where
|
||||||
|
addAndContinue t ts x = do
|
||||||
|
runTmuxCommand ("gcalcli add --noprompt " ++ x)
|
||||||
|
notify "Created Appointment."
|
||||||
|
done t ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Opens a customPrompt to create a project for a given task
|
||||||
|
-- Afterwards the task will be set as done and the remaining tasks will be processed.
|
||||||
|
makeProject
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
makeProject t ts = do
|
||||||
|
notify ("Please create your first task for the project: \n\n " ++ t !! 1)
|
||||||
|
customPrompt "task add" [] (addAndContinue t ts)
|
||||||
|
where
|
||||||
|
addAndContinue t ts x = do
|
||||||
|
runTmuxCommand ("task add " ++ x)
|
||||||
|
done t ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | Opens a customPrompt in order to modify the current taskwarrior task.
|
||||||
|
-- Afterwards the remaining tasks will be processed.
|
||||||
|
editTaskAction
|
||||||
|
:: [String] -- ^ the current task
|
||||||
|
-> [[String]] -- ^ the remaining tasks
|
||||||
|
-> X ()
|
||||||
|
editTaskAction t ts = do
|
||||||
|
notify "Please edit your task."
|
||||||
|
customPrompt ("task " ++ head t ++ " modify") [] (addAndContinue t ts)
|
||||||
|
where
|
||||||
|
addAndContinue t ts x = do
|
||||||
|
runTmuxCommand ("task " ++ head t ++ " modify " ++ x)
|
||||||
|
notify "Edited the task"
|
||||||
|
startInboxProcessing ts
|
||||||
|
|
||||||
|
|
||||||
|
-- | A dummy action for testing purposes
|
||||||
|
dummyAction :: X ()
|
||||||
|
dummyAction = createTWwindow >>= deleteWindow
|
||||||
|
where
|
||||||
|
createTWwindow =
|
||||||
|
createNewWindow (Rectangle 450 150 1000 60) Nothing "Test" True
|
270
home/xmonad/.xmonad/lib/TaskMonad/GridSelect.hs
Normal file
270
home/xmonad/.xmonad/lib/TaskMonad/GridSelect.hs
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
{-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : TaskMonad.GridSelect
|
||||||
|
-- Copyright : Max Magorsch <max@magorsch.de>
|
||||||
|
-- License : BSD-style (see LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : Max Magorsch <max@magorsch.de>
|
||||||
|
-- Stability : unstable
|
||||||
|
-- Portability : unportable
|
||||||
|
--
|
||||||
|
-- TaskMonad.GridSelect uses 'GridSelect.Extras' to display various information from taskwarrior.
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module TaskMonad.GridSelect
|
||||||
|
(
|
||||||
|
-- * Screenshot
|
||||||
|
-- $screenshots
|
||||||
|
--
|
||||||
|
|
||||||
|
-- * Possible GridSelects
|
||||||
|
taskSelect
|
||||||
|
, taskSelectWithConfig
|
||||||
|
, tagSelect
|
||||||
|
, tagSelectWithConfig
|
||||||
|
, projectSelect
|
||||||
|
, projectSelectWithConfig
|
||||||
|
, dueSelect
|
||||||
|
, dueSelectWithConfig
|
||||||
|
, togglePriority
|
||||||
|
, togglePriorityWithConfig
|
||||||
|
,
|
||||||
|
|
||||||
|
-- * Configuration
|
||||||
|
buildTWGSExtraConfig
|
||||||
|
, buildTWGSConfig
|
||||||
|
, defaultTWGSConfig
|
||||||
|
, defaultTWGSExtraConfig
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import System.IO
|
||||||
|
import Control.Monad ( filterM )
|
||||||
|
|
||||||
|
import XMonad hiding ( liftX )
|
||||||
|
import XMonad.Util.Font
|
||||||
|
import qualified XMonad.StackSet as W
|
||||||
|
import XMonad.Layout.Decoration
|
||||||
|
import XMonad.Prompt
|
||||||
|
import XMonad.Prompt.Input
|
||||||
|
import XMonad.Util.Image
|
||||||
|
import XMonad.Util.NamedWindows
|
||||||
|
import XMonad.Util.XUtils
|
||||||
|
import XMonad.Util.NamedScratchpad
|
||||||
|
import XMonad.Util.Run
|
||||||
|
import XMonad.Actions.GridSelect
|
||||||
|
|
||||||
|
import qualified GridSelect.Extras
|
||||||
|
|
||||||
|
import TaskMonad.Utils
|
||||||
|
import TaskMonad.ScratchPad
|
||||||
|
|
||||||
|
-- $screenshots
|
||||||
|
-- 'togglePriority' in action:
|
||||||
|
--
|
||||||
|
-- << https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-gridselect.png >>
|
||||||
|
|
||||||
|
|
||||||
|
-- | A GridSelect displaying a filtered list of all taskwarrior tasks
|
||||||
|
taskSelectWithConfig
|
||||||
|
:: String -- ^ a filter to be applied, please refer to [TaskWarrior Filter](https://taskwarrior.org/docs/filter.html) for further information
|
||||||
|
-> GSConfig (X ()) -- ^ the GridSelect config to be used
|
||||||
|
-> X () -- ^ the gridselect displaying all filtered tasks
|
||||||
|
taskSelectWithConfig filter gsConfig =
|
||||||
|
io (getTaskwarriorTaskList filter ["id", "description"]) >>= \bs -> case bs of
|
||||||
|
[] -> safeSpawn "firefox" []
|
||||||
|
_ -> runSelectedAction gsConfig . finishGS $ fmap openBuffer bs
|
||||||
|
where
|
||||||
|
finishGS = (("[Finish]", unsafeSpawn "") :)
|
||||||
|
openBuffer x = (x !! 1, twscratchpad (head x ++ " information"))
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around 'taskSelectWithConfig' using the default GSConfig
|
||||||
|
taskSelect
|
||||||
|
:: String -- ^ a filter to be applied, please refer to [TaskWarrior Filter](https://taskwarrior.org/docs/filter.html) for further information
|
||||||
|
-> X () -- ^ the gridselect displaying all filtered tasks
|
||||||
|
taskSelect filter = taskSelectWithConfig filter (buildTWGSConfig 300)
|
||||||
|
|
||||||
|
|
||||||
|
-- | A GridSelect displaying a list of the tags of all pending taskwarrior tasks. After a tag has been selected, a second gridselect showing a filtered list of taskwarrior tasks that have the selected tag will be displayed.
|
||||||
|
tagSelectWithConfig
|
||||||
|
:: (GSConfig (X ()), GSConfig (X ())) -- ^ A tuple containing two GSConfigs. The first one is used to configure the gridselect displaying the list of tags. The second one is used to configure the gridselect displaying the resulting fitlered list of tasks.
|
||||||
|
-> X () -- ^ a gridSelect displaying a list of the tags of all pending taskwarrior tasks
|
||||||
|
tagSelectWithConfig (fstGsConfig, sndGsConfig) =
|
||||||
|
io (getTaskwarriorIds "status:pending" "tags") >>= \bs -> case bs of
|
||||||
|
[] -> safeSpawn "firefox" []
|
||||||
|
_ -> runSelectedAction fstGsConfig . finishGS $ fmap openBuffer
|
||||||
|
(filteredTags bs)
|
||||||
|
where
|
||||||
|
finishGS = (("[Finish]", unsafeSpawn "") :)
|
||||||
|
openBuffer x = (x, taskSelectWithConfig ("+" ++ x) sndGsConfig)
|
||||||
|
filteredTags bs = [ x | x <- bs, x `notElem` hiddenTags ]
|
||||||
|
hiddenTags =
|
||||||
|
[ "BLOCKED"
|
||||||
|
, "UNBLOCKED"
|
||||||
|
, "UNBLOCKED"
|
||||||
|
, "DUE"
|
||||||
|
, "DUETODAY"
|
||||||
|
, "TODAY"
|
||||||
|
, "OVERDUE"
|
||||||
|
, "WEEK"
|
||||||
|
, "MONTH"
|
||||||
|
, "QUARTER"
|
||||||
|
, "YEAR"
|
||||||
|
, "ACTIVE"
|
||||||
|
, "SCHEDULED"
|
||||||
|
, "PARENT"
|
||||||
|
, "CHILD"
|
||||||
|
, "UNTIL"
|
||||||
|
, "WAITING"
|
||||||
|
, "ANNOTATED"
|
||||||
|
, "READY"
|
||||||
|
, "YESTERDAY"
|
||||||
|
, "TOMORROW"
|
||||||
|
, "TAGGED"
|
||||||
|
, "PENDING"
|
||||||
|
, "COMPLETED"
|
||||||
|
, "DELETED"
|
||||||
|
, "UDA"
|
||||||
|
, "ORPHAN"
|
||||||
|
, "PRIORITY"
|
||||||
|
, "PROJECT"
|
||||||
|
, "LATEST"
|
||||||
|
, "nocal"
|
||||||
|
, "nonag"
|
||||||
|
, "nocolor"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around 'tagSelectWithConfig' using the default GSConfig
|
||||||
|
tagSelect :: X ()
|
||||||
|
tagSelect = tagSelectWithConfig (defaultTWGSConfig, buildTWGSConfig 300)
|
||||||
|
|
||||||
|
|
||||||
|
-- | A GridSelect displaying a list of all pending projects. After a project has been selected, a second gridselect showing a filtered list of taskwarrior tasks that belong to the selected project will be displayed.
|
||||||
|
projectSelectWithConfig
|
||||||
|
:: (GSConfig (X ()), GSConfig (X ())) -- ^ A tuple containing two GSConfigs. The first one is used to configure the gridselect displaying the list of pending projects. The second one is used to configure the gridselect displaying the resulting filtered list of tasks.
|
||||||
|
-> X () -- ^ a GridSelect displaying a list of all pending projects
|
||||||
|
projectSelectWithConfig (fstGsConfig, sndGsConfig) =
|
||||||
|
io (getTaskwarriorIds "status:pending" "projects") >>= \bs -> case bs of
|
||||||
|
[] -> safeSpawn "firefox" []
|
||||||
|
_ -> runSelectedAction fstGsConfig . finishGS $ fmap openBuffer bs
|
||||||
|
where
|
||||||
|
finishGS = (("[Finish]", unsafeSpawn "") :)
|
||||||
|
openBuffer x = (x, taskSelectWithConfig ("project:" ++ x) sndGsConfig)
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around 'projectSelectWithConfig' using the default GSConfig
|
||||||
|
projectSelect :: X ()
|
||||||
|
projectSelect =
|
||||||
|
projectSelectWithConfig (defaultTWGSConfig, buildTWGSConfig 300)
|
||||||
|
|
||||||
|
|
||||||
|
-- | A GridSelect displaying a list of due dates. After a due date has been selected, a second gridselect showing a filtered list of taskwarrior tasks will be displayed.
|
||||||
|
dueSelectWithConfig
|
||||||
|
:: (GSConfig (X ()), GSConfig (X ())) -- ^ A tuple containing two GSConfigs. The first one is used to configure the gridselect displaying the list of due dates. The second one is used to configure the gridselect displaying the resulting filtered list of tasks.
|
||||||
|
-> X () -- ^ a GridSelect displaying a list of all due dates
|
||||||
|
dueSelectWithConfig (fstGsConfig, sndGsConfig) = runSelectedAction
|
||||||
|
fstGsConfig
|
||||||
|
actions
|
||||||
|
where
|
||||||
|
actions =
|
||||||
|
[ ("overdue" , taskSelectWithConfig "+OVERDUE" sndGsConfig)
|
||||||
|
, ("today" , taskSelectWithConfig "+TODAY" sndGsConfig)
|
||||||
|
, ("tomorrow", taskSelectWithConfig "+TOMORROW" sndGsConfig)
|
||||||
|
, ("week" , taskSelectWithConfig "+WEEK" sndGsConfig)
|
||||||
|
, ("month" , taskSelectWithConfig "+MONTH" sndGsConfig)
|
||||||
|
, ("year" , taskSelectWithConfig "+YEAR" sndGsConfig)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around 'dueSelectWithConfig' using the default GSConfig
|
||||||
|
dueSelect :: X ()
|
||||||
|
dueSelect = dueSelectWithConfig (defaultTWGSConfig, buildTWGSConfig 300)
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around 'togglePriorityWithConfig' using the default GridSelect.Extras.GSConfig
|
||||||
|
togglePriority
|
||||||
|
:: String -- ^ the priority that should be toggled
|
||||||
|
-> X () -- ^ the resulting gridselect
|
||||||
|
togglePriority = togglePriorityWithConfig (buildTWGSExtraConfig 300)
|
||||||
|
|
||||||
|
|
||||||
|
-- | A gridselect showing all pending tasks. The tasks are colored according to their priority. Selecting a task toggles its priority.
|
||||||
|
togglePriorityWithConfig
|
||||||
|
:: GridSelect.Extras.GSConfig (X ()) -- ^ a GridSelect.Extras.GSConfig used for the gridselect
|
||||||
|
-> String -- ^ the priority that should be toggled
|
||||||
|
-> X () -- ^ the resulting gridselect
|
||||||
|
togglePriorityWithConfig gsConfig priority =
|
||||||
|
io (getTaskwarriorTaskList "+INBOX" ["id", "description", "priority"])
|
||||||
|
>>= \bs -> case bs of
|
||||||
|
[] -> safeSpawn "firefox" []
|
||||||
|
_ ->
|
||||||
|
GridSelect.Extras.runSelectedActionWithMessageAndIcon
|
||||||
|
gsConfig
|
||||||
|
("Select " ++ priority ++ "s")
|
||||||
|
twicon
|
||||||
|
. startEmacs
|
||||||
|
$ fmap (openBuffer priority) bs
|
||||||
|
where
|
||||||
|
startEmacs = (("[Finish]", safeSpawn "task" []) :)
|
||||||
|
openBuffer priority x =
|
||||||
|
( if x !! 2 /= "" then x !! 2 ++ ": " ++ x !! 1 else x !! 1
|
||||||
|
, toggleP priority x
|
||||||
|
)
|
||||||
|
toggleP priority x = if x !! 2 == priority
|
||||||
|
then unsafeSpawn ("task " ++ head x ++ " modify priority:")
|
||||||
|
>> togglePriority priority
|
||||||
|
else unsafeSpawn ("task " ++ head x ++ " modify priority:" ++ priority)
|
||||||
|
>> togglePriority priority
|
||||||
|
|
||||||
|
|
||||||
|
-- | Method used to build a GridSelect.Extra.GSConfig by specifying a custom cellwidth
|
||||||
|
buildTWGSExtraConfig
|
||||||
|
:: Integer -- ^ the cellwidth
|
||||||
|
-> GridSelect.Extras.GSConfig (X ()) -- ^ the resulting GridSelect.Extra.GSConfig
|
||||||
|
buildTWGSExtraConfig cellwidth = GridSelect.Extras.def
|
||||||
|
{ GridSelect.Extras.gs_cellheight = 50
|
||||||
|
, GridSelect.Extras.gs_cellwidth = cellwidth
|
||||||
|
, GridSelect.Extras.gs_cellpadding = 10
|
||||||
|
, GridSelect.Extras.gs_font = "xft:Liberation Mono:size=9:antialias=true"
|
||||||
|
, GridSelect.Extras.gs_navigate = GridSelect.Extras.defaultNavigation
|
||||||
|
, GridSelect.Extras.gs_originFractX = 1 / 2
|
||||||
|
, GridSelect.Extras.gs_originFractY = 1 / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- | Method used to build a GSConfig by specifying a custom cellwidth
|
||||||
|
buildTWGSConfig
|
||||||
|
:: Integer -- ^ the cellwidth
|
||||||
|
-> GSConfig (X ()) -- ^ the resulting GSConfig
|
||||||
|
buildTWGSConfig cellwidth = (buildDefaultGSConfig myColorizer)
|
||||||
|
{ gs_cellheight = 50
|
||||||
|
, gs_cellwidth = cellwidth
|
||||||
|
, gs_cellpadding = 10
|
||||||
|
, gs_font = "xft:Liberation Mono:size=9:antialias=true"
|
||||||
|
, gs_navigate = defaultNavigation
|
||||||
|
, gs_originFractX = 1 / 2
|
||||||
|
, gs_originFractY = 1 / 2
|
||||||
|
}
|
||||||
|
where
|
||||||
|
myColorizer :: a -> Bool -> X (String, String)
|
||||||
|
myColorizer _ p | p = pure ("#f44336", "#1a1a1a")
|
||||||
|
| otherwise = pure ("#1a1a1a", "gray")
|
||||||
|
|
||||||
|
|
||||||
|
-- | The default GridSelect.Extra.GSConfig used for taskwarrior GridSelects
|
||||||
|
defaultTWGSExtraConfig :: GridSelect.Extras.GSConfig (X ())
|
||||||
|
defaultTWGSExtraConfig = buildTWGSExtraConfig 130
|
||||||
|
|
||||||
|
|
||||||
|
-- | The default GSConfig used for taskwarrior GridSelects
|
||||||
|
defaultTWGSConfig :: GSConfig (X ())
|
||||||
|
defaultTWGSConfig = buildTWGSConfig 130
|
||||||
|
|
||||||
|
|
116
home/xmonad/.xmonad/lib/TaskMonad/Prompt.hs
Normal file
116
home/xmonad/.xmonad/lib/TaskMonad/Prompt.hs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
{-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : TaskMonad.Prompt
|
||||||
|
-- Copyright : Max Magorsch <max@magorsch.de>
|
||||||
|
-- License : BSD-style (see LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : Max Magorsch <max@magorsch.de>
|
||||||
|
-- Stability : unstable
|
||||||
|
-- Portability : unportable
|
||||||
|
--
|
||||||
|
-- TaskMonad.Prompt provides wrappers around [XMonad.Prompt.Input]
|
||||||
|
-- (https://hackage.haskell.org/package/xmonad-contrib/docs/XMonad-Prompt-Input.html)
|
||||||
|
-- for usage with taskwarrior
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module TaskMonad.Prompt
|
||||||
|
( taskwarriorPrompt
|
||||||
|
,
|
||||||
|
-- * Screenshots
|
||||||
|
-- $screenshots
|
||||||
|
customPrompt
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import System.IO
|
||||||
|
import Control.Monad ( filterM )
|
||||||
|
|
||||||
|
import XMonad hiding ( liftX )
|
||||||
|
import XMonad.Util.Font
|
||||||
|
import qualified XMonad.StackSet as W
|
||||||
|
import XMonad.Layout.Decoration
|
||||||
|
import XMonad.Prompt
|
||||||
|
import XMonad.Prompt.Input
|
||||||
|
import XMonad.Util.Image
|
||||||
|
import XMonad.Util.NamedWindows
|
||||||
|
import XMonad.Util.XUtils
|
||||||
|
import XMonad.Util.NamedScratchpad
|
||||||
|
import XMonad.Util.Run
|
||||||
|
|
||||||
|
import qualified GridSelect.Extras
|
||||||
|
|
||||||
|
import TaskMonad.ScratchPad
|
||||||
|
import TaskMonad.Utils
|
||||||
|
import TaskMonad.GridSelect ( togglePriority
|
||||||
|
, defaultTWGSExtraConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
-- $screenshots
|
||||||
|
--
|
||||||
|
-- TaskMonad.Prompt in action:
|
||||||
|
--
|
||||||
|
-- <<https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-prompt.png>>
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around [XMonad.Prompt.Input]
|
||||||
|
-- (https://hackage.haskell.org/package/xmonad-contrib/docs/XMonad-Prompt-Input.html)
|
||||||
|
-- using a custom 'XPconfig'
|
||||||
|
customPrompt
|
||||||
|
:: String -- ^ title that will be displayed in the prompt
|
||||||
|
-> [String] -- ^ completion list
|
||||||
|
-> (String -> X ()) -- ^ action that takes the input of the prompt and returns and X ()
|
||||||
|
-> X () -- ^ the action that shows the prompt
|
||||||
|
customPrompt name completeList action =
|
||||||
|
inputPromptWithCompl myXPConfig name (mkComplFunFromList completeList)
|
||||||
|
?+ action
|
||||||
|
where
|
||||||
|
myXPConfig = def { position = CenteredAt 0.5 0.4
|
||||||
|
, alwaysHighlight = True
|
||||||
|
, height = 60
|
||||||
|
, promptBorderWidth = 1
|
||||||
|
, font = "xft:Inconsolata:size=14"
|
||||||
|
, borderColor = "#555555"
|
||||||
|
, bgColor = "#111111"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- | A wrapper around 'customPrompt' that can be used to execute taskwarrior
|
||||||
|
-- as well as custom commands.
|
||||||
|
--
|
||||||
|
-- You can specify a list of tuples which contain
|
||||||
|
-- custom actions as well as conditions for the custom actions, like this:
|
||||||
|
--
|
||||||
|
-- > taskwarriorPrompt [(\x -> x == "processInput", processInput)]
|
||||||
|
--
|
||||||
|
-- However, if none of the specified actions is true, a default action will be executed.
|
||||||
|
-- The default action shows taskwarrior reports in a scratchpad and executes all the other commands silently.
|
||||||
|
taskwarriorPrompt
|
||||||
|
:: [(String -> Bool, X ())] -- ^ a list of tuples which contain a condition for an action as well as the action
|
||||||
|
-> X () -- ^ the resulting TaskWarrior prompt
|
||||||
|
taskwarriorPrompt possibleActions =
|
||||||
|
customPrompt "task" defaulttwreports (possiblePromptAction possibleActions)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Recursively goes through a list of conditional actions. If a condition is fulfilled, the
|
||||||
|
-- related action will be executed, otherwise the next condition in the list will be evaluated.
|
||||||
|
-- In case no condition is fulfilled the 'defaultTWPromptAction' will finally be executed.
|
||||||
|
possiblePromptAction [] command = defaultTWPromptAction command
|
||||||
|
possiblePromptAction (p : ps) command =
|
||||||
|
if fst p command then snd p else possiblePromptAction ps command
|
||||||
|
|
||||||
|
|
||||||
|
-- | The default action that will be execute in the 'taskwarriorPrompt', if no other action was executed.
|
||||||
|
defaultTWPromptAction :: String -> X ()
|
||||||
|
defaultTWPromptAction command = if isTWReport command
|
||||||
|
then twscratchpad command
|
||||||
|
else twcommand command
|
||||||
|
where
|
||||||
|
twcommand command = io (execCommandWithOutput "task" command)
|
||||||
|
>>= \bs -> unsafeSpawn $ "notify-send '" ++ bs ++ "'"
|
||||||
|
|
138
home/xmonad/.xmonad/lib/TaskMonad/ScratchPad.hs
Normal file
138
home/xmonad/.xmonad/lib/TaskMonad/ScratchPad.hs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
{-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
-- |
|
||||||
|
-- Module : TaskMonad.ScratchPad
|
||||||
|
-- Copyright : Max Magorsch <max@magorsch.de>
|
||||||
|
-- License : BSD-style (see LICENSE)
|
||||||
|
--
|
||||||
|
-- Maintainer : Max Magorsch <max@magorsch.de>
|
||||||
|
-- Stability : unstable
|
||||||
|
-- Portability : unportable
|
||||||
|
--
|
||||||
|
-- A wrapper around [XMonad.Util.NamedScratchpad]
|
||||||
|
-- (hackage.haskell.org/package/xmonad-contrib/docs/XMonad-Util-NamedScratchpad.html)
|
||||||
|
-- that can be used to display taskwarrior commands
|
||||||
|
--
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module TaskMonad.ScratchPad
|
||||||
|
(
|
||||||
|
-- * Usage
|
||||||
|
-- $usage
|
||||||
|
|
||||||
|
-- * Screenshots
|
||||||
|
-- $screenshots
|
||||||
|
taskwarriorScratchpad
|
||||||
|
, taskwarriorScratchpads
|
||||||
|
, hideScratchpadAction
|
||||||
|
, twscratchpad
|
||||||
|
, runTmuxCommand
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import System.IO
|
||||||
|
import Control.Monad ( filterM )
|
||||||
|
|
||||||
|
import XMonad hiding ( liftX )
|
||||||
|
import XMonad.Util.Font
|
||||||
|
import qualified XMonad.StackSet as W
|
||||||
|
import XMonad.Layout.Decoration
|
||||||
|
import XMonad.Prompt
|
||||||
|
import XMonad.Prompt.Input
|
||||||
|
import XMonad.Util.Image
|
||||||
|
import XMonad.Util.NamedWindows
|
||||||
|
import XMonad.Util.XUtils
|
||||||
|
import XMonad.Util.NamedScratchpad
|
||||||
|
import XMonad.Util.Run
|
||||||
|
import XMonad.Actions.GridSelect
|
||||||
|
|
||||||
|
import qualified GridSelect.Extras
|
||||||
|
|
||||||
|
-- $usage
|
||||||
|
--
|
||||||
|
-- Just add a manage hook:
|
||||||
|
--
|
||||||
|
-- > , manageHook = namedScratchpadManageHook taskwarriorScratchpads
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- $screenshots
|
||||||
|
--
|
||||||
|
-- TaskMonad.Scratchpad in action:
|
||||||
|
--
|
||||||
|
-- <<https://raw.githubusercontent.com/mmagorsc/taskmonad/master/docs/images/taskmonad-scratchpad.png>>
|
||||||
|
|
||||||
|
|
||||||
|
-- | Open the TaskWarrior-ScratchPad
|
||||||
|
taskwarriorScratchpad :: X ()
|
||||||
|
taskwarriorScratchpad =
|
||||||
|
namedScratchpadAction taskwarriorScratchpads "taskwarrior"
|
||||||
|
|
||||||
|
-- | The TaskWarrior-Scratchpad which contains a tmux session
|
||||||
|
taskwarriorScratchpads :: [NamedScratchpad]
|
||||||
|
taskwarriorScratchpads =
|
||||||
|
[NS "taskwarrior" spawnTaskwarrior findTerm manageTerm] -- and a second ]
|
||||||
|
where
|
||||||
|
spawnTaskwarrior =
|
||||||
|
"alacritty" ++ " -t scratchpad" ++ " -e tmux new -A -s tw-scratch"
|
||||||
|
findTerm = appName =? "scratchpad" -- its window will be named "scratchpad" (see above)
|
||||||
|
manageTerm = customFloating $ W.RationalRect 0.25 0 0.5 0.6 -- l t w h
|
||||||
|
|
||||||
|
|
||||||
|
-- | Finds named scratchpad configuration by name
|
||||||
|
findByName :: NamedScratchpads -> String -> Maybe NamedScratchpad
|
||||||
|
findByName c s = listToMaybe $ filter ((s ==) . name) c
|
||||||
|
|
||||||
|
|
||||||
|
-- | Runs application which should appear in specified scratchpad
|
||||||
|
runApplication :: NamedScratchpad -> X ()
|
||||||
|
runApplication = spawn . cmd
|
||||||
|
|
||||||
|
|
||||||
|
-- | Modified version of XMonad.Util.NamedScratchpad.hideScratchpadAction
|
||||||
|
-- which can be used to just show a scratchpad and don't hide it
|
||||||
|
-- in case it is already shown
|
||||||
|
hideScratchpadAction
|
||||||
|
:: NamedScratchpads -- ^ Named scratchpads configuration
|
||||||
|
-> String -- ^ Scratchpad name
|
||||||
|
-> X ()
|
||||||
|
hideScratchpadAction confs n
|
||||||
|
| Just conf <- findByName confs n = withWindowSet $ \s -> do
|
||||||
|
-- try to find it on the current workspace
|
||||||
|
filterCurrent <- filterM
|
||||||
|
(runQuery (query conf))
|
||||||
|
((maybe [] W.integrate . W.stack . W.workspace . W.current) s)
|
||||||
|
case filterCurrent of
|
||||||
|
-- {- The following part is commented out, as it would hide the scratchpad -}
|
||||||
|
-- (x:_) -> do
|
||||||
|
-- -- create hidden workspace if it doesn't exist
|
||||||
|
-- if null (filter ((== scratchpadWorkspaceTag) . W.tag) (W.workspaces s))
|
||||||
|
-- then addHiddenWorkspace scratchpadWorkspaceTag
|
||||||
|
-- else return ()
|
||||||
|
-- -- push window there
|
||||||
|
-- windows $ W.shiftWin scratchpadWorkspaceTag x
|
||||||
|
[] -> do
|
||||||
|
-- try to find it on all workspaces
|
||||||
|
filterAll <- filterM (runQuery (query conf)) (W.allWindows s)
|
||||||
|
case filterAll of
|
||||||
|
(x : _) -> windows $ W.shiftWin (W.currentTag s) x
|
||||||
|
[] -> runApplication conf
|
||||||
|
| otherwise = return ()
|
||||||
|
|
||||||
|
|
||||||
|
-- | Send a taskwarrior command to the taskwarrior tmux session and open the taskwarrior scratchpad
|
||||||
|
twscratchpad :: String -> X ()
|
||||||
|
twscratchpad command =
|
||||||
|
runTmuxCommand ("clear && task " ++ command)
|
||||||
|
>> hideScratchpadAction taskwarriorScratchpads "taskwarrior"
|
||||||
|
|
||||||
|
|
||||||
|
-- | Send a command to the taskwarrior tmux session
|
||||||
|
runTmuxCommand :: MonadIO m => String -> m ()
|
||||||
|
runTmuxCommand command =
|
||||||
|
unsafeSpawn $ "tmux send-keys -t tw-scratch.0 '" ++ command ++ "' ENTER"
|
2743
home/xmonad/.xmonad/lib/TaskMonad/Utils.hs
Normal file
2743
home/xmonad/.xmonad/lib/TaskMonad/Utils.hs
Normal file
File diff suppressed because it is too large
Load diff
1
home/xmonad/.xmonad/lib/taskmonad
Submodule
1
home/xmonad/.xmonad/lib/taskmonad
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 287e232e9106dcef03209a91a2d98ad0cad102c7
|
|
@ -30,6 +30,7 @@ import XMonad.Layout.NoBorders (noBorders, smartBorders)
|
||||||
import XMonad.Hooks.DynamicLog
|
import XMonad.Hooks.DynamicLog
|
||||||
import XMonad.Util.Scratchpad
|
import XMonad.Util.Scratchpad
|
||||||
import XMonad.Util.NamedScratchpad
|
import XMonad.Util.NamedScratchpad
|
||||||
|
import XMonad.Util.Brightness as Bright
|
||||||
|
|
||||||
-- Layouts
|
-- Layouts
|
||||||
import XMonad.Layout.Accordion
|
import XMonad.Layout.Accordion
|
||||||
|
@ -63,6 +64,7 @@ import qualified XMonad.StackSet as W
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
|
||||||
import GruvboxColors as Colors
|
import GruvboxColors as Colors
|
||||||
|
import TaskMonad
|
||||||
|
|
||||||
|
|
||||||
home = "/home/horhik/"
|
home = "/home/horhik/"
|
||||||
|
@ -177,7 +179,7 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||||
, ((modm .|. shiftMask, xK_s ), spawn "flameshot gui")
|
, ((modm .|. shiftMask, xK_s ), spawn "flameshot gui")
|
||||||
, ((modm .|. mod1Mask , xK_space ), spawn "$HOME/.local/scripts/deadd_notify")
|
, ((modm .|. mod1Mask , xK_space ), spawn "$HOME/.local/scripts/deadd_notify")
|
||||||
-- change lang
|
-- change lang
|
||||||
, ((modm, xK_Control_R) , spawn "xkb-switch -n")
|
, ((modm, xK_Control_R) , spawn "setxkbmap us,ru; xkb-switch -n")
|
||||||
, ((modm, xK_Shift_R) , spawn "xkb-switch -n")
|
, ((modm, xK_Shift_R) , spawn "xkb-switch -n")
|
||||||
, ((modm, xK_d) , spawn "eww-toggl")
|
, ((modm, xK_d) , spawn "eww-toggl")
|
||||||
-- toggle fullscreen
|
-- toggle fullscreen
|
||||||
|
@ -191,6 +193,7 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||||
, ((modm .|. shiftMask , xK_d), namedScratchpadAction myScratchpads "todoist")
|
, ((modm .|. shiftMask , xK_d), namedScratchpadAction myScratchpads "todoist")
|
||||||
, ((modm .|. shiftMask , xK_n), namedScratchpadAction myScratchpads "rss_news")
|
, ((modm .|. shiftMask , xK_n), namedScratchpadAction myScratchpads "rss_news")
|
||||||
, ((modm .|. controlMask, xK_e), namedScratchpadAction myScratchpads "emacs")
|
, ((modm .|. controlMask, xK_e), namedScratchpadAction myScratchpads "emacs")
|
||||||
|
, ((modm , xK_w), taskwarriorPrompt [(\x -> x == "processInbox", processInbox)])
|
||||||
|
|
||||||
-- | Programs
|
-- | Programs
|
||||||
, ((modm .|. shiftMask, xK_z), spawn "zathura &") -- book reader (zathura)
|
, ((modm .|. shiftMask, xK_z), spawn "zathura &") -- book reader (zathura)
|
||||||
|
@ -224,8 +227,8 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||||
, (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]
|
, (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]
|
||||||
|
|
||||||
++
|
++
|
||||||
[ ((0, XF86.xF86XK_MonBrightnessUp ), spawn "light -A 5")
|
[ ((0, XF86.xF86XK_MonBrightnessUp ), Bright.increase)
|
||||||
, ((0, XF86.xF86XK_MonBrightnessDown), spawn "light -U 5")
|
, ((0, XF86.xF86XK_MonBrightnessDown), Bright.decrease)
|
||||||
, ((0, XF86.xF86XK_AudioPause ), spawn "playerctl play-pause")
|
, ((0, XF86.xF86XK_AudioPause ), spawn "playerctl play-pause")
|
||||||
, ((0, XF86.xF86XK_AudioPrev ), spawn "playerctl previous")
|
, ((0, XF86.xF86XK_AudioPrev ), spawn "playerctl previous")
|
||||||
, ((0, XF86.xF86XK_AudioMute ), spawn "pulsemixer --toggle-mute")
|
, ((0, XF86.xF86XK_AudioMute ), spawn "pulsemixer --toggle-mute")
|
||||||
|
@ -284,14 +287,14 @@ tall = renamed [Replace "tall"]
|
||||||
$ limitWindows 12
|
$ limitWindows 12
|
||||||
$ mySpacing 0
|
$ mySpacing 0
|
||||||
$ ResizableTall 1 (3/100) (1/2) []
|
$ ResizableTall 1 (3/100) (1/2) []
|
||||||
magnify = renamed [Replace "magnify"]
|
-- magnify = renamed [Replace "magnify"]
|
||||||
$ smartBorders
|
-- $ smartBorders
|
||||||
$ addTabs shrinkText myTabTheme
|
-- $ addTabs shrinkText myTabTheme
|
||||||
$ subLayout [] (smartBorders Simplest)
|
-- $ subLayout [] (smartBorders Simplest)
|
||||||
$ magnifier
|
-- $ magnifier
|
||||||
$ limitWindows 12
|
-- $ limitWindows 12
|
||||||
$ mySpacing 0
|
-- $ mySpacing 0
|
||||||
$ ResizableTall 1 (3/100) (1/2) []
|
-- $ ResizableTall 1 (3/100) (1/2) []
|
||||||
monocle = renamed [Replace "monocle"]
|
monocle = renamed [Replace "monocle"]
|
||||||
$ noBorders
|
$ noBorders
|
||||||
-- $ addTabs shrinkText myTabTheme
|
-- $ addTabs shrinkText myTabTheme
|
||||||
|
@ -319,15 +322,15 @@ threeCol = renamed [Replace "threeCol"]
|
||||||
$ subLayout [] (smartBorders Simplest)
|
$ subLayout [] (smartBorders Simplest)
|
||||||
$ limitWindows 7
|
$ limitWindows 7
|
||||||
$ ThreeCol 1 (3/100) (1/2)
|
$ ThreeCol 1 (3/100) (1/2)
|
||||||
threeRow = renamed [Replace "threeRow"]
|
-- threeRow = renamed [Replace "threeRow"]
|
||||||
$ smartBorders
|
-- $ smartBorders
|
||||||
$ addTabs shrinkText myTabTheme
|
-- $ addTabs shrinkText myTabTheme
|
||||||
$ subLayout [] (smartBorders Simplest)
|
-- $ subLayout [] (smartBorders Simplest)
|
||||||
$ limitWindows 7
|
-- $ limitWindows 7
|
||||||
-- Mirror takes a layout and rotates it by 90 degrees.
|
-- -- Mirror takes a layout and rotates it by 90 degrees.
|
||||||
-- So we are applying Mirror to the ThreeCol layout.
|
-- -- So we are applying Mirror to the ThreeCol layout.
|
||||||
$ Mirror
|
-- $ Mirror
|
||||||
$ ThreeCol 1 (3/100) (1/2)
|
-- $ ThreeCol 1 (3/100) (1/2)
|
||||||
tabs = renamed [Replace "tabs"]
|
tabs = renamed [Replace "tabs"]
|
||||||
-- I cannot add spacing to this layout because it will
|
-- I cannot add spacing to this layout because it will
|
||||||
-- add spacing between window and tabs which looks bad.
|
-- add spacing between window and tabs which looks bad.
|
||||||
|
@ -361,16 +364,12 @@ myLayoutHook = avoidStruts $ mouseResize $ windowArrange $ T.toggleLayouts float
|
||||||
$ mkToggle (NBFULL ?? NOBORDERS ?? EOT) myDefaultLayout
|
$ mkToggle (NBFULL ?? NOBORDERS ?? EOT) myDefaultLayout
|
||||||
where
|
where
|
||||||
myDefaultLayout = withBorder myBorderWidth tall
|
myDefaultLayout = withBorder myBorderWidth tall
|
||||||
||| noBorders magnify
|
|
||||||
||| monocle
|
||| monocle
|
||||||
||| floats
|
||| floats
|
||||||
||| noBorders tabs
|
||| noBorders tabs
|
||||||
||| grid
|
||| grid
|
||||||
||| spirals
|
||| spirals
|
||||||
||| threeCol
|
||| threeCol
|
||||||
||| threeRow
|
|
||||||
||| noBorders tallAccordion
|
|
||||||
||| noBorders wideAccordion
|
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
-- Window rules:
|
-- Window rules:
|
||||||
|
@ -394,10 +393,12 @@ myManageHook = (composeAll
|
||||||
, className =? "TerminalDropdown" --> doFloat
|
, className =? "TerminalDropdown" --> doFloat
|
||||||
, className =? "Nemo" --> doCenter
|
, className =? "Nemo" --> doCenter
|
||||||
, title =? "dropdown" --> doFloat
|
, title =? "dropdown" --> doFloat
|
||||||
|
, title =? "scratchpad" --> doFloat
|
||||||
, resource =? "desktop_window" --> doIgnore
|
, resource =? "desktop_window" --> doIgnore
|
||||||
, resource =? "kdesktop" --> doIgnore
|
, resource =? "kdesktop" --> doIgnore
|
||||||
])
|
])
|
||||||
<+> namedScratchpadManageHook myScratchpads
|
<+> namedScratchpadManageHook myScratchpads
|
||||||
|
<+> namedScratchpadManageHook taskwarriorScratchpads
|
||||||
where
|
where
|
||||||
doCenter = customFloating $ W.RationalRect l t w h
|
doCenter = customFloating $ W.RationalRect l t w h
|
||||||
where
|
where
|
||||||
|
@ -411,6 +412,7 @@ myManageHook = (composeAll
|
||||||
|
|
||||||
myScratchpads = [
|
myScratchpads = [
|
||||||
NS "terminal" spawnTerm findTerm manageTerm
|
NS "terminal" spawnTerm findTerm manageTerm
|
||||||
|
, NS "tw-term" spawnTerm findTerm manageTerm
|
||||||
, NS "htop" "alacritty -t htop -e htop " (title =? "htop") defaultFloating
|
, NS "htop" "alacritty -t htop -e htop " (title =? "htop") defaultFloating
|
||||||
, NS "pomo" "pomodone" (title =? "PomoDoneApp") defaultFloating
|
, NS "pomo" "pomodone" (title =? "PomoDoneApp") defaultFloating
|
||||||
, NS "notion" "notion" (title =? "Notion") defaultFloating
|
, NS "notion" "notion" (title =? "Notion") defaultFloating
|
||||||
|
@ -421,6 +423,19 @@ myScratchpads = [
|
||||||
, NS "rss_news" spawnRSS findRSS manageRSS
|
, NS "rss_news" spawnRSS findRSS manageRSS
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
|
classTW = "scratchpad"
|
||||||
|
titleTW = "scratchpad"
|
||||||
|
spawnTW = "alacritty --t " ++ titleTW ++ " --class " ++ classTW
|
||||||
|
findTW = title =? titleTW
|
||||||
|
manageTW = customFloating $ W.RationalRect l t w h
|
||||||
|
where
|
||||||
|
h = 0.3 -- height, 50%
|
||||||
|
w = 0.2 -- width, 50%
|
||||||
|
t = 0 -- bottom edge
|
||||||
|
l = (1 - w) / 2 -- centered left/right
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
classTerm = "TerminalDropdown"
|
classTerm = "TerminalDropdown"
|
||||||
titleTerm = "!dropdown!"
|
titleTerm = "!dropdown!"
|
||||||
spawnTerm = "alacritty -t " ++ titleTerm ++ " --class " ++ classTerm
|
spawnTerm = "alacritty -t " ++ titleTerm ++ " --class " ++ classTerm
|
||||||
|
@ -546,6 +561,7 @@ myStartupHook = do
|
||||||
spawnOnce "setxkbmap us,ru &"
|
spawnOnce "setxkbmap us,ru &"
|
||||||
spawnOnce "eww daemon"
|
spawnOnce "eww daemon"
|
||||||
spawnOnce "nextcloud"
|
spawnOnce "nextcloud"
|
||||||
|
spawnOnce "thunderbird"
|
||||||
spawnOnce "superproductivity"
|
spawnOnce "superproductivity"
|
||||||
spawnOnce "syncthing"
|
spawnOnce "syncthing"
|
||||||
spawnOnce "sh ssh-agent bash ; ssh-add ~/.ssh/arch"
|
spawnOnce "sh ssh-agent bash ; ssh-add ~/.ssh/arch"
|
||||||
|
@ -558,7 +574,7 @@ myStartupHook = do
|
||||||
-- spawnOnce ("cd /home/horhik/Freenet/downloads/fms; ./fms --daemon &")
|
-- spawnOnce ("cd /home/horhik/Freenet/downloads/fms; ./fms --daemon &")
|
||||||
spawnOnce "xautolock -time 25 -locker i3lock-fancy-multimonitor -notifier 'xkb-switch -s us' &"
|
spawnOnce "xautolock -time 25 -locker i3lock-fancy-multimonitor -notifier 'xkb-switch -s us' &"
|
||||||
spawnOnce "eval '$(ssh-agent -s)'; ssh-add ~/.ssh/id_rsa &"
|
spawnOnce "eval '$(ssh-agent -s)'; ssh-add ~/.ssh/id_rsa &"
|
||||||
spawnOnce "xrandr --output HDMI-A-0 --left-of eDP &"
|
spawnOnce "xrandr --output HDMI-A-0 --right-of eDP &"
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
-- Now run xmonad with all the defaults we set up.
|
-- Now run xmonad with all the defaults we set up.
|
||||||
|
|
Loading…
Reference in a new issue