How to raise a window in GTK3 (i.e. unminimize it, bring it on top of other windows and focus it)?

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;
}

You want gtk_window_present_with_time() if you have a GdkEvent that you can use to extract the event serial (or “timestamp”), or gtk_window_present() if you don’t have that.

None of these methods will guarantee that your window will have key focus. Focus stealing prevention is actually a thing that happens in window managers: it prevents random windows popping up while you’re typing your something that could be your password into another application. Key focus is always something that the user gives to a window, and applications cannot rely on that happening automatically.

Thank you! This actually explains everything I was missing.

So if I understand that correctly, to properly restore-and-activate a window that has been “minimized to tray”, I need to capture the “tray-icon-click” event, extract its timestamp, and pass it into present_with_time(), correct?

For context, we’re fixing Betterbird: Clicking the tray icon does not raise the window on Linux · Issue #202 · Betterbird/thunderbird-patches · GitHub

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.