mirror of
https://github.com/xmonad/xmonad.git
synced 2025-05-19 08:30:21 -07:00
Revise XDG handling
Improve handling of the whole XDG situation. This now looks as follows 1. If all three of xmonad's environment variables (XMONAD_DATA_DIR, XMONAD_CONFIG_DIR, and XMONAD_CACHE_DIR) are set, use them. 2. If there is a build script `build' or configuration `xmonad.hs' in `~/.xmonad', set all three directories to `~/.xmonad'. 3. Otherwise, use XDG_DATA_HOME, XDG_CONFIG_HOME, and XDG_CACHE_HOME (or their respective fallbacks). If none of the above exist, we default to using the XDG_* variables, creating the necessary directories if needed.
This commit is contained in:
parent
e8f48b77f9
commit
735fb58f6c
@ -1,5 +1,6 @@
|
||||
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving,
|
||||
MultiParamTypeClasses, TypeSynonymInstances, DeriveDataTypeable #-}
|
||||
MultiParamTypeClasses, TypeSynonymInstances, DeriveDataTypeable,
|
||||
LambdaCase, NamedFieldPuns, DeriveTraversable #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
@ -28,7 +29,7 @@ module XMonad.Core (
|
||||
getAtom, spawn, spawnPID, xfork, recompile, trace, whenJust, whenX,
|
||||
getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName,
|
||||
atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, withWindowAttributes,
|
||||
ManageHook, Query(..), runQuery
|
||||
ManageHook, Query(..), runQuery, Directories(..), Dirs, getDirs
|
||||
) where
|
||||
|
||||
import XMonad.StackSet hiding (modify)
|
||||
@ -36,10 +37,12 @@ import XMonad.StackSet hiding (modify)
|
||||
import Prelude
|
||||
import Control.Exception.Extensible (fromException, try, bracket, throw, finally, SomeException(..))
|
||||
import qualified Control.Exception.Extensible as E
|
||||
import Control.Applicative ((<|>), empty)
|
||||
import Control.Monad.Fail
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Data.Semigroup
|
||||
import Data.Traversable (for)
|
||||
import Data.Default
|
||||
import System.FilePath
|
||||
import System.IO
|
||||
@ -57,7 +60,6 @@ import Graphics.X11.Xlib.Extras (getWindowAttributes, WindowAttributes, Event)
|
||||
import Data.Typeable
|
||||
import Data.List ((\\))
|
||||
import Data.Maybe (isJust,fromMaybe)
|
||||
import System.Environment (lookupEnv)
|
||||
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
@ -91,8 +93,8 @@ data XConf = XConf
|
||||
, mousePosition :: !(Maybe (Position, Position))
|
||||
-- ^ position of the mouse according to
|
||||
-- the event currently being processed
|
||||
, currentEvent :: !(Maybe Event)
|
||||
-- ^ event currently being processed
|
||||
, currentEvent :: !(Maybe Event) -- ^ event currently being processed
|
||||
, dirs :: !Dirs -- ^ directories to use
|
||||
}
|
||||
|
||||
-- todo, better name
|
||||
@ -447,126 +449,97 @@ runOnWorkspaces job = do
|
||||
$ current ws : visible ws
|
||||
modify $ \s -> s { windowset = ws { current = c, visible = v, hidden = h } }
|
||||
|
||||
-- | Return the path to the xmonad configuration directory. This
|
||||
-- directory is where user configuration files are stored (e.g, the
|
||||
-- xmonad.hs file). You may also create a @lib@ subdirectory in the
|
||||
-- configuration directory and the default recompile command will add
|
||||
-- it to the GHC include path.
|
||||
-- | All the directories that xmonad will use. They will be used for
|
||||
-- the following purposes:
|
||||
--
|
||||
-- Several directories are considered. In order of
|
||||
-- preference:
|
||||
-- * @dataDir@: This directory is used by XMonad to store data files
|
||||
-- such as the run-time state file and the configuration binary
|
||||
-- generated by GHC.
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_CONFIG_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_CONFIG_HOME/xmonad@ directory.
|
||||
-- * @cfgDir@: This directory is where user configuration files are
|
||||
-- stored (e.g, the xmonad.hs file). You may also create a @lib@
|
||||
-- subdirectory in the configuration directory and the default recompile
|
||||
-- command will add it to the GHC include path.
|
||||
--
|
||||
-- The first directory that exists will be used. If none of the
|
||||
-- directories exist then (1) will be used if it is set, otherwise (2)
|
||||
-- will be used. Either way, a directory will be created if necessary.
|
||||
getXMonadDir :: MonadIO m => m String
|
||||
getXMonadDir =
|
||||
findFirstDirWithEnv "XMONAD_CONFIG_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGConfig "xmonad"
|
||||
]
|
||||
-- * @cacheDir@: This directory is used to store temporary files that
|
||||
-- can easily be recreated. For example, the XPrompt history file.
|
||||
--
|
||||
-- For how these directories are chosen, see 'getDirs'.
|
||||
--
|
||||
data Directories a = Dirs
|
||||
{ dataDir :: !a
|
||||
, cfgDir :: !a
|
||||
, cacheDir :: !a
|
||||
}
|
||||
deriving (Show, Functor, Foldable, Traversable)
|
||||
|
||||
-- | Return the path to the xmonad cache directory. This directory is
|
||||
-- used to store temporary files that can easily be recreated. For
|
||||
-- example, the XPrompt history file.
|
||||
--
|
||||
-- Several directories are considered. In order of preference:
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_CACHE_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_CACHE_HOME/xmonad@ directory.
|
||||
--
|
||||
-- The first directory that exists will be used. If none of the
|
||||
-- directories exist then (1) will be used if it is set, otherwise (2)
|
||||
-- will be used. Either way, a directory will be created if necessary.
|
||||
getXMonadCacheDir :: MonadIO m => m String
|
||||
getXMonadCacheDir =
|
||||
findFirstDirWithEnv "XMONAD_CACHE_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGCache "xmonad"
|
||||
]
|
||||
-- | Convenient type alias for the most common case in which one might
|
||||
-- want to use the 'Directories' type.
|
||||
type Dirs = Directories FilePath
|
||||
|
||||
-- | Return the path to the xmonad data directory. This directory is
|
||||
-- used by XMonad to store data files such as the run-time state file
|
||||
-- and the configuration binary generated by GHC.
|
||||
-- | Build up the 'Dirs' that xmonad will use. They are chosen as
|
||||
-- follows:
|
||||
--
|
||||
-- Several directories are considered. In order of preference:
|
||||
-- 1. If all three of xmonad's environment variables (@XMONAD_DATA_DIR@,
|
||||
-- @XMONAD_CONFIG_DIR@, and @XMONAD_CACHE_DIR@) are set, use them.
|
||||
-- 2. If there is a build script called @build@ or configuration
|
||||
-- @xmonad.hs@ in @~\/.xmonad@, set all three directories to
|
||||
-- @~\/.xmonad@.
|
||||
-- 3. Otherwise, use the @xmonad@ directory in @XDG_DATA_HOME@,
|
||||
-- @XDG_CONFIG_HOME@, and @XDG_CACHE_HOME@ (or their respective
|
||||
-- fallbacks). These directories are created if necessary.
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_DATA_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_DATA_HOME/xmonad@ directory.
|
||||
-- The xmonad configuration file (or the build script, if present) is
|
||||
-- always assumed to be in @cfgDir@.
|
||||
--
|
||||
-- The first directory that exists will be used. If none of the
|
||||
-- directories exist then (1) will be used if it is set, otherwise (2)
|
||||
-- will be used. Either way, a directory will be created if necessary.
|
||||
getXMonadDataDir :: MonadIO m => m String
|
||||
getXMonadDataDir =
|
||||
findFirstDirWithEnv "XMONAD_DATA_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGData "xmonad"
|
||||
]
|
||||
|
||||
-- | Helper function that will find the first existing directory and
|
||||
-- return its path. If none of the directories can be found, create
|
||||
-- and return the first from the list. If the list is empty this
|
||||
-- function returns the historical @~\/.xmonad@ directory.
|
||||
findFirstDirOf :: MonadIO m => [IO FilePath] -> m FilePath
|
||||
findFirstDirOf [] = findFirstDirOf [getAppUserDataDirectory "xmonad"]
|
||||
findFirstDirOf possibles = do
|
||||
found <- go possibles
|
||||
|
||||
case found of
|
||||
Just path -> return path
|
||||
Nothing -> do
|
||||
primary <- io (head possibles)
|
||||
io (createDirectoryIfMissing True primary)
|
||||
return primary
|
||||
|
||||
getDirs :: IO Dirs
|
||||
getDirs = xmEnvDirs <|> xmDirs <|> xdgDirs
|
||||
where
|
||||
go [] = return Nothing
|
||||
go (x:xs) = do
|
||||
dir <- io x
|
||||
exists <- io (doesDirectoryExist dir)
|
||||
if exists then return (Just dir) else go xs
|
||||
-- | Check for xmonad's environment variables first
|
||||
xmEnvDirs :: IO Dirs
|
||||
xmEnvDirs = do
|
||||
let xmEnvs = Dirs{ dataDir = "XMONAD_DATA_DIR"
|
||||
, cfgDir = "XMONAD_CONFIG_DIR"
|
||||
, cacheDir = "XMONAD_CACHE_DIR"
|
||||
}
|
||||
maybe empty pure . sequenceA =<< traverse getEnv xmEnvs
|
||||
|
||||
-- | Simple wrapper around @findFirstDirOf@ that allows the primary
|
||||
-- path to be specified by an environment variable.
|
||||
findFirstDirWithEnv :: MonadIO m => String -> [IO FilePath] -> m FilePath
|
||||
findFirstDirWithEnv envName paths = do
|
||||
envPath' <- io (getEnv envName)
|
||||
-- | Check whether the config file or a build script is in the
|
||||
-- @~\/.xmonad@ directory
|
||||
xmDirs :: IO Dirs
|
||||
xmDirs = do
|
||||
xmDir <- getAppUserDataDirectory "xmonad"
|
||||
conf <- doesFileExist $ xmDir </> "xmonad.hs"
|
||||
build <- doesFileExist $ xmDir </> "build"
|
||||
|
||||
case envPath' of
|
||||
Nothing -> findFirstDirOf paths
|
||||
Just envPath -> findFirstDirOf (return envPath:paths)
|
||||
-- Place *everything* in ~/.xmonad if yes
|
||||
guard $ conf || build
|
||||
pure Dirs{ dataDir = xmDir, cfgDir = xmDir, cacheDir = xmDir }
|
||||
|
||||
-- | Helper function to retrieve the various XDG directories.
|
||||
-- This has been based on the implementation shipped with GHC version 8.0.1 or
|
||||
-- higher. Put here to preserve compatibility with older GHC versions.
|
||||
getXDGDirectory :: XDGDirectory -> FilePath -> IO FilePath
|
||||
getXDGDirectory xdgDir suffix =
|
||||
normalise . (</> suffix) <$>
|
||||
case xdgDir of
|
||||
XDGData -> get "XDG_DATA_HOME" ".local/share"
|
||||
XDGConfig -> get "XDG_CONFIG_HOME" ".config"
|
||||
XDGCache -> get "XDG_CACHE_HOME" ".cache"
|
||||
where
|
||||
get name fallback = do
|
||||
env <- lookupEnv name
|
||||
case env of
|
||||
Nothing -> fallback'
|
||||
Just path
|
||||
| isRelative path -> fallback'
|
||||
| otherwise -> return path
|
||||
where
|
||||
fallback' = (</> fallback) <$> getHomeDirectory
|
||||
data XDGDirectory = XDGData | XDGConfig | XDGCache
|
||||
-- | Use XDG directories as a fallback
|
||||
xdgDirs :: IO Dirs
|
||||
xdgDirs =
|
||||
for Dirs{ dataDir = XdgData, cfgDir = XdgConfig, cacheDir = XdgCache }
|
||||
$ \dir -> do d <- getXdgDirectory dir "xmonad"
|
||||
d <$ createDirectoryIfMissing True d
|
||||
|
||||
-- | Return the path to the xmonad configuration directory.
|
||||
getXMonadDir :: X String
|
||||
getXMonadDir = asks (cfgDir . dirs)
|
||||
{-# DEPRECATED getXMonadDir "Use `asks (cfgDir . dirs)' instead." #-}
|
||||
|
||||
-- | Return the path to the xmonad cache directory.
|
||||
getXMonadCacheDir :: X String
|
||||
getXMonadCacheDir = asks (cacheDir . dirs)
|
||||
{-# DEPRECATED getXMonadCacheDir "Use `asks (cacheDir . dirs)' instead." #-}
|
||||
|
||||
-- | Return the path to the xmonad data directory.
|
||||
getXMonadDataDir :: X String
|
||||
getXMonadDataDir = asks (dataDir . dirs)
|
||||
{-# DEPRECATED getXMonadDataDir "Use `asks (dataDir . dirs)' instead." #-}
|
||||
|
||||
-- | Get the name of the file used to store the xmonad window state.
|
||||
stateFileName :: (Functor m, MonadIO m) => m FilePath
|
||||
stateFileName :: X FilePath
|
||||
stateFileName = (</> "xmonad.state") <$> getXMonadDataDir
|
||||
|
||||
-- | 'recompile force', recompile the xmonad configuration file when
|
||||
@ -588,16 +561,14 @@ stateFileName = (</> "xmonad.state") <$> getXMonadDataDir
|
||||
--
|
||||
-- 'False' is returned if there are compilation errors.
|
||||
--
|
||||
recompile :: MonadIO m => Bool -> m Bool
|
||||
recompile force = io $ do
|
||||
cfgdir <- getXMonadDir
|
||||
datadir <- getXMonadDataDir
|
||||
recompile :: MonadIO m => Dirs -> Bool -> m Bool
|
||||
recompile Dirs{ cfgDir, dataDir } force = io $ do
|
||||
let binn = "xmonad-"++arch++"-"++os
|
||||
bin = datadir </> binn
|
||||
err = datadir </> "xmonad.errors"
|
||||
src = cfgdir </> "xmonad.hs"
|
||||
lib = cfgdir </> "lib"
|
||||
buildscript = cfgdir </> "build"
|
||||
bin = dataDir </> binn
|
||||
err = dataDir </> "xmonad.errors"
|
||||
src = cfgDir </> "xmonad.hs"
|
||||
lib = cfgDir </> "lib"
|
||||
buildscript = cfgDir </> "build"
|
||||
|
||||
libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles lib
|
||||
srcT <- getModTime src
|
||||
@ -640,8 +611,8 @@ recompile force = io $ do
|
||||
uninstallSignalHandlers
|
||||
status <- bracket (openFile err WriteMode) hClose $ \errHandle ->
|
||||
waitForProcess =<< if useBuildscript
|
||||
then compileScript bin cfgdir buildscript errHandle
|
||||
else compileGHC bin cfgdir errHandle
|
||||
then compileScript bin cfgDir buildscript errHandle
|
||||
else compileGHC bin cfgDir errHandle
|
||||
|
||||
-- re-enable SIGCHLD:
|
||||
installSignalHandlers
|
||||
|
@ -1,4 +1,4 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, NamedFieldPuns #-}
|
||||
----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Main
|
||||
@ -39,7 +39,7 @@ import XMonad.Operations
|
||||
import System.IO
|
||||
import System.Directory
|
||||
import System.Info
|
||||
import System.Environment
|
||||
import System.Environment (getArgs, getProgName, withArgs)
|
||||
import System.Posix.Process (executeFile)
|
||||
import System.Exit (exitFailure)
|
||||
import System.FilePath
|
||||
@ -59,17 +59,18 @@ xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
xmonad conf = do
|
||||
installSignalHandlers -- important to ignore SIGCHLD to avoid zombies
|
||||
|
||||
dirs <- getDirs
|
||||
let launch' args = do
|
||||
catchIO buildLaunch
|
||||
catchIO (buildLaunch dirs)
|
||||
conf'@XConfig { layoutHook = Layout l }
|
||||
<- handleExtraArgs conf args conf{ layoutHook = Layout (layoutHook conf) }
|
||||
withArgs [] $ launch (conf' { layoutHook = l })
|
||||
withArgs [] $ launch (conf' { layoutHook = l }) dirs
|
||||
|
||||
args <- getArgs
|
||||
case args of
|
||||
("--resume": ws : xs : args') -> migrateState ws xs >> launch' args'
|
||||
("--resume": ws : xs : args') -> migrateState dirs ws xs >> launch' args'
|
||||
["--help"] -> usage
|
||||
["--recompile"] -> recompile True >>= flip unless exitFailure
|
||||
["--recompile"] -> recompile dirs True >>= flip unless exitFailure
|
||||
["--restart"] -> sendRestart
|
||||
["--version"] -> putStrLn $ unwords shortVersion
|
||||
["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion
|
||||
@ -90,7 +91,7 @@ usage = do
|
||||
"Options:" :
|
||||
" --help Print this message" :
|
||||
" --version Print the version number" :
|
||||
" --recompile Recompile your ~/.xmonad/xmonad.hs" :
|
||||
" --recompile Recompile your xmonad.hs" :
|
||||
" --replace Replace the running window manager with xmonad" :
|
||||
" --restart Request a running xmonad process to restart" :
|
||||
[]
|
||||
@ -111,8 +112,8 @@ usage = do
|
||||
--
|
||||
-- * Missing XMonad\/XMonadContrib modules due to ghc upgrade
|
||||
--
|
||||
buildLaunch :: IO ()
|
||||
buildLaunch = do
|
||||
buildLaunch :: Dirs -> IO ()
|
||||
buildLaunch dirs@Dirs{ dataDir } = do
|
||||
whoami <- getProgName
|
||||
let compiledConfig = "xmonad-"++arch++"-"++os
|
||||
unless (whoami == compiledConfig) $ do
|
||||
@ -122,10 +123,9 @@ buildLaunch = do
|
||||
, " but the compiled configuration should be called "
|
||||
, show compiledConfig
|
||||
]
|
||||
recompile False
|
||||
dir <- getXMonadDataDir
|
||||
recompile dirs False
|
||||
args <- getArgs
|
||||
executeFile (dir </> compiledConfig) False args Nothing
|
||||
executeFile (dataDir </> compiledConfig) False args Nothing
|
||||
|
||||
sendRestart :: IO ()
|
||||
sendRestart = do
|
||||
@ -166,8 +166,8 @@ sendReplace = do
|
||||
-- function instead of 'xmonad'. You probably also want to have a key
|
||||
-- binding to the 'XMonad.Operations.restart` function that restarts
|
||||
-- your custom binary with the resume flag set to @True@.
|
||||
launch :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
launch initxmc = do
|
||||
launch :: (LayoutClass l Window, Read (l Window)) => XConfig l -> Dirs -> IO ()
|
||||
launch initxmc drs = do
|
||||
-- setup locale information from environment
|
||||
setLocale LC_ALL (Just "")
|
||||
-- ignore SIGPIPE and SIGCHLD
|
||||
@ -216,7 +216,9 @@ launch initxmc = do
|
||||
, buttonActions = mouseBindings xmc xmc
|
||||
, mouseFocused = False
|
||||
, mousePosition = Nothing
|
||||
, currentEvent = Nothing }
|
||||
, currentEvent = Nothing
|
||||
, dirs = drs
|
||||
}
|
||||
|
||||
st = XState
|
||||
{ windowset = initialWinset
|
||||
|
@ -1,5 +1,5 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, NamedFieldPuns #-}
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Operations
|
||||
@ -36,6 +36,7 @@ import qualified Control.Exception.Extensible as C
|
||||
|
||||
import System.IO
|
||||
import System.Directory
|
||||
import System.FilePath ((</>))
|
||||
import System.Posix.Process (executeFile)
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xinerama (getScreenInfo)
|
||||
@ -524,12 +525,11 @@ readStateFile xmc = do
|
||||
-- | Migrate state from a previously running xmonad instance that used
|
||||
-- the older @--resume@ technique.
|
||||
{-# DEPRECATED migrateState "will be removed some point in the future." #-}
|
||||
migrateState :: (Functor m, MonadIO m) => String -> String -> m ()
|
||||
migrateState ws xs = do
|
||||
migrateState :: (Functor m, MonadIO m) => Dirs -> String -> String -> m ()
|
||||
migrateState Dirs{ dataDir } ws xs = do
|
||||
io (putStrLn "WARNING: --resume is no longer supported.")
|
||||
whenJust stateData $ \s -> do
|
||||
path <- stateFileName
|
||||
catchIO (writeFile path $ show s)
|
||||
whenJust stateData $ \s ->
|
||||
catchIO (writeFile (dataDir </> "xmonad.state") $ show s)
|
||||
where
|
||||
stateData = StateFile <$> maybeRead ws <*> maybeRead xs
|
||||
maybeRead s = case reads s of
|
||||
|
Loading…
x
Reference in New Issue
Block a user