Making Python GTK3 window focus again after calling external application

24 Views Asked by At

I have a window with a button "Next". When clicked, or when the keyboard button "N" is hit, it opens a url in a browser. The desired behaviour is that the window should always be the focus, i.e., the url is opened in the background (the goal is to keep entering the keyboard button "N", without having to clic again on the window to highlight it). I have an implementation that works below, except when the browser is opened, the browser window receives the focus, so the keyboard press of "N" is received by the browser, not by the main window. I have tried other solutions (see enter link description here), which suggest to either use Gtk.window.present or to set the window as "always on top", but neither method work.

The comments in the code below enable switching from my implementation to the one described in the link provided, which inherits from Gtk.Application. Sadly, the outcome is the same. There are so many similar questions, and no solution works. I suspect there something fundamentally wrong on how this is implemented? Any insight?

import threading

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gio, Gdk, Gtk

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_gtk3agg import FigureCanvas
plt.switch_backend('GTK3Agg')

import webbrowser


#class consoleGUI(Gtk.Application):
class consoleGUI():

    urls = ('www.google.com', 'www.amazon.com', 'www.lenovo.com', 'www.ford.com')

    def __init__(self,):

        self.i = 0
        #super().__init__(application_id='org.gnome.Ee',
        #    flags = Gio.ApplicationFlags.FLAGS_NONE)

        self.win = Gtk.Window()
        #self.win.set_application(self)
        self.win.connect('destroy', self.quit)

        self.grid = Gtk.Grid()
        self.win.add(self.grid)

        self.fig, self.axes = plt.subplots(1, 1)

        self.canvas = FigureCanvas(self.fig)
        self.canvas.set_hexpand(True)
        self.canvas.set_vexpand(True)

        self.grid.attach(self.canvas,       0, 0, 4, 3)
        self.axes.plot(np.arange(10))

        self.nav_box = self._add_navigation_toolbar()
        self.grid.attach(self.nav_box,   0, 3, 4, 1)


        self.win.show_all()
        Gtk.main()

    def next(self, *args):
        self.i = (self.i + 1) % len(self.urls)
        #threading.Thread(
        #       target=webbrowser.open, args=(self.urls[self.i], 2,), ).start()
        webbrowser.open(self.urls[self.i], 2)
        self.win.present() # This won't work!

    def quit(self, *args):
        plt.close(self.fig)
        Gtk.main_quit()


    def _add_navigation_toolbar(self):

        box = Gtk.Box()
        box.set_orientation(Gtk.Orientation.HORIZONTAL)
        box.set_spacing(1)

        next_button = Gtk.ToolButton()
        next_button.set_label('Next')
        next_button._signal_handler = next_button.connect('clicked', self.next)
        next_button.set_tooltip_text('Show next\n(key: n)')
        self.next_accel = Gtk.AccelGroup()
        self.next_accel.connect(
            Gdk.keyval_from_name('n'), 0, 0, self._next_target_acc)
        self.win.add_accel_group(self.next_accel)

        quit_button = Gtk.ToolButton()
        quit_button.set_label('Quit') # TODO connect
        quit_button._signal_handler = quit_button.connect(
            'clicked', self.quit)
        quit_button.set_tooltip_text('Close inzimar and discard\nunsaved progress (key: q)')
        _accel = Gtk.AccelGroup()
        _accel.connect(Gdk.keyval_from_name('q'), 0, 0, self._quit_acc)
        self.win.add_accel_group(_accel)

        for button in (
            next_button, Gtk.SeparatorToolItem(), 
            quit_button,
            Gtk.SeparatorToolItem()):

            box.pack_start(button, True, True, 0)

        return box

    def _next_target_acc(self, *args):
        self.next(*args)

    def _quit_acc(self, *args):
        self.quit(*args)


if __name__=="__main__":
    
    consoleGUI()

Which I invoke simply as python3 console_gui.py

1

There are 1 best solutions below

1
pan-mroku On

Since web browser is another application, in general you can only ask window manager to kindly, maybe honor your call for your window to get focus. Many window managers will adhere but not all of them.

Try present_with_time or set_urgency_hint. AFAIK these are the functions to make it work, but it's possible that on some window managers it will never work without user going back to your window.
If you are really sure what you are doing, you could also grab the keyboard, so even when focus is elsewhere, a key press will still go to your app. But this could be perceived as disruptive by the user (what if I click through your app and then try to switch to a chat and write someone a message?)