xmonad-entryhelper (empty) → 0.1.0.0
raw patch · 11 files changed
+763/−0 lines, 11 filesdep +X11dep +basedep +directorysetup-changed
Dependencies added: X11, base, directory, extensible-exceptions, filepath, mtl, process, unix, xmonad, xmonad-contrib
Files
- CHANGELOG.md +3/−0
- LICENSE +20/−0
- README.md +229/−0
- Setup.hs +2/−0
- src/XMonad/Util/EntryHelper.hs +28/−0
- src/XMonad/Util/EntryHelper/Compile.hs +130/−0
- src/XMonad/Util/EntryHelper/Config.hs +141/−0
- src/XMonad/Util/EntryHelper/File.hs +77/−0
- src/XMonad/Util/EntryHelper/Generated.hs +22/−0
- src/XMonad/Util/EntryHelper/Util.hs +60/−0
- xmonad-entryhelper.cabal +51/−0
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+# 0.1.0.0++* initial version
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Javran Cheng++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,229 @@+# xmonad-entryhelper++[](https://travis-ci.org/Javran/xmonad-entryhelper)+[](https://hackage.haskell.org/package/xmonad-entryhelper)++xmonad-entryhelper makes your compiled XMonad config a standalone binary.++It simulates the XMonad's argument handling+and supports customized compliation.++**Table of Contents**++- [Introduction](#introduction)+- [Simple setup](#simple-setup)+- [Argument handling](#argument-handling)+- [Advanced features](#advanced-features)+- [Feedback](#feedback)++## Introduction++xmonad-entryhelper frees you from keeping a xmonad library as a system- or user- level dependency.+Instead, you can keep your XMonad configurations either as a local+[cabal project](https://www.haskell.org/cabal/) using+[cabal sandbox](https://www.fpcomplete.com/school/to-infinity-and-beyond/older-but-still-interesting/an-introduction-to-cabal-sandboxes-copy) or within+a protected environment like those created by [hsenv](https://github.com/tmhedberg/hsenv)++## Simple setup++1. After installation, modify your `xmonad.hs` accordingly:++ Your xmonad config might look like:++ -- some imports here+ import ...++ ...++ -- your main entry point+ main :: IO ()+ main = _++ Rename your `main` to something else, import `withHelper` from `XMonad.Util.EntryHelper`+ and use it to call your old `main`:++ -- some imports here+ import ...+ import XMonad.Util.EntryHelper (withHelper)++ ...++ -- your old main entry point+ oldMain :: IO ()+ oldMain = _++ -- your new main entry point+ main :: IO ()+ main = withHelper oldMain++ It is recommended to set the "restart xmonad" action (typically `mod-q` in your keybinding)+ to just invoke `xmonad --restart`. Although the default action,+ essentially `xmonad --recompile && xmonad --restart` should work properly,+ argument `--recompile` forces the compilation (which might involve+ removing all binaries and compiling everything).+ If you are using a build system like `make` or `cabal`,+ forcing a compilation might not be a desired behavior as build systems are in general designed+ to prevent recompilation.++2. Finally you need to have a writable local `PATH` directory.++ For example you can make directory `$HOME/bin`:++ mkdir -p ~/bin++ And append the following lines in your shell profile file+ (it's usually your `~/.bash_profile` file):++ ...+ # my local executable files+ export PATH="${HOME}/bin:${PATH}"++ Create soft link to your compiled `xmonad` binary:++ # the binary name varies depending on your OS and architecture,+ # check your ~/.xmonad/ directory to find out+ $ ln -s ~/.xmonad/xmonad-x86_64-linux ~/bin/xmonad++ And verify if `xmonad` is now leading to your compiled xmonad config:++ $ source ~/.profile+ $ which xmonad+ /home/username/bin/xmonad++ If this doesn't work, check articles like+ [Zsh/Bash startup files loading order](https://shreevatsa.wordpress.com/2008/03/30/zshbash-startup-files-loading-order-bashrc-zshrc-etc/)+ for troubleshooting.++3. Now you are free to remove XMonad from your system- or user- level packages.+Because your compiled XMonad will work on its own:++ $ xmonad --help+ xmonad-entryhelper - XMonad config entry point wrapper++ Usage: xmonad [OPTION]+ Options:+ --help Print this message+ --version Print XMonad's version number+ --recompile Recompile XMonad+ --replace Replace the running window manager with XMonad+ --restart Request a running XMonad process to restart++## Argument handling++Although this projects tries to resemble the argument handling behavior of XMonad,+there are not exact the same. The differences are:++* When invoked without argument or with `--replace` or `--resume` (this argument is not documented,+assumably intended for internal use only):++ * XMonad always does up-to-date checking internally and compile source codes as needed before+ invocations++ * EntryHelper doesn't do up-to-date checking.++ (TL;DR) This is because XMonad is using the following routing when executed:++ 1. XMonad started+ 2. Check for source files and recompile as needed+ 3. Execute the compiled binary+ 4. If any goes wrong, start xmonad using default configuration++ This routing only works if XMonad binary and the binary compiled by XMonad+ are two different programs. But as EntryHelper wants to make your compiled+ binary and the XMonad program the same file, the same routing will cause+ an infinite loop (`start => check => execute => start ...`).++ To solve this problem, in EntryHelper the up-to-date checking+ is considered one part of the compilation. And the compilation+ will not be executed unless `--recompile` or `--restart` is given.++ Additionally, if you are using a build system like `make` or `cabal` to handle compilation,+ leaving the job of up-to-date checking+ to the build system would be the simplest approach.++* When invoked with `--restart`:++ * EntryHelper will try to recompile (without forcing) before sending the request+ * both XMonad and EntryHelper send the restart request++## Advanced features++* Customized compilation and post-compilation handling++ By passing a `Config` (from `XMonad.Util.EntryHelper.Config`) to `withCustomHelper`,+ it is possible to customize the compilation and post-compilation actions.+ Read document of `Config` for detail.++* Customized shell command compilation++ You can invoke an arbitrary shell command to do the compilation using+ `compileUsingShell` from `XMonad.Util.EntryHelper.Compile`, the working+ directory for this shell command will be `~/.xmonad` and its `stdout` and `stderr`+ outputs will be redirected into `~/.xmonad/xmonad.errors`.++ Assuming you have set your environment variable `${XMONAD_HOME}`+ to point to the project home directory,+ and you are using `Makefile` to handle the compilation,+ the following example should work for you:++ import qualified XMonad.Util.EntryHelper as EH++ main :: IO ()+ main = EH.withCustomHelper mhConf+ where+ mhConf = EH.defaultConfig+ { EH.run = oldMain+ , EH.compile = \force -> do+ let cmd = if force+ then "cd ${XMONAD_HOME} && make clean && make all"+ else "cd ${XMONAD_HOME} && make all"+ EH.compileUsingShell cmd+ }++* Parallel compilation protection++ You might find `withLock` from `XMonad.Util.EntryHelper.Compile` useful+ to prevent yourself from parallel compilation+ (this is usually caused by hitting `mod-q` rapidly multiple times...).+ It creates a temprary file (typically `/tmp/xmonad.{username}.lock`) before compiling+ and deletes it after the compilation is done. When this temprary file exists,+ no other protected action with the same+ file lock is allowed to proceed and will return with a default value.++ To protect an `action` from parallel execution, all you have to do is to+ replace it with `withLock def action`, with `def` being a default value to return+ when it hits a file lock.++ Continue from the previous example:++ import qualified XMonad.Util.EntryHelper as EH++ main :: IO ()+ main = EH.withCustomHelper mhConf+ where+ mhConf = EH.defaultConfig+ { EH.run = oldMain+ -- adding "EH.withLock ExitSuccess $"+ , EH.compile = \force -> EH.withLock ExitSuccess $ do+ let cmd = if force+ then "cd ${XMONAD_HOME} && make clean && make all"+ else "cd ${XMONAD_HOME} && make all"+ EH.compileUsingShell cmd+ }++ Be careful not to protect an action more than once.++* Sending restart request to current xmonad instance++ `sendRestart` from `XMonad.Util.EntryHelper.Util` is an exact copy of the same+ function found in XMonad (unfortunately XMonad doesn't export it).+ Simply calling this function will sent a restart request to the running XMonad+ instance.++ Note that XMonad restarts by looking for the compiled binary to replace it,+ which means the binary file (e.g. `~/.xmonad/xmonad-x86_64-linux`) has to exist+ or otherwise your window manager session will crash.++## Feedback++Feel free to open issues for either bug report, enhancement or discussion.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/XMonad/Util/EntryHelper.hs view
@@ -0,0 +1,28 @@+{-|+ Copyright : (c) 2015 Javran Cheng+ License : MIT+ Maintainer : Javran.C@gmail.com+ Stability : unstable+ Portability : non-portable (requires X11)++ Re-exporting submodules++-}+module XMonad.Util.EntryHelper+ ( module XMonad.Util.EntryHelper.Util+ , module XMonad.Util.EntryHelper.Compile+ , module XMonad.Util.EntryHelper.File+ , module XMonad.Util.EntryHelper.Config+ , module XMonad.Util.EntryHelper.Generated+ ) where++import XMonad.Util.EntryHelper.Util+import XMonad.Util.EntryHelper.Compile+import XMonad.Util.EntryHelper.File+import XMonad.Util.EntryHelper.Config+import XMonad.Util.EntryHelper.Generated++-- import/export shortcut is not justified here+-- because it blocks document generation++{-# ANN module "HLint: ignore Use import/export shortcut" #-}
+ src/XMonad/Util/EntryHelper/Compile.hs view
@@ -0,0 +1,130 @@+{-|+ Copyright : (c) 2015 Javran Cheng+ License : MIT+ Maintainer : Javran.C@gmail.com+ Stability : unstable+ Portability : non-portable (requires X11)++ Compiling-related functions++-}+module XMonad.Util.EntryHelper.Compile+ ( defaultCompile+ , defaultPostCompile+ , compileUsingShell+ , withFileLock+ , withLock+ ) where++import Control.Applicative+import System.IO+import System.Posix.Process+import System.Process+import Control.Exception.Extensible+import System.Exit+import System.FilePath+import System.Directory+import Data.List+import System.Posix.User++import XMonad.Util.EntryHelper.File+import XMonad.Util.EntryHelper.Util++-- | the default compiling action.+-- checks whether any of the sources files under @"~\/.xmonad\/"@+-- is newer than the binary and recompiles XMonad if so.+defaultCompile :: Bool -> IO ExitCode+defaultCompile force = do+ b <- isSourceNewer+ if force || b+ then do+ bin <- binPath <$> getXMonadPaths+ let cmd = "ghc --make xmonad.hs -i -ilib -fforce-recomp -o " ++ bin+ compileUsingShell cmd+ else return ExitSuccess++-- | the default post-compiling action.+-- prints out error log to stderr and pops up a message+-- when the last compilation has failed.+defaultPostCompile :: ExitCode -> IO ()+defaultPostCompile ExitSuccess = return ()+defaultPostCompile st@(ExitFailure _) = do+ ghcErr <- readFile =<< getXMonadLog+ src <- getXMonadSrc+ let msg = unlines $+ [ "Error detected while loading xmonad configuration file: " ++ src]+ ++ lines (if null ghcErr then show st else ghcErr)+ ++ ["","Please check the file for errors."]+ hPutStrLn stderr msg+ _ <- forkProcess $ executeFile "xmessage" True ["-default", "okay", msg] Nothing+ return ()++-- | @compileUsingShell cmd@ spawns a new process to run a shell command+-- (shell expansion is applied).+-- The working directory of the shell command is @"~\/.xmonad\/"@, and+-- the process' stdout and stdout are redirected to @"~\/.xmonad\/xmonad.errors"@+compileUsingShell :: String -> IO ExitCode+compileUsingShell cmd = do+ -- please make sure "installSignalHandlers" hasn't been executed+ -- or has been undone by "uninstallSignalHandlers"+ -- see also: https://ghc.haskell.org/trac/ghc/ticket/5212+ dir <- getXMonadDir+ compileLogPath <- getXMonadLog+ hNullInput <- openFile "/dev/null" ReadMode+ hCompileLog <- openFile compileLogPath WriteMode+ hSetBuffering hCompileLog NoBuffering+ let cp = (shell cmd)+ { cwd = Just dir+ , std_in = UseHandle hNullInput+ , std_out = UseHandle hCompileLog+ , std_err = UseHandle hCompileLog+ }+ -- std_out and std_err are closed automatically+ -- so we don't need to take care of them.+ (_,_,_,ph) <- createProcess cp+ waitForProcess ph++-- | @withLock def action@ is the same as @withFileLock fpath def action@ with+-- @fpath@ being @"xmonad.${USERNAME}.lock"@ under your temporary directory.+-- Wrapping an action with more than one @withLock@ will not work.+--+-- See also: `withFileLock`, 'getTemporaryDirectory', 'getEffectiveUserName'+withLock :: a -> IO a -> IO a+withLock def action = do+ tmpDir <- getTemporaryDirectory+ -- https://ghc.haskell.org/trac/ghc/ticket/1487+ -- avoid using "getLoginName" here+ usr <- getEffectiveUserName+ let lockFile = tmpDir </> intercalate "." ["xmonad",usr,"lock"]+ withFileLock lockFile def action++-- | prevents an IO action from parallel execution by using a lock file.+-- @withFileLock fpath def action@ checks whether the file indicated by @fpath@+-- exists. And:+--+-- * returns @def@ if the file exists.+-- * creates @fpath@, executes the action, and deletes @fpath@ when the action+-- has completed. If @action@ has failed, @def@ will be returned instead.+--+-- Note that:+--+-- * the action will be protected by 'safeIO', meaning the lock file will be deleted+-- regardless of any error.+-- * No check on @fpath@ will be done by this function. Please make sure the lock file+-- does not exist.+-- * please prevent wrapping the action with same file lock multiple times,+-- in which case the action will never be executed.+withFileLock :: FilePath -> a -> IO a -> IO a+withFileLock fPath def action = do+ lock <- doesFileExist fPath+ if lock+ then skipCompile+ else doCompile+ where+ skipCompile = do+ putStrLn $ "Lock file " ++ fPath ++ " found, aborting ..."+ putStrLn "Delete lock file to continue."+ return def+ doCompile = bracket_ (writeFile fPath "")+ (removeFile fPath)+ (safeIO def action)
+ src/XMonad/Util/EntryHelper/Config.hs view
@@ -0,0 +1,141 @@+{-|+ Copyright : (c) 2015 Javran Cheng+ License : MIT+ Maintainer : Javran.C@gmail.com+ Stability : unstable+ Portability : non-portable (requires X11)++ Configuration for EntryHelper++-}+module XMonad.Util.EntryHelper.Config+ ( Config(..)+ , defaultConfig+ , withHelper+ , withCustomHelper+ ) where++import System.Environment+import System.Exit+import System.Info+import Data.Version (showVersion)+import Graphics.X11.Xinerama (compiledWithXinerama)+import XMonad.Main+import XMonad.Core hiding (recompile)++import qualified XMonad.Config as XMC++import XMonad.Util.EntryHelper.Generated+import XMonad.Util.EntryHelper.Util+import XMonad.Util.EntryHelper.Compile++-- | the configuration for EntryHelper.+--+-- * @run@ should execute XMonad using a customized configuration.+-- * @compile force@ should compile the source file and return a value which+-- will lately be consumed by @postCompile@. @force@ is just a hint about whether+-- the compilation should be forced. @compile@ is free to ignore it and do up-to-date check+-- on its own.+-- * @postCompile val@ should take action according to the @val@, usually produced by @compile@+--+-- Note that:+--+-- * @compile@ should create a new process for compilation, as otherwise things like `executeFile`+-- will replace the current process image with a new process image, make it impossible+-- for @postCompile@ to invoke.+-- * @force@ is just a hint about whether the compilation should be forced.+-- and @compile@ is free to ignore it and do up-to-date checking on its own.+-- * don't remove the binary file when the compilation has failed, as XMonad restart relies+-- on it.+data Config a = Config+ { run :: IO () -- ^ the action for executing XMonad+ , compile :: Bool -> IO a -- ^ the action for compiling XMonad+ , postCompile :: a -> IO () -- ^ the action after compiling XMonad+ }++-- | default config for xmonad-entryhelper,+-- invokes xmonad with its default config file+defaultConfig :: Config ExitCode+defaultConfig = Config+ { run = xmonad XMC.defaultConfig+ , compile = defaultCompile+ , postCompile = defaultPostCompile+ }++{-+-- I don't think this is necessary,+-- as users can use "xmonad config" or just their main entries for "run"+-- depending on their needs.++class Executable r where+ execute :: r -> IO ()++instance (LayoutClass l Window, Read (l Window)) => Executable (XConfig l) where+ execute = xmonad++instance Executable a => Executable (IO a) where+ execute a = a >>= execute++instance Executable () where+ execute = const (return ())+-}++-- | @withHelper e@ is the same as calling `withCustomHelper` with default+-- @compile@ and @postCompile actions@+--+-- Either of the following will work:+--+-- * replace your main entry with @main = withHelper yourOldMain@+-- * use @main = withHelper (xmonad cfg)@ if you have only customized your `XConfig`+withHelper :: IO () -> IO ()+withHelper e = withCustomHelper defaultConfig { run = e }++-- | simulates the way that XMonad handles its command line arguments.+--+-- * when called with no argument, the action in @run@ will be used+-- * when called with a string prefixed with @"--resume"@, or when called with+-- @"--replace"@, the action in @run@ will be used+-- * when called with @"--recompile"@ or @"--restart"@, 'compile' will be called. And 'postCompile'+-- will handle the results from compliation.+-- * additionally when called with @"--restart"@ a restart request will be sent to the current+-- XMonad instance after the compilation regardless of the compilation result.+withCustomHelper :: Config a -> IO ()+withCustomHelper cfg = do+ -- since XMonad has hard-coded to call "xmonad --resume{..}" on restart+ -- I guess there isn't much we can do to have more command line functionalities+ -- TODO: a pontential plan might be:+ -- try to simulate "xmonad" when the program name is "xmonad",+ -- and otherwise an alternative command line argument parsing strategy will be applied.+ args <- getArgs+ let launch = installSignalHandlers >> run cfg+ recompile force = compile cfg force >>= postCompile cfg+ case args of+ [] -> launch+ ("--resume":_) -> launch+ ["--help"] -> printHelp+ ["--recompile"] -> recompile True+ ["--replace"] -> launch+ ["--restart"] -> safeIO () (recompile False) >> sendRestart+ ["--version"] -> putStrLn $ unwords shortVersion+ ["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion+ _ -> printHelp >> exitFailure+ where+ shortVersion = [ "xmonad", xmonadVersion ]+ longVersion = [ "compiled by", compilerName, showVersion compilerVersion+ , "for", arch ++ "-" ++ os+ , "\nXinerama:", show compiledWithXinerama ]++printHelp :: IO ()+printHelp = do+ self <- getProgName+ putStr . unlines $+ [ "xmonad-entryhelper - XMonad config entry point wrapper"+ , ""+ , "Usage: " ++ self ++ " [OPTION]"+ , "Options:"+ , " --help Print this message"+ , " --version Print XMonad's version number"+ , " --recompile Recompile XMonad"+ , " --replace Replace the running window manager with XMonad"+ , " --restart Request a running XMonad process to restart"+ ]
+ src/XMonad/Util/EntryHelper/File.hs view
@@ -0,0 +1,77 @@+{-|+ Copyright : (c) 2015 Javran Cheng+ License : MIT+ Maintainer : Javran.C@gmail.com+ Stability : unstable+ Portability : non-portable (requires X11)++ Dealing with XMonad-related files++-}+module XMonad.Util.EntryHelper.File+ ( XMonadPaths(..)+ , getXMonadDir+ , getXMonadPaths+ , getXMonadBin+ , getXMonadLog+ , getXMonadSrc+ , getXMonadLibDir+ , isSourceNewer+ ) where++import Control.Applicative+import System.FilePath+import qualified XMonad.Core as XC+import System.Info+import System.Directory++import XMonad.Util.EntryHelper.Util++-- | XMonad default file paths+data XMonadPaths = XMonadPaths+ { dirPath :: FilePath -- ^ directory path+ , binPath :: FilePath -- ^ compiled binary path+ , logPath :: FilePath -- ^ error log path+ , srcPath :: FilePath -- ^ source file path (the path to "xmonad.hs")+ , libDirPath :: FilePath -- ^ directory path for library files+ } deriving (Show)++-- | gets information about XMonad-related paths+getXMonadPaths :: IO XMonadPaths+getXMonadPaths = (XMonadPaths+ <$> id -- dir+ <*> (</> "xmonad-"++arch++"-"++os) -- bin+ <*> (</> "xmonad.errors") -- log+ <*> (</> "xmonad.hs") -- src+ <*> (</> "lib")) <$> getXMonadDir -- lib++-- | gets information about XMonad-related paths, see also: 'XMonadPaths'+getXMonadBin, getXMonadLog, getXMonadSrc, getXMonadLibDir, getXMonadDir :: IO FilePath++getXMonadDir = XC.getXMonadDir+getXMonadBin = binPath <$> getXMonadPaths+getXMonadLog = logPath <$> getXMonadPaths+getXMonadSrc = srcPath <$> getXMonadPaths+getXMonadLibDir = libDirPath <$> getXMonadPaths++-- | returns true only when any of the followings is true:+--+-- * any of the source files under xmonad's default directory is newer than the binary+-- * the binary does not exist+isSourceNewer :: IO Bool+isSourceNewer = do+ paths <- getXMonadPaths+ let bin = binPath paths+ src = srcPath paths+ lib = libDirPath paths+ libTs <- mapM getModTime . filter isHaskellSourceFile =<< allFiles lib+ srcT <- getModTime src+ binT <- getModTime bin+ -- should be at least one element in (srcT: libTs)+ -- and "Just _" is always greater than "Nothing"+ -- therefore, this procedure returns true when one of the following happens:+ -- - when the binary file doesn't exist+ -- - when there are some source files newer than the binary+ return $ any (binT <) (srcT : libTs)+ where+ getModTime = safeIO' . getModificationTime
+ src/XMonad/Util/EntryHelper/Generated.hs view
@@ -0,0 +1,22 @@+{-|+ Copyright : (c) 2015 Javran Cheng+ License : MIT+ Maintainer : Javran.C@gmail.com+ Stability : unstable+ Portability : non-portable (requires X11)++ Information generated from cabal macros++-}+{-# LANGUAGE CPP #-}+module XMonad.Util.EntryHelper.Generated+ ( xmonadVersion+ ) where++-- | the XMonad version compiled with this library+xmonadVersion :: String+#ifdef VERSION_xmonad+xmonadVersion = VERSION_xmonad+#else+xmonadVersion = "(unknown version)"+#endif
+ src/XMonad/Util/EntryHelper/Util.hs view
@@ -0,0 +1,60 @@+{-|+ Copyright : (c) 2015 Javran Cheng+ License : MIT+ Maintainer : Javran.C@gmail.com+ Stability : unstable+ Portability : non-portable (requires X11)++ Miscellaneous utilities for safe IO action, scanning files, sending X events, etc.++-}+module XMonad.Util.EntryHelper.Util+ ( safeIO+ , safeIO'+ , isHaskellSourceFile+ , allFiles+ , sendRestart+ ) where++import Control.Applicative+import Control.Monad+import Control.Exception.Extensible+import System.Directory+import System.FilePath+import Graphics.X11.Xlib+import Graphics.X11.Xlib.Extras+import Data.List++-- | performs an IO action and captures all the exceptions,+-- a default value is returned when there are exceptions.+safeIO :: a -> IO a -> IO a+safeIO def action =+ catch action (\(SomeException _) -> return def)++-- | performs an IO action and wraps the resulting value in Maybe+safeIO' :: IO a -> IO (Maybe a)+safeIO' action = safeIO Nothing (Just <$> action)++-- | checks if a file name is a Haskell source file+isHaskellSourceFile :: FilePath -> Bool+isHaskellSourceFile = (`elem` words ".hs .lhs .hsc") . takeExtension++-- | gets a list of all files under a given directory and its subdirectories+allFiles :: FilePath -> IO [FilePath]+allFiles t = do+ let prep = map (t </>) . filter (`notElem` [".", ".."])+ cs <- prep <$> safeIO [] (getDirectoryContents t)+ ds <- filterM doesDirectoryExist cs+ concat . ((cs \\ ds):) <$> mapM allFiles ds++-- | sends restart request to the current XMonad instance+sendRestart :: IO ()+sendRestart = do+ dpy <- openDisplay ""+ rw <- rootWindow dpy $ defaultScreen dpy+ xmonad_restart <- internAtom dpy "XMONAD_RESTART" False+ allocaXEvent $ \e -> do+ setEventType e clientMessage+ setClientMessageEvent e rw xmonad_restart 32 0 currentTime+ sendEvent dpy rw False structureNotifyMask e+ sync dpy False
+ xmonad-entryhelper.cabal view
@@ -0,0 +1,51 @@+name: xmonad-entryhelper+version: 0.1.0.0+synopsis: XMonad config entry point wrapper+description:+ xmonad-entryhelper makes your compiled XMonad config a standalone binary.+ .+ It simulates the XMonad's argument handling and supports customized compliation.+ .+ Please check+ <https://github.com/Javran/xmonad-entryhelper/blob/master/README.md README>+ for details.++homepage: https://github.com/Javran/xmonad-entryhelper+bug-reports: https://github.com/Javran/xmonad-entryhelper/issues+license: MIT+license-file: LICENSE+author: Javran Cheng+maintainer: Javran.c@gmail.com+copyright: Copyright (c) 2015 Javran Cheng+category: XMonad+build-type: Simple+extra-source-files: README.md, CHANGELOG.md+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/Javran/xmonad-entryhelper.git++library+ exposed-modules:+ XMonad.Util.EntryHelper+ XMonad.Util.EntryHelper.Util+ XMonad.Util.EntryHelper.Compile+ XMonad.Util.EntryHelper.File+ XMonad.Util.EntryHelper.Config+ XMonad.Util.EntryHelper.Generated++ build-depends: base <5,+ mtl,+ extensible-exceptions,+ unix,+ filepath,+ process,+ directory,+ X11,+ xmonad,+ xmonad-contrib++ hs-source-dirs: src+ ghc-options: -Wall+ default-language: Haskell2010