diff --git a/docs/userguide b/docs/userguide index d6dbde00..5eb48554 100644 --- a/docs/userguide +++ b/docs/userguide @@ -981,6 +981,31 @@ force_display_urgency_hint ms force_display_urgency_hint 500 ms --------------------------------- +=== Focus on window activation + +If a window is activated, e.g., via +google-chrome www.google.com+, it may request +to take focus. Since this may not preferable, different reactions can be configured. + +Note that this does not apply to any window that is opened, i.e., typically opening +an application will focus that window independent of this configuration. + +*Syntax*: +---------------------------------------------------- +focus_on_window_activation +---------------------------------------------------- + +The different modes will act as follows: + +smart:: + This is the default behavior. If the window requesting focus is on an active + workspace, it will receive the focus. Otherwise, the urgency hint will be set. +urgent:: + The window will always be marked urgent, but the focus will not be stolen. +focus:: + The window will always be focused and not be marked urgent. +none:: + The window will neither be focused, nor be marked urgent. + == Configuring i3bar The bar at the bottom of your monitor is drawn by a separate process called diff --git a/include/config.h b/include/config.h index 9a0af0e6..5b534fb7 100644 --- a/include/config.h +++ b/include/config.h @@ -167,6 +167,18 @@ struct Config { * flag can be delayed using an urgency timer. */ float workspace_urgency_timer; + /** Behavior when a window sends a NET_ACTIVE_WINDOW message. */ + enum { + /* Focus if the target workspace is visible, set urgency hint otherwise. */ + FOWA_SMART, + /* Always set the urgency hint. */ + FOWA_URGENT, + /* Always focus the window. */ + FOWA_FOCUS, + /* Ignore the request (no focus, no urgency hint). */ + FOWA_NONE + } focus_on_window_activation; + /** The default border style for new windows. */ border_style_t default_border; diff --git a/include/config_directives.h b/include/config_directives.h index 6c960b1f..8f99e648 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(focus_on_window_activation, const char *mode); CFGFUN(hide_edge_borders, const char *borders); CFGFUN(assign, const char *workspace); CFGFUN(ipc_socket, const char *path); diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 25be5cf1..e23c37c8 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -38,6 +38,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 + 'focus_on_window_activation' -> FOCUS_ON_WINDOW_ACTIVATION 'workspace' -> WORKSPACE 'ipc_socket', 'ipc-socket' -> IPC_SOCKET 'restart_state' -> RESTART_STATE @@ -210,6 +211,11 @@ state FORCE_DISPLAY_URGENCY_HINT_MS: end -> call cfg_force_display_urgency_hint(&duration_ms) +# focus_on_window_activation +state FOCUS_ON_WINDOW_ACTIVATION: + mode = word + -> call cfg_focus_on_window_activation($mode) + # workspace output state WORKSPACE: workspace = word diff --git a/src/config_directives.c b/src/config_directives.c index c8b25c76..039cb29e 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -329,6 +329,23 @@ CFGFUN(force_display_urgency_hint, const long duration_ms) { config.workspace_urgency_timer = duration_ms / 1000.0; } +CFGFUN(focus_on_window_activation, const char *mode) { + if (strcmp(mode, "smart") == 0) + config.focus_on_window_activation = FOWA_SMART; + else if (strcmp(mode, "urgent") == 0) + config.focus_on_window_activation = FOWA_URGENT; + else if (strcmp(mode, "focus") == 0) + config.focus_on_window_activation = FOWA_FOCUS; + else if (strcmp(mode, "none") == 0) + config.focus_on_window_activation = FOWA_NONE; + else { + ELOG("Unknown focus_on_window_activation mode \"%s\", ignoring it.\n", mode); + return; + } + + DLOG("Set new focus_on_window_activation mode = %i", config.focus_on_window_activation); +} + CFGFUN(workspace, const char *workspace, const char *output) { DLOG("Assigning workspace \"%s\" to output \"%s\"\n", workspace, output); /* Check for earlier assignments of the same workspace so that we diff --git a/src/handlers.c b/src/handlers.c index 041f7e36..c80c279e 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -743,16 +743,17 @@ static void handle_client_message(xcb_client_message_event_t *event) { workspace_show(ws); con_focus(con); } else { - /* If the request is from an application, only focus if the - * workspace is visible. Otherwise set the urgency hint. */ - if (workspace_is_visible(ws)) { - DLOG("Request to focus con on a visible workspace. Focusing con = %p\n", con); + /* Request is from an application. */ + + if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { + DLOG("Focusing con = %p\n", con); workspace_show(ws); con_focus(con); - } else { - DLOG("Request to focus con on a hidden workspace. Setting urgent con = %p\n", con); + } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { + DLOG("Marking con = %p urgent\n", con); con_set_urgency(con, true); - } + } else + DLOG("Ignoring request for con = %p", con); } tree_render(); diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 1e91d47f..a6b1fe0c 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -433,7 +433,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', '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', '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', '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', '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'; diff --git a/testcases/t/240-focus-on-window-activation.t b/testcases/t/240-focus-on-window-activation.t new file mode 100644 index 00000000..5f0b6e1c --- /dev/null +++ b/testcases/t/240-focus-on-window-activation.t @@ -0,0 +1,206 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for the focus_on_window_activation directive +# Ticket: #1426 +use i3test i3_autostart => 0; +use List::Util qw(first); + +sub send_net_active_window { + my ($id) = @_; + + my $msg = pack "CCSLLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $id, # destination window + $x->atom(name => '_NET_ACTIVE_WINDOW')->id, + 0, # source + 0, 0, 0, 0; + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); +} + +sub get_urgency_for_window_on_workspace { + my ($ws, $con) = @_; + + my $current = first { $_->{window} == $con->{id} } @{get_ws_content($ws)}; + return $current->{urgent}; +} + +##################################################################### +# 1: If mode is set to 'urgent' and the target workspace is visible, +# check that the urgent flag is set and focus is not lost. +##################################################################### + +my $config = <id); +sync_with_i3; + +is($x->input_focus, $second->id, 'second window is still focused'); +is(get_urgency_for_window_on_workspace($ws, $first), 1, 'first window is marked urgent'); + +exit_gracefully($pid); + +##################################################################### +# 2: If mode is set to 'urgent' and the target workspace is not +# visible, check that the urgent flag is set and focus is not lost. +##################################################################### + +my $config = <id); +sync_with_i3; + +is(focused_ws(), $ws2, 'second workspace is still focused'); +is($x->input_focus, $second->id, 'second window is still focused'); +is(get_urgency_for_window_on_workspace($ws1, $first), 1, 'first window is marked urgent'); + +exit_gracefully($pid); + +##################################################################### +# 3: If mode is set to 'focus' and the target workspace is visible, +# check that the focus is switched. +##################################################################### + +my $config = <id); +sync_with_i3; + +is($x->input_focus, $first->id, 'first window is now focused'); +ok(!get_urgency_for_window_on_workspace($ws, $first), 'first window is not marked urgent'); + +exit_gracefully($pid); + +##################################################################### +# 4: If mode is set to 'focus' and the target workspace is not +# visible, check that the focus switched. +##################################################################### + +my $config = <id); +sync_with_i3; + +is(focused_ws(), $ws1, 'first workspace is now focused'); +is($x->input_focus, $first->id, 'first window is now focused'); +ok(!get_urgency_for_window_on_workspace($ws1, $first), 'first window is not marked urgent'); + +exit_gracefully($pid); + +##################################################################### +# 5: If mode is set to 'none' and the target workspace is visible, +# check that nothing happens. +##################################################################### + +my $config = <id); +sync_with_i3; + +is($x->input_focus, $second->id, 'second window is still focused'); +ok(!get_urgency_for_window_on_workspace($ws, $first), 'first window is not marked urgent'); + +exit_gracefully($pid); + +##################################################################### +# 6: If mode is set to 'none' and the target workspace is not +# visible, check that nothing happens. +##################################################################### + +my $config = <id); +sync_with_i3; + +is(focused_ws(), $ws2, 'second workspace is still focused'); +is($x->input_focus, $second->id, 'second window is still focused'); +ok(!get_urgency_for_window_on_workspace($ws1, $first), 'first window is not marked urgent'); + +exit_gracefully($pid); + +done_testing;