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 •7 pystray/_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ÞL pystray/_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ïëF F pystray/_info.py# coding: utf8
__author__ = u'Moses Palmér'
__version__ = (0, 3, 2)
PK CB“Hùb56 6 ' 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*.
PK CB“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"}PK CB“H¼5{. . pystray-0.3.2.dist-info/pbr.json{"is_release": true, "git_version": "0de8508"}PK CB“HÀ m§ % pystray-0.3.2.dist-info/top_level.txtpystray
PK McH“×2 pystray-0.3.2.dist-info/zip-safe
PK CB“Hìndªn n pystray-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
PK CB“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*.
PK CB“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 ¥w pystray/_base.pyPK
B“HïëF F RŽ pystray/_info.pyPK CB“Hùb56 6 ' ÆŽ pystray-0.3.2.dist-info/DESCRIPTION.rstPK CB“H¤={ͽ ½ % A— pystray-0.3.2.dist-info/metadata.jsonPK CB“H¼5{. . A› pystray-0.3.2.dist-info/pbr.jsonPK CB“HÀ m§ % › pystray-0.3.2.dist-info/top_level.txtPK McH“×2 ø› pystray-0.3.2.dist-info/zip-safePK CB“Hìndªn n 7œ pystray-0.3.2.dist-info/WHEELPK CB“H”'/: : àœ pystray-0.3.2.dist-info/METADATAPK CB“H®ê¹§^ ^ X¨ pystray-0.3.2.dist-info/RECORDPK ö ò¬