Spawn from current working directory in DWM

Suraj N. Kurapati

  1. Problem
    1. Solution

      Problem

      I switched to DWM recently after six productive years of WMII and one of the things I missed from my previous WMII configuration was the ability to open new programs in the currently focused client’s working directory.

      Why? It’s all about spatial locality. For example, when I’m editing in Vim, I like to start some new terminals (or file managers) in the same working directory as the file I’m editing to do additional things. Or the scenario could be the opposite: when I’m browsing the filesystem in a file manager, I want to start Vim inside the directory I am currently browsing.

      Without this feature, I have to manually navigate to that working directory in each helper application (terminal, file manager, or editor) that I launched. This becomes tiresome and inefficient as time goes on.

      Thus, to correct this imbalance and regain my productivity, I dusted off my C knowledge and ported this feature to DWM using just 27 statement-lines of code (SLOC), as you can see in the patch below.

      Solution

      This patch is also available on GitHub.

      diff --git a/dwm.c b/dwm.c
      index 1d78655..156ee60 100644
      --- a/dwm.c
      +++ b/dwm.c
      @@ -20,6 +20,7 @@
        *
        * To understand everything else, start reading main().
        */
      +#include <assert.h>
       #include <errno.h>
       #include <locale.h>
       #include <stdarg.h>
      @@ -28,6 +29,8 @@
       #include <stdlib.h>
       #include <string.h>
       #include <unistd.h>
      +#include <libgen.h>
      +#include <sys/stat.h>
       #include <sys/types.h>
       #include <sys/wait.h>
       #include <X11/cursorfont.h>
      @@ -1661,11 +1664,45 @@ sigchld(int unused) {
        while(0 < waitpid(-1, NULL, WNOHANG));
       }
      
      +#define SPAWN_CWD_DELIM " []{}()<>\"':"
      +
       void
       spawn(const Arg *arg) {
        if(fork() == 0) {
          if(dpy)
            close(ConnectionNumber(dpy));
      +       if(selmon->sel) {
      +           const char* const home = getenv("HOME");
      +           assert(home && strchr(home, '/'));
      +           const size_t homelen = strlen(home);
      +           char *cwd, *pathbuf = NULL;
      +           struct stat statbuf;
      +
      +           cwd = strtok(selmon->sel->name, SPAWN_CWD_DELIM);
      +           /* NOTE: strtok() alters selmon->sel->name in-place,
      +            * but that does not matter because we are going to
      +            * exec() below anyway; nothing else will use it */
      +           while(cwd) {
      +               if(*cwd == '~') { /* replace ~ with $HOME */
      +                   if(!(pathbuf = malloc(homelen + strlen(cwd)))) /* ~ counts for NULL term */
      +                       die("fatal: could not malloc() %u bytes\n", homelen + strlen(cwd));
      +                   strcpy(strcpy(pathbuf, home) + homelen, cwd + 1);
      +                   cwd = pathbuf;
      +               }
      +
      +               if(strchr(cwd, '/') && !stat(cwd, &statbuf)) {
      +                   if(!S_ISDIR(statbuf.st_mode))
      +                       cwd = dirname(cwd);
      +
      +                   if(!chdir(cwd))
      +                       break;
      +               }
      +
      +               cwd = strtok(NULL, SPAWN_CWD_DELIM);
      +           }
      +
      +           free(pathbuf);
      +       }
          setsid();
          execvp(((char **)arg->v)[0], (char **)arg->v);
          fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]);