]> git.sur5r.net Git - i3/i3/commitdiff
Merge pull request #1638 from hwangcc23/fix-1489
authorMichael Stapelberg <stapelberg@users.noreply.github.com>
Tue, 5 May 2015 07:43:43 +0000 (00:43 -0700)
committerMichael Stapelberg <stapelberg@users.noreply.github.com>
Tue, 5 May 2015 07:43:43 +0000 (00:43 -0700)
Add a timeout: delay_exit_on_zero_displays

1  2 
docs/userguide
include/config.h
include/config_directives.h
parser-specs/config.spec
src/config.c
src/config_directives.c
src/main.c
src/randr.c
testcases/t/201-config-parser.t

diff --combined docs/userguide
index 3a3cf00219d7f859a8162aa7b8440f79628d10be,66ba2cf8b7c81f5f41d073473f472b2246bcc33b..6975c943161b2c3fb712b11d2d78126210f5bd5e
@@@ -405,16 -405,13 +405,16 @@@ can configure mouse bindings in a simil
  
  *Syntax*:
  ----------------------------------
 -bindsym [--release] [--whole-window] [Modifiers+]button[n] command
 +bindsym [--release] [--border] [--whole-window] [Modifiers+]button[n] command
  ----------------------------------
  
  By default, the binding will only run when you click on the titlebar of the
 -window. If the +--whole-window+ flag is given, it will run when any part of the
 -window is clicked. If the +--release+ flag is given, it will run when the mouse
 -button is released.
 +window. If the +--release+ flag is given, it will run when the mouse button
 +is released.
 +
 +If the +--whole-window+ flag is given, the binding will also run when any part
 +of the window is clicked, with the exception of the border. To have a bind run
 +when the border is clicked, specify the +--border+ flag.
  
  *Examples*:
  --------------------------------
@@@ -724,10 -721,6 +724,10 @@@ commands will not run when restarting i
  also when restarting i3 you should use the +exec_always+
  keyword. These commands will be run in order.
  
 +See <<command_chaining>> for details on the special meaning of +;+ (semicolon)
 +and +,+ (comma): they chain commands together in i3, so you need to use quoted
 +strings if they appear in your command.
 +
  *Syntax*:
  -------------------
  exec [--no-startup-id] command
@@@ -1010,6 -1003,31 +1010,31 @@@ force_display_urgency_hint <timeout> m
  force_display_urgency_hint 500 ms
  ---------------------------------
  
+ === Delaying exiting on zero displays
+ Outputs may disappear momentarily and come back later. For example,
+ using a docking station that does not announce the undock (e.g. ACPI Undock 
+ event triggered through manually pushing a button before actually ejecting 
+ the notebook). During the removal of the notebook from the docking station,
+ all outputs disappear momentarily.
+ To prevent i3 from exiting when no output is available momentarily, you can 
+ tell i3 to delay a certain time first and check available outputs again using 
+ the +delay_exit_on_zero_displays+ directive. Setting the value to 0 disables 
+ this feature.
+ The default is 500ms.
+ *Syntax*:
+ ----------------------------------------
+ delay_exit_on_zero_displays <timeout> ms
+ ----------------------------------------
+ *Example*:
+ ----------------------------------
+ delay_exit_on_zero_displays 500 ms
+ ----------------------------------
  === Focus on window activation
  
  [[focus_on_window_activation]]
@@@ -1471,8 -1489,6 +1496,8 @@@ do this is to use the +i3-msg+ utility
  i3-msg border none
  --------------------------
  
 +[[command_chaining]]
 +
  Commands can be chained by using +;+ (a semicolon). So, to move a window to a
  specific workspace and immediately switch to that workspace, you can configure
  the following keybinding:
@@@ -1517,10 -1533,6 +1542,10 @@@ instance:
        Compares the window instance (the first part of WM_CLASS)
  window_role::
        Compares the window role (WM_WINDOW_ROLE).
 +window_type::
 +        Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
 +        +normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
 +        +popup_menu+ and +toolti+.
  id::
        Compares the X11 window ID, which you can get via +xwininfo+ for example.
  title::
@@@ -1546,11 -1558,7 +1571,11 @@@ information on how to use them
  What good is a window manager if you can’t actually start any applications?
  The exec command starts an application by passing the command you specify to a
  shell. This implies that you can use globbing (wildcards) and programs will be
 -searched in your $PATH.
 +searched in your +$PATH+.
 +
 +See <<command_chaining>> for details on the special meaning of +;+ (semicolon)
 +and +,+ (comma): they chain commands together in i3, so you need to use quoted
 +strings if they appear in your command.
  
  *Syntax*:
  ------------------------------
@@@ -1851,26 -1859,6 +1876,26 @@@ bindsym $mod+x move workspace to outpu
  bindsym $mod+x move container to output VGA1
  --------------------------------------------------------
  
 +=== Moving containers/workspaces to marks
 +
 +To move a container to another container with a specific mark (see <<vim_like_marks>>),
 +you can use the following command.
 +
 +The window will be moved right after the marked container in the tree, i.e., it ends up
 +in the same position as if you had opened a new window when the marked container was
 +focused. If the mark is on a split container, the window will appear as a new child
 +after the currently focused child within that container.
 +
 +*Syntax*:
 +------------------------------------
 +move window|container to mark <mark>
 +------------------------------------
 +
 +*Example*:
 +--------------------------------------------------------
 +for_window [instance="tabme"] move window to mark target
 +--------------------------------------------------------
 +
  [[resizingconfig]]
  
  === Resizing containers/windows
diff --combined include/config.h
index 4cc58a459c18f2a89b366be1d82f12df357f0423,d2f1f20631ae8ad77abe050af1120fd895e8ac54..75e0b127f96e084b9e566dad84b1db7e9ed0a3c7
@@@ -2,7 -2,7 +2,7 @@@
   * vim:ts=4:sw=4:expandtab
   *
   * i3 - an improved dynamic tiling window manager
 - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
 + * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
   *
   * include/config.h: Contains all structs/variables for the configurable
   * part of i3 as well as functions handling the configuration file (calling
@@@ -167,6 -167,10 +167,10 @@@ struct Config 
       * flag can be delayed using an urgency timer. */
      float workspace_urgency_timer;
  
+     /** Use a timer to delay exiting when no output is available.
+       * This can prevent i3 from exiting when all outputs disappear momentarily. */
+     float zero_disp_exit_timer_ms;
      /** Behavior when a window sends a NET_ACTIVE_WINDOW message. */
      enum {
          /* Focus if the target workspace is visible, set urgency hint otherwise. */
index 019b9bcdedfc9795523b4fcf1eb53d236636c316,7c9cfb6695d044551c179c3faefa0c153e9cfd49..1a7a39329e06c765e1de8ae3b637f36e94f64201
@@@ -2,7 -2,7 +2,7 @@@
   * vim:ts=4:sw=4:expandtab
   *
   * i3 - an improved dynamic tiling window manager
 - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
 + * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
   *
   * config_directives.h: all config storing functions (see config_parser.c)
   *
@@@ -51,6 -51,7 +51,7 @@@ CFGFUN(force_focus_wrapping, const cha
  CFGFUN(force_xinerama, const char *value);
  CFGFUN(fake_outputs, const char *outputs);
  CFGFUN(force_display_urgency_hint, const long duration_ms);
+ CFGFUN(delay_exit_on_zero_displays, const long duration_ms);
  CFGFUN(focus_on_window_activation, const char *mode);
  CFGFUN(show_marks, const char *value);
  CFGFUN(hide_edge_borders, const char *borders);
@@@ -64,10 -65,10 +65,10 @@@ CFGFUN(color_single, const char *colorc
  CFGFUN(floating_modifier, const char *modifiers);
  CFGFUN(new_window, const char *windowtype, const char *border, const long width);
  CFGFUN(workspace, const char *workspace, const char *output);
 -CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command);
 +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
  
  CFGFUN(enter_mode, const char *mode);
 -CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command);
 +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
  
  CFGFUN(bar_font, const char *font);
  CFGFUN(bar_separator_symbol, const char *separator);
diff --combined parser-specs/config.spec
index 422efe485947c23fb30d20598fe8a1a53de80467,a03568880a27d2079f89471a32b36af79e002b1f..986086c7d27d90e06644d0d34610cc2f52ed0c58
@@@ -1,7 -1,7 +1,7 @@@
  # vim:ts=2:sw=2:expandtab
  #
  # i3 - an improved dynamic tiling window manager
 -# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
 +# © 2009 Michael Stapelberg and contributors (see also: LICENSE)
  #
  # parser-specs/config.spec: Specification file for generate-command-parser.pl
  # which will generate the appropriate header files for our C parser.
@@@ -39,6 -39,7 +39,7 @@@ state INITIAL
    'workspace_auto_back_and_forth'          -> WORKSPACE_BACK_AND_FORTH
    'fake_outputs', 'fake-outputs'           -> FAKE_OUTPUTS
    'force_display_urgency_hint'             -> FORCE_DISPLAY_URGENCY_HINT
+   'delay_exit_on_zero_displays'            -> DELAY_EXIT_ON_ZERO_DISPLAYS
    'focus_on_window_activation'             -> FOCUS_ON_WINDOW_ACTIVATION
    'show_marks'                             -> SHOW_MARKS
    'workspace'                              -> WORKSPACE
@@@ -167,7 -168,6 +168,7 @@@ state CRITERIA
    ctype = 'window_role' -> CRITERION
    ctype = 'con_id'      -> CRITERION
    ctype = 'id'          -> CRITERION
 +  ctype = 'window_type' -> CRITERION
    ctype = 'con_mark'    -> CRITERION
    ctype = 'title'       -> CRITERION
    ctype = 'urgent'      -> CRITERION
@@@ -228,6 -228,17 +229,17 @@@ state FORCE_DISPLAY_URGENCY_HINT_MS
    end
        -> call cfg_force_display_urgency_hint(&duration_ms)
  
+ # delay_exit_on_zero_displays <delay> ms
+ state DELAY_EXIT_ON_ZERO_DISPLAYS:
+   duration_ms = number
+       -> DELAY_EXIT_ON_ZERO_DISPLAYS_MS
+ state DELAY_EXIT_ON_ZERO_DISPLAYS_MS:
+   'ms'
+       ->
+   end
+       -> call cfg_delay_exit_on_zero_displays(&duration_ms)
  # focus_on_window_activation <smart|urgent|focus|none>
  state FOCUS_ON_WINDOW_ACTIVATION:
    mode = word
@@@ -301,8 -312,6 +313,8 @@@ state FONT
  state BINDING:
    release = '--release'
        ->
 +  border = '--border'
 +      ->
    whole_window = '--whole-window'
        ->
    modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
  state BINDCOMMAND:
    release = '--release'
        ->
 +  border = '--border'
 +      ->
    whole_window = '--whole-window'
        ->
    command = string
 -      -> call cfg_binding($bindtype, $modifiers, $key, $release, $whole_window, $command)
 +      -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command)
  
  ################################################################################
  # Mode configuration
@@@ -354,8 -361,6 +366,8 @@@ state MODE_IGNORE_LINE
  state MODE_BINDING:
    release = '--release'
        ->
 +  border = '--border'
 +      ->
    whole_window = '--whole-window'
        ->
    modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', '$mod'
  state MODE_BINDCOMMAND:
    release = '--release'
        ->
 +  border = '--border'
 +      ->
    whole_window = '--whole-window'
        ->
    command = string
 -      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $whole_window, $command); MODE
 +      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command); MODE
  
  ################################################################################
  # Bar configuration (i3bar)
diff --combined src/config.c
index 6eb67a121fe6f377469674ed0db3ed58340fbf40,117ad571199f8b76fd206776e4e87fc495e5166c..bac9d7f369b168c6f9ed345d66c7c1281b606140
@@@ -4,7 -4,7 +4,7 @@@
   * vim:ts=4:sw=4:expandtab
   *
   * i3 - an improved dynamic tiling window manager
 - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
 + * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
   *
   * config.c: Configuration file (calling the parser (src/config_parser.c) with
   *           the correct path, switching key bindings mode).
@@@ -203,6 -203,10 +203,10 @@@ void load_configuration(xcb_connection_
      if (config.workspace_urgency_timer == 0)
          config.workspace_urgency_timer = 0.5;
  
+     /* Set default zero displays exit delay to 500ms */
+     if (config.zero_disp_exit_timer_ms == 0)
+         config.zero_disp_exit_timer_ms = 500;
      parse_configuration(override_configpath, true);
  
      if (reload) {
diff --combined src/config_directives.c
index ff1c280e4f6d7fac4adb51c70510b6d5722694a8,af0cf2eb2ad7c435770aced3f5739de0385224f1..ae78e0c0e87b255ad7e3c3a1ac63c24f27dc43f4
@@@ -4,7 -4,7 +4,7 @@@
   * vim:ts=4:sw=4:expandtab
   *
   * i3 - an improved dynamic tiling window manager
 - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
 + * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
   *
   * config_directives.c: all config storing functions (see config_parser.c)
   *
@@@ -89,31 -89,6 +89,31 @@@ CFGFUN(criteria_add, const char *ctype
          return;
      }
  
 +    if (strcmp(ctype, "window_type") == 0) {
 +        if (strcasecmp(cvalue, "normal") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_NORMAL;
 +        else if (strcasecmp(cvalue, "dialog") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_DIALOG;
 +        else if (strcasecmp(cvalue, "utility") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_UTILITY;
 +        else if (strcasecmp(cvalue, "toolbar") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLBAR;
 +        else if (strcasecmp(cvalue, "splash") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_SPLASH;
 +        else if (strcasecmp(cvalue, "menu") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_MENU;
 +        else if (strcasecmp(cvalue, "dropdown_menu") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_DROPDOWN_MENU;
 +        else if (strcasecmp(cvalue, "popup_menu") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_POPUP_MENU;
 +        else if (strcasecmp(cvalue, "tooltip") == 0)
 +            current_match->window_type = A__NET_WM_WINDOW_TYPE_TOOLTIP;
 +        else
 +            ELOG("unknown window_type value \"%s\"\n", cvalue);
 +
 +        return;
 +    }
 +
      if (strcmp(ctype, "con_mark") == 0) {
          current_match->mark = regex_new(cvalue);
          return;
@@@ -196,8 -171,8 +196,8 @@@ CFGFUN(font, const char *font) 
      font_pattern = sstrdup(font);
  }
  
 -CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command) {
 -    configure_binding(bindtype, modifiers, key, release, whole_window, command, DEFAULT_BINDING_MODE);
 +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
 +    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE);
  }
  
  /*******************************************************************************
  
  static char *current_mode;
  
 -CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *whole_window, const char *command) {
 -    configure_binding(bindtype, modifiers, key, release, whole_window, command, current_mode);
 +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
 +    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode);
  }
  
  CFGFUN(enter_mode, const char *modename) {
@@@ -354,6 -329,10 +354,10 @@@ CFGFUN(force_display_urgency_hint, cons
      config.workspace_urgency_timer = duration_ms / 1000.0;
  }
  
+ CFGFUN(delay_exit_on_zero_displays, const long duration_ms) {
+     config.zero_disp_exit_timer_ms = duration_ms;
+ }
  CFGFUN(focus_on_window_activation, const char *mode) {
      if (strcmp(mode, "smart") == 0)
          config.focus_on_window_activation = FOWA_SMART;
diff --combined src/main.c
index 86e40831902eef2051eb940d7fdd8b33cb91f01e,91478afa8bbae00e09604f88c0150a44bc798f8f..e8e0daa8c84a4bbfa783ef5afb8f691cda19de5e
@@@ -4,7 -4,7 +4,7 @@@
   * vim:ts=4:sw=4:expandtab
   *
   * i3 - an improved dynamic tiling window manager
 - * © 2009-2013 Michael Stapelberg and contributors (see also: LICENSE)
 + * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
   *
   * main.c: Initialization, main loop
   *
@@@ -188,7 -188,7 +188,7 @@@ static void handle_signal(int sig, sigi
  int main(int argc, char *argv[]) {
      /* Keep a symbol pointing to the I3_VERSION string constant so that we have
       * it in gdb backtraces. */
 -    const char *i3_version __attribute__((unused)) = I3_VERSION;
 +    const char *_i3_version __attribute__((unused)) = i3_version;
      char *override_configpath = NULL;
      bool autostart = true;
      char *layout_path = NULL;
                  only_check_config = true;
                  break;
              case 'v':
 -                printf("i3 version " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
 +                printf("i3 version %s © 2009 Michael Stapelberg and contributors\n", i3_version);
                  exit(EXIT_SUCCESS);
                  break;
              case 'm':
 -                printf("Binary i3 version:  " I3_VERSION " © 2009-2014 Michael Stapelberg and contributors\n");
 +                printf("Binary i3 version:  %s © 2009 Michael Stapelberg and contributors\n", i3_version);
                  display_running_version();
                  exit(EXIT_SUCCESS);
                  break;
          free(cwd);
      }
  
 -    LOG("i3 " I3_VERSION " starting\n");
 +    LOG("i3 %s starting\n", i3_version);
  
      conn = xcb_connect(NULL, &conn_screen);
      if (xcb_connection_has_error(conn))
      root_screen = xcb_aux_get_screen(conn, conn_screen);
      root = root_screen->root;
  
 +/* Place requests for the atoms we need as soon as possible */
 +#define xmacro(atom) \
 +    xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
 +#include "atoms.xmacro"
 +#undef xmacro
 +
      /* By default, we use the same depth and visual as the root window, which
       * usually is TrueColor (24 bit depth) and the corresponding visual.
       * However, we also check if a 32 bit depth and visual are available (for
      xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
      xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
  
 +/* Setup NetWM atoms */
 +#define xmacro(name)                                                                       \
 +    do {                                                                                   \
 +        xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
 +        if (!reply) {                                                                      \
 +            ELOG("Could not get atom " #name "\n");                                        \
 +            exit(-1);                                                                      \
 +        }                                                                                  \
 +        A_##name = reply->atom;                                                            \
 +        free(reply);                                                                       \
 +    } while (0);
 +#include "atoms.xmacro"
 +#undef xmacro
 +
      load_configuration(conn, override_configpath, false);
  
      if (config.ipc_socket_path == NULL) {
      }
      DLOG("root geometry reply: (%d, %d) %d x %d\n", greply->x, greply->y, greply->width, greply->height);
  
 -/* Place requests for the atoms we need as soon as possible */
 -#define xmacro(atom) \
 -    xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
 -#include "atoms.xmacro"
 -#undef xmacro
 -
      xcursor_load_cursors();
  
      /* Set a cursor for the root window (otherwise the root window will show no
  
      restore_connect();
  
 -/* Setup NetWM atoms */
 -#define xmacro(name)                                                                       \
 -    do {                                                                                   \
 -        xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name##_cookie, NULL); \
 -        if (!reply) {                                                                      \
 -            ELOG("Could not get atom " #name "\n");                                        \
 -            exit(-1);                                                                      \
 -        }                                                                                  \
 -        A_##name = reply->atom;                                                            \
 -        free(reply);                                                                       \
 -    } while (0);
 -#include "atoms.xmacro"
 -#undef xmacro
 -
      property_handlers_init();
  
      ewmh_setup_hints();
              ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n",
                   pointerreply->root_x, pointerreply->root_y);
              output = get_first_output();
+             if (!output)
+                 die("No usable outputs available.\n");
          }
  
          con_focus(con_descend_focused(output_get_content(output->con)));
diff --combined src/randr.c
index 9e236dcb517e8bab4214dba72821f4d5a3e10a12,2ef211194d37dd3ce66712de4051935e724558d6..1bd9993164d946b455db95ba3a5b0fd31f2ef503
@@@ -4,7 -4,7 +4,7 @@@
   * vim:ts=4:sw=4:expandtab
   *
   * i3 - an improved dynamic tiling window manager
 - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
 + * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
   *
   * For more information on RandR, please see the X.org RandR specification at
   * http://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
@@@ -69,7 -69,7 +69,7 @@@ Output *get_first_output(void) 
      if (output->active)
          return output;
  
-     die("No usable outputs available.\n");
+     return NULL;
  }
  
  /*
@@@ -555,12 -555,6 +555,12 @@@ static void handle_output(xcb_connectio
          return;
      }
  
 +    if (output->connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
 +        DLOG("Disabling output %s: it is disconnected\n", new->name);
 +        new->to_be_disabled = true;
 +        return;
 +    }
 +
      bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
                     update_if_necessary(&(new->rect.y), crtc->y) |
                     update_if_necessary(&(new->rect.width), crtc->width) |
      if (!new->active) {
          DLOG("width/height 0/0, disabling output\n");
          return;
+     } else {
+         new->to_be_disabled = false;
      }
  
      DLOG("mode: %dx%d+%d+%d\n", new->rect.width, new->rect.height,
      new->changed = true;
  }
  
- /*
-  * (Re-)queries the outputs via RandR and stores them in the list of outputs.
-  *
-  */
- void randr_query_outputs(void) {
+ static bool __randr_query_outputs(void) {
      Output *output, *other, *first;
      xcb_randr_get_output_primary_cookie_t pcookie;
      xcb_randr_get_screen_resources_current_cookie_t rcookie;
      xcb_randr_output_t *randr_outputs;
  
      if (randr_disabled)
-         return;
+         return true;
  
      /* Get screen resources (primary output, crtcs, outputs, modes) */
      rcookie = xcb_randr_get_screen_resources_current(conn, root);
          DLOG("primary output is %08x\n", primary->output);
      if ((res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL)) == NULL) {
          disable_randr(conn);
-         return;
+         return true;
      }
      cts = res->config_timestamp;
  
              DLOG("Output %s disabled, re-assigning workspaces/docks\n", output->name);
  
              first = get_first_output();
+             if (!first) {
+                 FREE(res);
+                 FREE(primary);
+                 return false;
+             }
  
              /* TODO: refactor the following code into a nice function. maybe
               * use an on_destroy callback which is implement differently for
  
      FREE(res);
      FREE(primary);
+     return true;
+ }
+ /*
+  * (Re-)queries the outputs via RandR and stores them in the list of outputs.
+  *
+  */
+ void randr_query_outputs(void) {
+     static bool first_query = true;
+     if (first_query) {
+         /* find monitors at least once via RandR */
+         if (!__randr_query_outputs())
+             die("No usable outputs available.\n");
+         first_query = false;
+     } else {
+         /* requery */
+         if (!__randr_query_outputs()) {
+             DLOG("sleep %f ms due to zero displays\n", config.zero_disp_exit_timer_ms);
+             usleep(config.zero_disp_exit_timer_ms * 1000);
+             if (!__randr_query_outputs())
+                 die("No usable outputs available.\n");
+         }
+     }
  }
  
  /*
index 7568de48394e4ba4c09edcce2af289fca191ee32,bb2ddf89f06c984baa1dccf5edcc1bb851e43837..de25e7ff70f52c3e6b02e763fba2e4fb71d58c86
@@@ -47,20 -47,16 +47,20 @@@ mode "meh" 
      bindsym --release Mod1+x exec foo
      bindsym --whole-window button3 nop
      bindsym --release --whole-window button3 nop
 +    bindsym --border button3 nop
 +    bindsym --release --border button3 nop
  }
  EOT
  
  my $expected = <<'EOT';
  cfg_enter_mode(meh)
 -cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), resize grow)
 -cfg_mode_binding(bindcode, Mod1, 44, (null), (null), resize shrink)
 -cfg_mode_binding(bindsym, Mod1, x, --release, (null), exec foo)
 -cfg_mode_binding(bindsym, (null), button3, (null), --whole-window, nop)
 -cfg_mode_binding(bindsym, (null), button3, --release, --whole-window, nop)
 +cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow)
 +cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink)
 +cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo)
 +cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, nop)
 +cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, nop)
 +cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), nop)
 +cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), nop)
  EOT
  
  is(parser_calls($config),
@@@ -369,6 -365,40 +369,40 @@@ is(parser_calls($config)
     $expected,
     'force_display_urgency_hint ok');
  
+ ################################################################################
+ # delay_exit_on_zero_displays
+ ################################################################################
+ is(parser_calls('delay_exit_on_zero_displays 300'),
+    "cfg_delay_exit_on_zero_displays(300)\n",
+    'delay_exit_on_zero_displays ok');
+ is(parser_calls('delay_exit_on_zero_displays 500 ms'),
+    "cfg_delay_exit_on_zero_displays(500)\n",
+    'delay_exit_on_zero_displays ok');
+ is(parser_calls('delay_exit_on_zero_displays 700ms'),
+    "cfg_delay_exit_on_zero_displays(700)\n",
+    'delay_exit_on_zero_displays ok');
+ $config = <<'EOT';
+ delay_exit_on_zero_displays 300
+ delay_exit_on_zero_displays 500 ms
+ delay_exit_on_zero_displays 700ms
+ delay_exit_on_zero_displays 700
+ EOT
+ $expected = <<'EOT';
+ cfg_delay_exit_on_zero_displays(300)
+ cfg_delay_exit_on_zero_displays(500)
+ cfg_delay_exit_on_zero_displays(700)
+ cfg_delay_exit_on_zero_displays(700)
+ EOT
+ is(parser_calls($config),
+    $expected,
+    'delay_exit_on_zero_displays ok');
  ################################################################################
  # workspace
  ################################################################################
@@@ -441,7 -471,7 +475,7 @@@ client.focused          #4c7899 #28557
  EOT
  
  my $expected_all_tokens = <<'EOT';
- ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'no_focus', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'focus_on_window_activation', 'show_marks', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
+ ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', 'bar', 'font', 'mode', 'floating_minimum_size', 'floating_maximum_size', 'floating_modifier', 'default_orientation', 'workspace_layout', 'new_window', 'new_float', 'hide_edge_borders', 'for_window', 'assign', 'no_focus', 'focus_follows_mouse', 'mouse_warping', 'force_focus_wrapping', 'force_xinerama', 'force-xinerama', 'workspace_auto_back_and_forth', 'fake_outputs', 'fake-outputs', 'force_display_urgency_hint', 'delay_exit_on_zero_displays', 'focus_on_window_activation', 'show_marks', 'workspace', 'ipc_socket', 'ipc-socket', 'restart_state', 'popup_during_fullscreen', 'exec_always', 'exec', 'client.background', 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
  EOT
  
  my $expected_end = <<'EOT';
@@@ -628,7 -658,7 +662,7 @@@ EO
  
  $expected = <<'EOT';
  cfg_enter_mode(yo)
 -cfg_mode_binding(bindsym, (null), x, (null), (null), resize shrink left)
 +cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left)
  ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}'
  ERROR: CONFIG: (in file <stdin>)
  ERROR: CONFIG: Line   1: mode "yo" {