packages feed

xmonad-entryhelper-0.1.0.0: README.md

# 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.