diff --git a/docs/usage.rst b/docs/usage.rst index eec0600..b39c6d8 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -40,6 +40,10 @@ icon* implementation for *OSX* will fail unless called from the main thread, and it also requires the application runloop to be running. ``pystray.Icon.run()`` will start the runloop. +If you only target *Windows*, calling ``run()`` from a thread other than the +main thread is safe, provided that you also create the ``Icon`` instance in the +same thread. + The ``run()`` method accepts an optional argument: ``setup``, a callable. The ``setup`` funciton will be run in a separate thread once the *system tray diff --git a/lib/pystray/_base.py b/lib/pystray/_base.py index 8b8c754..69d3926 100644 --- a/lib/pystray/_base.py +++ b/lib/pystray/_base.py @@ -176,9 +176,9 @@ def run(self, setup=None): thread once the loop has started. It is passed the icon as its sole argument. - If not specified, a simple setup function setting :attr:`visible` to - ``True`` is used. If you specify a custom setup function, you must - explicitly set this attribute. + If not specified, a simple setup function setting :attr:`visible` + to ``True`` is used. If you specify a custom setup function, you + must explicitly set this attribute. """ def setup_handler(): self.__queue.get() @@ -247,8 +247,10 @@ def _mark_ready(self): is called. """ self._running = True - self.update_menu() - self.__queue.put(True) + try: + self.update_menu() + finally: + self.__queue.put(True) def _handler(self, callback): """Generates a callback handler. @@ -384,8 +386,8 @@ def checked(self): """Whether this item is checked. This can be either ``True``, which implies that the item is checkable - and checked, ``False``, which implies that the item is checkable but not - checked, and ``None`` for uncheckable items. + and checked, ``False``, which implies that the item is checkable but + not checked, and ``None`` for uncheckable items. Depending on platform, uncheckable items may be rendered differently from unchecked items. @@ -396,8 +398,8 @@ def checked(self): def radio(self): """Whether this item is a radio button. - This is only used for checkable items. It is always set to ``False`` for - uncheckable items. + This is only used for checkable items. It is always set to ``False`` + for uncheckable items. """ if self.checked is not None: return self._radio(self) @@ -442,8 +444,8 @@ def _assert_action(self, action): less than the expected number of arguments, a wrapper will be returned. - :raises ValueError: if ``action`` requires more than the expected number - of arguments + :raises ValueError: if ``action`` requires more than the expected + number of arguments :return: a callable """ diff --git a/lib/pystray/_darwin.py b/lib/pystray/_darwin.py index bf2358a..d31b6b4 100644 --- a/lib/pystray/_darwin.py +++ b/lib/pystray/_darwin.py @@ -202,8 +202,8 @@ def _create_menu(self, descriptors, callbacks): return nsmenu def _create_menu_item(self, descriptor, callbacks): - """Creates a :class:`AppKit.NSMenuItem` from a :class:`pystray.MenuItem` - instance. + """Creates a :class:`AppKit.NSMenuItem` from a + :class:`pystray.MenuItem` instance. :param descriptor: The menu item descriptor. diff --git a/lib/pystray/_gtk.py b/lib/pystray/_gtk.py index 14c5084..d6aeae0 100644 --- a/lib/pystray/_gtk.py +++ b/lib/pystray/_gtk.py @@ -27,8 +27,10 @@ def __init__(self, *args, **kwargs): super(Icon, self).__init__(*args, **kwargs) self._status_icon = Gtk.StatusIcon.new() - self._status_icon.connect('activate', self._on_status_icon_activate) - self._status_icon.connect('popup-menu', self._on_status_icon_popup_menu) + self._status_icon.connect( + 'activate', self._on_status_icon_activate) + self._status_icon.connect( + 'popup-menu', self._on_status_icon_popup_menu) if self.icon: self._update_icon() diff --git a/lib/pystray/_util/gtk.py b/lib/pystray/_util/gtk.py index 3d21dbc..133493a 100644 --- a/lib/pystray/_util/gtk.py +++ b/lib/pystray/_util/gtk.py @@ -64,8 +64,13 @@ def _run(self): self._notifier = notify_dbus.Notifier() self._mark_ready() - # Make sure that we do not inhibit ctrl+c - signal.signal(signal.SIGINT, signal.SIG_DFL) + # Make sure that we do not inhibit ctrl+c; this is only possible from + # the main thread + try: + signal.signal(signal.SIGINT, signal.SIG_DFL) + except ValueError: + pass + try: self._loop.run() except: diff --git a/lib/pystray/_util/win32.py b/lib/pystray/_util/win32.py index de3e03d..c6c84fa 100644 --- a/lib/pystray/_util/win32.py +++ b/lib/pystray/_util/win32.py @@ -347,7 +347,8 @@ def _err(result, func, arguments): ChangeWindowMessageFilterEx.errcheck = _err except KeyError: - def ChangeWindowMessageFilterEx(hWnd, message, action, pCHangeFilterStruct): + def ChangeWindowMessageFilterEx( + hWnd, message, action, pCHangeFilterStruct): """A dummy implementation of ``ChangeWindowMessageFilterEx`` always returning ``TRUE``. diff --git a/lib/pystray/_win32.py b/lib/pystray/_win32.py index 23d039c..d41b966 100644 --- a/lib/pystray/_win32.py +++ b/lib/pystray/_win32.py @@ -238,22 +238,22 @@ def _create_window(self, atom): win32.GetModuleHandle(None), None) - # On Vista+, we must explicitly opt-in to receive WM_TASKBARCREATED when - # running with escalated privileges + # On Vista+, we must explicitly opt-in to receive WM_TASKBARCREATED + # when running with escalated privileges win32.ChangeWindowMessageFilterEx( hwnd, win32.WM_TASKBARCREATED, win32.MSGFLT_ALLOW, None) return hwnd def _create_menu(self, descriptors, callbacks): - """Creates a :class:`ctypes.wintypes.HMENU` from a :class:`pystray.Menu` - instance. + """Creates a :class:`ctypes.wintypes.HMENU` from a + :class:`pystray.Menu` instance. :param descriptors: The menu descriptors. If this is falsy, ``None`` is returned. :param callbacks: A list to which a callback is appended for every menu - item created. The menu item IDs correspond to the items in this list - plus one. + item created. The menu item IDs correspond to the items in this + list plus one. :return: a menu """ @@ -265,7 +265,7 @@ def _create_menu(self, descriptors, callbacks): hmenu = win32.CreatePopupMenu() for i, descriptor in enumerate(descriptors): # Append the callbacks before creating the menu items to ensure - # that the first item get the ID 1 + # that the first item gets the ID 1 callbacks.append(self._handler(descriptor)) menu_item = self._create_menu_item(descriptor, callbacks) win32.InsertMenuItem(hmenu, i, True, ctypes.byref(menu_item)) @@ -279,8 +279,8 @@ def _create_menu_item(self, descriptor, callbacks): :param descriptor: The menu item descriptor. :param callbacks: A list to which a callback is appended for every menu - item created. The menu item IDs correspond to the items in this list - plus one. + item created. The menu item IDs correspond to the items in this + list plus one. :return: a :class:`pystray._util.win32.MENUITEMINFO` """ diff --git a/lib/pystray/_xorg.py b/lib/pystray/_xorg.py index 368ac7b..67fcc75 100644 --- a/lib/pystray/_xorg.py +++ b/lib/pystray/_xorg.py @@ -142,8 +142,8 @@ def _show(self): try: self._assert_docked() except AssertionError: - # There is no systray selection owner, so we cannot dock; ignore and - # dock later + # There is no systray selection owner, so we cannot dock; ignore + # and dock later self._log.error( 'Failed to dock icon', exc_info=True) @@ -305,7 +305,8 @@ def on_client_message(event): # Create the atoms and a mapping from atom to actual implementation atoms = [ - self._display.intern_atom('_PYSTRAY_%s' % original.__name__.upper()) + self._display.intern_atom( + '_PYSTRAY_%s' % original.__name__.upper()) for original in args] handlers = { atom: wrapper(original) @@ -333,10 +334,12 @@ def _create_window(self): -1, -1, 1, 1, 0, screen.root_depth, event_mask=Xlib.X.ExposureMask | Xlib.X.StructureNotifyMask, window_class=Xlib.X.InputOutput) + flags = ( + Xlib.Xutil.PPosition | Xlib.Xutil.PSize | Xlib.Xutil.PMinSize) window.set_wm_class('%sSystemTrayIcon' % self.name, self.name) window.set_wm_name(self.title) window.set_wm_normal_hints( - flags=(Xlib.Xutil.PPosition | Xlib.Xutil.PSize | Xlib.Xutil.PMinSize), + flags=flags, min_width=24, min_height=24)