PK»A“H‚•z¡¡pystray/__init__.py# coding=utf-8 # pystray # Copyright (C) 2016 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import os import sys if os.environ.get('__PYSTRAY_GENERATE_DOCUMENTATION') == 'yes': from ._base import Icon else: Icon = None if sys.platform == 'darwin': if not Icon: from ._darwin import Icon elif sys.platform == 'win32': if not Icon: from ._win32 import Icon else: try: if not Icon: from ._xorg import Icon except: pass if not Icon: raise ImportError('this platform is not supported') PK»A“HtŒ¢HÒÒpystray/_darwin.py# coding=utf-8 # pystray # Copyright (C) 2016 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import io import AppKit import Foundation import objc import PIL from . import _base class Icon(_base.Icon): #: The selector for the button action _ACTION_SELECTOR = 'activate:sender' def __init__(self, *args, **kwargs): super(Icon, self).__init__(*args, **kwargs) #: The NSImage version of the icon self._icon_image = None def _show(self): self._assert_image() self._update_title() self._status_item.button().setHidden_(False) def _hide(self): self._status_item.button().setHidden_(True) def _update_icon(self): self._icon_image = None if self.visible: self._assert_image() def _update_title(self): self._status_item.button().setToolTip_(self.title) def _run(self): # Make sure there is an NSApplication instance self._app = AppKit.NSApplication.sharedApplication() # Make sure we have a delegate to handle the acttion events self._delegate = IconDelegate.alloc().init() self._delegate.icon = self self._status_bar = AppKit.NSStatusBar.systemStatusBar() self._status_item = self._status_bar.statusItemWithLength_( AppKit.NSVariableStatusItemLength) self._status_item.button().setTarget_(self._delegate) self._status_item.button().setAction_(self._ACTION_SELECTOR) self._status_item.button().setHidden_(True) # Notify the setup callback self._mark_ready() try: self._app.run() finally: self._status_bar.removeStatusItem_(self._status_item) def _stop(self): self._app.stop_(self._app) # Post a dummy event; stop_ will only set a flag in NSApp, so it will # not terminate until an event has been processed event = getattr( AppKit.NSEvent, 'otherEventWithType_' 'location_' 'modifierFlags_' 'timestamp_' 'windowNumber_' 'context_' 'subtype_' 'data1_' 'data2_')( AppKit.NSApplicationDefined, AppKit.NSPoint(0, 0), 0, 0.0, 0, None, 0, 0, 0) self._app.postEvent_atStart_(event, False) def _assert_image(self): """Asserts that the cached icon image exists. """ thickness = self._status_bar.thickness() size = (int(thickness), int(thickness)) if self._icon_image and self._icon_image.size() == size: return if self._icon.size == size: source = self._icon else: source = PIL.Image.new( 'RGB', size) source.paste(self._icon.resize( size, PIL.Image.ANTIALIAS)) # Convert the PIL image to an NSImage b = io.BytesIO() source.save(b, 'png') data = Foundation.NSData(b.getvalue()) self._icon_image = AppKit.NSImage.alloc().initWithData_(data) self._status_item.button().setImage_(self._icon_image) class IconDelegate(Foundation.NSObject): @objc.namedSelector(Icon._ACTION_SELECTOR) def activate(self, sender): self.icon.on_activate(self.icon) PK»A“H›ÊVß*ß*pystray/_win32.py# coding=utf-8 # pystray # Copyright (C) 2016 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import ctypes import os import six import sys import threading import tempfile from ctypes import windll, wintypes from six.moves import queue from . import _base class Icon(_base.Icon): _HWND_TO_ICON = {} def __init__(self, *args, **kwargs): super(Icon, self).__init__(*args, **kwargs) self._icon_handle = None self._hwnd = None # This is a mapping from win32 event codes to handlers used by the # mainloop self._message_handlers = { WM_STOP: self._on_stop, WM_NOTIFY: self._on_notify} self._queue = queue.Queue() # Create the message loop msg = wintypes.MSG() lpmsg = ctypes.byref(msg) PeekMessage(lpmsg, None, 0x0400, 0x0400, PM_NOREMOVE) self._atom = self._register_class() self._hwnd = self._create_window(self._atom) self._HWND_TO_ICON[self._hwnd] = self def __del__(self): if self._running: self._stop() if self._thread.ident != threading.current_thread().ident: self._thread.join() def _show(self): self._assert_icon_handle() self._message( NOTIFYICONDATA.NIM_ADD, NOTIFYICONDATA.NIF_MESSAGE | NOTIFYICONDATA.NIF_ICON | NOTIFYICONDATA.NIF_TIP, uCallbackMessage=WM_NOTIFY, hIcon=self._icon_handle, szTip=self.title) def _hide(self): self._message( NOTIFYICONDATA.NIM_DELETE, 0) def _update_icon(self): self._icon_handle = None self._assert_icon_handle() self._message( NOTIFYICONDATA.NIM_MODIFY, NOTIFYICONDATA.NIF_ICON, hIcon=self._icon_handle) def _update_title(self): self._message( NOTIFYICONDATA.NIM_MODIFY, NOTIFYICONDATA.NIF_TIP, szTip=self.title) def _run(self): self._mark_ready() # Run the event loop self._thread = threading.current_thread() self._mainloop() def _stop(self): PostMessage(self._hwnd, WM_STOP, 0, 0) def _mainloop(self): """The body of the main loop thread. This method retrieves all events from *Windows* and makes sure to dispatch clicks. """ # Pump messages try: while True: msg = wintypes.MSG() lpmsg = ctypes.byref(msg) while True: r = GetMessage(lpmsg, None, 0, 0) if not r: break elif r == -1: break else: TranslateMessage(lpmsg) DispatchMessage(lpmsg) # Make sure the icon is removed self._hide() except: # TODO: Report errors pass finally: try: self._hide() del self._HWND_TO_ICON[self._hwnd] except: pass DestroyWindow(self._hwnd) self._unregister_class(self._atom) def _on_stop(self, wparam, lparam): """Handles ``WM_STOP``. This method posts a quit message, causing the mainloop thread to terminate. """ PostQuitMessage(0) def _on_notify(self, wparam, lparam): """Handles ``WM_NOTIFY``. This method calls the activate callback. It will only be called for left button clicks. """ if lparam == WM_LBUTTONDOWN: self.on_activate(self) def _create_window(self, atom): """Creates the system tray icon window. :param atom: The window class atom. :return: a window """ hwnd = CreateWindowEx( 0, atom, None, 0, 0, 0, 0, 0, HWND_MESSAGE, None, GetModuleHandle(None), None) if not hwnd: raise ctypes.WinError(wintypes.get_last_error()) else: return hwnd def _message(self, code, flags, **kwargs): """Sends a message the the systray icon. This method adds ``cbSize``, ``hWnd``, ``hId`` and ``uFlags`` to the message data. :param int message: The message to send. This should be one of the ``NIM_*`` constants. :param int flags: The value of ``NOTIFYICONDATA::uFlags``. :param kwargs: Data for the :class:`NOTIFYICONDATA` object. """ r = Shell_NotifyIcon(code, ctypes.byref(NOTIFYICONDATA( cbSize=ctypes.sizeof(NOTIFYICONDATA), hWnd=self._hwnd, hID=id(self), uFlags=flags, **kwargs))) if not r: raise ctypes.WinError(wintypes.get_last_error()) def _assert_icon_handle(self): """Asserts that the cached icon handle exists. """ if self._icon_handle: return fd, icon_path = tempfile.mkstemp('.ico') try: with os.fdopen(fd, 'wb') as f: self._icon.save(f, format='ICO') hicon = LoadImage( None, wintypes.LPCWSTR(icon_path), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE) if not hicon: raise ctypes.WinError(wintypes.get_last_error()) else: self._icon_handle = hicon finally: try: os.unlink(icon_path) except: pass def _register_class(self): """Registers the systray window class. :return: the class atom """ window_class = WNDCLASSEX( cbSize=ctypes.sizeof(WNDCLASSEX), style=0, lpfnWndProc=_dispatcher, cbClsExtra=0, cbWndExtra=0, hInstance=GetModuleHandle(None), hIcon=None, hCursor=None, hbrBackground=COLOR_WINDOW + 1, lpszMenuName=None, lpszClassName='%s%dSystemTrayIcon' % (self.name, id(self)), hIconSm=None) atom = RegisterClassEx(ctypes.byref(window_class)) if not atom: raise ctypes.WinError(wintypes.get_last_error()) else: return atom def _unregister_class(self, atom): """Unregisters the systray window class. :param atom: The class atom returned by :meth:`_register_class`. """ r = UnregisterClassEx(atom, GetModuleHandle(None)) if not r: raise ctypes.WinError(wintypes.get_last_error()) WM_CREATE = 0x0001 WM_NCCREATE = 0x0081 WM_LBUTTONDOWN = 0x0201 WM_USER = 0x400 WM_STOP = WM_USER + 10 WM_NOTIFY = WM_USER + 11 HWND_MESSAGE = -3 PM_NOREMOVE = 0 COLOR_WINDOW = 5 IMAGE_ICON = 1 LR_LOADFROMFILE = 0x00000010 LR_DEFAULTSIZE = 0x00000040 NOTIFYICON_VERSION = 3 Shell_NotifyIcon = windll.shell32.Shell_NotifyIconW GetModuleHandle = windll.kernel32.GetModuleHandleW RegisterClassEx = windll.user32.RegisterClassExW CreateWindowEx = windll.user32.CreateWindowExW CreateWindowEx.argtypes = [ wintypes.DWORD, wintypes.LPVOID, wintypes.LPCWSTR, wintypes.DWORD, wintypes.INT, wintypes.INT, wintypes.INT, wintypes.INT, wintypes.HWND, wintypes.HMENU, wintypes.HINSTANCE, wintypes.LPVOID] CreateWindowEx.restype = wintypes.HWND DestroyWindow = windll.user32.DestroyWindow UnregisterClassEx = windll.user32.UnregisterClassW LoadImage = windll.user32.LoadImageW DispatchMessage = windll.user32.DispatchMessageW GetMessage = windll.user32.GetMessageW PeekMessage = windll.user32.PeekMessageW PostMessage = windll.user32.PostMessageW PostQuitMessage = windll.user32.PostQuitMessage TranslateMessage = windll.user32.TranslateMessage WNDPROC = ctypes.WINFUNCTYPE( ctypes.HRESULT, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM) class WNDCLASSEX(ctypes.Structure): _fields_ = [ ('cbSize', wintypes.UINT), ('style', wintypes.UINT), ('lpfnWndProc', WNDPROC), ('cbClsExtra', wintypes.INT), ('cbWndExtra', wintypes.INT), ('hInstance', wintypes.HANDLE), ('hIcon', wintypes.HICON), ('hCursor', wintypes.HANDLE), ('hbrBackground', wintypes.HBRUSH), ('lpszMenuName', wintypes.LPCWSTR), ('lpszClassName', wintypes.LPCWSTR), ('hIconSm', wintypes.HICON)] @WNDPROC def _dispatcher(hwnd, uMsg, wParam, lParam): try: return int(Icon._HWND_TO_ICON[hwnd]._message_handlers.get( uMsg, lambda w, l: 0)(wParam, lParam)) except KeyError: # Icon._HWND_TO_ICON[hwnd] is not yet set; this message is sent during # window creation, so we assume it is WM_CREATE or WM_NCCREATE and # return TRUE return 1 except: # TODO: Report return 0 class NOTIFYICONDATA(ctypes.Structure): class VERSION_OR_TIMEOUT(ctypes.Union): _fields_ = [ ('uTimeout', wintypes.UINT), ('uVersion', wintypes.UINT)] NIF_MESSAGE = 0x00000001 NIF_ICON = 0x00000002 NIF_TIP = 0x00000004 NIF_STATE = 0x00000008 NIF_INFO = 0x00000010 NIF_GUID = 0x00000020 NIF_REALTIME = 0x00000040 NIF_SHOWTIP = 0x00000080 NIM_ADD = 0x00000000 NIM_MODIFY = 0x00000001 NIM_DELETE = 0x00000002 NIM_SETFOCUS = 0x00000003 NIM_SETVERSION = 0x00000004 _fields_ = [ ('cbSize', wintypes.DWORD), ('hWnd', wintypes.HWND), ('uID', wintypes.UINT), ('uFlags', wintypes.UINT), ('uCallbackMessage', wintypes.UINT), ('hIcon', wintypes.HICON), ('szTip', wintypes.WCHAR * 64), ('dwState', wintypes.DWORD), ('dwStateMask', wintypes.DWORD), ('szInfo', wintypes.WCHAR * 256), ('version_or_timeout', VERSION_OR_TIMEOUT), ('szInfoTitle', wintypes.WCHAR * 64), ('dwInfoFlags', wintypes.DWORD), ('guidItem', wintypes.LPVOID), ('hBalloonIcon', wintypes.HICON)] _anonymous_ = [ 'version_or_timeout'] PK»A“Hœ‘Î4•7•7pystray/_xorg.py# coding=utf-8 # pystray # Copyright (C) 2016 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import contextlib import functools import six import sys import threading import types import PIL import Xlib.display import Xlib.threaded import Xlib.XK from six.moves import queue from . import _base # Create a display to verify that we have an X connection display = Xlib.display.Display() display.close() del display class XError(Exception): """An error that is thrown at the end of a code block managed by a :func:`display_manager` if an *X* error occurred. """ pass @contextlib.contextmanager def display_manager(display): """Traps *X* errors and raises an :class:``XError`` at the end if any error occurred. This handler also ensures that the :class:`Xlib.display.Display` being managed is sync'd. :param Xlib.display.Display display: The *X* display. """ errors = [] def handler(*args): errors.append(args) old_handler = display.set_error_handler(handler) try: yield display.sync() finally: display.set_error_handler(old_handler) if errors: raise XError(errors) class Icon(_base.Icon): _XEMBED_VERSION = 0 _XEMBED_MAPPED = 1 _SYSTEM_TRAY_REQUEST_DOCK = 0 def __init__(self, *args, **kwargs): super(Icon, self).__init__(*args, **kwargs) #: The properly scaled version of the icon image self._icon_data = None #: The window currently embedding this icon self._systray_manager = None # This is a mapping from X event codes to handlers used by the mainloop self._message_handlers = { Xlib.X.ButtonPress: self._on_button_press, Xlib.X.ConfigureNotify: self._on_expose, Xlib.X.DestroyNotify: self._on_destroy_notify, Xlib.X.Expose: self._on_expose} self._queue = queue.Queue() # Connect to X self._display = Xlib.display.Display() with display_manager(self._display): # Create the atoms; some of these are required when creating # the window self._create_atoms() # Create the window and get a graphics context self._window = self._create_window() self._gc = self._window.create_gc() # Rewrite the platform implementation methods to ensure they # are executed in this thread self._rewrite_implementation( self._show, self._hide, self._update_icon, self._update_title, self._stop) def __del__(self): try: # Destroying the window will stop the mainloop thread if self._running: self._stop() if threading.current_thread().ident != self._thread.ident: self._thread.join() finally: self._display.close() def _show(self): """The implementation of :meth:`_show`, executed in the mainloop thread. """ try: self._assert_docked() except AssertionError: # There is no systray selection owner, so we cannot dock; # ignore and dock later pass def _hide(self): """The implementation of :meth:`_hide`, executed in the mainloop thread. """ if self._systray_manager: self._undock_window() def _update_icon(self): """The implementation of :meth:`_update_icon`, executed in the mainloop thread. """ try: self._assert_docked() except AssertionError: # If we are not docked, we cannot update the icon return # Setting _icon_data to None will force regeneration of the icon # from _icon self._icon_data = None self._draw() def _update_title(self): """The implementation of :meth:`_update_title`, executed in the mainloop thread. """ # The title is the window name self._window.set_wm_name(self.title) def _run(self): self._mark_ready() # Run the event loop self._thread = threading.current_thread() self._mainloop() def _stop(self): """Stops the mainloop. """ self._window.destroy() self._display.flush() def _mainloop(self): """The body of the main loop thread. This method retrieves all events from *X* and makes sure to dispatch clicks. """ try: for event in self._events(): # If the systray window is destroyed, the icon has been hidden if (event.type == Xlib.X.DestroyNotify and event.window == self._window): break self._message_handlers.get(event.type, lambda e: None)(event) except: # TODO: Report errors pass def _on_button_press(self, event): """Handles ``Xlib.X.ButtonPress``. This method calls the activate callback. It will only be called for left button clicks. """ if event.detail == 1: self.on_activate(self) def _on_destroy_notify(self, event): """Handles ``Xlib.X.DestroyNotify``. This method clears :attr:`_systray_manager` if it is destroyed. """ # Handle only the systray manager window; the destroy notification # for our own window is handled in the event loop if event.window.id != self._systray_manager.id: return # Try to locate a new systray selection owner self._systray_manager = None try: self._assert_docked() except AssertionError: # There is no new selection owner; we must retry later pass def _on_expose(self, event): """Handles ``Xlib.X.ConfigureNotify`` and ``Xlib.X.Expose``. This method redraws the window. """ # Redraw only our own window if event.window.id != self._window.id: return self._draw() def _create_atoms(self): """Creates the atoms used by the *XEMBED* and *systray* specifications. """ self._xembed_info = self._display.intern_atom( '_XEMBED_INFO') self._net_system_tray_sx = self._display.intern_atom( '_NET_SYSTEM_TRAY_S%d' % ( self._display.get_default_screen())) self._net_system_tray_opcode = self._display.intern_atom( '_NET_SYSTEM_TRAY_OPCODE') def _rewrite_implementation(self, *args): """Overwrites the platform implementation methods with ones causing the mainloop to execute the code instead. :param args: The methods to rewrite. """ def dispatcher(original, atom): @functools.wraps(original) def inner(self): # Just invoke the method if we are currently in the correct # thread if threading.current_thread().ident == self._thread.ident: original() else: self._send_message(self._window, atom) self._display.flush() # Wait for the mainloop to execute the actual method, wait # for completion and reraise any exceptions result = self._queue.get() if result is not True: six.reraise(*result) return types.MethodType(inner, self) def wrapper(original): @functools.wraps(original) def inner(): try: original() self._queue.put(True) except: self._queue.put(sys.exc_info()) return inner def on_client_message(event): handlers.get(event.client_type, lambda: None)() # Create the atoms and a mapping from atom to actual implementation atoms = [ self._display.intern_atom('_PYSTRAY_%s' % original.__name__.upper()) for original in args] handlers = { atom: wrapper(original) for original, atom in zip(args, atoms)} # Replace the old methods for original, atom in zip(args, atoms): setattr( self, original.__name__, dispatcher(original, atom)) # Make sure that we handle ClientMessage self._message_handlers[Xlib.X.ClientMessage] = on_client_message def _create_window(self): """Creates the system tray icon window. :return: a window """ with display_manager(self._display): # Create the window screen = self._display.screen() window = screen.root.create_window( -1, -1, 1, 1, 0, screen.root_depth, event_mask=Xlib.X.ExposureMask | Xlib.X.StructureNotifyMask, window_class=Xlib.X.InputOutput) window.set_wm_class('%sSystemTrayIcon' % self.name, self.name) window.set_wm_name(self.title) # Enable XEMBED for the window window.change_property(self._xembed_info, self._xembed_info, 32, [ self._XEMBED_VERSION, self._XEMBED_MAPPED]) return window def _draw(self): """Paints the icon image. """ try: dim = self._window.get_geometry() self._assert_icon_data(dim.width, dim.height) self._window.put_pil_image(self._gc, 0, 0, self._icon_data) except Xlib.error.BadDrawable: # The window has been destroyed; ignore pass def _assert_icon_data(self, width, height): """Asserts that the cached icon data matches the requested dimensions. If no cached icon data exists, or its dimensions do not match the requested size, the image is generated. :param int width: The requested width. :param int height: The requested height. """ if self._icon_data and self._icon_data.size == (width, height): return self._icon_data = PIL.Image.new( 'RGB', (width, height)) self._icon_data.paste(self._icon.resize( (width, height), PIL.Image.ANTIALIAS)) def _assert_docked(self): """Asserts that the icon is docked in the systray. :raises AssertionError: if the window is not docked """ self._dock_window() assert self._systray_manager def _dock_window(self): """Docks the window in the systray. """ # Get the selection owner systray_manager = self._get_systray_manager() if not systray_manager: return self._systray_manager = systray_manager # Request being docked self._send_message( self._systray_manager, self._net_system_tray_opcode, self._SYSTEM_TRAY_REQUEST_DOCK, self._window.id) # Make sure we get destroy notifications systray_manager.change_attributes( event_mask=Xlib.X.StructureNotifyMask) self._display.flush() self._systray_manager = systray_manager def _undock_window(self): """Undocks the window from the systray. """ # Make sure we get do not get any notifications try: self._systray_manager.change_attributes( event_mask=Xlib.X.NoEventMask) except XError: # The systray manager may have been destroyed pass self._window.unmap() self._window.reparent(self._display.screen().root, 0, 0) self._systray_manager = None self._display.flush() def _get_systray_manager(self): """Returns the *X* window that owns the systray selection. :return: the window owning the selection, or ``None`` if no window owns it """ self._display.grab_server() try: systray_manager = self._display.get_selection_owner( self._net_system_tray_sx) finally: self._display.ungrab_server() self._display.flush() if systray_manager != Xlib.X.NONE: return self._display.create_resource_object( 'window', systray_manager.id) def _send_message(self, window, client_type, l0=0, l1=0, l2=0, l3=0): """Sends a generic client message message. This method does not trap *X* errors; that is up to the caller. :param int l0: Message specific data. :param int l1: Message specific data. :param int l2: Message specific data. :param int l3: Message specific data. """ self._display.send_event( window, Xlib.display.event.ClientMessage( type=Xlib.X.ClientMessage, client_type=client_type, window=window.id, data=( 32, (Xlib.X.CurrentTime, l0, l1, l2, l3))), event_mask=Xlib.X.NoEventMask) def _events(self): """Yields all events. """ while True: event = self._display.next_event() if not event: break else: yield event PK»A“H5KÞLpystray/_base.py# coding=utf-8 # pystray # Copyright (C) 2016 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import threading from six.moves import queue class Icon(object): """A representation of a system tray icon. The icon is initially hidden. Call :meth:`show` to show it. :param str name: The name of the icon. This is used by the system to identify the icon. :param icon: The icon to use. If this is specified, it must be a :class:`PIL.Image.Image` instance. :param str title: A short title for the icon. :param callable on_activate: A callback for when the system tray icon is activated. It is passed the icon as its sole argument. """ def __init__(self, name, icon=None, title=None, on_activate=None): self._name = name if icon: self._icon = icon else: self._icon = None if title: self._title = title else: self._title = '' self._visible = False if on_activate: self.on_activate = on_activate else: self.on_activate = lambda icon: None self.__queue = queue.Queue() self._running = False def __del__(self): if self.visible: self._hide() @property def name(self): """The name passed to the constructor. """ return self._name @property def icon(self): """The current icon. Setting this to a falsy value will hide the icon. Setting this to an image while the icon is hidden has no effect until the icon is shown using :meth:`show`. """ return self._icon @icon.setter def icon(self, value): self._icon = value if value: if self.visible: self._update_icon() else: if self.visible: self.visible = False @property def title(self): """The current icon title. """ return self._title @title.setter def title(self, value): if value != self._title: self._title = value if self.visible: self._update_title() @property def visible(self): """Whether the icon is currently visible. :raises ValueError: if set to ``True`` and no icon image has been set """ return self._visible @visible.setter def visible(self, value): if self._visible == value: return if value: if not self._icon: raise ValueError('cannot show icon without icon data') self._show() self._visible = True else: self._hide() self._visible = False def run(self, setup=None): """Enters the loop handling events for the icon. This method is blocking until :meth:`stop` is called. It *must* be called from the main thread. :param callable setup: An optional callback to execute in a separate thread once the loop has started. It is passed the icon as its sole argument. """ def setup_handler(): self.__queue.get() if setup: setup(self) self._setup_thread = threading.Thread(target=setup_handler) self._setup_thread.start() self._run() self._running = True def stop(self): """Stops the loop handling events for the icon. This method can be called either from the ``on_activate`` callback or from a different thread. """ self._stop() if self._setup_thread.ident != threading.current_thread().ident: self._setup_thread.join() self._running = False def _mark_ready(self): """Marks the icon as ready. The setup callback passed to :meth:`run` will not be called until this method has been invoked. """ self.__queue.put(True) def _show(self): """The implementation of the :meth:`show` method. This is a platform dependent implementation. """ raise NotImplementedError() def _hide(self): """The implementation of the :meth:`hide` method. This is a platform dependent implementation. """ raise NotImplementedError() def _update_icon(self): """Updates the image for an already shown icon. This is a platform dependent implementation. """ raise NotImplementedError() def _update_title(self): """Updates the title for an already shown icon. This is a platform dependent implementation. """ raise NotImplementedError() def _run(self): """Runs the event loop. This method must call :meth:`_mark_ready` once the loop is ready. This is a platform dependent implementation. """ raise NotImplementedError() def _stop(self): """Stops the event loop. This is a platform dependent implementation. """ raise NotImplementedError() PK B“HïëFFpystray/_info.py# coding: utf8 __author__ = u'Moses Palmér' __version__ = (0, 3, 2) PKCB“Hùb566'pystray-0.3.2.dist-info/DESCRIPTION.rstpystray Package Documentation ============================= This library allows you to create a *system tray icon*. Creating a *system tray icon* ----------------------------- In order to create a *system tray icon*, the class ``pystray.Icon`` is used:: import pystray icon = pystray.Icon('test name') In order for the icon to be displayed, we must provide an icon. This icon must be specified as a ``PIL.Image.Image``:: from PIL import Image, ImageDraw # Generate an image image = Image.new('RGB', (width, height), color1) dc = ImageDraw.Draw(image) dc.rectangle((width // 2, 0, width, height // 2), fill=color2) dc.rectangle((0, height // 2, width // 2, height), fill=color2) icon.image = image To ensure that your application runs on all platforms, you must then run the following code to show the icon:: def setup(icon): icon.visible = True icon.run(setup) The call to ``pystray.Icon.run()`` is blocking, and it must be performed from the main thread of the application. The reason for this is that the *system tray icon* implementation for *OSX* must be run from the main thread, and it requires the application runloop to be running. ``pystray.Icon.run()`` will start the runloop. The code in ``setup()`` will be run in a separate thread once the *system tray icon* is ready. The icon does not wait for it to complete, so you may put any code that would follow the call to ``pystray.Icon.run()`` in it. ``pystray.Icon.run()`` will not complete until ``~pystray.Icon.stop()`` is called. Release Notes ============= v0.3.2 - Universal wheel ------------------------ * Make sure to build a universal wheel for all python versions. v0.3.1 - No-change packaging update ----------------------------------- * Do not package an old version of ``pynput``. v0.3 - Proper Python 3 Support ------------------------------ * Corrected Python 3 bugs. * Made ``Icon.run()`` mandatory on all platforms. v0.2 - Initial Release ---------------------- * Support for adding a system tray icon on *Linux*, *Mac OSX* and *Windows*. PKCB“H¤={ͽ½%pystray-0.3.2.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows NT/2000", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4"], "extensions": {"python.details": {"contacts": [{"email": "moses.palmer@gmail.com", "name": "Moses Palm\u00c3\u00a9r", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/moses-palmer/pystray"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["system", "tray", "icon", "systray", "icon"], "license": "LGPLv3", "metadata_version": "2.0", "name": "pystray", "run_requires": [{"requires": ["Pillow"]}], "summary": "Provides systray integration", "version": "0.3.2"}PKCB“H¼5{.. pystray-0.3.2.dist-info/pbr.json{"is_release": true, "git_version": "0de8508"}PKCB“HÀm§%pystray-0.3.2.dist-info/top_level.txtpystray PKMcH“×2 pystray-0.3.2.dist-info/zip-safe PKCB“Hìndªnnpystray-0.3.2.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKCB“H”'/: : pystray-0.3.2.dist-info/METADATAMetadata-Version: 2.0 Name: pystray Version: 0.3.2 Summary: Provides systray integration Home-page: https://github.com/moses-palmer/pystray Author: Moses Palmér Author-email: moses.palmer@gmail.com License: LGPLv3 Keywords: system tray icon,systray icon Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Requires-Dist: Pillow pystray Package Documentation ============================= This library allows you to create a *system tray icon*. Creating a *system tray icon* ----------------------------- In order to create a *system tray icon*, the class ``pystray.Icon`` is used:: import pystray icon = pystray.Icon('test name') In order for the icon to be displayed, we must provide an icon. This icon must be specified as a ``PIL.Image.Image``:: from PIL import Image, ImageDraw # Generate an image image = Image.new('RGB', (width, height), color1) dc = ImageDraw.Draw(image) dc.rectangle((width // 2, 0, width, height // 2), fill=color2) dc.rectangle((0, height // 2, width // 2, height), fill=color2) icon.image = image To ensure that your application runs on all platforms, you must then run the following code to show the icon:: def setup(icon): icon.visible = True icon.run(setup) The call to ``pystray.Icon.run()`` is blocking, and it must be performed from the main thread of the application. The reason for this is that the *system tray icon* implementation for *OSX* must be run from the main thread, and it requires the application runloop to be running. ``pystray.Icon.run()`` will start the runloop. The code in ``setup()`` will be run in a separate thread once the *system tray icon* is ready. The icon does not wait for it to complete, so you may put any code that would follow the call to ``pystray.Icon.run()`` in it. ``pystray.Icon.run()`` will not complete until ``~pystray.Icon.stop()`` is called. Release Notes ============= v0.3.2 - Universal wheel ------------------------ * Make sure to build a universal wheel for all python versions. v0.3.1 - No-change packaging update ----------------------------------- * Do not package an old version of ``pynput``. v0.3 - Proper Python 3 Support ------------------------------ * Corrected Python 3 bugs. * Made ``Icon.run()`` mandatory on all platforms. v0.2 - Initial Release ---------------------- * Support for adding a system tray icon on *Linux*, *Mac OSX* and *Windows*. PKCB“H®ê¹§^^pystray-0.3.2.dist-info/RECORDpystray/__init__.py,sha256=Q0lc5c50n5FbOoSd4pvyhHxcA9kK3eajskFI1u5qx98,1185 pystray/_base.py,sha256=GMGfq3qu1KlWOFS0WYAXXI1-U7GO2JjXQIqqYZQMcqQ,5759 pystray/_darwin.py,sha256=BFS6vJUzjWcaprqqqnR4KAUqxIyKJl-nudrfEhQ1Uhs,4050 pystray/_info.py,sha256=S88RKR__gfoWPmbDtRuwdh-XLcHfqep12iKhBQsbqLw,70 pystray/_win32.py,sha256=yqcmQn3BRELmu25joH4JhHTRLhJIBuRQWSEjE_BtyMg,10975 pystray/_xorg.py,sha256=XHWR3X2bibSKqwD4ABkqpJ4vQYLkxf5ibkeB96-B4BA,14229 pystray-0.3.2.dist-info/DESCRIPTION.rst,sha256=8lZDCxW6-gCTwIocLf30pZEvE42Nw3uX1Vo6UOW7W2o,2102 pystray-0.3.2.dist-info/METADATA,sha256=dZE7UFpfIRmlZH1Z-6W3g6GwKNufFR7OYdhdhdehJEw,2874 pystray-0.3.2.dist-info/RECORD,, pystray-0.3.2.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 pystray-0.3.2.dist-info/metadata.json,sha256=lAlDkW0C5Qrbk1-XTMCcVJfRkU5diggCI5ne8hec6zM,957 pystray-0.3.2.dist-info/pbr.json,sha256=xSgmAAu4OumZg-Dz3zpY8R6HNWwneTE2iliuA1w2d4E,46 pystray-0.3.2.dist-info/top_level.txt,sha256=6xvZ5fFN6P3IotgMJLAHeWonXXZzDdf47BhMLsXEHlg,8 pystray-0.3.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 PK»A“H‚•z¡¡pystray/__init__.pyPK»A“HtŒ¢HÒÒÒpystray/_darwin.pyPK»A“H›ÊVß*ß*Ôpystray/_win32.pyPK»A“Hœ‘Î4•7•7â?pystray/_xorg.pyPK»A“H5KÞL¥wpystray/_base.pyPK B“HïëFFRŽpystray/_info.pyPKCB“Hùb566'ÆŽpystray-0.3.2.dist-info/DESCRIPTION.rstPKCB“H¤={ͽ½%A—pystray-0.3.2.dist-info/metadata.jsonPKCB“H¼5{.. A›pystray-0.3.2.dist-info/pbr.jsonPKCB“HÀm§%­›pystray-0.3.2.dist-info/top_level.txtPKMcH“×2 ø›pystray-0.3.2.dist-info/zip-safePKCB“Hìndªnn7œpystray-0.3.2.dist-info/WHEELPKCB“H”'/: : àœpystray-0.3.2.dist-info/METADATAPKCB“H®ê¹§^^X¨pystray-0.3.2.dist-info/RECORDPKöò¬