Changed all showing/hiding logic

It seems most GTK apps like mintMenu and the default Gnome2/Mate menu
use the GDK grab focus functions to grab input (pointer/keyboard).
These functions appear to suck all input to the app like a blackhole.
This patch gives more interactivity to the mintMenu window, making it
a first class object in the desktop.

Things that work now:
* When the window is open, hovering on other desktop icons highlights them

* Clicking outside the window propagates the event (e.g. select another
window)

* Alt-tab and other events unfocus and hide the window

* Scrollbars in the mintMenu finally work right

* Probably several other small things
This commit is contained in:
hordepfo 2014-01-22 20:16:27 +00:00
parent 893fd879a2
commit d567469f4d
5 changed files with 200 additions and 101 deletions

View File

@ -58,10 +58,11 @@ class GlobalKeyBinding(GObject.GObject, threading.Thread):
self.keymap = capi.get_widget (gdk.gdk_keymap_get_default()) self.keymap = capi.get_widget (gdk.gdk_keymap_get_default())
self.display = Display() self.display = Display()
self.screen = self.display.screen() self.screen = self.display.screen()
self.root = self.screen.root self.window = self.screen.root
self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask)
self.map_modifiers() self.map_modifiers()
self.raw_keyval = None self.raw_keyval = None
self.keytext = ""
def is_hotkey(self, key, modifier): def is_hotkey(self, key, modifier):
keymatch = False keymatch = False
@ -101,21 +102,39 @@ class GlobalKeyBinding(GObject.GObject, threading.Thread):
self.modifiers = None self.modifiers = None
return False return False
self.keytext = key
self.keycode = self.get_keycode(keyval) self.keycode = self.get_keycode(keyval)
self.modifiers = int(modifiers) self.modifiers = int(modifiers)
catch = error.CatchError(error.BadAccess) catch = error.CatchError(error.BadAccess)
for ignored_mask in self.ignored_masks: for ignored_mask in self.ignored_masks:
mod = modifiers | ignored_mask mod = modifiers | ignored_mask
result = self.root.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch) result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch)
self.display.sync() self.display.flush()
# sync has been blocking. Don't know why.
#self.display.sync()
if catch.get_error(): if catch.get_error():
return False return False
return True return True
def ungrab(self): def ungrab(self):
if self.keycode: if self.keycode:
self.root.ungrab_key(self.keycode, X.AnyModifier, self.root) self.window.ungrab_key(self.keycode, X.AnyModifier, self.window)
def rebind(self, key):
self.ungrab()
if key != "":
self.grab(key)
else:
self.keytext = ""
def set_focus_window(self, window = None):
self.ungrab()
if window is None:
self.window = self.screen.root
else:
self.window = self.display.create_resource_object("window", gdk.gdk_x11_drawable_get_xid(hash(window)))
self.grab(self.keytext)
def get_mask_combinations(self, mask): def get_mask_combinations(self, mask):
return [x for x in xrange(mask+1) if not (x & ~mask)] return [x for x in xrange(mask+1) if not (x & ~mask)]

View File

@ -4,7 +4,7 @@
<!-- interface-naming-policy toplevel-contextual --> <!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="mainWindow"> <object class="GtkWindow" id="mainWindow">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK</property>
<property name="type">popup</property> <property name="type">toplevel</property>
<property name="resizable">False</property> <property name="resizable">False</property>
<property name="type_hint">menu</property> <property name="type_hint">menu</property>
<property name="skip_taskbar_hint">True</property> <property name="skip_taskbar_hint">True</property>

View File

@ -21,6 +21,7 @@ try:
import capi import capi
import xdg.Config import xdg.Config
import keybinding import keybinding
import pointerMonitor
except Exception, e: except Exception, e:
print e print e
sys.exit( 1 ) sys.exit( 1 )
@ -87,12 +88,9 @@ class MainWindow( object ):
self.panesToColor = [ ] self.panesToColor = [ ]
self.headingsToColor = [ ] self.headingsToColor = [ ]
self.window.connect( "map-event", self.onMap )
self.window.connect( "show", self.onShow )
self.window.connect( "unmap-event", self.onUnmap )
self.window.connect( "button-press-event", self.onButtonPress )
self.window.connect( "key-press-event", self.onKeyPress ) self.window.connect( "key-press-event", self.onKeyPress )
self.window.connect( "grab-broken-event", self.onGrabBroken ) self.window.connect( "focus-in-event", self.onFocusIn )
self.loseFocusId = self.window.connect( "focus-out-event", self.onFocusOut )
self.window.stick() self.window.stick()
@ -436,86 +434,59 @@ class MainWindow( object ):
#print NAME+u" reloaded" #print NAME+u" reloaded"
def onKeyPress( self, widget, event ):
if event.keyval == Gdk.KEY_Escape:
self.hide()
return True
return False
def show( self ): def show( self ):
self.window.present() self.window.present()
self.window.window.focus( Gdk.CURRENT_TIME )
# Hack for opacity not showing on first composited draw # Hack for opacity not showing on first composited draw
if self.firstTime: if self.firstTime:
self.firstTime = False self.firstTime = False
self.SetupMintMenuOpacity() self.SetupMintMenuOpacity()
for plugin in self.plugins.values():
if hasattr( plugin, "onShowMenu" ):
plugin.onShowMenu()
if ( "applications" in self.plugins ) and ( hasattr( self.plugins["applications"], "focusSearchEntry" ) ): if ( "applications" in self.plugins ) and ( hasattr( self.plugins["applications"], "focusSearchEntry" ) ):
if (self.startWithFavorites): if (self.startWithFavorites):
self.plugins["applications"].changeTab(0) self.plugins["applications"].changeTab(0)
self.plugins["applications"].focusSearchEntry() self.plugins["applications"].focusSearchEntry()
def grab( self ): def hide( self, forceHide = False ):
gdk.gdk_pointer_grab (hash(self.window.window), True, Gdk.EventMask.BUTTON_PRESS_MASK, None, None, Gdk.CURRENT_TIME)
Gdk.keyboard_grab( self.window.window, False, Gdk.CURRENT_TIME )
Gtk.grab_add(self.window)
def ungrab( self ):
Gtk.grab_remove(self.window)
self.window.hide()
Gdk.pointer_ungrab(Gdk.CURRENT_TIME)
Gdk.keyboard_ungrab(Gdk.CURRENT_TIME)
def onMap( self, widget, event ):
self.grab()
def onShow( self, widget ):
for plugin in self.plugins.values():
if hasattr( plugin, "onShowMenu" ):
plugin.onShowMenu()
def onUnmap( self, widget, event ):
self.ungrab()
for plugin in self.plugins.values(): for plugin in self.plugins.values():
if hasattr( plugin, "onHideMenu" ): if hasattr( plugin, "onHideMenu" ):
plugin.onHideMenu() plugin.onHideMenu()
def onKeyPress( self, widget, event ):
if event.keyval == Gdk.KEY_Escape or self.keybinder.is_hotkey(event.keyval, event.get_state()):
self.hide()
return True
return False
def onButtonPress( self, widget, event ):
# Check if the pointer is within the menu, else hide the menu
winatptr = Gdk.window_at_pointer()
if winatptr:
win = winatptr[0]
while win:
if win == self.window.window:
break
win = capi.get_widget(gdk.gdk_window_get_parent (hash(win)))
if not win:
self.hide( True )
else:
self.hide( True )
return True
def onGrabBroken( self, widget, event ):
if event.grab_broken.grab_window:
try:
win = event.grab_broken.grab_window
data = c_void_p()
gdk.gdk_window_get_user_data(hash(win), byref(data))
theft = capi.get_widget(ctypes.cast(data, POINTER(capi._PyGObject_Functions)))
theft.connect( "event", self.onGrabTheftEvent )
except Exception, detail:
print detail
self.window.hide()
def onGrabTheftEvent( self, widget, event ):
if event.type == Gdk.EventType.UNMAP or event.type == Gdk.EventType.SELECTION_CLEAR:
self.grab()
def hide(self, forceHide = False):
self.window.hide() self.window.hide()
def onFocusIn( self, *args ):
def dummy( *args ): pass
signalId = GObject.signal_lookup( "focus-out-event", self.window )
while True:
result = GObject.signal_handler_find( self.window,
GObject.SignalMatchType.ID | GObject.SignalMatchType.UNBLOCKED,
signalId, 0, None, dummy, dummy )
if result == 0:
self.window.handler_unblock( self.loseFocusId )
else:
break
return False
def onFocusOut( self, *args):
if self.window.get_visible():
self.hide()
return False
def stopHiding( self ):
self.window.handler_block( self.loseFocusId )
class MenuWin( object ): class MenuWin( object ):
def __init__( self, applet, iid ): def __init__( self, applet, iid ):
@ -542,8 +513,9 @@ class MenuWin( object ):
self.applet.connect("enter-notify-event", self.enter_notify) self.applet.connect("enter-notify-event", self.enter_notify)
self.applet.connect("leave-notify-event", self.leave_notify) self.applet.connect("leave-notify-event", self.leave_notify)
self.mainwin = MainWindow( self.button_box, self.settings, self.keybinder ) self.mainwin = MainWindow( self.button_box, self.settings, self.keybinder )
self.mainwin.window.connect( "map-event", lambda *args: self.applet.set_state( Gtk.StateType.SELECTED ) ) self.mainwin.window.connect( "map-event", self.onWindowMap )
self.mainwin.window.connect( "unmap-event", lambda *args: self.applet.set_state( Gtk.StateType.NORMAL ) ) self.mainwin.window.connect( "unmap-event", self.onWindowUnmap )
self.mainwin.window.connect( "realize", self.onRealize )
self.mainwin.window.connect( "size-allocate", lambda *args: self.positionMenu() ) self.mainwin.window.connect( "size-allocate", lambda *args: self.positionMenu() )
self.mainwin.window.set_name("mintmenu") # Name used in Gtk RC files self.mainwin.window.set_name("mintmenu") # Name used in Gtk RC files
@ -552,23 +524,46 @@ class MenuWin( object ):
Gtk.Window.set_default_icon_name( self.mainwin.icon ) Gtk.Window.set_default_icon_name( self.mainwin.icon )
self.bind_hot_key() self.bind_hot_key()
self.applet.set_can_focus(False)
self.pointerMonitor = pointerMonitor.PointerMonitor()
self.pointerMonitor.connect("activate", self.onPointerOutside)
def onWindowMap( self, *args ):
self.applet.set_state( Gtk.StateType.SELECTED )
self.keybinder.set_focus_window( self.mainwin.window.window )
self.pointerMonitor.grabPointer()
return False
def onWindowUnmap( self, *args ):
self.applet.set_state( Gtk.StateType.NORMAL )
self.keybinder.set_focus_window()
self.pointerMonitor.ungrabPointer()
return False
def onRealize( self, *args):
self.pointerMonitor.addWindowToMonitor( self.mainwin.window.window )
self.pointerMonitor.addWindowToMonitor( self.applet.window )
self.pointerMonitor.start()
return False
def onPointerOutside(self, *args):
self.mainwin.hide()
return True
def onBindingPress(self, binder): def onBindingPress(self, binder):
try: self.toggleMenu()
if self.mainwin.window.get_visible(): return True
self.mainwin.window.hide()
self.mainwin.toggle.set_active(False)
else:
MenuWin.showMenu(self,self.mainwin.toggle)
self.mainwin.window.show()
#self.mainwin.wTree.get_widget( 'PluginTabs' ).set_curremenu_editor = SetGconf( self.client, "string", "/apps/usp/menu_editor", "mozo" )
except Exception, cause:
print cause
def enter_notify(self, applet, event): def enter_notify(self, applet, event):
self.do_image(self.buttonIcon, True) self.do_image(self.buttonIcon, True)
def leave_notify(self, applet, event): def leave_notify(self, applet, event):
# Hack for mate-panel-test-applets focus issue (this can be commented)
if event.state & Gdk.ModifierType.BUTTON1_MASK and applet.state & Gtk.StateType.SELECTED:
if event.x >= 0 and event.y >= 0 and event.x < applet.window.get_width() and event.y < applet.window.get_height():
self.mainwin.stopHiding()
self.do_image(self.buttonIcon, False) self.do_image(self.buttonIcon, False)
def do_image(self, image_file, saturate): def do_image(self, image_file, saturate):
@ -694,10 +689,8 @@ class MenuWin( object ):
pass pass
def hotkeyChanged (self, schema, key): def hotkeyChanged (self, schema, key):
self.keybinder.ungrab()
self.hotkeyText = self.settings.get_string( "hot-key" ) self.hotkeyText = self.settings.get_string( "hot-key" )
if self.hotkeyText != "": self.keybinder.rebind(self.hotkeyText)
self.keybinder.grab(self.hotkeyText)
def sizeButton( self ): def sizeButton( self ):
if self.hideIcon: if self.hideIcon:

View File

@ -576,12 +576,22 @@ class pluginclass( object ):
def Todos( self ): def Todos( self ):
self.searchEntry.connect( "popup-menu", self.blockOnPopup )
self.searchEntry.connect( "button-press-event", self.blockOnRightPress )
self.searchEntry.connect( "changed", self.Filter ) self.searchEntry.connect( "changed", self.Filter )
self.searchEntry.connect( "activate", self.Search ) self.searchEntry.connect( "activate", self.Search )
self.showAllAppsButton.connect( "clicked", lambda widget: self.changeTab( 1 ) ) self.showAllAppsButton.connect( "clicked", lambda widget: self.changeTab( 1 ) )
self.showFavoritesButton.connect( "clicked", lambda widget: self.changeTab( 0 ) ) self.showFavoritesButton.connect( "clicked", lambda widget: self.changeTab( 0 ) )
self.buildButtonList() self.buildButtonList()
def blockOnPopup( self, *args ):
self.mintMenuWin.stopHiding()
return False
def blockOnRightPress( self, widget, event ):
if event.button == 3:
self.mintMenuWin.stopHiding()
return False
def focusSearchEntry( self ): def focusSearchEntry( self ):
# grab_focus() does select all text, # grab_focus() does select all text,
@ -951,10 +961,8 @@ class pluginclass( object ):
mTree.append(propsMenuItem) mTree.append(propsMenuItem)
mTree.show_all() mTree.show_all()
self.mintMenuWin.stopHiding()
gtk.gtk_menu_popup(hash(mTree), None, None, None, None, ev.button, ev.time) gtk.gtk_menu_popup(hash(mTree), None, None, None, None, ev.button, ev.time)
#self.mintMenuWin.grab()
mTree.connect( 'deactivate', self.onMenuPopupDeactivate)
else: else:
mTree = Gtk.Menu() mTree = Gtk.Menu()
mTree.set_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK | mTree.set_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.POINTER_MOTION_HINT_MASK |
@ -972,9 +980,8 @@ class pluginclass( object ):
removeMenuItem.connect( "activate", self.onFavoritesRemove, widget ) removeMenuItem.connect( "activate", self.onFavoritesRemove, widget )
insertSpaceMenuItem.connect( "activate", self.onFavoritesInsertSpace, widget, insertBefore ) insertSpaceMenuItem.connect( "activate", self.onFavoritesInsertSpace, widget, insertBefore )
insertSeparatorMenuItem.connect( "activate", self.onFavoritesInsertSeparator, widget, insertBefore ) insertSeparatorMenuItem.connect( "activate", self.onFavoritesInsertSeparator, widget, insertBefore )
self.mintMenuWin.stopHiding()
gtk.gtk_menu_popup(hash(mTree), None, None, None, None, ev.button, ev.time) gtk.gtk_menu_popup(hash(mTree), None, None, None, None, ev.button, ev.time)
#self.mintMenuWin.grab()
mTree.connect( 'deactivate', self.onMenuPopupDeactivate)
def menuPopup( self, widget, event ): def menuPopup( self, widget, event ):
if event.button == 3: if event.button == 3:
@ -1035,11 +1042,8 @@ class pluginclass( object ):
startupMenuItem.set_active( False ) startupMenuItem.set_active( False )
startupMenuItem.connect( "toggled", self.onAddToStartup, widget ) startupMenuItem.connect( "toggled", self.onAddToStartup, widget )
mTree.connect( 'deactivate', self.onMenuPopupDeactivate) self.mintMenuWin.stopHiding()
gtk.gtk_menu_popup(hash(mTree), None, None, None, None, event.button, event.time) gtk.gtk_menu_popup(hash(mTree), None, None, None, None, event.button, event.time)
def onMenuPopupDeactivate( self, widget):
self.mintMenuWin.grab()
def searchPopup( self, widget=None, event=None ): def searchPopup( self, widget=None, event=None ):
menu = Gtk.Menu() menu = Gtk.Menu()
@ -1114,14 +1118,13 @@ class pluginclass( object ):
menu.append(menuItem) menu.append(menuItem)
menu.show_all() menu.show_all()
self.mintMenuWin.stopHiding()
gtk.gtk_menu_popup(hash(menu), None, None, None, None, event.button, event.time) gtk.gtk_menu_popup(hash(menu), None, None, None, None, event.button, event.time)
#menu.attach_to_widget(self.searchButton, None) #menu.attach_to_widget(self.searchButton, None)
#menu.reposition() #menu.reposition()
#menu.reposition() #menu.reposition()
#self.mintMenuWin.grab()
menu.connect( 'deactivate', self.onMenuPopupDeactivate)
return True return True
def pos_func(self, menu=None): def pos_func(self, menu=None):

View File

@ -0,0 +1,84 @@
import gi
gi.require_version("Gtk", "2.0")
from Xlib.display import Display
from Xlib import X, error
from gi.repository import Gtk, Gdk, GObject, GLib
import threading
import ctypes
from ctypes import *
gdk = CDLL("libgdk-x11-2.0.so.0")
gtk = CDLL("libgtk-x11-2.0.so.0")
class PointerMonitor(GObject.GObject, threading.Thread):
__gsignals__ = {
'activate': (GObject.SignalFlags.RUN_LAST, None, ()),
}
def __init__(self):
GObject.GObject.__init__ (self)
threading.Thread.__init__ (self)
self.setDaemon (True)
self.display = Display()
self.root = self.display.screen().root
self.windows = []
self.topWindows = []
# Receives GDK windows
def addWindowToMonitor(self, window):
xWindow = self.display.create_resource_object("window", gdk.gdk_x11_drawable_get_xid(hash(window)))
self.windows.append(xWindow)
self.topWindows.append(self.getToplevelParent(xWindow))
def getToplevelParent(self, window):
parent = window.query_tree().parent
if parent == self.root:
return window
else:
return self.getToplevelParent(parent)
def grabPointer(self):
self.root.grab_button(X.AnyButton, X.AnyModifier, True, X.ButtonPressMask, X.GrabModeSync, X.GrabModeAsync, 0, 0)
self.display.flush()
def ungrabPointer(self):
self.root.ungrab_button(X.AnyButton, X.AnyModifier)
self.display.flush()
def idle(self):
self.emit("activate")
return False
def activate(self):
GLib.idle_add(self.run)
def run(self):
self.running = True
while self.running:
event = self.display.next_event()
try:
if event.type == X.ButtonPress:
# Check if pointer is inside monitored windows
for w, topW in zip(self.windows, self.topWindows):
if event.child == topW:
if topW == w:
break
else:
p = w.query_pointer()
g = w.get_geometry()
if p.win_x >= 0 and p.win_y >= 0 and p.win_x <= g.width and p.win_y <= g.height:
break
else:
# Is outside, so activate
GLib.idle_add(self.idle)
self.display.allow_events(X.ReplayPointer, event.time)
except Exception as e:
print "Unexpected error: " + str(e)
def stop(self):
self.running = False
self.root.ungrab_button(X.AnyButton, X.AnyModifier)
self.display.close()