]> git.sur5r.net Git - i3/i3/commitdiff
implement delayed urgency hint reset
authorSimon Elsbrock <simon@iodev.org>
Sat, 22 Sep 2012 11:48:22 +0000 (13:48 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 22 Sep 2012 12:12:09 +0000 (14:12 +0200)
If there is a client with an urgency hint on another workspace and
switching to this workspace would cause the urgency to be reset (by
moving the focusing to the client), delay the reset by some time. This
gives the user the chance to see it.

This commit adds the possibility to configure the urgency delay timer
duration using the 'force_display_urgency_hint' directive. Also,
documentation and a testcase was added to allow for automated checks of
the intended behavior.

fixes #482

docs/userguide
include/config.h
include/data.h
src/cfgparse.l
src/cfgparse.y
src/config.c
src/handlers.c
src/tree.c
src/workspace.c
testcases/t/198-urgency-timer.t [new file with mode: 0644]

index 525cfd77dbcae19c4272ace93486e785bc349d0c..bc208652d7f941e282d40990a306a737c724a409 100644 (file)
@@ -874,6 +874,30 @@ workspace_auto_back_and_forth <yes|no>
 workspace_auto_back_and_forth yes
 ---------------------------------
 
+=== Delaying urgency hint reset on workspace change
+
+If an application on another workspace sets an urgency hint, switching to this
+workspace may lead to immediate focus of the application, which also means the
+window decoration color would be immediately resetted to +client.focused+. This
+may make it unnecessarily hard to tell which window originally raised the
+event.
+
+In order to prevent this, you can tell i3 to delay resetting the urgency state
+by a certain time using the +force_display_urgency_hint+ directive. Setting the
+value to 0 disables this feature.
+
+The default is 500ms.
+
+*Syntax*:
+---------------------------------------
+force_display_urgency_hint <timeout> ms
+---------------------------------------
+
+*Example*:
+---------------------------------
+force_display_urgency_hint 500 ms
+---------------------------------
+
 == Configuring i3bar
 
 The bar at the bottom of your monitor is drawn by a separate process called
index 669cfe44db58b1b7b00a5837cb758dbce5789a57..056aa5ae731ad2ce4c0b95d0c3ae5288a452c1ef 100644 (file)
@@ -149,6 +149,13 @@ struct Config {
      * between two workspaces. */
     bool workspace_auto_back_and_forth;
 
+    /** By default, urgency is cleared immediately when switching to another
+     * workspace leads to focusing the con with the urgency hint. When having
+     * multiple windows on that workspace, the user needs to guess which
+     * application raised the event. To prevent this, the reset of the urgency
+     * flag can be delayed using an urgency timer. */
+    float workspace_urgency_timer;
+
     /** The default border style for new windows. */
     border_style_t default_border;
 
index a2c6859ed0f47bdcde3153a4bd3a2cd1bb410046..e78354f444989aa66d5ff057c30732903a0dfe79 100644 (file)
@@ -496,6 +496,9 @@ struct Con {
      * inside this container (if any) sets the urgency hint, for example. */
     bool urgent;
 
+    /* timer used for disabling urgency */
+    struct ev_timer *urgency_timer;
+
     /* ids/pixmap/graphics context for the frame window */
     xcb_window_t frame;
     xcb_pixmap_t pixmap;
index 8ee2a1dad75fdcb5f414b59437c9f46c9662e815..b752851beb4e6e35100c288942e435c2778fa57c 100644 (file)
@@ -212,6 +212,8 @@ force-xinerama                  { return TOK_FORCE_XINERAMA; }
 fake_outputs                    { WS_STRING; return TOK_FAKE_OUTPUTS; }
 fake-outputs                    { WS_STRING; return TOK_FAKE_OUTPUTS; }
 workspace_auto_back_and_forth   { return TOK_WORKSPACE_AUTO_BAF; }
+force_display_urgency_hint      { return TOK_WORKSPACE_URGENCY_TIMER; }
+ms                              { return TOK_TIME_MS; }
 workspace_bar                   { return TOKWORKSPACEBAR; }
 popup_during_fullscreen         { return TOK_POPUP_DURING_FULLSCREEN; }
 ignore                          { return TOK_IGNORE; }
index 29c519f0469f3aa6544993cb5701a9a8499e5759..bcd7d20c5e5c8964cab7cf830d405cf1b208e581 100644 (file)
@@ -728,6 +728,7 @@ void parse_file(const char *f) {
 %token  <color>         TOKCOLOR
 %token                  TOKARROW                    "→"
 %token                  TOKMODE                     "mode"
+%token                  TOK_TIME_MS                 "ms"
 %token                  TOK_BAR                     "bar"
 %token                  TOK_ORIENTATION             "default_orientation"
 %token                  TOK_HORIZ                   "horizontal"
@@ -746,6 +747,7 @@ void parse_file(const char *f) {
 %token                  TOK_FORCE_XINERAMA          "force_xinerama"
 %token                  TOK_FAKE_OUTPUTS            "fake_outputs"
 %token                  TOK_WORKSPACE_AUTO_BAF      "workspace_auto_back_and_forth"
+%token                  TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint"
 %token                  TOKWORKSPACEBAR             "workspace_bar"
 %token                  TOK_DEFAULT                 "default"
 %token                  TOK_STACKING                "stacking"
@@ -819,6 +821,7 @@ void parse_file(const char *f) {
 %type   <number>        optional_release
 %type   <string>        command
 %type   <string>        word_or_number
+%type   <string>        duration
 %type   <string>        qstring_or_number
 %type   <string>        optional_workspace_name
 %type   <string>        workspace_name
@@ -848,6 +851,7 @@ line:
     | force_focus_wrapping
     | force_xinerama
     | fake_outputs
+    | force_display_urgency_hint
     | workspace_back_and_forth
     | workspace_bar
     | workspace
@@ -1052,6 +1056,10 @@ word_or_number:
     }
     ;
 
+duration:
+    NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); }
+    ;
+
 mode:
     TOKMODE QUOTEDSTRING '{' modelines '}'
     {
@@ -1548,6 +1556,14 @@ workspace_back_and_forth:
     }
     ;
 
+force_display_urgency_hint:
+    TOK_WORKSPACE_URGENCY_TIMER duration
+    {
+        DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0);
+        config.workspace_urgency_timer = atoi($2) / 1000.0;
+    }
+    ;
+
 workspace_bar:
     TOKWORKSPACEBAR bool
     {
index 0bd6811a7c2944316c4ac34a28597779fbb8cc3d..0cfa8eb6ac92a380b5b77dccbb00ab286dd7d64b 100644 (file)
@@ -420,6 +420,10 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
     /* Set default_orientation to NO_ORIENTATION for auto orientation. */
     config.default_orientation = NO_ORIENTATION;
 
+    /* Set default urgency reset delay to 500ms */
+    if (config.workspace_urgency_timer == 0)
+        config.workspace_urgency_timer = 0.5;
+
     parse_configuration(override_configpath);
 
     if (reload) {
index 21a873420d989430394f7d1b25043ea76ca6e100..f9099cc147dca70663a371efaffb2ec58004bdfd 100644 (file)
@@ -836,7 +836,13 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
     }
 
     /* Update the flag on the client directly */
-    con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
+    bool hint_urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
+
+    if (con->urgency_timer == NULL) {
+        con->urgent = hint_urgent;
+    } else
+        DLOG("Discarding urgency WM_HINT because timer is running\n");
+
     //CLIENT_LOG(con);
     if (con->window) {
         if (con->urgent) {
index 321bc78a5951b2b9fa13c78819301f53049b5d49..4f34946cc58c13e654c8ad031e68d859f65dde6d 100644 (file)
@@ -255,6 +255,15 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
     x_con_kill(con);
 
     con_detach(con);
+
+    /* disable urgency timer, if needed */
+    if (con->urgency_timer != NULL) {
+        DLOG("Removing urgency timer of con %p\n", con);
+        workspace_update_urgent_flag(con_get_workspace(con));
+        ev_timer_stop(main_loop, con->urgency_timer);
+        FREE(con->urgency_timer);
+    }
+
     if (con->type != CT_FLOATING_CON) {
         /* If the container is *not* floating, we might need to re-distribute
          * percentage values for the resized containers. */
index 6f560ad9d4c0b4d71fed6938033cb5e6f49ae11a..71102e5cbc9d9ecf11c39f5389524bcb90ea3584 100644 (file)
@@ -311,6 +311,23 @@ static void workspace_reassign_sticky(Con *con) {
         workspace_reassign_sticky(current);
 }
 
+/*
+ * Callback to reset the urgent flag of the given con to false. May be started by
+ * _workspace_show to avoid urgency hints being lost by switching to a workspace
+ * focusing the con.
+ *
+ */
+static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
+    Con *con = w->data;
+
+    DLOG("Resetting urgency flag of con %p by timer\n", con);
+    con->urgent = false;
+    workspace_update_urgent_flag(con_get_workspace(con));
+    tree_render();
+
+    ev_timer_stop(main_loop, con->urgency_timer);
+    FREE(con->urgency_timer);
+}
 
 static void _workspace_show(Con *workspace) {
     Con *current, *old = NULL;
@@ -350,6 +367,43 @@ static void _workspace_show(Con *workspace) {
     LOG("switching to %p\n", workspace);
     Con *next = con_descend_focused(workspace);
 
+    /* Memorize current output */
+    Con *old_output = con_get_output(focused);
+
+    /* Display urgency hint for a while if the newly visible workspace would
+     * focus and thereby immediately destroy it */
+    if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
+        /* focus for now… */
+        con_focus(next);
+
+        /* … but immediately reset urgency flags; they will be set to false by
+         * the timer callback in case the container is focused at the time of
+         * its expiration */
+        focused->urgent = true;
+        workspace->urgent = true;
+
+        if (focused->urgency_timer == NULL) {
+            DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
+                    focused, workspace);
+            focused->urgency_timer = scalloc(sizeof(struct ev_timer));
+            /* use a repeating timer to allow for easy resets */
+            ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb,
+                    config.workspace_urgency_timer, config.workspace_urgency_timer);
+            focused->urgency_timer->data = focused;
+            ev_timer_start(main_loop, focused->urgency_timer);
+        } else {
+            DLOG("Resetting urgency timer of con %p on workspace %p\n",
+                    focused, workspace);
+            ev_timer_again(main_loop, focused->urgency_timer);
+        }
+    } else
+        con_focus(next);
+
+    /* Close old workspace if necessary. This must be done *after* doing
+     * urgency handling, because tree_close() will do a con_focus() on the next
+     * client, which will clear the urgency flag too early. Also, there is no
+     * way for con_focus() to know about when to clear urgency immediately and
+     * when to defer it. */
     if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
         /* check if this workspace is currently visible */
         if (!workspace_is_visible(old)) {
@@ -359,10 +413,6 @@ static void _workspace_show(Con *workspace) {
         }
     }
 
-    /* Memorize current output */
-    Con *old_output = con_get_output(focused);
-
-    con_focus(next);
     workspace->fullscreen_mode = CF_OUTPUT;
     LOG("focused now = %p / %s\n", focused, focused->name);
 
diff --git a/testcases/t/198-urgency-timer.t b/testcases/t/198-urgency-timer.t
new file mode 100644 (file)
index 0000000..d3cdb3d
--- /dev/null
@@ -0,0 +1,95 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Tests whether the urgency timer works as expected and does not break
+# urgency handling.
+#
+
+use List::Util qw(first);
+use i3test i3_autostart => 0;
+use Time::HiRes qw(sleep);
+
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+force_display_urgency_hint 150ms
+EOT
+my $pid = launch_with_config($config);
+
+#####################################################################
+# Initial setup: one window on ws1, empty ws2
+#####################################################################
+
+my $tmp1 = fresh_workspace;
+my $w = open_window;
+
+my $tmp2 = fresh_workspace;
+cmd "workspace $tmp2";
+
+$w->add_hint('urgency');
+sync_with_i3;
+
+#######################################################################
+# Create a window on ws1, then switch to ws2, set urgency, switch back
+#######################################################################
+
+isnt($x->input_focus, $w->id, 'window not focused');
+
+my @content = @{get_ws_content($tmp1)};
+my @urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, "window marked as urgent");
+
+# switch to ws1
+cmd "workspace $tmp1";
+
+# this will start the timer
+sleep(0.1);
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window still marked as urgent');
+
+# now check if the timer was triggered
+cmd "workspace $tmp2";
+
+sleep(0.1);
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 0, 'window not marked as urgent anymore');
+
+#######################################################################
+# Create another window on ws1, focus it, switch to ws2, make the other
+# window urgent, and switch back. This should not trigger the timer.
+#######################################################################
+
+cmd "workspace $tmp1";
+my $w2 = open_window;
+is($x->input_focus, $w2->id, 'window 2 focused');
+
+cmd "workspace $tmp2";
+$w->add_hint('urgency');
+sync_with_i3;
+
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window 1 marked as urgent');
+
+# Switch back to ws1. This should focus w2.
+cmd "workspace $tmp1";
+is($x->input_focus, $w2->id, 'window 2 focused');
+
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 1, 'window 1 still marked as urgent');
+
+# explicitly focusing the window should result in immediate urgency reset
+cmd '[id="' . $w->id . '"] focus';
+@content = @{get_ws_content($tmp1)};
+@urgent = grep { $_->{urgent} } @content;
+is(@urgent, 0, 'window 1 not marked as urgent anymore');
+
+done_testing;