Blog | Florian Schrödl

Open a terminal with the same directory as the previous terminal in XMonad and ZSH

September 28, 2021
377 words
1 minutes

*Update* [2021-11-03]

I'm using xcwd now, which is a much simpler implementation, thanks to liskin!

I'm leaving the old implementation up though as it might be still interesting.

xcwd Implementation

xcwd gets the working path from the current X windows.

So it can also get the working directory from your editor for example 🎉

The implementation is straighforward, for example with alacritty you simply add a binding like this:

((modMask, xK_Return), spawn (myTerminal ++ " --working-directory \"`xcwd`\""))

ZSH Hook Implementation

Many configs solve this by setting the title of the terminal to the current directory (or join the path with the title and a seperator).

But this is a workaround and requires unsafe parsing (& ugly window titles).

We can solve this issue by creating a custom X property where we store our shell path.

Adding a Zsh hook to set the window property

I use Zsh as my main shell, where we can set up a hook to define the property:

# Sets a x window property on every directory change in the shell
# So I can stay in the same directory when opening a new terminal
function my_set_xwindow_path_hook() {
  if [[ $TERM == "xterm-256color" && ! -z $WINDOWID ]]; then
    xprop -id $WINDOWID -f MY_XWINDOW_PATH 8s -set MY_XWINDOW_PATH "$(pwd)"
  fi
}
chpwd_functions=(${chpwd_functions[@]} "my_set_xwindow_path_hook")
precmd_functions=(${precmd_functions[@]} "my_set_xwindow_path_hook")

Source: dotfiles/.zshrc at e7470a0f1ff627eae74c1207c4911c8cc03be284 · floscr/dotfiles

This makes use of hook functions:

  • chpwd_functions - Executed whenever the cwd is changed
  • precmd_functions - Executed before each prompt

Now when we query a windows X properties with a tool like xprop you will get this:

$ xprop | grep "MY_XWINDOW_PATH(STRING)"
MY_XWINDOW_PATH(STRING) = "/tmp"

Adding the XMonad logic

For this I'm using alacritty and set myTerminal variable in my XMonad config. Alacritty offsers a --working-directory flag to set the initial directory.

To open the terminal we'll create a function that checks the currenty focused window for the MY_XWINDOW_PATH property:

-- Open terminal in the path that is set in the xproperty "MY_XWINDOW_PATH"
-- otherwise use default terminal
openTerminal = do
  let
    spawnEmptyTerminal = spawn myTerminal

    trySpawnTerminalAtCwd w = do
        cwd <- runQuery (stringProperty "MY_XWINDOW_PATH") w
        case cwd of
          [] -> spawnEmptyTerminal
          xs -> spawn (myTerminal ++ " --working-directory \"" ++ show xs ++ "\"")

    in withWindowSet $ \w -> case (W.peek w) of
        Just win -> trySpawnTerminalAtCwd win
        Nothing  -> spawnEmptyTerminal

Source: dotfiles/xmonad.hs at 986849c223a4a9413d39cf492c80ba24f1feda86 · floscr/dotfiles

Here we check if there is a currently focused window and if it has the X property. We don't need to check for the window classname or title and we could extend this to any sort of application. (For instance, launching a terminal from your non-terminal editors path).

Now we call the function with a keybinding with the default binding Mod+Return

(modMask, xK_Return), openTerminal)
Read more stories about "xmonad" ->