i3 - improved tiling WM


Dont set input focus and send WM_TAKE_FOCUS

Patch status: merged

Patch by Tony Crisci

Long description:

If input focus is set by the window manager, it is not necessary to send
WM_TAKE_FOCUS because it has already taken focus.

http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7

> The goal is to support window managers that want to assign the input
> focus to a top-level window in such a way that the top-level window
> either can assign it to one of its subwindows or can decline the offer
> of the focus. For example, a clock or a text editor with no currently
> open frames might not want to take focus even though the window
> manager generally believes that clients should take the input focus
> after being deiconified or raised.

Both setting input focus and sending WM_TAKE_FOCUS is effectively
setting focus on the window twice which is certainly against the spirit
of the spec, if not the letter.

fixes #1167

To apply this patch, use:
curl http://cr.i3wm.org/patch/503/raw.patch | git am

b/src/x.c

32
@@ -987,20 +987,16 @@ void x_push_changes(Con *con) {
33
             /* Invalidate focused_id to correctly focus new windows with the same ID */
34
             focused_id = XCB_NONE;
35
         } else {
36
-            bool set_focus = true;
37
             if (focused->window != NULL &&
38
-                focused->window->needs_take_focus) {
39
+                focused->window->needs_take_focus &&
40
+                focused->window->doesnt_accept_focus) {
41
                 DLOG("Updating focus by sending WM_TAKE_FOCUS to window 0x%08x (focused: %p / %s)\n",
42
                      to_focus, focused, focused->name);
43
                 send_take_focus(to_focus, last_timestamp);
44
-                set_focus = !focused->window->doesnt_accept_focus;
45
-                DLOG("set_focus = %d\n", set_focus);
46
 
47
-                if (!set_focus && to_focus != last_focused && is_con_attached(focused))
48
+                if (to_focus != last_focused && is_con_attached(focused))
49
                    ipc_send_window_event("focus", focused);
50
-            }
51
-
52
-            if (set_focus) {
53
+            } else {
54
                 DLOG("Updating focus (focused: %p / %s) to X11 window 0x%08x\n", focused, focused->name, to_focus);
55
                 /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
56
                  * no focus change events for our own focus changes. We only want

b/testcases/t/158-wm_take_focus.t

61
@@ -16,12 +16,14 @@
62
 #
63
 # Tests if the WM_TAKE_FOCUS protocol is correctly handled by i3
64
 #
65
+# For more information on the protocol and input handling, see:
66
+# http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
67
+#
68
 use i3test;
69
 
70
-subtest 'Window without WM_TAKE_FOCUS', sub {
71
-    fresh_workspace;
72
+sub recv_take_focus {
73
+    my ($window) = @_;
74
 
75
-    my $window = open_window;
76
     # sync_with_i3 will send a ClientMessage to i3 and i3 will send the same
77
     # payload back to $window->id.
78
     my $myrnd = sync_with_i3(
79
@@ -44,12 +46,20 @@ subtest 'Window without WM_TAKE_FOCUS', sub {
80
         return ($rnd == $myrnd);
81
     };
82
 
83
-    ok($first_event_is_clientmessage, 'did not receive ClientMessage');
84
+    return !$first_event_is_clientmessage;
85
+}
86
+
87
+subtest 'Window without WM_TAKE_FOCUS', sub {
88
+    fresh_workspace;
89
+
90
+    my $window = open_window;
91
+
92
+    ok(!recv_take_focus($window), 'did not receive ClientMessage');
93
 
94
     done_testing;
95
 };
96
 
97
-subtest 'Window with WM_TAKE_FOCUS', sub {
98
+subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub {
99
     fresh_workspace;
100
 
101
     my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
102
@@ -59,16 +69,31 @@ subtest 'Window with WM_TAKE_FOCUS', sub {
103
         protocols => [ $take_focus ],
104
     });
105
 
106
+    # add an (empty) WM_HINTS property without the InputHint
107
+    $window->delete_hint('input');
108
+
109
     $window->map;
110
 
111
-    ok(wait_for_event(1, sub {
112
-        return 0 unless $_[0]->{response_type} == 161;
113
-        my ($data, $time) = unpack("L2", $_[0]->{data});
114
-        return ($data == $take_focus->id);
115
-    }), 'got ClientMessage with WM_TAKE_FOCUS atom');
116
+    ok(recv_take_focus($window), 'got ClientMessage with WM_TAKE_FOCUS atom');
117
 
118
     done_testing;
119
 };
120
 
121
+# If the InputHint is unspecified, i3 should use the simpler method of focusing
122
+# the window directly rather than using the WM_TAKE_FOCUS protocol.
123
+# XXX: The code paths for an unspecified and set InputHint are
124
+# nearly identical presently, so this is currently used also as a proxy test
125
+# for the latter case.
126
+subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub {
127
+    fresh_workspace;
128
+
129
+    my $take_focus = $x->atom(name => 'WM_TAKE_FOCUS');
130
+
131
+    my $window = open_window({ protocols => [ $take_focus ] });
132
+
133
+    ok(!recv_take_focus($window), 'did not receive ClientMessage');
134
+
135
+    done_testing;
136
+};
137
 
138
 done_testing;