packages feed

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 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++[![Build Status](https://travis-ci.org/Javran/xmonad-entryhelper.svg?branch=master)](https://travis-ci.org/Javran/xmonad-entryhelper)+[![Version](https://img.shields.io/hackage/v/xmonad-entryhelper.svg)](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