From 556e8368462f51cd9541203e1c672c217f66c5a9 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Sun, 11 Sep 2022 21:07:54 +0000 Subject: [PATCH] home/xmonad: Choose projects plugin (#5) Co-authored-by: janabhumi Reviewed-on: https://git.pleshevski.ru/pleshevskiy/myconfig/pulls/5 Co-authored-by: Dmitriy Pleshevskiy Co-committed-by: Dmitriy Pleshevskiy --- home/ui/xmonad/config.hs | 294 +++++++++++++++++++++++++++------------ 1 file changed, 208 insertions(+), 86 deletions(-) diff --git a/home/ui/xmonad/config.hs b/home/ui/xmonad/config.hs index b3c30a7..87c5ef1 100644 --- a/home/ui/xmonad/config.hs +++ b/home/ui/xmonad/config.hs @@ -8,9 +8,11 @@ -- import qualified Codec.Binary.UTF8.String as UTF8 +import Control.Monad (replicateM_, sequence_) import qualified DBus as D import qualified DBus.Client as D -import qualified Data.Map as M +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map import Data.Monoid import System.Exit import XMonad @@ -38,8 +40,10 @@ import XMonad.Layout.LimitWindows (limitWindows) import XMonad.Layout.NoBorders (smartBorders) import XMonad.Layout.Spacing (spacing) import XMonad.Layout.ThreeColumns (ThreeCol (..)) +import XMonad.Prompt (XPConfig, XPType (..), XPrompt (..), mkComplFunFromList', mkXPromptWithModes) import qualified XMonad.StackSet as W import XMonad.Util.EZConfig +import qualified XMonad.Util.ExtensibleState as XS import XMonad.Util.Run -- The preferred terminal program, which is used in a binding below and by @@ -101,7 +105,7 @@ main = mkDbusClient >>= main' main' :: D.Client -> IO () main' dbus = - xmonad . docks . ewmhFullscreen . ewmh $ + xmonad . docks . ewmhFullscreen . ewmh . dynProjects $ def { -- simple stuff terminal = myTerminal, @@ -122,6 +126,8 @@ main' dbus = logHook = myPolybarLogHook dbus, startupHook = myStartupHook } + where + dynProjects = dynamicProjects myProjects ------------------------------------------------------------------------ -- Polybar settings (needs DBus client). @@ -191,7 +197,9 @@ myLayout = avoidStruts . smartBorders $ (Mirror tiled ||| tiled ||| column3 ||| -- per-workspace layout choices. -- -- By default, do nothing. -myStartupHook = return () +myStartupHook = do + spawn "kotatogram-desktop" + spawn "nheko" ------------------------------------------------------------------------ -- Status bars and logging @@ -249,99 +257,101 @@ myManageHook = manageApps -- Key bindings. Add, modify or remove key bindings here. -- myKeys conf = - let easyMotionConfig = - def - { sKeys = AnyKeys [xK_a, xK_o, xK_e, xK_u, xK_h, xK_t, xK_n, xK_s] - } + mkKeymap conf $ + apps_kb ++ workspaces_kb ++ windows_kb ++ layout_kb ++ system_kb ++ misc_kb + where + apps_kb = + [ -- launch a terminal + ("M-S-", spawn $ XMonad.terminal conf), + -- launch a 'flameshot' to screenshot + ("M-S-s", safeSpawn "flameshot" ["gui"]), + -- launch 'librewolf' browser + ("M-S-b", spawn "librewolf"), + -- launch 'dmenu_run' to choose applications + ("M-p", spawn "dmenu_run") + -- Open calculator + -- ("", spawn "gnome-calculator"), + ] - apps_kb = - [ -- launch a terminal - ("M-S-", spawn $ XMonad.terminal conf), - -- launch a 'flameshot' to screenshot - ("M-S-s", safeSpawn "flameshot" ["gui"]), - -- launch 'librewolf' browser - ("M-S-b", spawn "librewolf"), - -- launch 'dmenu_run' to choose applications - ("M-p", spawn "dmenu_run") - -- Open calculator - -- ("", spawn "gnome-calculator"), - ] + workspaces_kb = + -- + -- mod-[1..9], Switch to workspace N + -- mod-shift-[1..9], Move client to workspace N + -- + [ ("M-" ++ m ++ show k, windows $ f i) + | (i, k) <- zip (XMonad.workspaces conf) [1 .. 9], + (f, m) <- [(W.greedyView, ""), (W.shift, "S-")] + ] - workspaces_kb = + windows_kb = + [ -- close focused window + ("M4-S-c", kill), + -- Resize viewed windows to the correct size + ("M-n", refresh), + -- Easy moution to focus windows + ("M-s", selectWindow easyMotionConfig >>= (`whenJust` windows . W.focusWindow)), + -- Move focus to the next window + ("M-j", windows W.focusDown), + -- Move focus to the previous window + ("M-k", windows W.focusUp), + -- Move focus to the master window + ("M-m", windows W.focusMaster), + -- Swap the focused window and the master window + ("M-", windows W.swapMaster), + -- Swap the focused window with the next window + ("M-S-j", windows W.swapDown), + -- Swap the focused window with the previous window + ("M-S-k", windows W.swapUp) + ] + where + easyMotionConfig = + def + { sKeys = AnyKeys [xK_a, xK_o, xK_e, xK_u, xK_h, xK_t, xK_n, xK_s] + } + + layout_kb = + [ -- Rotate through the available layout algorithms + ("M-", cycleThroughLayouts ["Full", "Mirror Spacing Tall"]), + ("M-", cycleThroughLayouts ["Spacing ThreeCol", "Spacing Tall", "Mirror Spacing Tall"]), + -- Reset the layouts on the current workspace to default + ("M-S-", setLayout $ XMonad.layoutHook conf), + -- Shrink the master area + ("M-h", sendMessage Shrink), + -- Expand the master area + ("M-l", sendMessage Expand), + -- Push window back into tiling + ("M-t", withFocused $ windows . W.sink), + -- Increment the number of windows in the master area + ("M-,", sendMessage $ IncMasterN 1), + -- Deincrement the number of windows in the master area + ("M-.", sendMessage $ IncMasterN (-1)) + -- Toggle the status bar gap + -- Use this binding with avoidStruts from Hooks.ManageDocks. + -- See also the statusBar function from Hooks.DynamicLog. -- - -- mod-[1..9], Switch to workspace N - -- mod-shift-[1..9], Move client to workspace N - -- - [ ("M-" ++ m ++ show k, windows $ f i) - | (i, k) <- zip (XMonad.workspaces conf) [1 .. 9], - (f, m) <- [(W.greedyView, ""), (W.shift, "S-")] - ] + -- , ("M-b", sendMessage ToggleStruts) + ] - windows_kb = - [ -- close focused window - ("M4-S-c", kill), - -- Resize viewed windows to the correct size - ("M-n", refresh), - -- Easy moution to focus windows - ("M-s", selectWindow easyMotionConfig >>= (`whenJust` windows . W.focusWindow)), - -- Move focus to the next window - ("M-j", windows W.focusDown), - -- Move focus to the previous window - ("M-k", windows W.focusUp), - -- Move focus to the master window - ("M-m", windows W.focusMaster), - -- Swap the focused window and the master window - ("M-", windows W.swapMaster), - -- Swap the focused window with the next window - ("M-S-j", windows W.swapDown), - -- Swap the focused window with the previous window - ("M-S-k", windows W.swapUp) - ] + system_kb = + [ -- Lock screen + ("M4-l", spawn "betterlockscreen --lock --display 1 -- -e"), + -- Quit xmonad + ("M4-S-q", io exitSuccess) + ] - layout_kb = - [ -- Rotate through the available layout algorithms - ("M-", cycleThroughLayouts ["Full", "Mirror Spacing Tall"]), - ("M-", cycleThroughLayouts ["Spacing ThreeCol", "Spacing Tall", "Mirror Spacing Tall"]), - -- Reset the layouts on the current workspace to default - ("M-S-", setLayout $ XMonad.layoutHook conf), - -- Shrink the master area - ("M-h", sendMessage Shrink), - -- Expand the master area - ("M-l", sendMessage Expand), - -- Push window back into tiling - ("M-t", withFocused $ windows . W.sink), - -- Increment the number of windows in the master area - ("M-,", sendMessage $ IncMasterN 1), - -- Deincrement the number of windows in the master area - ("M-.", sendMessage $ IncMasterN (-1)) - -- Toggle the status bar gap - -- Use this binding with avoidStruts from Hooks.ManageDocks. - -- See also the statusBar function from Hooks.DynamicLog. - -- - -- , ("M-b", sendMessage ToggleStruts) - ] - - system_kb = - [ -- Lock screen - ("M4-l", spawn "betterlockscreen --lock --display 1 -- -e"), - -- Quit xmonad - ("M4-S-q", io exitSuccess) - ] - - misc_kb = - [ -- Change volume - ("", spawn "amixer -q sset Master toggle"), - ("", spawn "amixer -q sset Master 5%+"), - ("", spawn "amixer -q sset Master 5%-") - ] - in mkKeymap conf $ - apps_kb ++ workspaces_kb ++ windows_kb ++ layout_kb ++ system_kb ++ misc_kb + misc_kb = + [ ("M-o", projectPrompt def), + -- Change volume + ("", spawn "amixer -q sset Master toggle"), + ("", spawn "amixer -q sset Master 5%+"), + ("", spawn "amixer -q sset Master 5%-") + ] ------------------------------------------------------------------------ -- Mouse bindings: default actions bound to mouse events -- myMouseBindings (XConfig {XMonad.modMask = modm}) = - M.fromList + Map.fromList -- mod-button1, Set the window to floating mode and move by dragging [ ((modm, button1), \w -> focus w >> mouseMoveWindow w >> windows W.shiftMaster), -- mod-button2, Raise the window to the top of the stack @@ -350,3 +360,115 @@ myMouseBindings (XConfig {XMonad.modMask = modm}) = ((modm, button3), \w -> focus w >> mouseResizeWindow w >> windows W.shiftMaster) -- you may also bind events to the mouse scroll wheel (button4 and button5) ] + +myProjects :: [Project] +myProjects = + [ -- my work + Project + { projectName = "bm-back", + projectStartHook = do + let workdir = "~/projects/binarymanagement/bm-back" + spawn $ terminal' workdir $ Just "docker-compose -f docker-compose.dev.yml up -d" + replicateM_ 3 $ spawn $ terminal workdir + }, + Project + { projectName = "bm-front", + projectStartHook = do + let workdir = "~/projects/binarymanagement/bm-front" + replicateM_ 3 $ spawn $ terminal workdir + }, + -- personal + Project + { projectName = "myconfig", + projectStartHook = replicateM_ 2 $ spawn $ terminal "~/repos/myconfig" + }, + Project + { projectName = "tas", + projectStartHook = replicateM_ 2 $ spawn $ terminal "~/repos/tas" + }, + -- community + Project + { projectName = "dexios", + projectStartHook = replicateM_ 2 $ spawn $ terminal "~/repos/dexios" + }, + Project + { projectName = "home-manager (nix)", + projectStartHook = replicateM_ 2 $ spawn $ terminal "~/repos/home-manager" + } + ] + where + terminal :: String -> String + terminal wd = terminal' wd Nothing + + terminal' :: String -> Maybe String -> String + terminal' wd' cmd' = + "alacritty" ++ workdir ++ command + where + workdir = " --working-directory " ++ wd' + command = case cmd' of + Just c -> " --command " ++ c + _ -> "" + +------------------------------------------------------------------------ +-- Project +type ProjectName = String + +data Project = Project + { projectName :: !ProjectName, + projectStartHook :: !(X ()) + } + +type ProjectTable = Map ProjectName Project + +data ProjectState = ProjectState {projects :: !ProjectTable} + +instance ExtensionClass ProjectState where + initialValue = ProjectState Map.empty + +data ProjectPrompt = ProjectPrompt XPConfig [ProjectName] + +dynamicProjects :: [Project] -> XConfig a -> XConfig a +dynamicProjects ps c = + c {startupHook = dynamicProjectsStartupHook ps <> startupHook c} + +dynamicProjectsStartupHook :: [Project] -> X () +dynamicProjectsStartupHook ps = XS.modify go + where + go :: ProjectState -> ProjectState + go s = s {projects = update $ projects s} + + update :: ProjectTable -> ProjectTable + update = Map.union (Map.fromList $ map entry ps) + + entry :: Project -> (ProjectName, Project) + entry p = (projectName p, p) + +instance XPrompt ProjectPrompt where + showXPrompt (ProjectPrompt _ _) = "Choose Project: " + + completionFunction (ProjectPrompt c ns) = mkComplFunFromList' c ns + + modeAction (ProjectPrompt _ _) buf auto = do + let name = if null auto then buf else auto + ps <- XS.gets projects + + case Map.lookup name ps of + Just p -> openProject p + Nothing -> return () + +openProject :: Project -> X () +openProject p = do + -- kill all windows in the current workspace + wins <- gets (W.integrate' . W.stack . W.workspace . W.current . windowset) + sequence_ $ map killWindow wins + + projectStartHook p + +projectPrompt :: XPConfig -> X () +projectPrompt c = do + ps <- XS.gets projects + + let names = Map.keys ps + modes = [XPT $ ProjectPrompt c names] + + mkXPromptWithModes modes c