Auto format
This commit is contained in:
parent
0c63d98727
commit
a5f057d2f1
13 changed files with 497 additions and 402 deletions
1
Setup.hs
1
Setup.hs
|
@ -1,2 +1,3 @@
|
|||
import Distribution.Simple
|
||||
|
||||
main = defaultMain
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
hsPkgs.cabal-install
|
||||
cabal2nix
|
||||
haskellPackages.ghcid
|
||||
haskellPackages.fourmolu
|
||||
];
|
||||
};
|
||||
});
|
||||
|
|
21
fourmolu.yaml
Normal file
21
fourmolu.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Generated from web app, for more information, see: https://fourmolu.github.io/config/
|
||||
indentation: 2
|
||||
column-limit: 80
|
||||
function-arrows: trailing
|
||||
comma-style: leading
|
||||
import-export-style: diff-friendly
|
||||
indent-wheres: true
|
||||
record-brace-space: false
|
||||
newlines-between-decls: 1
|
||||
haddock-style: multi-line
|
||||
haddock-style-module: null
|
||||
let-style: auto
|
||||
in-style: right-align
|
||||
single-constraint-parens: always
|
||||
single-deriving-parens: always
|
||||
unicode: never
|
||||
respectful: true
|
||||
import-grouping: legacy
|
||||
sort-constraints: false
|
||||
sort-derived-classes: false
|
||||
sort-deriving-clauses: false
|
|
@ -1,15 +1,18 @@
|
|||
|
||||
module KeyBindings (
|
||||
KeyBindings.modify
|
||||
KeyBindings.modify,
|
||||
) where
|
||||
|
||||
import Control.Monad (void)
|
||||
import Data.Foldable (forM_)
|
||||
import Data.List (sort, isSuffixOf)
|
||||
import Data.List (isSuffixOf, sort)
|
||||
import Data.Maybe (isJust)
|
||||
import Graphics.X11.Types
|
||||
import System.Exit
|
||||
|
||||
-- import Text.EditDistance
|
||||
|
||||
import qualified Nord as N
|
||||
import qualified Scratchpad as R
|
||||
import XMonad
|
||||
import XMonad.Actions.CycleWS
|
||||
import XMonad.Actions.DynamicProjects
|
||||
|
@ -20,58 +23,58 @@ import XMonad.Prompt
|
|||
import XMonad.Prompt.ConfirmPrompt
|
||||
import XMonad.Prompt.FuzzyMatch
|
||||
import XMonad.Prompt.Shell
|
||||
import qualified XMonad.StackSet as W
|
||||
import XMonad.Util.EZConfig
|
||||
import XMonad.Util.NamedScratchpad
|
||||
import qualified Nord as N
|
||||
import qualified Scratchpad as R
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
|
||||
-- Custom (in libs)
|
||||
import Password (passPrompt)
|
||||
|
||||
modify :: XConfig l -> XConfig l
|
||||
modify conf = conf
|
||||
{ modMask = mod4Mask -- Use the "Win" key for the mod key
|
||||
}
|
||||
|
||||
`additionalKeysP` -- Add some extra key bindings:
|
||||
[ ("M-S-q", confirmPrompt promptConfig "exit" (io exitSuccess))
|
||||
, ("<XF86MonBrightnessDown>", spawn "/run/current-system/sw/bin/xbacklight -10")
|
||||
, ("<XF86MonBrightnessUp>", spawn "/run/current-system/sw/bin/xbacklight +10")
|
||||
, ("<XF86AudioPlay>", spawn "/run/current-system/sw/bin/mpc toggle")
|
||||
, ("<XF86AudioNext>", spawn "/run/current-system/sw/bin/mpc next")
|
||||
, ("<XF86AudioPrev>", spawn "/run/current-system/sw/bin/mpc prev")
|
||||
, ("M-<Delete>", kill)
|
||||
, ("M-<Down>", windows W.focusDown)
|
||||
, ("M-<Esc>", sendMessage (Toggle "Full"))
|
||||
, ("M-$", sendMessage (Toggle "Full"))
|
||||
, ("M-<Left>", prevWS)
|
||||
, ("M-i", prevScreen)
|
||||
, ("M-e", nextScreen)
|
||||
, ("M-<Right>", nextWS)
|
||||
, ("M-<Tab>", toggleWS' ["NSP"])
|
||||
, ("M-<Up>", windows W.focusUp)
|
||||
, ("M-S-<Delete>", spawn "/run/current-system/sw/bin/mpc pause; /run/current-system/sw/bin/xset s activate")
|
||||
, ("M-S-<Left>", shiftToPrev >> prevWS)
|
||||
, ("M-S-<Right>", shiftToNext >> nextWS)
|
||||
, ("M-s s", spawn "flameshot gui")
|
||||
, ("M-s t", spawn "/run/current-system/sw/bin/scrot /tmp/screen.png")
|
||||
-- Workspace and tasks
|
||||
, ("M-b", switchProjectPrompt promptConfig)
|
||||
, ("M-p m", shiftToProjectPrompt promptConfig)
|
||||
, ("M-p n", switchProjectPrompt promptConfig)
|
||||
, ("M-p r", renameProjectPrompt promptConfig)
|
||||
, ("M-p s", shellPrompt promptConfig)
|
||||
, ("M-p c", withFocused centerWindow)
|
||||
, ("M-d", passPrompt promptConfig)
|
||||
-- Scratchpads
|
||||
, ("M-p p", namedScratchpadAction R.pads "htop")
|
||||
-- Dunst
|
||||
, ("C-<Space>", spawn "dunstctl close")
|
||||
, ("C-S-<Space>", spawn "dunstctl close-all")
|
||||
, ("M-n p", spawn "dunstctl set-paused toggle")
|
||||
]
|
||||
modify conf =
|
||||
conf
|
||||
{ modMask = mod4Mask -- Use the "Win" key for the mod key
|
||||
}
|
||||
`additionalKeysP` [ ("M-S-q", confirmPrompt promptConfig "exit" (io exitSuccess)) -- Add some extra key bindings:
|
||||
, ("<XF86MonBrightnessDown>", spawn "/run/current-system/sw/bin/xbacklight -10")
|
||||
, ("<XF86MonBrightnessUp>", spawn "/run/current-system/sw/bin/xbacklight +10")
|
||||
, ("<XF86AudioPlay>", spawn "/run/current-system/sw/bin/mpc toggle")
|
||||
, ("<XF86AudioNext>", spawn "/run/current-system/sw/bin/mpc next")
|
||||
, ("<XF86AudioPrev>", spawn "/run/current-system/sw/bin/mpc prev")
|
||||
, ("M-<Delete>", kill)
|
||||
, ("M-<Down>", windows W.focusDown)
|
||||
, ("M-<Esc>", sendMessage (Toggle "Full"))
|
||||
, ("M-$", sendMessage (Toggle "Full"))
|
||||
, ("M-<Left>", prevWS)
|
||||
, ("M-i", prevScreen)
|
||||
, ("M-e", nextScreen)
|
||||
, ("M-<Right>", nextWS)
|
||||
, ("M-<Tab>", toggleWS' ["NSP"])
|
||||
, ("M-<Up>", windows W.focusUp)
|
||||
,
|
||||
( "M-S-<Delete>"
|
||||
, spawn
|
||||
"/run/current-system/sw/bin/mpc pause; /run/current-system/sw/bin/xset s activate"
|
||||
)
|
||||
, ("M-S-<Left>", shiftToPrev >> prevWS)
|
||||
, ("M-S-<Right>", shiftToNext >> nextWS)
|
||||
, ("M-s s", spawn "flameshot gui")
|
||||
, ("M-s t", spawn "/run/current-system/sw/bin/scrot /tmp/screen.png")
|
||||
, -- Workspace and tasks
|
||||
("M-b", switchProjectPrompt promptConfig)
|
||||
, ("M-p m", shiftToProjectPrompt promptConfig)
|
||||
, ("M-p n", switchProjectPrompt promptConfig)
|
||||
, ("M-p r", renameProjectPrompt promptConfig)
|
||||
, ("M-p s", shellPrompt promptConfig)
|
||||
, ("M-p c", withFocused centerWindow)
|
||||
, ("M-d", passPrompt promptConfig)
|
||||
, -- Scratchpads
|
||||
("M-p p", namedScratchpadAction R.pads "htop")
|
||||
, -- Dunst
|
||||
("C-<Space>", spawn "dunstctl close")
|
||||
, ("C-S-<Space>", spawn "dunstctl close-all")
|
||||
, ("M-n p", spawn "dunstctl set-paused toggle")
|
||||
]
|
||||
|
||||
viewProject :: WorkspaceId -> X ()
|
||||
viewProject id = do
|
||||
|
@ -81,29 +84,30 @@ viewProject id = do
|
|||
-- Borrowed from https://www.reddit.com/r/xmonad/comments/gzq316/how_can_i_centre_a_floating_window_without/fthtx29/
|
||||
centerWindow :: Window -> X ()
|
||||
centerWindow win = do
|
||||
(_, W.RationalRect x y w h) <- floatLocation win
|
||||
windows $ W.float win (W.RationalRect ((1 - w) / 2) ((1 - h) / 2) w h)
|
||||
return ()
|
||||
(_, W.RationalRect x y w h) <- floatLocation win
|
||||
windows $ W.float win (W.RationalRect ((1 - w) / 2) ((1 - h) / 2) w h)
|
||||
return ()
|
||||
|
||||
promptConfig = def
|
||||
{ position = Bottom
|
||||
, alwaysHighlight = True
|
||||
, borderColor = N.nord9
|
||||
-- Normal
|
||||
, bgColor = N.nord9
|
||||
, fgColor = N.background
|
||||
-- Selection
|
||||
, bgHLight = N.nord6
|
||||
, fgHLight = N.background
|
||||
--
|
||||
, defaultText = ""
|
||||
, font = "xft:Iosevka Samae:style=Regular:size=8:charwidth=5"
|
||||
, height = 24
|
||||
, promptBorderWidth = 5
|
||||
-- Fuzzysearch by default
|
||||
, searchPredicate = fuzzyMatch
|
||||
, sorter = fuzzySort
|
||||
}
|
||||
promptConfig =
|
||||
def
|
||||
{ position = Bottom
|
||||
, alwaysHighlight = True
|
||||
, borderColor = N.nord9
|
||||
, -- Normal
|
||||
bgColor = N.nord9
|
||||
, fgColor = N.background
|
||||
, -- Selection
|
||||
bgHLight = N.nord6
|
||||
, fgHLight = N.background
|
||||
, --
|
||||
defaultText = ""
|
||||
, font = "xft:Iosevka Samae:style=Regular:size=8:charwidth=5"
|
||||
, height = 24
|
||||
, promptBorderWidth = 5
|
||||
, -- Fuzzysearch by default
|
||||
searchPredicate = fuzzyMatch
|
||||
, sorter = fuzzySort
|
||||
}
|
||||
|
||||
-- -- Slightly taken from
|
||||
-- -- https://mail.haskell.org/pipermail/xmonad/2010-October/010671.html
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
-- Micro lemonbar lib
|
||||
module LemonBar
|
||||
( tag
|
||||
, block
|
||||
, underline
|
||||
, overline
|
||||
, fColor
|
||||
, bColor
|
||||
, uColor
|
||||
, color
|
||||
module LemonBar (
|
||||
tag,
|
||||
block,
|
||||
underline,
|
||||
overline,
|
||||
fColor,
|
||||
bColor,
|
||||
uColor,
|
||||
color,
|
||||
) where
|
||||
|
||||
tag t v = foldl (++) "" ["%{",t,v,"}"]
|
||||
block t o c s = (tag t o) ++ s ++ (tag t c)
|
||||
underline = block "" "+u" "-u"
|
||||
overline = block "" "+o" "-o"
|
||||
fColor f = block "F" f "-"
|
||||
bColor b = block "B" b "-"
|
||||
uColor u = block "U" u "-"
|
||||
color f b = fColor f . bColor b . uColor f
|
||||
tag t v = foldl (++) "" ["%{", t, v, "}"]
|
||||
block t o c s = (tag t o) ++ s ++ (tag t c)
|
||||
underline = block "" "+u" "-u"
|
||||
overline = block "" "+o" "-o"
|
||||
fColor f = block "F" f "-"
|
||||
bColor b = block "B" b "-"
|
||||
uColor u = block "U" u "-"
|
||||
color f b = fColor f . bColor b . uColor f
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
module MouseBindings (
|
||||
MouseBindings.modify
|
||||
MouseBindings.modify,
|
||||
) where
|
||||
|
||||
import XMonad
|
||||
import XMonad.Actions.UpdatePointer
|
||||
|
||||
modify :: XConfig l -> XConfig l
|
||||
modify conf = conf
|
||||
{ logHook = logHook conf >> updatePointer (0.5,0.5) (0,0)
|
||||
}
|
||||
modify conf =
|
||||
conf
|
||||
{ logHook = logHook conf >> updatePointer (0.5, 0.5) (0, 0)
|
||||
}
|
||||
|
|
100
lib/Nord.hs
100
lib/Nord.hs
|
@ -1,91 +1,91 @@
|
|||
module Nord
|
||||
( nord0
|
||||
, nord1
|
||||
, nord2
|
||||
, nord3
|
||||
, nord4
|
||||
, nord5
|
||||
, nord6
|
||||
, nord7
|
||||
, nord8
|
||||
, nord9
|
||||
, nord10
|
||||
, nord11
|
||||
, nord12
|
||||
, nord13
|
||||
, nord14
|
||||
, nord15
|
||||
, yellow
|
||||
, orange
|
||||
, red
|
||||
, purple
|
||||
, green
|
||||
, background
|
||||
, backgroundhl
|
||||
, foreground
|
||||
, foregroundhl
|
||||
, foregroundll
|
||||
module Nord (
|
||||
nord0,
|
||||
nord1,
|
||||
nord2,
|
||||
nord3,
|
||||
nord4,
|
||||
nord5,
|
||||
nord6,
|
||||
nord7,
|
||||
nord8,
|
||||
nord9,
|
||||
nord10,
|
||||
nord11,
|
||||
nord12,
|
||||
nord13,
|
||||
nord14,
|
||||
nord15,
|
||||
yellow,
|
||||
orange,
|
||||
red,
|
||||
purple,
|
||||
green,
|
||||
background,
|
||||
backgroundhl,
|
||||
foreground,
|
||||
foregroundhl,
|
||||
foregroundll,
|
||||
) where
|
||||
|
||||
-- POLAR NIGHT
|
||||
-- The origin color or the Polar Night palette.
|
||||
nord0 = "#2E3440";
|
||||
nord0 = "#2E3440"
|
||||
|
||||
-- A brighter shade color based on nord0.
|
||||
nord1 = "#3B4252";
|
||||
nord1 = "#3B4252"
|
||||
|
||||
-- An even more brighter shade color of nord0.
|
||||
nord2 = "#434C5E";
|
||||
nord2 = "#434C5E"
|
||||
|
||||
-- The brightest shade color based on nord0.
|
||||
nord3 = "#4C566A";
|
||||
nord3 = "#4C566A"
|
||||
|
||||
-- SNOW STORM
|
||||
-- The origin color or the Snow Storm palette.
|
||||
nord4 = "#D8DEE9";
|
||||
nord4 = "#D8DEE9"
|
||||
|
||||
-- A brighter shade color of nord4.
|
||||
nord5 = "#E5E9F0";
|
||||
nord5 = "#E5E9F0"
|
||||
|
||||
-- The brightest shade color based on nord4.
|
||||
nord6 = "#ECEFF4";
|
||||
nord6 = "#ECEFF4"
|
||||
|
||||
-- FROST
|
||||
-- A calm and highly contrasted color reminiscent of frozen polar water.
|
||||
nord7 = "#8FBCBB";
|
||||
nord7 = "#8FBCBB"
|
||||
|
||||
-- The bright and shiny primary accent color reminiscent of pure and clear ice.
|
||||
nord8 = "#88C0D0";
|
||||
nord8 = "#88C0D0"
|
||||
|
||||
-- A more darkened and less saturated color reminiscent of arctic waters.
|
||||
nord9 = "#81A1C1";
|
||||
nord9 = "#81A1C1"
|
||||
|
||||
-- A dark and intensive color reminiscent of the deep arctic ocean.
|
||||
nord10 = "#5E81AC";
|
||||
nord10 = "#5E81AC"
|
||||
|
||||
-- AURORA
|
||||
-- RED
|
||||
nord11 = "#BF616A";
|
||||
nord11 = "#BF616A"
|
||||
|
||||
-- ORANGE
|
||||
nord12 = "#D08770";
|
||||
nord12 = "#D08770"
|
||||
|
||||
-- YELLOW
|
||||
nord13 = "#EBCB8B";
|
||||
nord13 = "#EBCB8B"
|
||||
|
||||
-- GREEN
|
||||
nord14 = "#A3BE8C";
|
||||
nord14 = "#A3BE8C"
|
||||
|
||||
-- PURPLE
|
||||
nord15 = "#B48EAD";
|
||||
nord15 = "#B48EAD"
|
||||
|
||||
foregroundhl = nord6
|
||||
foreground = nord5
|
||||
foreground = nord5
|
||||
foregroundll = nord4
|
||||
backgroundhl = nord1
|
||||
background = nord0
|
||||
red = nord11
|
||||
orange = nord12
|
||||
yellow = nord13
|
||||
green = nord14
|
||||
purple = nord15
|
||||
background = nord0
|
||||
red = nord11
|
||||
orange = nord12
|
||||
yellow = nord13
|
||||
green = nord14
|
||||
purple = nord15
|
||||
|
|
|
@ -3,26 +3,26 @@
|
|||
|
||||
module Password (passGeneratePrompt, passPrompt) where
|
||||
|
||||
import Control.Monad.Trans (liftIO)
|
||||
import Data.Function (on)
|
||||
import Data.List (foldl', scanl', sort, sortBy)
|
||||
import System.Directory (getHomeDirectory)
|
||||
import System.FilePath.Posix (dropExtension, takeExtension, (</>))
|
||||
import System.Posix.Env (getEnv)
|
||||
import XMonad.Core
|
||||
import XMonad.Prompt
|
||||
import XMonad.Util.Run (runProcessWithInput)
|
||||
import Control.Monad.Trans (liftIO)
|
||||
import Data.Function (on)
|
||||
import Data.List (foldl', scanl', sort, sortBy)
|
||||
import System.Directory (getHomeDirectory)
|
||||
import System.FilePath.Posix (dropExtension, takeExtension, (</>))
|
||||
import System.Posix.Env (getEnv)
|
||||
import XMonad.Core
|
||||
import XMonad.Prompt
|
||||
import XMonad.Util.Run (runProcessWithInput)
|
||||
|
||||
newtype Pass = Pass { passLabel :: String }
|
||||
newtype Pass = Pass {passLabel :: String}
|
||||
|
||||
-- Rosetta code levenshtein
|
||||
levenshtein :: String -> String -> Int
|
||||
levenshtein s1 s2 = last $ foldl' transform [0..length s1] s2
|
||||
levenshtein s1 s2 = last $ foldl' transform [0 .. length s1] s2
|
||||
where
|
||||
transform [] _ = []
|
||||
transform ns@(n:ns1) c = scanl' calc (n+1) $ zip3 s1 ns ns1
|
||||
transform ns@(n : ns1) c = scanl' calc (n + 1) $ zip3 s1 ns ns1
|
||||
where
|
||||
calc z (c1, x, y) = minimum [y+1, z+1, x + (fromEnum (c1 /= c) * 2)]
|
||||
calc z (c1, x, y) = minimum [y + 1, z + 1, x + (fromEnum (c1 /= c) * 2)]
|
||||
|
||||
instance XPrompt Pass where
|
||||
showXPrompt p = passLabel p <> ": "
|
||||
|
@ -44,25 +44,36 @@ mkPassPrompt label f conf = do
|
|||
where
|
||||
getPasswords = do
|
||||
passwordStoreDir <- (</> "pass") <$> getHomeDirectory
|
||||
files <- runProcessWithInput "find"
|
||||
[ passwordStoreDir, "-type", "f", "-name", "*.gpg", "-printf"
|
||||
, "%p\n"] []
|
||||
files <-
|
||||
runProcessWithInput
|
||||
"find"
|
||||
[ passwordStoreDir
|
||||
, "-type"
|
||||
, "f"
|
||||
, "-name"
|
||||
, "*.gpg"
|
||||
, "-printf"
|
||||
, "%p\n"
|
||||
]
|
||||
[]
|
||||
return . lines $ files
|
||||
|
||||
-- | Find all entries (`allPasses`) matching `input`
|
||||
passComplFun :: [String] -> String -> IO [String]
|
||||
passComplFun allPasses input = pure $
|
||||
sortBy (compare `on` levenshtein input)
|
||||
. take 10
|
||||
. filter (consumes input)
|
||||
$ allPasses
|
||||
passComplFun allPasses input =
|
||||
pure
|
||||
$ sortBy (compare `on` levenshtein input)
|
||||
. take 10
|
||||
. filter (consumes input)
|
||||
$ allPasses
|
||||
where
|
||||
consumes [] _ = True -- everything consumed
|
||||
consumes (_:_) [] = False -- all not consumed
|
||||
consumes (a:xs) (a':ys) | a == a' = consumes xs ys
|
||||
| otherwise = consumes (a:xs) ys
|
||||
consumes [] _ = True -- everything consumed
|
||||
consumes (_ : _) [] = False -- all not consumed
|
||||
consumes (a : xs) (a' : ys)
|
||||
| a == a' = consumes xs ys
|
||||
| otherwise = consumes (a : xs) ys
|
||||
|
||||
selectPassword :: String -> X ()
|
||||
selectPassword pass = spawn $ "gpg --decrypt " ++ pass ++ " | copy"
|
||||
-- “copy” comes with the xmonad module in the nix configuration
|
||||
|
||||
-- “copy” comes with the xmonad module in the nix configuration
|
||||
|
|
245
lib/Projects.hs
245
lib/Projects.hs
|
@ -1,5 +1,5 @@
|
|||
module Projects (
|
||||
Projects.modify
|
||||
Projects.modify,
|
||||
) where
|
||||
|
||||
import XMonad
|
||||
|
@ -12,18 +12,20 @@ spawnGuiTextEditor :: X ()
|
|||
spawnGuiTextEditor = safeSpawn "neovide" []
|
||||
|
||||
singleTermAppWithName :: String -> String -> Project
|
||||
singleTermAppWithName name app = Project
|
||||
{ projectName = name
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do safeSpawn term ["--command=zsh -c " <> app]
|
||||
}
|
||||
singleTermAppWithName name app =
|
||||
Project
|
||||
{ projectName = name
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do safeSpawn term ["--command=zsh -c " <> app]
|
||||
}
|
||||
|
||||
singleAppWithName :: String -> String -> Project
|
||||
singleAppWithName name app = Project
|
||||
{ projectName = name
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do spawn app
|
||||
}
|
||||
singleAppWithName name app =
|
||||
Project
|
||||
{ projectName = name
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do spawn app
|
||||
}
|
||||
|
||||
singleApp :: String -> Project
|
||||
singleApp app = singleAppWithName app app
|
||||
|
@ -42,100 +44,135 @@ projects =
|
|||
, singleAppWithName "signal" "signal-desktop"
|
||||
, singleAppWithName "vcv" "Rack"
|
||||
, singleTermAppWithName "email" "neomutt"
|
||||
, Project { projectName = "admin"
|
||||
, projectDirectory = "~/admin/nixos-config"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "nixpkgs"
|
||||
, projectDirectory = "~/admin/nixpkgs"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "overlays-personal"
|
||||
, projectDirectory = "~/admin/overlays-personal"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "steam"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ safeSpawn "steam" ["-pipewire"]
|
||||
}
|
||||
, Project { projectName = "youtube"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ safeSpawn "brave"
|
||||
, Project
|
||||
{ projectName = "admin"
|
||||
, projectDirectory = "~/admin/nixos-config"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "nixpkgs"
|
||||
, projectDirectory = "~/admin/nixpkgs"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "overlays-personal"
|
||||
, projectDirectory = "~/admin/overlays-personal"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "steam"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ safeSpawn "steam" ["-pipewire"]
|
||||
}
|
||||
, Project
|
||||
{ projectName = "youtube"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook =
|
||||
Just $
|
||||
safeSpawn
|
||||
"brave"
|
||||
["--app=youtube.com"]
|
||||
}
|
||||
, Project { projectName = "cdc-config"
|
||||
, projectDirectory = "~/admin/cdc-config"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "saehkoepoika-config"
|
||||
, projectDirectory = "~/admin/saehkoepoika-configuration-nix"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "cdc-documentation"
|
||||
, projectDirectory = "~/admin/cdc-documentation"
|
||||
, projectStartHook = Just $ do
|
||||
safeSpawnProg term
|
||||
spawnGuiTextEditor
|
||||
}
|
||||
, Project { projectName = "adventOfCode"
|
||||
, projectDirectory = "~/candy/adventofcode"
|
||||
, projectStartHook = Just $ do
|
||||
safeSpawnProg term
|
||||
spawnGuiTextEditor
|
||||
}
|
||||
, Project { projectName = "rukokuoppa"
|
||||
, projectDirectory = "~/candy/rukokuoppa"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "notes"
|
||||
, projectDirectory = "~/zk/org-roam-private"
|
||||
, projectStartHook = Just $ safeSpawn "emacs" ["~/zk/org-roam-private"]
|
||||
}
|
||||
, Project { projectName = "groceries"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ safeSpawnProg "obsidian"
|
||||
}
|
||||
, Project { projectName = "xmonad"
|
||||
, projectDirectory = "~/.xmonad"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "waymonad"
|
||||
, projectDirectory = "~/candy/waymonad"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "accounting"
|
||||
, projectDirectory = "~/accounting"
|
||||
, projectStartHook = Just $ do safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "arrangements"
|
||||
, projectDirectory = "~/candy/Arrangements"
|
||||
, projectStartHook = Just $ do spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "flim"
|
||||
, projectDirectory = "/flims/rtorrent/download"
|
||||
, projectStartHook = Just $ do safeSpawnProg term
|
||||
}
|
||||
, Project { projectName = "sound"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do safeSpawnProg "pavucontrol"
|
||||
safeSpawnProg "easyeffects"
|
||||
}
|
||||
, Project { projectName = "web"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do spawn "firefox"
|
||||
}
|
||||
}
|
||||
, Project
|
||||
{ projectName = "cdc-config"
|
||||
, projectDirectory = "~/admin/cdc-config"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "saehkoepoika-config"
|
||||
, projectDirectory = "~/admin/saehkoepoika-configuration-nix"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "cdc-documentation"
|
||||
, projectDirectory = "~/admin/cdc-documentation"
|
||||
, projectStartHook = Just $ do
|
||||
safeSpawnProg term
|
||||
spawnGuiTextEditor
|
||||
}
|
||||
, Project
|
||||
{ projectName = "adventOfCode"
|
||||
, projectDirectory = "~/candy/adventofcode"
|
||||
, projectStartHook = Just $ do
|
||||
safeSpawnProg term
|
||||
spawnGuiTextEditor
|
||||
}
|
||||
, Project
|
||||
{ projectName = "rukokuoppa"
|
||||
, projectDirectory = "~/candy/rukokuoppa"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "notes"
|
||||
, projectDirectory = "~/zk/org-roam-private"
|
||||
, projectStartHook = Just $ safeSpawn "emacs" ["~/zk/org-roam-private"]
|
||||
}
|
||||
, Project
|
||||
{ projectName = "groceries"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ safeSpawnProg "obsidian"
|
||||
}
|
||||
, Project
|
||||
{ projectName = "xmonad"
|
||||
, projectDirectory = "~/.xmonad"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "waymonad"
|
||||
, projectDirectory = "~/candy/waymonad"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "accounting"
|
||||
, projectDirectory = "~/accounting"
|
||||
, projectStartHook = Just $ do safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "arrangements"
|
||||
, projectDirectory = "~/candy/Arrangements"
|
||||
, projectStartHook = Just $ do
|
||||
spawnGuiTextEditor
|
||||
safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "flim"
|
||||
, projectDirectory = "/flims/rtorrent/download"
|
||||
, projectStartHook = Just $ do safeSpawnProg term
|
||||
}
|
||||
, Project
|
||||
{ projectName = "sound"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do
|
||||
safeSpawnProg "pavucontrol"
|
||||
safeSpawnProg "easyeffects"
|
||||
}
|
||||
, Project
|
||||
{ projectName = "web"
|
||||
, projectDirectory = "/tmp"
|
||||
, projectStartHook = Just $ do spawn "firefox"
|
||||
}
|
||||
]
|
||||
|
||||
modify :: XConfig l -> XConfig l
|
||||
modify conf = dynamicProjects projects conf
|
||||
{ workspaces = [] }
|
||||
|
||||
modify conf =
|
||||
dynamicProjects
|
||||
projects
|
||||
conf
|
||||
{ workspaces = []
|
||||
}
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
|
||||
module Scratchpad (
|
||||
Scratchpad.modify,
|
||||
Scratchpad.pads
|
||||
Scratchpad.pads,
|
||||
) where
|
||||
|
||||
import Control.Monad (void)
|
||||
import XMonad.Core
|
||||
import XMonad.ManageHook
|
||||
import XMonad.Util.NamedScratchpad
|
||||
import qualified XMonad.StackSet as W
|
||||
import XMonad.Util.NamedScratchpad
|
||||
|
||||
modify :: XConfig l -> XConfig l
|
||||
modify conf = conf
|
||||
{ manageHook = manageHook conf <> namedScratchpadManageHook pads
|
||||
}
|
||||
modify conf =
|
||||
conf
|
||||
{ manageHook = manageHook conf <> namedScratchpadManageHook pads
|
||||
}
|
||||
|
||||
pads =
|
||||
[ NS "htop" "ghostty --x11-instance-name=htop --command=tmux" (appName =? "htop")
|
||||
(customFloating $ W.RationalRect (1/3) (1/3) (1/3) (1/3))
|
||||
-- , NS "stardict" "stardict" (className =? "Stardict")
|
||||
-- (customFloating $ W.RationalRect (1/6) (1/6) (2/3) (2/3))
|
||||
-- , NS "notes" "gvim --role notes ~/notes.txt" (role =? "notes") nonFloating
|
||||
] where role = stringProperty "WM_WINDOW_ROLE"
|
||||
[ NS
|
||||
"htop"
|
||||
"ghostty --x11-instance-name=htop --command=tmux"
|
||||
(appName =? "htop")
|
||||
(customFloating $ W.RationalRect (1 / 3) (1 / 3) (1 / 3) (1 / 3))
|
||||
-- , NS "stardict" "stardict" (className =? "Stardict")
|
||||
-- (customFloating $ W.RationalRect (1/6) (1/6) (2/3) (2/3))
|
||||
-- , NS "notes" "gvim --role notes ~/notes.txt" (role =? "notes") nonFloating
|
||||
]
|
||||
where
|
||||
role = stringProperty "WM_WINDOW_ROLE"
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
module Solarized
|
||||
( base03
|
||||
, base02
|
||||
, base01
|
||||
, base00
|
||||
, base0
|
||||
, base1
|
||||
, base2
|
||||
, base3
|
||||
, yellow
|
||||
, orange
|
||||
, red
|
||||
, magenta
|
||||
, violet
|
||||
, blue
|
||||
, cyan
|
||||
, green
|
||||
, background
|
||||
, backgroundhl
|
||||
, foreground
|
||||
, foregroundhl
|
||||
, foregroundll
|
||||
module Solarized (
|
||||
base03,
|
||||
base02,
|
||||
base01,
|
||||
base00,
|
||||
base0,
|
||||
base1,
|
||||
base2,
|
||||
base3,
|
||||
yellow,
|
||||
orange,
|
||||
red,
|
||||
magenta,
|
||||
violet,
|
||||
blue,
|
||||
cyan,
|
||||
green,
|
||||
background,
|
||||
backgroundhl,
|
||||
foreground,
|
||||
foregroundhl,
|
||||
foregroundll,
|
||||
) where
|
||||
|
||||
base03 = "#002b36"
|
||||
base02 = "#073642"
|
||||
base01 = "#586e75"
|
||||
base00 = "#657b83"
|
||||
base0 = "#839496"
|
||||
base1 = "#93a1a1"
|
||||
base2 = "#eee8d5"
|
||||
base3 = "#fdf6e3"
|
||||
yellow = "#b58900"
|
||||
orange = "#cb4b16"
|
||||
red = "#dc322f"
|
||||
base03 = "#002b36"
|
||||
base02 = "#073642"
|
||||
base01 = "#586e75"
|
||||
base00 = "#657b83"
|
||||
base0 = "#839496"
|
||||
base1 = "#93a1a1"
|
||||
base2 = "#eee8d5"
|
||||
base3 = "#fdf6e3"
|
||||
yellow = "#b58900"
|
||||
orange = "#cb4b16"
|
||||
red = "#dc322f"
|
||||
magenta = "#d33682"
|
||||
violet = "#6c71c4"
|
||||
blue = "#268bd2"
|
||||
cyan = "#3aa198"
|
||||
green = "#859900"
|
||||
violet = "#6c71c4"
|
||||
blue = "#268bd2"
|
||||
cyan = "#3aa198"
|
||||
green = "#859900"
|
||||
|
||||
foregroundhl = base1
|
||||
foreground = base0
|
||||
foreground = base0
|
||||
foregroundll = base01
|
||||
backgroundhl = base02
|
||||
background = base03
|
||||
background = base03
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
{-# LANGUAGE FlexibleContexts #-}
|
||||
|
||||
module StatusBar (
|
||||
StatusBar.modify
|
||||
StatusBar.modify,
|
||||
) where
|
||||
|
||||
import Data.List
|
||||
import Data.Monoid
|
||||
import Nord as N
|
||||
import XMonad.Core
|
||||
import XMonad.Hooks.DynamicBars (dynStatusBarStartup,dynStatusBarEventHook,multiPP)
|
||||
import XMonad.Hooks.DynamicBars (
|
||||
dynStatusBarEventHook,
|
||||
dynStatusBarStartup,
|
||||
multiPP,
|
||||
)
|
||||
import XMonad.Hooks.DynamicLog
|
||||
import XMonad.ManageHook
|
||||
import XMonad.Util.Run
|
||||
|
@ -17,53 +21,58 @@ import XMonad.Util.Run
|
|||
import GHC.IO.Handle (Handle)
|
||||
|
||||
modify :: XConfig l -> XConfig l
|
||||
modify conf = conf
|
||||
{ startupHook = do
|
||||
startupHook conf
|
||||
dynStatusBarStartup runXmobar killXmobar
|
||||
, handleEventHook = handleEventHook conf <+> dynStatusBarEventHook runXmobar killXmobar
|
||||
, logHook = do
|
||||
logHook conf
|
||||
multiPP currentPP otherPP
|
||||
}
|
||||
modify conf =
|
||||
conf
|
||||
{ startupHook = do
|
||||
startupHook conf
|
||||
dynStatusBarStartup runXmobar killXmobar
|
||||
, handleEventHook =
|
||||
handleEventHook conf <+> dynStatusBarEventHook runXmobar killXmobar
|
||||
, logHook = do
|
||||
logHook conf
|
||||
multiPP currentPP otherPP
|
||||
}
|
||||
|
||||
runXmobar :: ScreenId -> IO Handle
|
||||
runXmobar (S id) = spawnPipe
|
||||
$ "/run/current-system/sw/bin/xmobar --screen="
|
||||
<> show id
|
||||
<> " /home/eeva/.xmonad/xmobarrc"
|
||||
runXmobar (S id) =
|
||||
spawnPipe $
|
||||
"/run/current-system/sw/bin/xmobar --screen="
|
||||
<> show id
|
||||
<> " /home/eeva/.xmonad/xmobarrc"
|
||||
|
||||
killXmobar :: IO ()
|
||||
-- killXmobar = spawn "/run/current-system/sw/bin/pkill xmobar"
|
||||
killXmobar = return ()
|
||||
|
||||
otherPP = currentPP
|
||||
{ ppCurrent = xmobarColor N.foreground N.background
|
||||
, ppVisible = xmobarColor N.foreground N.background
|
||||
, ppHidden = xmobarColor N.foreground N.background
|
||||
, ppHiddenNoWindows = xmobarColor N.backgroundhl N.background
|
||||
}
|
||||
otherPP =
|
||||
currentPP
|
||||
{ ppCurrent = xmobarColor N.foreground N.background
|
||||
, ppVisible = xmobarColor N.foreground N.background
|
||||
, ppHidden = xmobarColor N.foreground N.background
|
||||
, ppHiddenNoWindows = xmobarColor N.backgroundhl N.background
|
||||
}
|
||||
|
||||
currentPP = def
|
||||
{ ppCurrent = xmobarColor' N.orange
|
||||
, ppVisible = xmobarColor' N.yellow -- other screen
|
||||
, ppHidden = xmobarColor' N.foreground -- other workspaces with windows
|
||||
, ppHiddenNoWindows = xmobarColor' N.foregroundll -- other workspaces
|
||||
, ppSep = " "
|
||||
, ppWsSep = " "
|
||||
, ppLayout = printLayout
|
||||
, ppTitle = printTitle
|
||||
}
|
||||
where xmobarColor' fg = xmobarColor fg S.base03
|
||||
|
||||
printLayout s | "Mirror ResizableTall" `isSuffixOf` s = "[ — ]"
|
||||
printLayout s | "ResizableTall" `isSuffixOf` s = "[ | ]"
|
||||
printLayout "Full" = "[ F ]"
|
||||
printLayout l = "[" ++ l ++ "]"
|
||||
|
||||
printTitle t = let t' = shorten 120 t
|
||||
in xmobarColor' S.foregroundll "« "
|
||||
++ xmobarColor' S.foregroundhl t'
|
||||
++ xmobarColor' S.foregroundll " »"
|
||||
currentPP =
|
||||
def
|
||||
{ ppCurrent = xmobarColor' N.orange
|
||||
, ppVisible = xmobarColor' N.yellow -- other screen
|
||||
, ppHidden = xmobarColor' N.foreground -- other workspaces with windows
|
||||
, ppHiddenNoWindows = xmobarColor' N.foregroundll -- other workspaces
|
||||
, ppSep = " "
|
||||
, ppWsSep = " "
|
||||
, ppLayout = printLayout
|
||||
, ppTitle = printTitle
|
||||
}
|
||||
where
|
||||
xmobarColor' fg = xmobarColor fg S.base03
|
||||
|
||||
printLayout s | "Mirror ResizableTall" `isSuffixOf` s = "[ — ]"
|
||||
printLayout s | "ResizableTall" `isSuffixOf` s = "[ | ]"
|
||||
printLayout "Full" = "[ F ]"
|
||||
printLayout l = "[" ++ l ++ "]"
|
||||
|
||||
printTitle t =
|
||||
let t' = shorten 120 t
|
||||
in xmobarColor' S.foregroundll "« "
|
||||
++ xmobarColor' S.foregroundhl t'
|
||||
++ xmobarColor' S.foregroundll " »"
|
||||
|
|
81
xmonad.hs
81
xmonad.hs
|
@ -4,19 +4,24 @@
|
|||
|
||||
module Main (main) where
|
||||
|
||||
import qualified Nord as N
|
||||
import XMonad
|
||||
import XMonad.Actions.DynamicProjects
|
||||
import XMonad.Config.Desktop
|
||||
import XMonad.Hooks.DynamicLog
|
||||
import XMonad.Hooks.ManageHelpers
|
||||
import XMonad.Layout.NoBorders (noBorders, smartBorders, hasBorder, BorderMessage (HasBorder))
|
||||
import XMonad.Layout.ResizableTile (ResizableTall(..))
|
||||
import XMonad.Layout.NoBorders (
|
||||
BorderMessage (HasBorder),
|
||||
hasBorder,
|
||||
noBorders,
|
||||
smartBorders,
|
||||
)
|
||||
import XMonad.Layout.ResizableTile (ResizableTall (..))
|
||||
import XMonad.Layout.Spacing
|
||||
import qualified XMonad.Layout.Spacing as SS (Border (..))
|
||||
import XMonad.Layout.ToggleLayouts (toggleLayouts)
|
||||
import XMonad.Util.EZConfig
|
||||
import XMonad.Util.Hacks (fixSteamFlicker)
|
||||
import qualified Nord as N
|
||||
import qualified XMonad.Layout.Spacing as SS (Border(..))
|
||||
|
||||
-- Tidy modules
|
||||
import KeyBindings as Keys (modify)
|
||||
|
@ -25,50 +30,50 @@ import Projects (modify)
|
|||
import Scratchpad (modify)
|
||||
|
||||
--------- toggle btw vvvvvvvvvv or vvvvv
|
||||
layouts = toggleLayouts fullscreen tiled
|
||||
layouts = toggleLayouts fullscreen tiled
|
||||
where
|
||||
smallBorder = SS.Border 5 5 5 5
|
||||
fullscreen = noBorders Full
|
||||
tiled = smarts $ resizableTall ||| Mirror resizableTall
|
||||
smarts = spacingRaw True smallBorder True smallBorder True . smartBorders
|
||||
resizableTall = ResizableTall 1 (5/100) (1/2) []
|
||||
smallBorder = SS.Border 5 5 5 5
|
||||
fullscreen = noBorders Full
|
||||
tiled = smarts $ resizableTall ||| Mirror resizableTall
|
||||
smarts = spacingRaw True smallBorder True smallBorder True . smartBorders
|
||||
resizableTall = ResizableTall 1 (5 / 100) (1 / 2) []
|
||||
|
||||
-- Use the `xprop' tool to get the info you need for these matches.
|
||||
-- For className, use the second value that xprop gives you.
|
||||
-------------- Here be the law of windows
|
||||
myManageHook = composeAll
|
||||
[ className =? "Patchage" --> doShift "patchage"
|
||||
, className =? "Pavucontrol" --> doShift "music"
|
||||
, className =? "Pinentry" --> doCenterFloat
|
||||
, className =? "REAPER" --> hasBorder False
|
||||
, className =? "Renoise" --> hasBorder False
|
||||
, className =? "steam" --> doShift "steam"
|
||||
, className =? "steamwebhelper" --> doShift "steam"
|
||||
, className =? "cs2" --> doShift "steam"
|
||||
, isDialog --> doCenterFloat
|
||||
|
||||
-- Move transient windows to their parent:
|
||||
, transience'
|
||||
]
|
||||
myManageHook =
|
||||
composeAll
|
||||
[ className =? "Patchage" --> doShift "patchage"
|
||||
, className =? "Pavucontrol" --> doShift "music"
|
||||
, className =? "Pinentry" --> doCenterFloat
|
||||
, className =? "REAPER" --> hasBorder False
|
||||
, className =? "Renoise" --> hasBorder False
|
||||
, className =? "steam" --> doShift "steam"
|
||||
, className =? "steamwebhelper" --> doShift "steam"
|
||||
, className =? "cs2" --> doShift "steam"
|
||||
, isDialog --> doCenterFloat
|
||||
, -- Move transient windows to their parent:
|
||||
transience'
|
||||
]
|
||||
|
||||
fixSteam :: XConfig l -> XConfig l
|
||||
fixSteam c = c { handleEventHook = fixSteamFlicker <+> handleEventHook c }
|
||||
fixSteam c = c{handleEventHook = fixSteamFlicker <+> handleEventHook c}
|
||||
|
||||
------------ build the full config
|
||||
withConfig =
|
||||
fixSteam -- And workaround steam bug
|
||||
$ Projects.modify -- Apply projects config
|
||||
$ Keys.modify -- Apply keybindings config
|
||||
$ Mouse.modify -- Apply mouse bindings config
|
||||
$ Scratchpad.modify -- Apply scratchpad managehook config
|
||||
$ desktopConfig -- on a default desktop config
|
||||
{ manageHook = myManageHook
|
||||
, layoutHook = desktopLayoutModifiers layouts
|
||||
, terminal = "ghostty"
|
||||
, normalBorderColor = N.backgroundhl
|
||||
, focusedBorderColor = N.nord9
|
||||
, borderWidth = 5
|
||||
}
|
||||
fixSteam $ -- And workaround steam bug
|
||||
Projects.modify $ -- Apply projects config
|
||||
Keys.modify $ -- Apply keybindings config
|
||||
Mouse.modify $ -- Apply mouse bindings config
|
||||
Scratchpad.modify $ -- Apply scratchpad managehook config
|
||||
desktopConfig -- on a default desktop config
|
||||
{ manageHook = myManageHook
|
||||
, layoutHook = desktopLayoutModifiers layouts
|
||||
, terminal = "ghostty"
|
||||
, normalBorderColor = N.backgroundhl
|
||||
, focusedBorderColor = N.nord9
|
||||
, borderWidth = 5
|
||||
}
|
||||
|
||||
------ and pass it to xmonad:
|
||||
main = xmonad withConfig
|
||||
|
|
Loading…
Add table
Reference in a new issue