Gtk.Window.present says:
[gtk_window_present()] This function should not be used as when it is called, it is too late to gather a valid timestamp to allow focus stealing prevention to work correctly.
Unfortunately, it does not say what to use instead. So far I’ve tried:
- gtk_window_present
- gtk_window_set_keep_above
- gtk_window_deiconify
- gtk_widget_show_all
- gtk_widget_grab_focus
None of those bring the window in question to the foreground, above other windows, and focus it.
Other than this, I’ve also tried a workaround I’ve learned from Qt - create a modal messagebox and destroy it 10 ms later. This does not work either - even the messagebox is inactive.
I can deduce that the problem lies in GTK and not in my window manager, because raising windows with Qt (using the recommended way of doing it, which is a modal messagebox) works just fine, as well as (according to the random reports on the Internet) even the Python bindings for GTK, and all my other applications that minimize to tray and have to focus the window upon restoring. Including Thunderbird 45.8.0 with the FireTray addon, which is (I think) using GTK2 or maybe even GTK3. So this is most certainly possible - at least in terms of the window manager (not sure about GTK).
Sometimes it may appear that gtk_window_present() is working properly. To reproduce the problem, you have to:
- Create a window with a button on it that minimizes the window and starts a timeout to restore it again (this is important, because the problem only appears after you’ve clicked any clickable widget inside the window)
- Minimize the window using that button, and click a few other windows while it’s minimized (this is important, because the problem only appears if you switch focus between other windows a few times while the problem window is minimized)
- Then the problem window is restored - usually below other windows, and always unfocused, even if it appears to be above other windows.
This can be reproduced at least on MATE and KDE (I can’t talk about other WMs since unlike those two, I have not confirmed that with my own eyes).
Here is the sample code to test this. It creates a window with a button, you click the button, the window minimizes, gives you 3 sec to shuffle other windows, then it restores again. Put any attempts to restore and focus the window in the first function.
#include <gtk/gtk.h>
// Install libgtk-3-dev
// Compile this file with:
// gcc `pkg-config --cflags gtk+-3.0` -o test test.c `pkg-config --libs gtk+-3.0` -Wno-incompatible-pointer-types
// All the other attempts to raise a window
static gboolean above(GtkWindow* window)
{
g_print("Raising...\n");
// gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
// gtk_widget_show_all (window);
// gtk_window_set_keep_above(GTK_WINDOW(window), FALSE);
// gtk_widget_grab_focus(window);
// gtk_window_deiconify(GTK_WINDOW(window));
gtk_window_present(GTK_WINDOW(window));
return FALSE;
}
// Attempts to raise a window using a popup
static gboolean popup(GtkWindow* window)
{
g_print("Popup...\n");
GtkMessageDialog *dialog = gtk_message_dialog_new (GTK_WINDOW(window),
GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO,
GTK_BUTTONS_CLOSE,
"Temporary Popup");
// Non-blocking
g_signal_connect(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
gtk_widget_show_all(GTK_WINDOW(dialog));
// Scheduling destruction
g_print("Scheduling a dialog destruction...\n");
g_timeout_add(1000, G_CALLBACK(gtk_widget_destroy), dialog);
return FALSE; // Stop re-running the timeout
}
// Minimize a window and set a 3-second timeout to do "The Shuffle" before raising it back
static gboolean button_clicked(GtkWidget* button, GtkWindow* window)
{
g_print("Minimizing...\n");
gtk_window_iconify(GTK_WINDOW(window));
// Proof that "gtk_window_present" does not raise the window properly
// g_timeout_add(3000, G_CALLBACK(gtk_window_present), GTK_WINDOW(window));
// A popup does not work either!..
// g_print("Scheduling a raise via popup...\n");
// g_timeout_add(3000, G_CALLBACK(popup), GTK_WINDOW(window));
g_print("Scheduling a raise via set_keep_above...\n");
g_timeout_add(3000, G_CALLBACK(above), GTK_WINDOW(window));
return FALSE; // Stop re-running the timeout
}
// Apparently you need this in a separate function to automatically pass a style
static void activate(GtkApplication* app, gpointer user_data)
{
GtkWidget *window;
window = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (window), "CUNT");
gtk_window_resize (GTK_WINDOW (window), 800, 600);
gtk_window_move(GTK_WINDOW (window), 600, 300);
gtk_widget_show_all (window);
GtkWidget *button = gtk_button_new_with_label("Minimize and then restore");
g_signal_connect(GTK_BUTTON(button), "clicked", G_CALLBACK(button_clicked), GTK_WINDOW(window));
gtk_container_add(GTK_CONTAINER(window), button);
gtk_widget_show(button);
}
// Entrypoint
int main(int argc, char **argv)
{
GtkApplication *app;
int retval;
app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
retval = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return retval;
}