I was trying to toy around with the pygtk module to make an app indicator in my top bar in Ubuntu 17.10.
With a little research, I have successfully made a menu where you can pause the main thread (which just prints "Update" to the console currently.)
Now I've stumbled across a problem and couldn't find an answer.
I plan to make a radio streamer for the top bar and the menu closing on every click would be very undesirable for navigation when searching.
How can I make the menu stay open when clicking an item?
Here is my code:
import os
import signal
import time
from threading import Thread
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("AppIndicator3", "0.1")
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
from gi.repository import GObject as gobject
class Indicator:
def __init__(self, name=None, icon=None):
self.path = os.path.abspath(os.path.dirname(__file__))
signal.signal(signal.SIGINT, signal.SIG_DFL)
if name is None:
self.name = "MyApp"
else:
self.name = name
if icon is None:
self.icon = gtk.STOCK_INFO
else:
self.icon = icon
self.indicator = appindicator.Indicator.new(
self.name, self.icon,
appindicator.IndicatorCategory.SYSTEM_SERVICES
)
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
self.indicator.set_menu(self.create_menu())
self.running = True
self.paused = False
self.thread = Thread(target=self.update)
self.thread.setDaemon(True)
self.thread.start()
def create_menu(self):
menu = gtk.Menu()
pause_entry = gtk.MenuItem("Pause")
pause_entry.connect("activate", self.pause)
menu.append(pause_entry)
quit_entry = gtk.MenuItem("Quit")
quit_entry.connect("activate", self.quit)
menu.append(quit_entry)
menu.show_all()
return menu
def update(self):
while self.running:
time.sleep(1)
if not self.paused:
print("Update", flush=True)
def start(self):
gobject.threads_init()
gtk.main()
def pause(self, menu):
self.paused = not self.paused
text = "Resume" if self.paused else "Pause"
for widget in menu.get_children():
print(widget.get_text())
widget.set_text(text)
def quit(self, menu=None):
self.running = False
gtk.main_quit()
if __name__ == "__main__":
indicator = Indicator()
indicator.start()
Unfortunately, I don't think you can make the menu stay open when clicking on an item.
This was an intentional design decision taken by the AppIndicator developers in order to ensure that the indicator would function in a consistent and uniform way and that no custom actions(e.g. right-click, mouseover etc) would be possible to be applied on by end users.
I have created an appindicator that displays news in real time and when I researched the related pygtk/appindicator API reference material; the only allowed supported menu action that I came across was that of scrolling.
With regards to your radio streamer project I suggest have the menu as the main focal point of your application and have the menu items appropriately bidden to pygtk windows. Then use callbacks and signals to perform your necessary actions. This is the approach I undertook in my little project too and turned out pretty nicely.
Related
OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.
My python application stops responding whenever I do anything "wx" a few times...whether that be clicking buttons, menus, inside the text area, etc.
This seems to be a problem with my use of windows hooks elsewhere in the program. When the two are used together, things go bad.
While the example below might not make much sense outside the context of the real application, it is what I came up with as a minimal example. I took everything out I could in order to try to diagnose the problem with minimal extra complexities.
It really seems like the second windows message pump called from the worker thread is the factor that makes it happen. If I comment that out, the app doesn't hang...or do anything useful...Hooks requires a pump, and I can't put it on my UI thread or it will the hooks will timeout, which led me to this POC in the first place...
If you run this and click the mouse a dozen times or so, you will notice the text blocks get garbled and then the app just hangs. Does anyone have any insight as to why?
I am using
python 2.7.16 32 bit
pyHook 1.5.1
wxPython 3.0.2.0 32bit for python 2.7
(update) I've also tried with the same results
python 2.7.16 32 bit
pyHook 1.5.1
wxPython 4.0.6 32 bit for python 2.7
We use these in the real application, because the my higher ups want to continue to support Windows XP (A whole different conversation)
main.py
import wx
from twisted.internet import wxreactor
from windows_hooks import WindowsHooksWrapper
from main_window import MainWindow
def main():
hook_wrapper = WindowsHooksWrapper()
hook_wrapper.start()
app = wx.App(False)
frame = MainWindow(None, 'Hooks Testing', hook_wrapper)
from twisted.internet import reactor
reactor.registerWxApp(app)
reactor.run()
hook_wrapper.stop()
if __name__ == "__main__":
wxreactor.install()
main()
windows_hooks.py
import pyHook
import threading
import pythoncom
class WindowsHooksWrapper(object):
def __init__(self):
self.hook_manager = None
self.started = False
self.thread = threading.Thread(target=self.thread_proc)
self.window_to_publish_to = None
print "HookWrapper created on Id {}".format(threading.current_thread().ident)
def __del__(self):
self.stop()
def start(self):
if self.started:
self.stop()
self.started = True
self.thread.start()
def stop(self):
if not self.started:
return
self.started = False
self.thread.join()
def on_mouse_event(self, event):
"""
Called back from pyHooks library on a mouse event
:param event: event passed from pyHooks
:return: True if we are to pass the event on to other hooks and the process it was intended
for. False to consume the event.
"""
if self.window_to_publish_to:
from twisted.internet import reactor
reactor.callFromThread(self.window_to_publish_to.print_to_text_box, event)
return True
def thread_proc(self):
print "Thread started with Id {}".format(threading.current_thread().ident)
# Evidently, the hook must be registered on the same thread with the windows msg pump or
# it will not work and no indication of error is seen
# Also note that for exception safety, when the hook manager goes out of scope, the
# documentation says that it unregisters all outstanding hooks
self.hook_manager = pyHook.HookManager()
self.hook_manager.MouseAll = self.on_mouse_event
self.hook_manager.HookMouse()
while self.started:
pythoncom.PumpMessages()
print "Thread exiting..."
self.hook_manager.UnhookMouse()
self.hook_manager = None
main_window.py
import threading
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title, hook_manager):
wx.Frame.__init__(self, parent, title=title, size=(800, 600))
self.hook_manager = hook_manager
self.CreateStatusBar()
menu_file = wx.Menu()
menu_item_exit = menu_file.Append(wx.ID_EXIT, "E&xit", " Terminate the program")
menu_help = wx.Menu()
menu_item_about = menu_help.Append(wx.ID_ABOUT, "&About", " Information about this program")
menu_bar = wx.MenuBar()
menu_bar.Append(menu_file, "&File")
menu_bar.Append(menu_help, "&Help")
self.SetMenuBar(menu_bar)
self.panel = MainPanel(self, hook_manager)
self.Bind(wx.EVT_MENU, self.on_about, menu_item_about)
self.Bind(wx.EVT_MENU, self.on_exit, menu_item_exit)
self.Show(True)
def on_about(self, e):
dlg = wx.MessageDialog(self, "A window to test Windows Hooks", "About Test Windows Hooks",
wx.OK)
dlg.ShowModal()
dlg.Destroy()
def on_exit(self, e):
self.Close(True)
class MainPanel(wx.Panel):
def __init__(self, parent, hook_manager):
self.hook_manager = hook_manager
hook_manager.window_to_publish_to = self
self.consuming = False
wx.Panel.__init__(self, parent)
self.textbox = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)
self.horizontal = wx.BoxSizer()
self.horizontal.Add(self.textbox, proportion=1, flag=wx.EXPAND)
self.sizer_vertical = wx.BoxSizer(wx.VERTICAL)
self.sizer_vertical.Add(self.horizontal, proportion=1, flag=wx.EXPAND)
self.SetSizerAndFit(self.sizer_vertical)
self.called_back_count = 0
def print_to_text_box(self, event):
self.called_back_count += 1
print "Printing message {} on Thread with Id {}".format(self.called_back_count,
threading.current_thread().ident)
self.textbox.AppendText('MessageName: {}\n'.format(event.MessageName))
self.textbox.AppendText('Message: {}\n'.format(event.Message))
self.textbox.AppendText('Time: {}\n'.format(event.Time))
self.textbox.AppendText('Window: {}\n'.format(event.Window))
self.textbox.AppendText('WindowName: {}\n'.format(event.WindowName))
self.textbox.AppendText('Position: {}\n'.format(event.Position))
self.textbox.AppendText('Wheel: {}\n'.format(event.Wheel))
self.textbox.AppendText('Injected: {}\n'.format(event.Injected))
self.textbox.AppendText('---\n')
I've also tried a version without Twisted and used wxPostEvent with a custom event instead, but we were suspecting that might be the problem, so I changed it to use twisted and it's still no good.
I'll post an edited listing with that in a bit.
I whipped up a little wallpaper-changing application for myself in Python. It makes an icon in the notification tray, which can be used to switch modes between "nice" and "naughty" depending on who's around :P and to force a wallpaper change. If left alone, it still changes to a random wallpaper once every 10 minutes, selecting randomly from a directory full of images that I continually add to. Everything was working great, until I upgraded from Ubuntu 14.04 "Trusty" to Ubuntu 15.10 "Wily." Now, the application still runs, and will change wallpapers once every 10 minutes like it should, but the icon is gone. It makes a space in the tray for the icon, but the icon no longer appears in it, nor does the empty space respond to any mouse clicks, left or right (left click used to force a wallpaper change, right click used to give me a menu of the two modes). No warning or error messages appear on the console when I run the application. I'm not too experienced with Python, and can't figure out what the hell is the problem. Following is the (very short) code for the applet. Sorry if there are any awful practices in the code, like I said I'm really not a python guy, it just seemed the easiest way to do what I wanted to do so I went with it. If anyone can help me figure out the problem I'd appreciate it!
PS "./change_wall" is just a bash script which does some other stuff besides just changing the wallpaper. I know the problem isn't there because the automatic wallpaper changes are still working, it's just the tray icon / control interface that's FUBAR.
#!/usr/bin/python
import os
import wx
import time
import thread
class TaskBarIcon(wx.TaskBarIcon):
def __init__(self):
super(TaskBarIcon, self).__init__()
os.chdir("/home/caleb/walls")
self.SetIcon(wx.Icon('walls.xpm', wx.BITMAP_TYPE_XPM), "Wallpaper switcher")
self.set_nice(None)
self.Bind(wx.EVT_TASKBAR_LEFT_UP, self.set_new_wall_x)
def CreatePopupMenu(self):
menu = wx.Menu()
nice_item = menu.AppendRadioItem(-1, "Nice")
naughty_item = menu.AppendRadioItem(-1, "Naughty")
if self.type == 'nice':
nice_item.Check()
elif self.type == 'naughty':
naughty_item.Check()
menu.Bind(wx.EVT_MENU, self.set_nice, nice_item)
menu.Bind(wx.EVT_MENU, self.set_naughty, naughty_item)
return menu
def set_nice(self, event):
self.type = 'nice'
self.set_new_wall()
def set_naughty(self, event):
self.type = 'naughty'
self.set_new_wall()
def set_new_wall(self):
os.system("./change_wall " + self.type)
self.last_changed_time = time.time()
def set_new_wall_x(self, event):
self.set_new_wall()
def main():
app = wx.App(False)
the_icon = TaskBarIcon()
thread.start_new_thread(app.MainLoop, ())
while 1:
if (time.time() > the_icon.last_changed_time + 600):
the_icon.set_new_wall()
if __name__ == '__main__':
main()
Other than checking the things in my comment, see if this code functions, the icon used should exist on your machine.
import wx
TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = '/usr/share/icons/gnome/16x16/emotes/face-cool.png'
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.AppendItem(item)
return item
class TaskBarIcon(wx.TaskBarIcon):
def __init__(self):
wx.TaskBarIcon.__init__(self)
self.set_icon(TRAY_ICON)
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
self.Bind(wx.EVT_TASKBAR_RIGHT_DOWN, self.on_right_down)
def CreatePopupMenu(self):
menu = wx.Menu()
create_menu_item(menu, 'Say Hello', self.on_hello)
menu.AppendSeparator()
create_menu_item(menu, 'Exit', self.on_exit)
return menu
def set_icon(self, path):
icon = wx.IconFromBitmap(wx.Bitmap(path))
self.SetIcon(icon, TRAY_TOOLTIP)
def on_left_down(self, event):
print 'Tray icon was left-clicked.'
def on_right_down(self, event):
print 'Tray icon was right-clicked.'
event.Skip()
def on_hello(self, event):
print 'Hello, world!'
def on_exit(self, event):
wx.CallAfter(self.Destroy)
def main():
app = wx.App()
TaskBarIcon()
app.MainLoop()
if __name__ == '__main__':
main()
Well, rather than continue to muck around with wxPython and this Project Phoenix nonsense, I've switched to just using PyGTK and I have to say, overall I'm liking this a lot better. My code is now behaving exactly as I want it to, as follows:
#!/usr/bin/python
import gobject
import gtk
import os
import thread
import time
last_changed_time = 0
mode = "nice"
def set_mode (new_mode):
global mode
mode = new_mode
change_wall()
def make_menu(event_button, event_time, data=None):
menu = gtk.Menu()
nice_item = gtk.CheckMenuItem("Nice")
naughty_item = gtk.CheckMenuItem("Naughty")
kill_item = gtk.MenuItem("Quit")
if mode == "nice":
nice_item.set_active(True)
if mode == "naughty":
naughty_item.set_active(True)
menu.append(nice_item)
menu.append(naughty_item)
menu.append(kill_item)
nice_item.connect_object("activate", set_mode, ("nice"))
naughty_item.connect_object("activate", set_mode, ("naughty"))
kill_item.connect_object("activate", gtk.main_quit, ())
nice_item.show()
naughty_item.show()
kill_item.show()
menu.popup(None, None, None, event_button, event_time)
def change_wall():
global last_changed_time
os.system("./change_wall " + mode)
last_changed_time = time.time()
def on_right_click(data, event_button, event_time):
make_menu(event_button, event_time)
def on_left_click(event):
change_wall()
def auto_update():
while 1:
time.sleep(1)
if time.time() > last_changed_time + 600:
change_wall()
if __name__ == '__main__':
gobject.threads_init()
os.chdir("/home/caleb/walls")
icon = gtk.status_icon_new_from_file("walls.xpm")
icon.connect('popup-menu', on_right_click)
icon.connect('activate', on_left_click)
change_wall()
thread.start_new_thread(auto_update, ())
gtk.main()
I had to insert the time.sleep(1) call before every check to see whether it's time to auto-update in order to prevent the icon/menu itself from becoming rather laggy. I never ran into that problem when I was using wx; is there a more elegant way to solve this?
Thank you again for the help!
Because you are running wx.python 3 it looks like you will need to change a few of your calls:
IconFromBitmap becomes Icon
TaskBarIcon becomes adv.TaskBarIcon
There may be some other minor discrepancies but those appear to be the obvious ones.
Edit:
The other option is to load a previous version of wxpython.
For this, you can use wxversion like so, in your imports:
import wxversion
wxversion.select("2.8-gtk2-unicode")
import wx
I wrote global shortcut example for hide and show my windows with key like 'F12', I used python-xlib and some script named 'pyxhook' everything works fine except when i want to hide() and show() window few times my proccess turning a zombie, but same code working with hiding and showing just button.
#!/usr/bin/python
# -*- coding: utf-8; -*-
from gi.repository import Gtk, GObject
from pyxhook import HookManager
GObject.threads_init()
class Win(Gtk.Window):
def __init__(self):
super(Win, self).__init__()
self.connect('destroy', Gtk.main_quit)
self.button = Gtk.Button()
self.add(self.button)
self.resize(200,150)
self.show_all()
def handle_global_keypress(self, event):
if event.Key == 'F12':
if self.get_visible():
self.hide()
else:
self.show()
### this part works fine with button
#if self.button.get_visible():
# self.button.hide()
#else:
# self.button.show()
def main():
app = Win()
hm = HookManager()
hm.HookKeyboard()
hm.KeyDown = app.handle_global_keypress
hm.start()
Gtk.main()
hm.cancel()
if __name__ == "__main__":
main()
edit: i solved my problem using Keybinder library instead of coding pure python keybinder.
http://kaizer.se/wiki/keybinder/
I'm unable to answer your specific question but I might suggest another option. Guake console implements this very same behavior but using dbus:
http://guake.org/
In the dbusiface.py file you can find:
import dbus
import dbus.service
import dbus.glib
import gtk
import guake.common
dbus.glib.threads_init()
DBUS_PATH = '/org/guake/RemoteControl'
DBUS_NAME = 'org.guake.RemoteControl'
class DbusManager(dbus.service.Object):
def __init__(self, guakeinstance):
self.guake = guakeinstance
self.bus = dbus.SessionBus()
bus_name = dbus.service.BusName(DBUS_NAME, bus=self.bus)
super(DbusManager, self).__init__(bus_name, DBUS_PATH)
#dbus.service.method(DBUS_NAME)
def show_hide(self):
self.guake.show_hide()
Among others methods. This is worth to explore. Please also note that Guake is developed using PyGtk and not PyGObject, but anyway you can get some ideas.
I'm starting to write a small panel applet for Gnome and I'd like the user to be able to left-click on the status icon to see some options and information e.g. similar to sound icon in Gnome 3, where you can set volume via left-click while set preferences via right-click.
Right-click code is this:
statusicon.connect("popup-menu", right_button_click)
where right_button_click is the name of the function that gets called on right-click event. The important part is "popup-menu". What would be alternative for setting left-click event?
This is the tiny example showing how it works.
#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class TrayIcon(Gtk.StatusIcon):
def __init__(self):
Gtk.StatusIcon.__init__(self)
self.set_from_icon_name('help-about')
self.set_has_tooltip(True)
self.set_visible(True)
self.connect("popup_menu", self.on_secondary_click)
def on_secondary_click(self, widget, button, time):
menu = Gtk.Menu()
menu_item1 = Gtk.MenuItem("First Entry")
menu.append(menu_item1)
menu_item2 = Gtk.MenuItem("Quit")
menu.append(menu_item2)
menu_item2.connect("activate", Gtk.main_quit)
menu.show_all()
menu.popup(None, None, None, self, 3, time)
if __name__ == '__main__':
tray = TrayIcon()
Gtk.main()
First thing is to look into the gnome code for the volume control, and that's is this
Second, you should look into the API documentation for GtkStatusIcon, and that one is here
That should be enough.
This is a late response, but i'm just posting this in case someone else ever looks for left click control over gtkstatusicon.
The direct alternative is
statusicon.connect("activate", left_button_click)
This is a sample for trayicon popup menu working with left click instead of the (common) right click.
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class TrayIcon(gtk.StatusIcon):
def __init__(self):
gtk.StatusIcon.__init__(self)
self.set_from_icon_name('help-about')
self.set_has_tooltip(True)
self.set_visible(True)
self.connect("activate", self.on_click)
def greetme(self,data=None):
msg=gtk.MessageDialog(None, gtk.DIALOG_MODAL,gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Greetings")
msg.run()
msg.destroy()
def on_click(self,data):
event=gtk.get_current_event()
btn=event.button #this gets the button value of gtk event.
time=gtk.get_current_event_time() # required by menu popup.
menu = gtk.Menu()
menu_item1 = gtk.MenuItem("First Entry")
menu.append(menu_item1)
menu_item1.connect("activate", self.greetme)
menu_item2 = gtk.MenuItem("Quit")
menu.append(menu_item2)
menu_item2.connect("activate", gtk.main_quit)
menu.show_all()
menu.popup(None, None, None, btn, time)
#button can be hardcoded (i.e 1) but time must be correct.
if __name__ == '__main__':
tray = TrayIcon()
gtk.main()
Also , there is this alternative :
statusicon.connect("button-press-event", button_click)
Bellow sample code raise the same popup menu in gtktrayicon in BOTH right and left click.
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class TrayIcon(gtk.StatusIcon):
def __init__(self):
gtk.StatusIcon.__init__(self)
self.set_from_icon_name('help-about')
self.set_has_tooltip(True)
self.set_visible(True)
self.connect("button-press-event", self.on_click)
def greetme(self,data=None):
msg=gtk.MessageDialog(None, gtk.DIALOG_MODAL,gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Greetings")
msg.run()
msg.destroy()
def on_click(self,data,event):
#event in this case is sent by the status icon connect.
btn=event.button
#By controlling this event.button value (1-2-3 for left-middle-right click) you can call other functions.
time=gtk.get_current_event_time() # required by the popup.
menu = gtk.Menu()
menu_item1 = gtk.MenuItem("First Entry")
menu.append(menu_item1)
menu_item1.connect("activate", self.greetme)
menu_item2 = gtk.MenuItem("Quit")
menu.append(menu_item2)
menu_item2.connect("activate", gtk.main_quit)
menu.show_all()
menu.popup(None, None, None, btn, time)
if __name__ == '__main__':
tray = TrayIcon()
gtk.main()
Hope above code helps.
George V.