Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for icon tooltip and "Double-click" event #49

Open
firedm opened this issue May 9, 2020 · 7 comments
Open

Support for icon tooltip and "Double-click" event #49

firedm opened this issue May 9, 2020 · 7 comments

Comments

@firedm
Copy link

firedm commented May 9, 2020

Hello,
Thanks for this outstanding work, this is a nice cross-platform and lightweight package,

I was looking for a lightweight systray that can work with tkinter and yours was my first choice until i found something in documentation says that pystray must run from main loop, then i decided to use "infi.systray" which supports windows only, everything works on windows flawlessly no issues,

Then i tried to find a way to run your pystray from a thread, and finally it did work "I didn't know before that main thread is the requirement for mac only", and i have to create menu and run icon from same thread.

everything works well with pystray on windows, however i miss 2 features:
1- set/update tooltip, in infi.systray i can use systray.update(hover_text='my tooltip')
2- Double-click

which, I hope you could add support for both.

here is an example code, where I run Icon from a thread "not worry about mac support right now"

from PIL import Image
import threading, io, base64
from pystray import Icon, Menu, MenuItem

APP_ICON2 = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAKfSURB\nVDiNZdNfaNVlHAbwz/n9zu/M6Vz7o41yzplg4BCFolbZWi2FiBIMvQq9qJAuCokugqCWFAgJxSgS\n+kN5Uxb9uVJyVgc0wgg65UpczLl2HHPacM7z23bmOaeLnU6bvlcP7/s8z/d5X94n4Ya1dz3Fp9GF\n1vLmORzDB3T3zWcn/oc9VYy/hd3tssEWAxrErgmNqpHW6he3FnAAL9Cdn2fQU8X4keXiB/f7Vrus\nvFBOSiwSi8wI9Wv0pvuMq/4ej9CdD+cM7n5nmXj7576wsTaW7HpIPEtuqiBXCORErkoJlHT420nN\nq6cl60kfSZTvnPnYN8H9hjR8ckDdzh3Ge08I6m7y444XXTw3VkmSkzItaZ9NBYINIQ+83C7b/pyT\n8ktqBW1trmROyzy7V76YkGxeIZs5WxHHIqGiCYuCC5bMJPHwFgPyQqWWVv2v9ohFZmvrrdy51eFt\nLy0Qz+HF1rvgd02bA7Q0mhKLXDo9VCHd/voembcPuThyuSKeSVXLlc+TirAqQOmaxHUTIi3bugxn\nzspJKdXVe+y9PfZPfqnj+a1zCQVQSmJ4VM26+eJYZOzUgMe/3mes/7zGtSt99cqnzvx23rK1K8Qi\n0VyC4SR6f7B63T2GK+JY5ND217Q8usn4pdip42dM5BOeeONJhw/+pEZev0Y4GtKZHbF090ajQaBU\nMbiSDwz+MWJ48B+ThVAssripwWQ+ITk0qNeaAp4JSY/RefOvbrmrw5BJVTe8x3+4r2/MnctnfTTS\nZEbyXboPln9i57Ep0b0/a75tg1GBksuqF4iXmtFs0ocjTSYs+g67SBfKBukCnZ9NiepPWHXHVVVB\nvWlVCiJFoZK/NDpqTWFusl3XlWlBndsoPoXN5TonMFiu8/t0/zmf/S9tcxBW9J/R4gAAAABJRU5E\nrkJggg==\n'

def create_image():
	buffer = io.BytesIO(base64.b64decode(APP_ICON2))
	img = Image.open(buffer)
	return img

def quit(icon, item):
	icon.stop()

def show():
	print('show main window')

def create_icon():
	# i can use one click using default=True, but no option for double-click
	menu = Menu(MenuItem("Start / Show", show, default=True), MenuItem("Minimize to Systray", 
		lambda: None), MenuItem("Close to Systray", lambda: None), MenuItem("Quit", quit))
	icon = Icon('test', create_image(), menu=menu)
	return icon

def foo():
	print('start icon')
	icon = create_icon()
	icon.run()
		
threading.Thread(target=foo).start()
@suurjaak
Copy link

+1 for double-click support.

I know you want to keep this library as uniform as possible, but double-clicking a tray icon is a very common need. Seems it's only Linux that does not support double-clicking the tray icons. Most Windows programs with a tray icon have no action for single-left-click but a special action for double-left-click - e.g. bringing something to the foreground, or executing some other default action. And Windows support for this would be really trivial, e.g. _win32.py:Icon._on_notify:

if lparam == win32.WM_LBUTTONDBLCLK:  # 0x0203
    self()

@7gxycn08
Copy link

7gxycn08 commented Jan 4, 2023

Made a function that Shows tkinter window when tray icon is double clicked.
Might be helpful in implementing it into Pystray.

You can call the function using Thread which will show and hide/destroy Tkinter main window when clicking menu buttons or double clicking the icon.

import tkinter
from PyQt5.QtWidgets import QApplication, QMenu, QSystemTrayIcon
from PyQt5.QtGui import QIcon


 def Tray_Icon():
    def close_tray_icon():
        root.deiconify()
        tray_icon.hide()
        app.exit()
        sys.exit()
    def onDoubleClick(reason):
        if reason == QSystemTrayIcon.DoubleClick:
            root.deiconify()
            tray_icon.hide()
            app.exit()
            sys.exit()

    root.withdraw()
    app = QApplication(sys.argv)

    tray_icon = QSystemTrayIcon()
    tray_icon.setIcon(QIcon('icon.png'))

    menu = QMenu()
    menu.addAction("Show").triggered.connect(lambda: close_tray_icon())
    menu.addAction("Exit").triggered.connect(lambda: root.destroy())
    tray_icon.activated.connect(onDoubleClick)

    tray_icon.setContextMenu(menu)
    tray_icon.show()
    sys.exit(app.exec_())


root = tkinter.Tk()
root.title("MyTKinterProgram")
root.geometry('720x940')
root.protocol('WM_DELETE_WINDOW', lambda: Thread(target=Tray_Icon,daemon=True).start())
root.mainloop()

@JulienFr
Copy link

JulienFr commented Jan 12, 2024

You do not need to add this double-click behavior in pystray.
You can overwrite pystray._win32.Icon. _on_notify callback with any behavior in an pystray._win32.Icon subclass.
This does a new Icon backend that you instantiate only on e.g. win32 platform.

...
if lparam == win32.WM_LBUTTONDBLCLK:
    # your custom behavior from e.g. tkinter

@daveblackuk
Copy link

Julien,

useful, can you please provide a full code example.

Thanks

@JulienFr
Copy link

JulienFr commented Feb 4, 2024

Win32PystrayIcon is used in case of win32 sys platform.
It triggers the on_double_click callback defined in the global Icon instance, when the left double-click is detected from win32 on notify event.

from PIL import Image
import threading, io, base64, sys
from pystray import Icon, Menu, MenuItem

APP_ICON2 = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAKfSURB\nVDiNZdNfaNVlHAbwz/n9zu/M6Vz7o41yzplg4BCFolbZWi2FiBIMvQq9qJAuCokugqCWFAgJxSgS\n+kN5Uxb9uVJyVgc0wgg65UpczLl2HHPacM7z23bmOaeLnU6bvlcP7/s8z/d5X94n4Ya1dz3Fp9GF\n1vLmORzDB3T3zWcn/oc9VYy/hd3tssEWAxrErgmNqpHW6he3FnAAL9Cdn2fQU8X4keXiB/f7Vrus\nvFBOSiwSi8wI9Wv0pvuMq/4ej9CdD+cM7n5nmXj7576wsTaW7HpIPEtuqiBXCORErkoJlHT420nN\nq6cl60kfSZTvnPnYN8H9hjR8ckDdzh3Ge08I6m7y444XXTw3VkmSkzItaZ9NBYINIQ+83C7b/pyT\n8ktqBW1trmROyzy7V76YkGxeIZs5WxHHIqGiCYuCC5bMJPHwFgPyQqWWVv2v9ohFZmvrrdy51eFt\nLy0Qz+HF1rvgd02bA7Q0mhKLXDo9VCHd/voembcPuThyuSKeSVXLlc+TirAqQOmaxHUTIi3bugxn\nzspJKdXVe+y9PfZPfqnj+a1zCQVQSmJ4VM26+eJYZOzUgMe/3mes/7zGtSt99cqnzvx23rK1K8Qi\n0VyC4SR6f7B63T2GK+JY5ND217Q8usn4pdip42dM5BOeeONJhw/+pEZev0Y4GtKZHbF090ajQaBU\nMbiSDwz+MWJ48B+ThVAssripwWQ+ITk0qNeaAp4JSY/RefOvbrmrw5BJVTe8x3+4r2/MnctnfTTS\nZEbyXboPln9i57Ep0b0/a75tg1GBksuqF4iXmtFs0ocjTSYs+g67SBfKBukCnZ9NiepPWHXHVVVB\nvWlVCiJFoZK/NDpqTWFusl3XlWlBndsoPoXN5TonMFiu8/t0/zmf/S9tcxBW9J/R4gAAAABJRU5E\nrkJggg==\n'


class Win32PystrayIcon(Icon):
	WM_LBUTTONDBLCLK = 0x0203

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)

		if 'on_double_click' in kwargs:
			self.on_double_click = kwargs['on_double_click']

	def _on_notify(self, wparam, lparam):
		super()._on_notify(wparam, lparam)
		if lparam == self.WM_LBUTTONDBLCLK:
			self.on_double_click(self, None)


if sys.platform == 'win32':
	Icon = Win32PystrayIcon


def create_image():
	buffer = io.BytesIO(base64.b64decode(APP_ICON2))
	img = Image.open(buffer)
	return img


def quit(icon, item):
	icon.stop()


def show():
	print('show main window')


def create_icon():
	# i can use one click using default=True, but no option for double-click
	menu = Menu(
		MenuItem("Start / Show", show, default=True), MenuItem(
			"Minimize to Systray",
			lambda: None
		), MenuItem("Close to Systray", lambda: None), MenuItem("Quit", quit)
	)
	icon = Icon(
		'test', create_image(), menu=menu,
		**{
			'on_double_click': lambda icon, _: print('double-clicked!')
		} if sys.platform == "win32" else {}
	)
	return icon


def foo():
	print('start icon')
	icon = create_icon()
	icon.run()


threading.Thread(target=foo).start()

@daveblackuk
Copy link

thats very useful, thank you

@WillianBR
Copy link

I understand the wish to keep this library portable across OS. But, I need to handle the click into the notification Window.

If the notification message is showed to the user and him click into the notification, I want to trigger a action. In my case it'll be start the default browser to show a URL content (The log entry URL).

Does anybody have a ideal about where I should change the code?

That would be if the OS is MS-Windows only!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants