2020-03-15 15:50:07 -04:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2020-03-20 01:12:20 -04:00
|
|
|
{-# LANGUAGE MultiWayIf #-}
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
module Main (main) where
|
|
|
|
|
2020-03-22 23:23:02 -04:00
|
|
|
import ACPI
|
|
|
|
import DBus.Common
|
|
|
|
import DBus.IntelBacklight
|
|
|
|
import DBus.Screensaver
|
2020-03-26 09:37:46 -04:00
|
|
|
import Notify
|
|
|
|
import Process
|
|
|
|
import SendXMsg
|
|
|
|
import Shell
|
2020-03-22 23:23:02 -04:00
|
|
|
import qualified Theme as T
|
2020-03-26 09:37:46 -04:00
|
|
|
import WorkspaceMon
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-26 09:37:46 -04:00
|
|
|
import Control.Arrow (first)
|
2020-03-22 23:23:02 -04:00
|
|
|
import Control.Concurrent
|
|
|
|
import Control.Monad
|
|
|
|
( forM
|
|
|
|
, forM_
|
|
|
|
, mapM_
|
|
|
|
, void
|
|
|
|
, when
|
|
|
|
)
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-22 23:23:02 -04:00
|
|
|
import Data.List (find, sortBy, sortOn)
|
|
|
|
import qualified Data.Map.Lazy as M
|
|
|
|
import Data.Maybe (catMaybes, isJust)
|
|
|
|
import Data.Monoid (All (..))
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-26 09:37:46 -04:00
|
|
|
import DBus.Client (Client)
|
|
|
|
|
2020-03-22 23:23:02 -04:00
|
|
|
import Graphics.X11.Types
|
|
|
|
import Graphics.X11.Xlib.Atom
|
|
|
|
import Graphics.X11.Xlib.Extras
|
|
|
|
import Graphics.X11.Xrandr
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-22 23:23:02 -04:00
|
|
|
import System.Directory
|
|
|
|
import System.Exit
|
|
|
|
import System.IO
|
|
|
|
import System.Posix.IO
|
|
|
|
import System.Posix.Process
|
|
|
|
import System.Posix.Types
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-22 23:23:02 -04:00
|
|
|
import Text.Read (readMaybe)
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-25 18:55:52 -04:00
|
|
|
import Xmobar.Common
|
|
|
|
|
2020-03-22 23:23:02 -04:00
|
|
|
import XMonad
|
|
|
|
import XMonad.Actions.CopyWindow
|
|
|
|
import XMonad.Actions.CycleWS
|
|
|
|
import XMonad.Actions.DynamicWorkspaces
|
|
|
|
import XMonad.Actions.PhysicalScreens
|
|
|
|
import XMonad.Actions.Volume
|
|
|
|
import XMonad.Hooks.EwmhDesktops
|
|
|
|
import XMonad.Hooks.ManageDocks
|
|
|
|
import XMonad.Hooks.ManageHelpers
|
|
|
|
import XMonad.Layout.Named
|
|
|
|
import XMonad.Layout.NoBorders
|
|
|
|
import XMonad.Layout.NoFrillsDecoration
|
|
|
|
import XMonad.Layout.PerWorkspace
|
|
|
|
import XMonad.Layout.Tabbed
|
|
|
|
import XMonad.Prompt
|
|
|
|
import XMonad.Prompt.ConfirmPrompt
|
2020-03-26 09:37:46 -04:00
|
|
|
import qualified XMonad.StackSet as W
|
2020-03-22 23:23:02 -04:00
|
|
|
import XMonad.Util.EZConfig
|
|
|
|
import XMonad.Util.NamedActions
|
|
|
|
import XMonad.Util.Run
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
main :: IO ()
|
2020-03-15 15:50:07 -04:00
|
|
|
main = do
|
2020-03-20 00:51:36 -04:00
|
|
|
dbClient <- startXMonadService
|
2020-03-15 15:50:07 -04:00
|
|
|
(barPID, h) <- spawnPipe' "xmobar"
|
2020-03-22 23:20:10 -04:00
|
|
|
_ <- forkIO runPowermon
|
2020-03-25 14:45:21 -04:00
|
|
|
_ <- forkIO runWorkspaceMon'
|
2020-03-15 15:50:07 -04:00
|
|
|
launch
|
|
|
|
$ ewmh
|
2020-03-26 09:48:02 -04:00
|
|
|
$ addDescrKeys' ((myModMask, xK_F1), showKeybindings) (mkKeys [barPID] dbClient)
|
2020-03-15 15:50:07 -04:00
|
|
|
$ def { terminal = myTerm
|
|
|
|
, modMask = myModMask
|
|
|
|
, layoutHook = myLayouts
|
|
|
|
, manageHook = myManageHook <+> manageDocks <+> manageHook def
|
|
|
|
, handleEventHook = myEventHook <+> docksEventHook <+> handleEventHook def
|
|
|
|
, startupHook = docksStartupHook <+> startupHook def
|
|
|
|
, workspaces = myWorkspaces
|
|
|
|
, logHook = myLoghook h
|
|
|
|
, clickJustFocuses = False
|
|
|
|
, focusFollowsMouse = False
|
2020-03-22 17:59:49 -04:00
|
|
|
, normalBorderColor = T.bordersColor
|
|
|
|
, focusedBorderColor = T.selectedBordersColor
|
2020-03-15 15:50:07 -04:00
|
|
|
}
|
|
|
|
|
2020-03-25 14:45:21 -04:00
|
|
|
runWorkspaceMon' :: IO ()
|
|
|
|
runWorkspaceMon' = runWorkspaceMon
|
|
|
|
$ fromList [ (myGimpClass, myGimpWorkspace)
|
|
|
|
, (myVMClass, myVMWorkspace)
|
|
|
|
]
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
spawnPipe' :: MonadIO m => String -> m (ProcessID, Handle)
|
|
|
|
spawnPipe' x = io $ do
|
2020-03-20 01:12:20 -04:00
|
|
|
(rd, wr) <- createPipe
|
|
|
|
setFdOption wr CloseOnExec True
|
|
|
|
h <- fdToHandle wr
|
|
|
|
hSetBuffering h LineBuffering
|
|
|
|
p <- xfork $ do
|
|
|
|
_ <- dupTo rd stdInput
|
|
|
|
executeFile "/bin/sh" False ["-c", x] Nothing
|
|
|
|
closeFd rd
|
|
|
|
return (p, h)
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
myWorkspaces :: [String]
|
2020-03-15 15:50:07 -04:00
|
|
|
myWorkspaces = map show [1..10 :: Int]
|
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
myVMWorkspace :: String
|
2020-03-15 15:50:07 -04:00
|
|
|
myVMWorkspace = "VM"
|
2020-03-20 01:15:22 -04:00
|
|
|
|
2020-03-25 14:09:07 -04:00
|
|
|
myVMClass :: String
|
|
|
|
myVMClass = "VirtualBoxVM"
|
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
myGimpWorkspace :: String
|
2020-03-15 15:50:07 -04:00
|
|
|
myGimpWorkspace = "GIMP"
|
|
|
|
|
2020-03-25 14:09:07 -04:00
|
|
|
myGimpClass :: String
|
|
|
|
myGimpClass = "Gimp"
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
myLayouts = onWorkspace myVMWorkspace (noBorders Full)
|
|
|
|
-- $ onWorkspace myGimpWorkspace gimpLayout
|
|
|
|
$ tall ||| single ||| full
|
|
|
|
where
|
2020-03-16 14:03:02 -04:00
|
|
|
addTopBar = noFrillsDeco shrinkText T.tabbedTheme
|
2020-03-15 15:50:07 -04:00
|
|
|
tall = named "Tall"
|
|
|
|
$ avoidStruts
|
|
|
|
$ addTopBar
|
|
|
|
$ noBorders
|
|
|
|
$ Tall 1 0.03 0.5
|
|
|
|
single = named "Tabbed"
|
|
|
|
-- $ addTopBar
|
|
|
|
$ avoidStruts
|
|
|
|
$ noBorders
|
2020-03-16 14:03:02 -04:00
|
|
|
$ tabbedAlways shrinkText T.tabbedTheme
|
2020-03-15 15:50:07 -04:00
|
|
|
full = named "Full"
|
|
|
|
$ noBorders Full
|
|
|
|
-- gimpLayout = named "Gimp Layout"
|
|
|
|
-- $ avoidStruts
|
|
|
|
-- $ (tabbedAlways shrinkText defaultTheme) ****||* Full
|
|
|
|
-- -- $ withIM (11/64) (Or (Title "Toolbox") (Title "Tool Options"))
|
|
|
|
-- -- $ (tabbedAlways shrinkText defaultTheme)
|
|
|
|
|
|
|
|
-- | Format workspace and layout in loghook
|
|
|
|
-- The format will be like "[<1> 2 3] 4 5 | LAYOUT" where each digit
|
|
|
|
-- is the workspace and LAYOUT is the current layout. Each workspace
|
|
|
|
-- in the brackets is currently visible and the order reflects the
|
|
|
|
-- physical location of each screen. The "<>" is the workspace
|
|
|
|
-- that currently has focus
|
2020-03-20 01:15:22 -04:00
|
|
|
myLoghook :: Handle -> X ()
|
2020-03-15 15:50:07 -04:00
|
|
|
myLoghook h = withWindowSet $ io . hPutStrLn h . myWindowSetXinerama
|
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
myWindowSetXinerama
|
|
|
|
:: LayoutClass layout a1 =>
|
|
|
|
W.StackSet String (layout a1) a2 ScreenId ScreenDetail -> String
|
2020-03-15 15:50:07 -04:00
|
|
|
myWindowSetXinerama ws = wsString ++ sep ++ layout
|
|
|
|
where
|
2020-03-25 18:55:52 -04:00
|
|
|
wsString = wrapColorBg T.backdropFgColor "" $ onscreen ++ offscreen'
|
2020-03-16 14:03:02 -04:00
|
|
|
offscreen' = if null offscreen then "" else " " ++ offscreen
|
2020-03-25 18:55:52 -04:00
|
|
|
sep = wrapColorBg T.backdropFgColor "" " : "
|
|
|
|
onscreen = wrapColorBg hilightFgColor hilightBgColor
|
|
|
|
$ (\s -> " " ++ s ++ " ")
|
2020-03-15 15:50:07 -04:00
|
|
|
$ unwords
|
|
|
|
$ map (fmtTags . W.tag . W.workspace)
|
|
|
|
. sortBy compareXCoord
|
|
|
|
$ W.current ws : W.visible ws
|
|
|
|
fmtTags t = if t == W.currentTag ws
|
2020-03-25 18:55:52 -04:00
|
|
|
then wrapColorBg T.fgColor hilightBgColor t
|
2020-03-15 15:50:07 -04:00
|
|
|
else t
|
|
|
|
offscreen = unwords
|
|
|
|
$ map W.tag
|
|
|
|
. filter (isJust . W.stack)
|
|
|
|
. sortOn W.tag
|
|
|
|
$ W.hidden ws
|
2020-03-16 14:03:02 -04:00
|
|
|
hilightBgColor = "#8fc7ff"
|
|
|
|
hilightFgColor = T.blend' 0.5 hilightBgColor T.fgColor
|
2020-03-15 15:50:07 -04:00
|
|
|
layout = description . W.layout . W.workspace . W.current $ ws
|
|
|
|
compareXCoord s0 s1 = compare x0 x1
|
|
|
|
where
|
|
|
|
(_, Rectangle x0 _ _ _) = getScreenIdAndRectangle s0
|
|
|
|
(_, Rectangle x1 _ _ _) = getScreenIdAndRectangle s1
|
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
myManageHook :: ManageHook
|
2020-03-15 15:50:07 -04:00
|
|
|
myManageHook = composeOne
|
|
|
|
-- assume virtualbox is not run with the toolbar in fullscreen mode
|
|
|
|
-- as this makes a new window that confusingly must go over the
|
|
|
|
-- actual VM window
|
2020-03-25 14:09:07 -04:00
|
|
|
[ className =? myVMClass -?> doShift myVMWorkspace
|
2020-03-15 15:50:07 -04:00
|
|
|
-- the seafile applet
|
|
|
|
, className =? "Seafile Client" -?> doFloat
|
|
|
|
-- gnucash
|
|
|
|
, (className =? "Gnucash" <&&> title =? "Transaction Import Assistant") -?> doFloat
|
|
|
|
-- xsane
|
|
|
|
, className =? "Xsane" -?> doFloat
|
|
|
|
-- all of GIMP
|
2020-03-25 14:09:07 -04:00
|
|
|
, className =? myGimpClass -?> doFloat >> doShift myGimpWorkspace
|
2020-03-15 15:50:07 -04:00
|
|
|
-- , title =? "GIMP Startup" -?> doIgnore
|
|
|
|
-- plots and graphics created by R
|
|
|
|
, className =? "R_x11" -?> doFloat
|
|
|
|
, className =? "mpv" -?> doFloat
|
|
|
|
-- the floating windows created by the brave browser
|
|
|
|
, stringProperty "WM_NAME" =? "Brave" -?> doFloat
|
|
|
|
, (stringProperty "WM_WINDOW_ROLE" =? "pop-up"
|
|
|
|
<&&> className =? "Brave-browser") -?> doFloat
|
|
|
|
-- the dialog windows created by the zotero addon in Google Docs
|
|
|
|
, (className =? "Zotero" <&&> resource =? "Toplevel") -?> doFloat
|
|
|
|
, isDialog -?> doCenterFloat
|
|
|
|
]
|
|
|
|
|
2020-03-20 01:15:22 -04:00
|
|
|
myEventHook :: Event -> X All
|
2020-03-15 15:50:07 -04:00
|
|
|
myEventHook ClientMessageEvent { ev_message_type = t, ev_data = d }
|
|
|
|
| t == bITMAP = do
|
2020-03-25 14:45:21 -04:00
|
|
|
let (xtype, tag) = splitXMsg d
|
|
|
|
case xtype of
|
|
|
|
Workspace -> removeEmptyWorkspaceByTag tag
|
|
|
|
ACPI -> do
|
2020-03-22 23:20:10 -04:00
|
|
|
let acpiTag = toEnum <$> readMaybe tag :: Maybe ACPIEvent
|
2020-03-15 15:50:07 -04:00
|
|
|
forM_ acpiTag $ \case
|
2020-03-26 09:37:46 -04:00
|
|
|
Power -> runPowerPrompt
|
2020-03-16 14:03:02 -04:00
|
|
|
Sleep -> confirmPrompt T.promptTheme "suspend?" runSuspend
|
2020-03-15 15:50:07 -04:00
|
|
|
LidClose -> do
|
|
|
|
status <- io isDischarging
|
|
|
|
forM_ status $ \s -> runScreenLock >> when s runSuspend
|
|
|
|
return (All True)
|
|
|
|
myEventHook _ = return (All True)
|
|
|
|
|
|
|
|
data PowerPrompt = PowerPrompt
|
|
|
|
|
|
|
|
instance XPrompt PowerPrompt where
|
2020-03-16 19:34:13 -04:00
|
|
|
showXPrompt PowerPrompt = "(P)oweroff (S)uspend (H)ibernate (R)eboot:"
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runScreenLock :: X ()
|
|
|
|
runScreenLock = spawn "screenlock"
|
|
|
|
|
|
|
|
runPowerOff :: X ()
|
|
|
|
runPowerOff = spawn "systemctl poweroff"
|
|
|
|
|
|
|
|
runSuspend :: X ()
|
|
|
|
runSuspend = spawn "systemctl suspend"
|
|
|
|
|
|
|
|
runHibernate :: X ()
|
|
|
|
runHibernate = spawn "systemctl hibernate"
|
|
|
|
|
|
|
|
runReboot :: X ()
|
|
|
|
runReboot = spawn "systemctl reboot"
|
|
|
|
|
2020-03-22 23:46:56 -04:00
|
|
|
data PowerAction = Poweroff
|
|
|
|
| Shutdown
|
|
|
|
| Hibernate
|
|
|
|
| Reboot
|
|
|
|
deriving (Eq)
|
|
|
|
|
|
|
|
instance Enum PowerAction where
|
|
|
|
toEnum 0 = Poweroff
|
|
|
|
toEnum 1 = Shutdown
|
|
|
|
toEnum 2 = Hibernate
|
|
|
|
toEnum 3 = Reboot
|
|
|
|
toEnum _ = errorWithoutStackTrace "Main.Enum.PowerAction.toEnum: bad argument"
|
|
|
|
|
|
|
|
fromEnum Poweroff = 0
|
|
|
|
fromEnum Shutdown = 1
|
|
|
|
fromEnum Hibernate = 2
|
|
|
|
fromEnum Reboot = 3
|
|
|
|
|
2020-03-26 09:37:46 -04:00
|
|
|
runPowerPrompt :: X ()
|
|
|
|
runPowerPrompt = mkXPrompt PowerPrompt theme comp executeAction
|
2020-03-15 15:50:07 -04:00
|
|
|
where
|
2020-03-16 19:34:13 -04:00
|
|
|
comp = mkComplFunFromList []
|
|
|
|
theme = T.promptTheme { promptKeymap = keymap }
|
|
|
|
keymap = M.fromList
|
2020-03-16 19:36:12 -04:00
|
|
|
$ ((controlMask, xK_g), quit) :
|
2020-03-16 19:34:13 -04:00
|
|
|
map (first $ (,) 0)
|
2020-03-22 23:46:56 -04:00
|
|
|
[ (xK_p, sendAction Poweroff)
|
|
|
|
, (xK_s, sendAction Shutdown)
|
|
|
|
, (xK_h, sendAction Hibernate)
|
|
|
|
, (xK_r, sendAction Reboot)
|
2020-03-16 19:34:13 -04:00
|
|
|
, (xK_Return, quit)
|
|
|
|
, (xK_Escape, quit)
|
2020-03-15 15:50:07 -04:00
|
|
|
]
|
2020-03-22 23:46:56 -04:00
|
|
|
sendAction a = setInput (show $ fromEnum a) >> setSuccess True >> setDone True
|
|
|
|
executeAction a = case toEnum $ read a of
|
|
|
|
Poweroff -> runPowerOff
|
|
|
|
Shutdown -> runScreenLock >> runSuspend
|
|
|
|
Hibernate -> runScreenLock >> runHibernate
|
|
|
|
Reboot -> runReboot
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-26 09:37:46 -04:00
|
|
|
runQuitPrompt :: X ()
|
|
|
|
runQuitPrompt = confirmPrompt T.promptTheme "quit?" $ io exitSuccess
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-17 00:30:04 -04:00
|
|
|
-- TODO for some reason the screen never wakes up after suspend when
|
|
|
|
-- the nvidia card is up, so block suspend if nvidia card is running
|
|
|
|
-- and warn user
|
2020-03-16 20:16:53 -04:00
|
|
|
isUsingNvidia :: IO Bool
|
|
|
|
isUsingNvidia = doesDirectoryExist "/sys/module/nvidia"
|
|
|
|
|
|
|
|
runOptimusPrompt :: X ()
|
|
|
|
runOptimusPrompt = do
|
|
|
|
nvidiaOn <- io isUsingNvidia
|
|
|
|
switch $ if nvidiaOn then "intel" else "nvidia"
|
|
|
|
where
|
|
|
|
switch mode = confirmPrompt T.promptTheme (prompt mode) (cmd mode)
|
|
|
|
prompt mode = "gpu switch to " ++ mode ++ "?"
|
|
|
|
cmd mode = spawnCmd "optimus-manager"
|
|
|
|
["--switch", mode, "--no-confirm"]
|
|
|
|
>> io exitSuccess
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
-- shell commands
|
|
|
|
|
|
|
|
myTerm :: String
|
|
|
|
myTerm = "urxvt"
|
|
|
|
|
|
|
|
runTerm :: X ()
|
|
|
|
runTerm = spawn myTerm
|
|
|
|
|
|
|
|
runCalc :: X ()
|
|
|
|
runCalc = spawnCmd myTerm ["-e", "R"]
|
|
|
|
|
|
|
|
myDmenuCmd :: String
|
|
|
|
myDmenuCmd = "rofi"
|
|
|
|
|
2020-03-17 23:40:09 -04:00
|
|
|
-- | Focus rofi on the current workspace always
|
|
|
|
-- Apparently xrandr and xinerama order monitors differently, which
|
|
|
|
-- means they have different indices. Since rofi uses the former and
|
|
|
|
-- xmonad uses the latter, this function is to figure out the xrandr
|
|
|
|
-- screen name based on the xinerama screen that is currently in
|
|
|
|
-- focus. The steps to do this:
|
|
|
|
-- 1) get the coordinates of the currently focuses xinerama screen
|
|
|
|
-- 2) get list of Xrandr outputs and filter which ones are connected
|
|
|
|
-- 3) match the coordinates of the xinerama screen with the xrandr
|
|
|
|
-- output and return the latter's name (eg "DP-0") which can be
|
|
|
|
-- fed to Rofi
|
2020-03-17 00:30:04 -04:00
|
|
|
getMonitorName :: X (Maybe String)
|
|
|
|
getMonitorName = do
|
|
|
|
dpy <- asks display
|
|
|
|
root <- asks theRoot
|
2020-03-17 23:24:44 -04:00
|
|
|
-- these are the focused screen coordinates according to xinerama
|
|
|
|
(sx, sy) <- getCoords
|
|
|
|
io $ do
|
|
|
|
res <- xrrGetScreenResourcesCurrent dpy root
|
|
|
|
outputs <- forM res $ \res' ->
|
|
|
|
forM (xrr_sr_outputs res') $ \output -> do
|
|
|
|
oi <- xrrGetOutputInfo dpy res' output
|
|
|
|
case oi of
|
|
|
|
-- connection: 0 == connected, 1 == disconnected
|
|
|
|
Just XRROutputInfo { xrr_oi_connection = 0
|
|
|
|
, xrr_oi_name = name
|
|
|
|
, xrr_oi_crtc = crtc
|
|
|
|
} -> do
|
|
|
|
ci <- xrrGetCrtcInfo dpy res' crtc
|
|
|
|
return $ (\ci' -> Just (name, xrr_ci_x ci', xrr_ci_y ci'))
|
|
|
|
=<< ci
|
|
|
|
_ -> return Nothing
|
|
|
|
return $ (\(name, _, _) -> Just name)
|
|
|
|
=<< find (\(_, x, y) -> x == sx && y == sy) . catMaybes
|
|
|
|
=<< outputs
|
2020-03-17 00:30:04 -04:00
|
|
|
where
|
2020-03-17 23:24:44 -04:00
|
|
|
getCoords = do
|
|
|
|
(Rectangle x y _ _) <- getFocusedScreen
|
|
|
|
return (fromIntegral x, fromIntegral y)
|
2020-03-17 00:30:04 -04:00
|
|
|
|
2020-03-26 09:37:46 -04:00
|
|
|
getFocusedScreen :: X Rectangle
|
|
|
|
getFocusedScreen = withWindowSet $ return . screenRect . W.screenDetail . W.current
|
|
|
|
|
2020-03-17 23:40:09 -04:00
|
|
|
spawnDmenuCmd :: String -> [String] -> X ()
|
|
|
|
spawnDmenuCmd cmd args = do
|
2020-03-17 00:30:04 -04:00
|
|
|
name <- getMonitorName
|
|
|
|
case name of
|
2020-03-20 01:12:20 -04:00
|
|
|
Just n -> spawnCmd cmd $ ["-m", n] ++ args
|
2020-03-17 00:30:04 -04:00
|
|
|
Nothing -> io $ putStrLn "fail"
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-17 23:40:09 -04:00
|
|
|
spawnDmenuCmd' :: [String] -> X ()
|
|
|
|
spawnDmenuCmd' = spawnDmenuCmd myDmenuCmd
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
runCmdMenu :: X ()
|
2020-03-17 23:40:09 -04:00
|
|
|
runCmdMenu = spawnDmenuCmd' ["-show", "run"]
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runAppMenu :: X ()
|
2020-03-17 23:40:09 -04:00
|
|
|
runAppMenu = spawnDmenuCmd' ["-show", "drun"]
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runClipMenu :: X ()
|
2020-03-20 01:12:20 -04:00
|
|
|
runClipMenu = spawnDmenuCmd'
|
2020-03-15 15:50:07 -04:00
|
|
|
[ "-modi", "\"clipboard:greenclip print\""
|
|
|
|
, "-show", "clipboard"
|
|
|
|
, "-run-command", "'{cmd}'"
|
|
|
|
, "-theme-str", "'#element.selected.normal { background-color: #00c44e; }'"
|
|
|
|
]
|
|
|
|
|
|
|
|
runWinMenu :: X ()
|
2020-03-17 23:40:09 -04:00
|
|
|
runWinMenu = spawnDmenuCmd' ["-show", "window"]
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runNetMenu :: X ()
|
2020-03-17 23:40:09 -04:00
|
|
|
runNetMenu = spawnDmenuCmd "networkmanager_dmenu" []
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runDevMenu :: X ()
|
2020-03-17 23:40:09 -04:00
|
|
|
runDevMenu = spawnDmenuCmd "rofi-devices" []
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runBrowser :: X ()
|
|
|
|
runBrowser = spawn "brave"
|
|
|
|
|
|
|
|
runEditor :: X ()
|
|
|
|
runEditor = spawnCmd "emacsclient"
|
|
|
|
["-c", "-e", "\"(select-frame-set-input-focus (selected-frame))\""]
|
|
|
|
|
|
|
|
runFileManager :: X ()
|
|
|
|
runFileManager = spawn "pcmanfm"
|
|
|
|
|
2020-03-15 16:25:50 -04:00
|
|
|
getScreenshotDir :: IO FilePath
|
|
|
|
getScreenshotDir = do
|
|
|
|
h <- getHomeDirectory
|
|
|
|
return $ h ++ "/Pictures/screenshots"
|
|
|
|
|
|
|
|
runFlameshot :: String -> X ()
|
|
|
|
runFlameshot mode = do
|
|
|
|
ssDir <- io getScreenshotDir
|
|
|
|
spawnCmd "flameshot" $ mode : ["-p", ssDir]
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
-- TODO this will steal focus from the current window (and puts it
|
|
|
|
-- in the root window?) ...need to fix
|
2020-03-15 16:25:50 -04:00
|
|
|
runAreaCapture :: X ()
|
|
|
|
runAreaCapture = runFlameshot "gui"
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
-- myWindowCap = "screencap -w" --external script
|
|
|
|
|
2020-03-15 16:25:50 -04:00
|
|
|
runScreenCapture :: X ()
|
|
|
|
runScreenCapture = runFlameshot "screen"
|
|
|
|
|
|
|
|
runDesktopCapture :: X ()
|
|
|
|
runDesktopCapture = runFlameshot "full"
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
runVBox :: X ()
|
2020-03-25 14:09:07 -04:00
|
|
|
runVBox = spawnCmd "vbox-start" ["win8raw"]
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runGimp :: X ()
|
2020-03-25 13:38:41 -04:00
|
|
|
runGimp = spawnCmd "gimp" []
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-20 00:51:36 -04:00
|
|
|
runCleanup :: [ProcessID] -> Client -> X ()
|
|
|
|
runCleanup ps client = io $ do
|
|
|
|
mapM_ killPID ps
|
|
|
|
stopXMonadService client
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runRestart :: X ()
|
|
|
|
runRestart = restart "xmonad" True
|
|
|
|
|
|
|
|
runRecompile :: X ()
|
|
|
|
runRecompile = do
|
|
|
|
-- assume that the conf directory contains a valid stack project
|
|
|
|
-- TODO this is hacky AF
|
|
|
|
confDir <- getXMonadDir
|
|
|
|
spawn $ cmd confDir
|
|
|
|
where
|
2020-03-16 16:02:40 -04:00
|
|
|
cmd c = fmtCmd "cd" [c]
|
|
|
|
#!&& fmtCmd "stack" ["install", ":xmonad"]
|
2020-03-18 12:17:39 -04:00
|
|
|
#!&& fmtNotifyCmd defNoteInfo { body = Just $ Text "compilation succeeded" }
|
|
|
|
#!|| fmtNotifyCmd defNoteError { body = Just $ Text "compilation failed" }
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
myMultimediaCtl :: String
|
|
|
|
myMultimediaCtl = "playerctl"
|
|
|
|
|
|
|
|
runTogglePlay :: X ()
|
|
|
|
runTogglePlay = spawnCmd myMultimediaCtl ["play-pause"]
|
|
|
|
|
|
|
|
runPrevTrack :: X ()
|
|
|
|
runPrevTrack = spawnCmd myMultimediaCtl ["previous"]
|
|
|
|
|
|
|
|
runNextTrack :: X ()
|
|
|
|
runNextTrack = spawnCmd myMultimediaCtl ["next"]
|
|
|
|
|
|
|
|
runStopPlay :: X ()
|
|
|
|
runStopPlay = spawnCmd myMultimediaCtl ["stop"]
|
|
|
|
|
|
|
|
runVolumeDown :: X ()
|
|
|
|
runVolumeDown = void (lowerVolume 2)
|
|
|
|
|
|
|
|
runVolumeUp :: X ()
|
|
|
|
runVolumeUp = void (raiseVolume 2)
|
|
|
|
|
|
|
|
runVolumeMute :: X ()
|
|
|
|
runVolumeMute = void toggleMute
|
|
|
|
|
|
|
|
runToggleBluetooth :: X ()
|
2020-03-16 16:01:04 -04:00
|
|
|
runToggleBluetooth = spawn
|
|
|
|
$ "bluetoothctl show | grep -q \"Powered: no\""
|
|
|
|
#!&& "a=on"
|
|
|
|
#!|| "a=off"
|
2020-03-16 16:02:40 -04:00
|
|
|
#!>> fmtCmd "bluetoothctl" ["power", "$a", ">", "/dev/null"]
|
2020-03-18 12:17:39 -04:00
|
|
|
#!&& fmtNotifyCmd defNoteInfo { body = Just $ Text "bluetooth powered $a" }
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runIncBacklight :: X ()
|
2020-03-20 15:41:13 -04:00
|
|
|
runIncBacklight = io $ void callIncBrightness
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runDecBacklight :: X ()
|
2020-03-20 15:41:13 -04:00
|
|
|
runDecBacklight = io $ void callDecBrightness
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runMinBacklight :: X ()
|
2020-03-20 15:41:13 -04:00
|
|
|
runMinBacklight = io $ void callMinBrightness
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
runMaxBacklight :: X ()
|
2020-03-20 15:41:13 -04:00
|
|
|
runMaxBacklight = io $ void callMaxBrightness
|
2020-03-15 15:50:07 -04:00
|
|
|
|
2020-03-20 20:10:15 -04:00
|
|
|
toggleDPMS :: X ()
|
|
|
|
toggleDPMS = io $ void callToggle
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
-- keybindings
|
|
|
|
|
|
|
|
showKeybindings :: [((KeyMask, KeySym), NamedAction)] -> NamedAction
|
2020-03-17 18:18:32 -04:00
|
|
|
showKeybindings x = addName "Show Keybindings" $ do
|
|
|
|
name <- getMonitorName
|
|
|
|
case name of
|
|
|
|
Just n -> do
|
|
|
|
h <- spawnPipe $ cmd n
|
|
|
|
io $ hPutStr h (unlines $ showKm x)
|
|
|
|
io $ hClose h
|
|
|
|
return ()
|
|
|
|
Nothing -> io $ putStrLn "fail"
|
|
|
|
where cmd name = fmtCmd myDmenuCmd
|
2020-03-15 15:50:07 -04:00
|
|
|
[ "-dmenu"
|
2020-03-17 18:18:32 -04:00
|
|
|
, "-m"
|
|
|
|
, name
|
2020-03-15 15:50:07 -04:00
|
|
|
, "-p"
|
|
|
|
, "commands"
|
|
|
|
, "-theme-str"
|
|
|
|
, "'#element.selected.normal { background-color: #a200ff; }'"
|
|
|
|
]
|
|
|
|
|
2020-03-26 21:02:58 -04:00
|
|
|
appendOrSwitch :: WorkspaceId -> X () -> X ()
|
|
|
|
appendOrSwitch tag cmd = do
|
|
|
|
occupied <- withWindowSet $ \ws ->
|
|
|
|
return $ elem tag
|
|
|
|
$ map W.tag
|
|
|
|
-- list of all workspaces with windows on them
|
|
|
|
$ W.workspace (W.current ws)
|
|
|
|
: W.hidden ws
|
|
|
|
++ map W.workspace (W.visible ws)
|
|
|
|
if occupied then windows $ W.view tag else appendWorkspace tag >> cmd
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
myModMask :: KeyMask
|
|
|
|
myModMask = mod4Mask
|
|
|
|
|
|
|
|
mkNamedSubmap
|
|
|
|
:: XConfig l
|
|
|
|
-> String
|
2020-03-26 09:48:02 -04:00
|
|
|
-> [(String, String, X ())]
|
2020-03-15 15:50:07 -04:00
|
|
|
-> [((KeyMask, KeySym), NamedAction)]
|
2020-03-20 01:12:20 -04:00
|
|
|
mkNamedSubmap c sectionName bindings =
|
2020-03-26 09:48:02 -04:00
|
|
|
(subtitle sectionName:) $ mkNamedKeymap c
|
|
|
|
$ map (\(key, name, cmd) -> (key, addName name cmd)) bindings
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
-- NOTE: the following bindings are used by dunst:
|
|
|
|
-- "M-~", "M-<esc>", "M-S-<esc>", "M-S-."
|
2020-03-26 09:48:02 -04:00
|
|
|
mkKeys :: [ProcessID] -> Client -> XConfig Layout -> [((KeyMask, KeySym), NamedAction)]
|
|
|
|
mkKeys hs client c =
|
2020-03-15 15:50:07 -04:00
|
|
|
mkNamedSubmap c "Window Layouts"
|
2020-03-26 21:03:34 -04:00
|
|
|
[ ("M-j", "focus down", windows W.focusDown)
|
|
|
|
, ("M-k", "focus up", windows W.focusUp)
|
|
|
|
, ("M-m", "focus master", windows W.focusMaster)
|
|
|
|
, ("M-S-j", "swap down", windows W.swapDown)
|
|
|
|
, ("M-S-k", "swap up", windows W.swapUp)
|
|
|
|
, ("M-S-m", "swap master", windows W.swapMaster)
|
|
|
|
, ("M-<Return>", "next layout", sendMessage NextLayout)
|
2020-03-26 21:06:38 -04:00
|
|
|
, ("M-S-<Return>", "reset layout", setLayout $ layoutHook c)
|
2020-03-26 21:03:34 -04:00
|
|
|
, ("M-t", "sink tiling", withFocused $ windows . W.sink)
|
|
|
|
, ("M--", "shrink", sendMessage Shrink)
|
|
|
|
, ("M-=", "expand", sendMessage Expand)
|
2020-03-26 21:06:38 -04:00
|
|
|
, ("M-S--", "remove master window", sendMessage $ IncMasterN (-1))
|
|
|
|
, ("M-S-=", "add master window", sendMessage $ IncMasterN 1)
|
2020-03-15 15:50:07 -04:00
|
|
|
] ++
|
|
|
|
|
|
|
|
mkNamedSubmap c "Workspaces"
|
2020-03-26 21:06:38 -04:00
|
|
|
-- ASSUME standard workspaces only use numbers 0-9 (otherwise we won't get
|
2020-03-26 21:03:34 -04:00
|
|
|
-- valid keysyms)
|
2020-03-26 21:41:56 -04:00
|
|
|
([ (mods ++ n, msg ++ n, f n) | n <- myWorkspaces
|
|
|
|
, (mods, msg, f) <-
|
|
|
|
[ ("M-", "switch to workspace ", windows . W.view)
|
|
|
|
, ("M-S-", "move client to workspace ", windows . W.shift)
|
|
|
|
, ("M-C-", "follow client to workspace ", \n' -> do
|
|
|
|
windows $ W.shift n'
|
|
|
|
windows $ W.view n')
|
|
|
|
]
|
|
|
|
] ++
|
|
|
|
[ ("M-M1-l", "move up workspace", moveTo Next HiddenNonEmptyWS)
|
|
|
|
, ("M-M1-h", "move down workspace", moveTo Prev HiddenNonEmptyWS)
|
|
|
|
]) ++
|
2020-03-15 15:50:07 -04:00
|
|
|
|
|
|
|
mkNamedSubmap c "Screens"
|
2020-03-26 09:48:02 -04:00
|
|
|
[ ("M-l", "move up screen", nextScreen)
|
|
|
|
, ("M-h", "move down screen", prevScreen)
|
2020-03-26 21:41:56 -04:00
|
|
|
, ("M-C-l", "follow client up screen", shiftNextScreen >> nextScreen)
|
|
|
|
, ("M-C-h", "follow client down screen", shiftPrevScreen >> prevScreen)
|
|
|
|
, ("M-S-l", "shift workspace up screen", swapNextScreen >> nextScreen)
|
|
|
|
, ("M-S-h", "shift workspace down screen", swapPrevScreen >> prevScreen)
|
2020-03-15 15:50:07 -04:00
|
|
|
] ++
|
|
|
|
|
|
|
|
mkNamedSubmap c "Actions"
|
2020-03-26 09:48:02 -04:00
|
|
|
[ ("M-q", "close window", kill1)
|
|
|
|
, ("M-r", "run program", runCmdMenu)
|
|
|
|
, ("M-C-s", "capture area", runAreaCapture)
|
|
|
|
, ("M-C-S-s", "capture screen", runScreenCapture)
|
|
|
|
, ("M-C-d", "capture desktop", runDesktopCapture)
|
|
|
|
-- , ("M-C-S-s", "capture focused window", spawn myWindowCap)
|
2020-03-15 15:50:07 -04:00
|
|
|
] ++
|
|
|
|
|
|
|
|
mkNamedSubmap c "Launchers"
|
2020-03-26 09:48:02 -04:00
|
|
|
[ ("<XF86Search>", "select/launch app", runAppMenu)
|
|
|
|
, ("M-g", "launch clipboard manager", runClipMenu)
|
|
|
|
, ("M-a", "launch network selector", runNetMenu)
|
|
|
|
, ("M-w", "launch window selector", runWinMenu)
|
|
|
|
, ("M-u", "launch device selector", runDevMenu)
|
|
|
|
, ("M-C-e", "launch editor", runEditor)
|
|
|
|
, ("M-C-w", "launch browser", runBrowser)
|
|
|
|
, ("M-C-t", "launch terminal", runTerm)
|
|
|
|
, ("M-C-q", "launch calc", runCalc)
|
|
|
|
, ("M-C-f", "launch file manager", runFileManager)
|
2020-03-26 21:02:58 -04:00
|
|
|
, ("M-C-v", "launch windows VM", appendOrSwitch myVMWorkspace runVBox)
|
|
|
|
, ("M-C-g", "launch GIMP", appendOrSwitch myGimpWorkspace runGimp)
|
2020-03-15 15:50:07 -04:00
|
|
|
] ++
|
|
|
|
|
|
|
|
mkNamedSubmap c "Multimedia"
|
2020-03-26 09:48:02 -04:00
|
|
|
[ ("<XF86AudioPlay>", "toggle play/pause", runTogglePlay)
|
|
|
|
, ("<XF86AudioPrev>", "previous track", runPrevTrack)
|
|
|
|
, ("<XF86AudioNext>", "next track", runNextTrack)
|
|
|
|
, ("<XF86AudioStop>", "stop", runStopPlay)
|
|
|
|
, ("<XF86AudioLowerVolume>", "volume down", runVolumeDown)
|
|
|
|
, ("<XF86AudioRaiseVolume>", "volume up", runVolumeUp)
|
|
|
|
, ("<XF86AudioMute>", "volume mute", runVolumeMute)
|
2020-03-15 15:50:07 -04:00
|
|
|
] ++
|
|
|
|
|
2020-03-26 22:02:20 -04:00
|
|
|
-- dummy map for dunst commands (defined separately but this makes them show
|
|
|
|
-- up in the help menu)
|
|
|
|
mkNamedSubmap c "Dunst"
|
|
|
|
[ ("M-`", "dunst history", return ())
|
|
|
|
, ("M-S-`", "dunst close", return ())
|
|
|
|
, ("M-M1-`", "dunst context menu", return ())
|
|
|
|
, ("M-C-`", "dunst close all", return ())
|
|
|
|
] ++
|
|
|
|
|
2020-03-15 15:50:07 -04:00
|
|
|
mkNamedSubmap c "System"
|
2020-03-26 09:48:02 -04:00
|
|
|
[ ("M-.", "backlight up", runIncBacklight)
|
|
|
|
, ("M-,", "backlight down", runDecBacklight)
|
|
|
|
, ("M-M1-,", "backlight min", runMinBacklight)
|
|
|
|
, ("M-M1-.", "backlight max", runMaxBacklight)
|
|
|
|
, ("M-<End>", "power menu", runPowerPrompt)
|
|
|
|
, ("M-<Home>", "quit xmonad", runQuitPrompt)
|
2020-03-26 21:45:26 -04:00
|
|
|
, ("M-<Delete>", "lock screen", runScreenLock)
|
|
|
|
-- M-<F1> reserved for showing the keymap
|
|
|
|
, ("M-<F2>", "restart xmonad", runCleanup hs client >> runRestart)
|
|
|
|
, ("M-<F3>", "recompile xmonad", runRecompile)
|
|
|
|
, ("M-<F10>", "toggle bluetooth", runToggleBluetooth)
|
|
|
|
, ("M-<F11>", "toggle screensaver", toggleDPMS)
|
|
|
|
, ("M-<F12>", "switch gpu", runOptimusPrompt)
|
2020-03-15 15:50:07 -04:00
|
|
|
]
|