Cockatrice, Qt 5, X11, and window focus
I recently started using Cockatrice to play a card game with a friend. It is a neat program and works really well for playing the game. However, one aspect of its behaviour annoyed me. This is a story about me investigating this behaviour and trying to come up with a solution.
The annoying behaviour is how Cockatrice behaves when you click in most places in its window. In other programs, clicking in their windows won't grab keyboard focus. But Cockatrice was stealing the focus. This means I'd start typing, but my text would no show up where I expected it to. It would go to Cockatrice (which I did not want).
Clicking in the window not giving focus might sound a little strange. In most GUI systems (window managers) you will click in a window and expect your focus to go to that window. This isn't the case in how I have my system configured. I use a tiling window manager called ratpoison which primarily interacts with windows through the keyboard. To switch to a different window and give it focus, I do things like hit Ctrl+t and then 0 (to activate window 0). This is a bit like how GNU Screen works. Normally I can click on a window, such in Firefox to click on a link, and my keyboard focus will stay where it is. This lets me keep keyboard focus in, for example, a chat room, while still casually browsing.
Cockatrice is the only program I've seen behave this way. I started out by thinking this is a weird intentional action in Cockatrice's code. I started digging through the code to figure out how the chat box widget was getting focus. I thought at first that Cockatrice was programmed to give focus to the chat box this way. I looked at several focus related parts of the code. From testing the behaviour more, I discovered that the behaviour was not happening only while actually in a game, and focus was not only going to the chat box, both of which I originally believed. In fact, almost anywhere I clicked in Cockatrice I found it would steal focus this way. After investigating another part of the program that I knew exhibited the behaviour (clicking on cards in the deck editor), I started working my way up to the top level of the program. I could not find any code that appeared related to what I was seeing.
As the behaviour was showing at a very global level in Cockatrice, I wondered if it could be a setting or behaviour inherited from Qt 5, the framework Cockatrice uses for its GUI. To test this theory, I downloaded and compiled Qt 5 from source (I wanted to be able to turn on debug symbols). This was actually a lot easier to do than I expected, and what was even better was I did not have to install anything globally system wide (not needing to make install is the default for a development build). I followed this documentation: Building Qt 5 from Git and Linux building.
Once I had Qt 5 compiled in my home directory (~/bin/qt5
), I wrote a small
program that made a window with 2 buttons (from a tutorial on the Qt wiki). It
looks like this:
$ cat main.cpp
#include <QApplication>
#include <QPushButton>
int main(int argc, char **argv)
{
QApplication app (argc, argv);
QPushButton button1 ("test");
QPushButton button2 ("other", &button1);
button1.show();
return app.exec();
}
Then to compile this I created the file main.pro
(input for the Qt qmake
tool):
$ cat main.pro
TEMPLATE += app
QT += gui widgets
SOURCES += main.cpp
QMAKE_CXXFLAGS_DEBUG *= -pg
QMAKE_LFLAGS_DEBUG *= -pg
config += debug
Afterwards I compile it this way (I set PATH
to use the Qt I just compiled):
$ export PATH=~/bin/qt5/qtbase/bin:$PATH
$ qmake # generate a Makefile
$ make
I ended up with the executable program main
.
Running this program I found it behaved just like Cockatrice - it stole my keyboard focus! Clearly I was blaming Cockatrice unfairly.
I looked through the Qt documentation for a setting I could use to change this
behaviour (something like setAttribute()
for instance), but did not see
anything.
At this point I wanted to look in the Qt code to figure out how this was happening. It is quite a large code base and I wanted to narrow down what to look at. I thought a way to find out exactly what functions were being called in my program would be an easy way to track this down. To do that, I decided being able to trace and generate a call graph would work. I've never generated a call graph this way for a C++ program before, so I did a bunch of googling. I tried using gdb, but with little success. I think it would be possible, but from what I found it was a little hacky.
I found that Valgrind has a tool called Callgrind. I initially thought this would not be a good option because I wanted to be sure that every function call would be recorded, and I figured it was a profiler (like gprof) and would miss some due to sampling only periodically. But after reading a post by Baptiste Wicht I became convinced this could do what I wanted.
To gather the data I ran my program like this (I already had compiled with debug symbols):
$ valgrind --tool=callgrind ./main
I made sure to trigger the keyboard focus theft, and exited cleanly.
Then I ran KCacheGrind:
$ kcachegrind
This listed hundreds of function calls. I started out searching for functions with 'focus' in their name. KCacheGrind listed the filenames each function was in and using that I found the functions in the Qt source. To quickly find what function was actually doing this I made a few of the likely looking functions return early and not actually do what they should. Doing this I discovered that there is a function called
QWindow::requestActivate()
that caused the focus theft. It in turn calls (to interact with X):
QXcbWindow::requestActivateWindow()
The QWindow method I found was called by a method related to mouse input:
QXcbWindow::handleButtonPressEvent()
The relevant snippet of code is this:
if (!isWheel && window() != QGuiApplication::focusWindow()) {
QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(window()))->eventReceiver();
if (!(w->flags() & Qt::WindowDoesNotAcceptFocus))
w->requestActivate();
}
What this means is that when I click in the window, Qt sets the window activated which ends up giving it my keyboard focus. This seems strange to me because normally even without this code the window will receive focus anyway (if you have a window manager that behaves that way). It seems to me a job for the window manager rather than Qt.
I'm not sure it could really be called a bug though. I also don't know what the repercussions of changing this would be on other platforms. There may be a reason it is needed. It could be that is there but does not need to be and just doesn't harm anything except in weird setups like my own and so there has been no reason to change it.
It also occurs to me that my window manager should be able to see these requests and say "no" and not activate the window. Even if there is a window trying to get focus this way, why let it? Leave it up to me to change focus when I want. An exception being things like new windows that it makes a bit more sense to allow granting focus to. I'm going to investigate doing this. Even if I am successful I may still contact the Qt developers and raise what I've found to see if changing this would have any benefit.
Update:
I created a patch to my window manager, ratpoison, that makes it so the Qt programs cannot steal keyboard focus. This resolves the issue, though I'm not sure it is the best or correct way to solve the problem. It feels like kind of a hack. I'll describe what I did.
I started out by reading the ratpoison source code. I discovered that its main
loop is responding to X events, and that it uses the
Xlib X client library (which the
X.org site says is deprecated and should no longer be used, but ratpoison is a
fairly old project). ratpoison calls XNextEvent()
repeatedly which retrieves
the next event (every kind of event). It then takes action depending on the
event type.
I found in the Xlib
documentation
mention of several likely sounding events related to my problem. In particular,
the FocusIn
and FocusOut
events. I modified ratpoison to tell me what
events it saw: I log via syslog()
for each event it sees in its
delegate_event()
function (its built in print debug method I could not figure
out how to see). From this log I saw several event numbers at the time the
focus theft occurred. I looked these up in /usr/include/X11/X.h
to see what
the event names were. This told me that there were FocusIn
and FocusOut
events happening (among others including PropertyNotify
), so I could at least
know that much.
At this point I wanted to know more about how Qt was grabbing the focus. I
hoped this would provide more information about how I could block it from doing
so. I found that Qt is using a different C library for interacting with X:
xcb. (X.org claims this is the one to use for
new development). In Qt's QXcbWindow::requestActivateWindow()
I found there
were two ways it would do this (depending on a multi line condition): One where
it sends an event, xcb_send_event()
with type _NET_ACTIVE_WINDOW
, and the
other using the clearly named xcb_set_input_focus()
. Adding in a quick print
call I found that the second was in use in my environment. From the
documentation about this function, it generates FocusIn
and FocusOut
events, but I could not see any way to block it. In fact as I continued reading
I started to believe blocking it would not be possible, because it is
instructing the X server to give focus rather than ratpoison.
Given what I found, I came up with a theory that I could act when a FocusIn
event happened, and change keyboard focus back to where it should be
immediately if I didn't agree with the focus change. To do this I would need to
record when ratpoison changes the window focus. Then once I see a FocusIn
event, I could compare it with what ratpoison last set the focus to, and switch
focus back if ratpoison didn't perform the update.
How does ratpoison set keyboard focus though? In Xlib there are two functions
to grab the keyboard focus that I found: XGrabKeyboard()
and
XSetInputFocus()
. I found ratpoison uses both of these, but only
XSetInputFocus()
when giving a window full focus.
To find which ratpoison uses, I looked at the functions for its "select"
command which lets me switch between windows. I traced this from cmd_select()
(in action.c) to goto_window()
(in window.c) to set_active_frame()
(in
split.c) to give_window_focus()
(in window.c) and finally to
set_window_focus()
(in globals.c). I found that in this last function
ratpoison uses the Xlib function XSetInputFocus()
.
At this point I added a new global variable, focussed_window
, and update it
to the Window
(an Xlib type which is an integer) receiving focus when we
change focus. I put this into set_window_focus()
and one other function. This
means whenever ratpoison changes window focus, it will remember the last window
it gave focus to.
I then went back to ratpoison's event loop. There is already a function called
when a FocusIn
event happens. I discovered it did not do anything for the
exact condition I'm interested in. I added a check for there being a FocusIn
event with a mode NotifyNormal
(the FocusIn
event can have several modes).
Then when this happens, I compare the window parameter of the event with my new
global, focussed_window
, and if they differ, I set focus back to the original
window using set_window_focus()
.
From my testing this worked great. No longer would the Qt program steal my
focus. However, in my debugging logs I saw something strange: I would see a
FocusIn
event being reverted and then accepted during regular window focus
changes (that I initiated). What I saw was:
- Ratpoison sets focus to itself (waiting on my input to choose a window).
- We choose a window.
- Ratpoison sets focus to the original window.
- Ratpoison sets focus to the chosen window.
- Receive
FocusIn
event. This is telling us about the focus to the original window in #3. - Receive
FocusIn
event. Ignore as this is telling us about the window we want (in #4).
We see two FocusIn
events. We have set the window to focus in the global
variable before seeing the two events though (in step #4), so the first looks
wrong (its fallen behind). (I believe the first does not trigger a FocusIn
I
log due to being a different mode). That is my analysis anyway. I'm not
absolutely certain.
This doesn't affect the behaviour for the user but seems wasteful. It is confusing to read the log at least until I came up with an explanation. I don't know there is a way to avoid it. Possibly we could avoid the focus returning to the original window and go directly to the chosen one. There may be a reason it is this way though. For example, what if the chosen window doesn't exist?
I applied my patch to the ratpoison Git head and it works well. I'm using it right now. I plan to send a mail to the ratpoison development list and see if anyone has any feedback on this solution. Maybe there is a better way, or maybe there is something that this will totally break.
A good thing that came out of this is I learned a lot about some software I had little knowledge about before: Qt, Xlib, xcb, and ratpoison internals. I've come up with a list of things I'd like to improve in ratpoison as well, so I hope I can make more improvements and learn more.
Update:
I discovered there is already a Qt bug report about this very issue! The name is Qt5 xcb steals focus on click. It seems that it is Qt being too aggressive.