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

feature added: update of menu options #18

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ Alternatively, you can use easy_install.

Usage
-----
Creating an icon with one option in the context menu:
### Creating an icon with one option in the context menu:

from infi.systray import SysTrayIcon
def say_hello(systray):
print "Hello, World!"
menu_options = (("Say Hello", None, say_hello),)
menu_options = (("Say Hello", "hello.ico", say_hello),)
systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options)
systray.start()

Expand All @@ -26,12 +26,29 @@ if None is specified, a default system icon will be displayed.
The second parameter is the hover text to show when the mouse is hovered over the systray icon.
The traybar will run in its own thread, so the using script can continue to run.

The icon and/or hover text can be updated using the update() method with the appropriate `hover_text` or `icon` keyword argument:
For the parameters of `menu_options`: cf the section **Menu** below

### Updating : icon, hover text and/or menu options
The icon and/or hover text and/or menu options can be updated using the update() method with the appropriate `hover_text` or `icon` or `menu_options` keyword argument:

#### Updating the hover_text:

for item in ['item1', 'item2', 'item3']:
systray.update(hover_text=item)
do_something(item)

#### Updating the menu options:

def say_hello(systray):
systray.update(menu_options=menu_optionsbye)
def say_bye(systray):
systray.update(menu_options=menu_optionshello)
menu_optionshello = (("Say Hello","hello.ico", say_hello),)
menu_optionsbye = (("Say Bye", "bye.ico", say_bye),)
systray = SysTrayIcon("icon.ico", "Hello/Bye", menu_optionshello)
systray.start()

### Shutting down
To destroy the icon when the program ends, call

systray.shutdown()
Expand All @@ -50,16 +67,23 @@ To perform operations when Quit is selected, pass "on_quit=callback" as a parame
program.shutdown()
systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options, on_quit=on_quit_callback)

### Menu :
#### Parameters

menu_options = (("Say Hello", "hello.ico", say_hello),)

`menu_options` must be a list of 3-tuples. Each 3-tuple specifies a context menu options.
The first value in each tuple is the context menu string.
The second value is the icon (some versions of Windows can show icons next to each option in the context menu). If None is passed, no icon is displayed for the option
The third value is the command to execute when the context menu is selected by the user.

#### Default Menu index and Double click
When the user double-clicks the systray icon, the first option specified in menu_options will be executed. The default
command may be changed to a different option by setting the parameter "default_menu_index", e.g.:

systray = SysTrayIcon("icon.ico", "Example tray icon", menu_options, default_menu_index=2)

menu_options must be a list of 3-tuples. Each 3-tuple specifies a context menu options. The first value in each tuple
is the context menu string.
Some versions of Windows can show icons next to each option in the context menu. This icon can be specified in
the second value of the tuples. If None is passed, no icon is displayed for the option.
The third value is the command to execute when the context menu is selected by the user.
#### Sub-menus

It is possible to create sub-menus in the context menu by recursively passing a list of 3-tuple options as the third
value of an option, instead of passing a callback function. e.g.
Expand Down
13 changes: 11 additions & 2 deletions src/infi/systray/traybar.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,24 @@ def shutdown(self):
PostMessage(self._hwnd, WM_CLOSE, 0, 0)
self._message_loop_thread.join()

def update(self, icon=None, hover_text=None):
""" update icon image and/or hover text """
def update(self, icon=None, hover_text=None, menu_options=None):
""" update icon image and/or hover text and/or menu options"""
if icon:
self._icon = icon
self._load_icon()
if hover_text:
self._hover_text = hover_text
# "if menu_options" added to be allow the update of the menu options
if menu_options:
menu_options = menu_options + (('Quit', None, SysTrayIcon.QUIT),)
self._next_action_id = SysTrayIcon.FIRST_ID
self._menu_actions_by_id = set()
self._menu_options = self._add_ids_to_menu_options(list(menu_options))
self._menu_actions_by_id = dict(self._menu_actions_by_id)
self._menu = None # detroy the old menu created by right clicking the icon
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I'm afraid of here is that we're not freeing the previous menu (and submenus and icons, maybe?), potentially causing resource leaks.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible but I don't know how to prevent this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would DestroyMenu do the trick?
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-destroymenu

You'll probably have to add an alias for it in win32_adapter.py but I managed to get this working by doing:

if menu_options:
    [the menu update code]
    ctypes.windll.user32.DestroyMenu(self._menu)
    self._menu = None

self._refresh_icon()


def _add_ids_to_menu_options(self, menu_options):
result = []
for menu_option in menu_options:
Expand Down