2
votes

A Python 3 and Gtk 3.22.5 application responds to a global key binding, thanks to the Keybinder library. Currently it invokes a simple callback:

def keybinder_callback(self, keystr, user_data):
    print("Handling", keystr, user_data)

Elsewhere in the application is a Gtk.Window that contains a Gtk.MenuBar. The keybinder_callback needs to activate the menu bar as if the user had clicked the mouse on it.

(it's a very basic dock-type application rather than one with a typical application window)

I have tried sending a signal to a menu item:

self.menubar.get_children()[0].emit("activate-item")

without any joy. I also tried faking a button press

from Xlib.ext.xtest import fake_input
fake_input(display,X.ButtonPress,1)

which also had no effect, but feels like the wrong way to do it anyway. I thought sending a signal would be more appropriate.

Can a widget be activated programmatically as if a user mouse-clicked on it?

(it doesn't have to be a simulated mouse-click - it just needs to activate and focus the widget in the same way that a mouse-click would)


I have written an example that has a keybinder_callback like this:

def keybinder_callback(self, keystr, user_data):
  print("Handling", keystr, user_data)
  print("Event time:", Keybinder.get_current_event_time())
  activate_the_menu()

I need to add some command to that function that will activate_the_menu.

I have tried many things, including capturing real events (by monitoring with xev) and simulating them ( ENTER_NOTIFY and FOCUS_CHANGE), by injecting them into Gdk.main_do_event.

I've tried calling menu.popup, menu.popup_at_widget, menubar.select_item and other numerous things. All to no avail.

Running out of ideas, I've even dusted off my old Xlib book...

Incidentally, whilst not a proper solution, this works from a shell:

$ xdotool mousemove 1605 10 click 1 mousemove restore

but not reliably from the keybinder_callback:

run("xdotool mousemove %d 10 click 1 mousemove restore" %
            (self.get_position().root_x+5) , shell=True)
1
Take a look at GtkMenu.popup_at_widget, but be careful, I almost froze my PC while playing around with it. Not really sure what happened, but after the menu popped up I couldn't close the program anymore.Aran-Fey
Well I tried self.menu.popup_at_widget(self,Gdk.Gravity.STATIC,Gdk.Gravity.STATIC,None) but got Warning: g_object_set_data: assertion 'G_IS_OBJECT (object)' failedstarfry

1 Answers

0
votes

After trying many things, I found that this works.

import Xlib
from Xlib import X
from Xlib.display import Display
from Xlib.ext.xtest import fake_input
import time

def keybinder_callback(self, keystr, user_data):
    time.sleep(0.2)
    x = self.get_position().root_x+5
    display = Display()
    mpos = display.screen().root.query_pointer()._data
    display.screen().root.warp_pointer(x,5)
    display.sync()
    fake_input(display,X.ButtonPress,1, X.CurrentTime, X.NONE, x, 5)
    display.sync()
    fake_input(display,X.ButtonRelease,1)
    display.screen().root.warp_pointer(mpos['root_x'],
                                       mpos['root_y'])
    display.sync()

I found that the time delay was necessary to avoid this error:

Gdk-CRITICAL **: Window 0x1e7c660 has not been made visible in GdkSeatGrabPrepareFunc