diff --git a/docs/userguide b/docs/userguide index 3a3cf002..6975c943 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1010,6 +1010,31 @@ force_display_urgency_hint ms 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 ms +---------------------------------------- + +*Example*: +---------------------------------- +delay_exit_on_zero_displays 500 ms +---------------------------------- + === Focus on window activation [[focus_on_window_activation]] diff --git a/include/config.h b/include/config.h index 4cc58a45..75e0b127 100644 --- a/include/config.h +++ b/include/config.h @@ -167,6 +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. */ diff --git a/include/config_directives.h b/include/config_directives.h index 019b9bcd..1a7a3932 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -51,6 +51,7 @@ CFGFUN(force_focus_wrapping, const char *value); 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); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 422efe48..986086c7 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -39,6 +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 @@ -228,6 +229,17 @@ state FORCE_DISPLAY_URGENCY_HINT_MS: end -> call cfg_force_display_urgency_hint(&duration_ms) +# delay_exit_on_zero_displays 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 state FOCUS_ON_WINDOW_ACTIVATION: mode = word diff --git a/src/config.c b/src/config.c index 6eb67a12..bac9d7f3 100644 --- a/src/config.c +++ b/src/config.c @@ -203,6 +203,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath, 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 --git a/src/config_directives.c b/src/config_directives.c index ff1c280e..ae78e0c0 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -354,6 +354,10 @@ CFGFUN(force_display_urgency_hint, const long duration_ms) { 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 --git a/src/main.c b/src/main.c index 86e40831..e8e0daa8 100644 --- a/src/main.c +++ b/src/main.c @@ -621,6 +621,8 @@ int main(int argc, char *argv[]) { 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 --git a/src/randr.c b/src/randr.c index 9e236dcb..1bd99931 100644 --- a/src/randr.c +++ b/src/randr.c @@ -69,7 +69,7 @@ Output *get_first_output(void) { if (output->active) return output; - die("No usable outputs available.\n"); + return NULL; } /* @@ -570,6 +570,8 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, 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, @@ -591,11 +593,7 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, 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; @@ -609,7 +607,7 @@ void randr_query_outputs(void) { 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); @@ -621,7 +619,7 @@ void randr_query_outputs(void) { 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; @@ -703,6 +701,11 @@ void randr_query_outputs(void) { 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 @@ -818,6 +821,32 @@ void randr_query_outputs(void) { 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"); + } + } } /* diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 7568de48..de25e7ff 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -369,6 +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 +475,7 @@ client.focused #4c7899 #285577 #ffffff #2e9ef4 EOT my $expected_all_tokens = <<'EOT'; -ERROR: CONFIG: Expected one of these tokens: , '#', '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: , '#', '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';