PK k{Hz 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 -lxHtH 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 HHV* * 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 HH47 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 HH5KL 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 IH[ywC C pystray/_info.py# coding: utf8
__author__ = u'Moses Palmér'
__version__ = (0, 3)
PK )\H^A) pynput/__init__.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
from . import mouse
PK )\H#1vC C pynput/_info.py# coding: utf8
__author__ = u'Moses Palmér'
__version__ = (0, 2)
PK )\HY pynput/_util/__init__.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
PK )\HIH H pynput/_util/xorg.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
class X11Error(Exception):
"""An error that is thrown at the end of a code block managed by a
:func:`display_manager` if an *X11* error occurred.
"""
pass
def display_manager(display):
"""Traps *X* errors and raises an :class:``X11Error`` 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.
:return: the display
:rtype: Xlib.display.Display
"""
from contextlib import contextmanager
@contextmanager
def manager():
errors = []
def handler(*args):
errors.append(args)
old_handler = display.set_error_handler(handler)
yield display
display.sync()
display.set_error_handler(old_handler)
if errors:
raise X11Error(errors)
return manager()
PK )\Hb b pynput/_util/win32.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
import ctypes
import threading
from ctypes import windll, wintypes
class MessageLoop(object):
"""A class representing a message loop.
"""
#: The message that signals this loop to terminate
WM_STOP = 0x0401
_GetMessage = windll.user32.GetMessageW
_PeekMessage = windll.user32.PeekMessageW
_PostThreadMessage = windll.user32.PostThreadMessageW
PM_NOREMOVE = 0
def __init__(
self,
initialize=lambda message_loop: None,
finalize=lambda message_loop: None):
self._threadid = None
self._initialize = initialize
self._finalize = finalize
self._event = threading.Event()
self.thread = None
def __iter__(self):
"""Initialises the message loop and yields all messages until
:meth:`stop` is called.
:raises AssertionError: if :meth:`start` has not been called
"""
assert self._threadid is not None
try:
# Pump messages until WM_STOP
while True:
msg = wintypes.MSG()
lpmsg = ctypes.byref(msg)
r = self._GetMessage(lpmsg, None, 0, 0)
if r <= 0 or msg.message == self.WM_STOP:
break
else:
yield msg
finally:
self._finalize(self)
self._threadid = None
self.thread = None
def start(self):
"""Starts the message loop.
This method must be called before iterating over messages, and it must
be called from the same thread.
"""
self._threadid = GetCurrentThreadId()
self.thread = threading.current_thread()
# Create the message loop
msg = wintypes.MSG()
lpmsg = ctypes.byref(msg)
self._PeekMessage(lpmsg, None, 0x0400, 0x0400, self.PM_NOREMOVE)
# Let the called perform initialisation
self._initialize(self)
# Set the event to signal to other threads that the loop is created
self._event.set()
def stop(self):
"""Stops the message loop.
"""
self._event.wait()
self._PostThreadMessage(self._threadid, self.WM_STOP, 0, 0)
if self.thread != threading.current_thread():
self.thread.join()
class SystemHook(object):
"""A class to handle Windows hooks.
"""
_SetWindowsHookEx = windll.user32.SetWindowsHookExW
_UnhookWindowsHookEx = windll.user32.UnhookWindowsHookEx
_CallNextHookEx = windll.user32.CallNextHookEx
_HOOKPROC = wintypes.WINFUNCTYPE(
wintypes.LPARAM,
ctypes.c_int32, wintypes.WPARAM, wintypes.LPARAM)
#: The registered hook procedures
_HOOKS = {}
def __init__(self, hook_id, on_hook=lambda code, msg, lpdata: None):
self.hook_id = hook_id
self.on_hook = on_hook
self._hook = None
def __enter__(self):
key = threading.current_thread()
assert key not in self._HOOKS
# Add ourself to lookup table and install the hook
self._HOOKS[key] = self
self._hook = self._SetWindowsHookEx(
self.hook_id,
self._handler,
None,
0)
return self
def __exit__(self, type, value, traceback):
key = threading.current_thread()
assert key in self._HOOKS
if self._hook is not None:
# Uninstall the hook and remove ourself from lookup table
self._UnhookWindowsHookEx(self._hook)
del self._HOOKS[key]
@staticmethod
@_HOOKPROC
def _handler(code, msg, lpdata):
key = threading.current_thread()
self = SystemHook._HOOKS.get(key, None)
if self:
self.on_hook(code, msg, lpdata)
# Always call the next hook
return SystemHook._CallNextHookEx(0, code, msg, lpdata)
GetCurrentThreadId = windll.kernel32.GetCurrentThreadId
SendInput = windll.user32.SendInput
class MOUSEINPUT(ctypes.Structure):
"""Contains information about a simulated mouse event.
"""
MOVE = 0x0001
LEFTDOWN = 0x0002
LEFTUP = 0x0004
RIGHTDOWN = 0x0008
RIGHTUP = 0x0010
MIDDLEDOWN = 0x0020
MIDDLEUP = 0x0040
XDOWN = 0x0080
XUP = 0x0100
WHEEL = 0x0800
HWHEEL = 0x1000
ABSOLUTE = 0x8000
XBUTTON1 = 0x0001
XBUTTON2 = 0x0002
_fields_ = [
('dx', wintypes.LONG),
('dy', wintypes.LONG),
('mouseData', wintypes.DWORD),
('dwFlags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
class KEYBDINPUT(ctypes.Structure):
"""Contains information about a simulated keyboard event.
"""
EXTENDEDKEY = 0x0001
KEYUP = 0x0002
SCANCODE = 0x0008
UNICODE = 0x0004
_fields_ = [
('wVk', wintypes.WORD),
('wScan', wintypes.WORD),
('dwFlags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
class HARDWAREINPUT(ctypes.Structure):
"""Contains information about a simulated message generated by an input
device other than a keyboard or mouse.
"""
_fields_ = [
('uMsg', wintypes.DWORD),
('wParamL', wintypes.WORD),
('wParamH', wintypes.WORD)]
class INPUT_union(ctypes.Union):
"""Represents the union of input types in :class:`INPUT`.
"""
_fields_ = [
('mi', MOUSEINPUT),
('ki', KEYBDINPUT),
('hi', HARDWAREINPUT)]
class INPUT(ctypes.Structure):
"""Used by :attr:`SendInput` to store information for synthesizing input
events such as keystrokes, mouse movement, and mouse clicks.
"""
MOUSE = 0
KEYBOARD = 1
HARDWARE = 2
_fields_ = [
('type', wintypes.DWORD),
('value', INPUT_union)]
PK )\HV]^ ^ pynput/mouse/__init__.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
import os
import sys
if os.environ.get('__PYNPUT_GENERATE_DOCUMENTATION') == 'yes':
from ._base import Controller, Listener
else:
Controller = None
Listener = None
if sys.platform == 'darwin':
if Controller is None and Listener is None:
from ._darwin import Controller, Listener
elif sys.platform == 'win32':
if Controller is None and Listener is None:
from ._win32 import Controller, Listener
else:
if Controller is None and Listener is None:
try:
from ._xorg import Controller, Listener
except:
pass
if not Controller or not Listener:
raise ImportError('this platform is not supported')
PK )\Hڊ pynput/mouse/_darwin.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
import enum
import Quartz
import threading
from AppKit import NSEvent, NSScreen
from . import _base
def _button_value(base_name, mouse_button):
"""Generates the value tuple for a :class:`Controller.Button` value.
:param str base_name: The base name for the button. This shuld be a string
like ``'kCGEventLeftMouse'``.
:param int mouse_button: The mouse button ID.
:return: a value tuple
"""
return (
tuple(
getattr(Quartz, '%sMouse%s' % (base_name, name))
for name in ('Down', 'Up', 'Dragged')),
mouse_button)
class Controller(_base.Controller):
class Button(enum.Enum):
"""The various buttons.
"""
left = _button_value('kCGEventLeft', 0)
middle = _button_value('kCGEventOther', 2)
right = _button_value('kCGEventRight', 1)
def __init__(self, *args, **kwargs):
super(Controller, self).__init__(*args, **kwargs)
self._click = None
self._drag_button = None
def _position_get(self):
pos = NSEvent.mouseLocation()
return pos.x, Quartz.CGDisplayPixelsHigh(0) - pos.y
def _position_set(self, pos):
try:
(_, _, mouse_type), mouse_button = self._drag_button
except TypeError:
mouse_type = Quartz.kCGEventMouseMoved
mouse_button = 0
Quartz.CGEventPost(
Quartz.kCGHIDEventTap,
Quartz.CGEventCreateMouseEvent(
None,
mouse_type,
pos,
mouse_button))
def _scroll(self, dx, dy):
while dx != 0 or dy != 0:
xval = 1 if dx > 0 else -1 if dx < 0 else 0
dx -= xval
yval = 1 if dy > 0 else -1 if dy < 0 else 0
dy -= yval
Quartz.CGEventPost(
Quartz.kCGHIDEventTap,
Quartz.CGEventCreateScrollWheelEvent(
None,
Quartz.kCGScrollEventUnitPixel,
2,
yval * 1,
xval * 1))
def _press(self, button):
(press, release, drag), mouse_button = button.value
event = Quartz.CGEventCreateMouseEvent(
None,
press,
self.position,
mouse_button)
# If we are performing a click, we need to set this state flag
if self._click is not None:
self._click += 1
Quartz.CGEventSetIntegerValueField(
event,
Quartz.kCGMouseEventClickState,
self._click)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
# Store the button to enable dragging
self._drag_button = button
def _release(self, button):
(press, release, drag), mouse_button = button.value
event = Quartz.CGEventCreateMouseEvent(
None,
release,
self.position,
mouse_button)
# If we are performing a click, we need to set this state flag
if self._click is not None:
Quartz.CGEventSetIntegerValueField(
event,
Quartz.kCGMouseEventClickState,
self._click)
Quartz.CGEventPost(Quartz.kCGHIDEventTap, event)
if button == self._drag_button:
self._drag_button = None
def __enter__(self):
self._click = 0
return self
def __exit__(self, type, value, traceback):
self._click = None
class Listener(_base.Listener):
#: The events that we listen to
_TAP_EVENTS = (
0
| Quartz.CGEventMaskBit(Quartz.kCGEventMouseMoved)
| Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseDown)
| Quartz.CGEventMaskBit(Quartz.kCGEventLeftMouseUp)
| Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseDown)
| Quartz.CGEventMaskBit(Quartz.kCGEventRightMouseUp)
| Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseDown)
| Quartz.CGEventMaskBit(Quartz.kCGEventOtherMouseUp)
| Quartz.CGEventMaskBit(Quartz.kCGEventScrollWheel))
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._loop = None
def _run(self):
try:
tap = Quartz.CGEventTapCreate(
Quartz.kCGSessionEventTap,
Quartz.kCGHeadInsertEventTap,
Quartz.kCGEventTapOptionDefault,
self._TAP_EVENTS,
self._handler,
None)
loop_source = Quartz.CFMachPortCreateRunLoopSource(
None, tap, 0)
self._loop = Quartz.CFRunLoopGetCurrent()
Quartz.CFRunLoopAddSource(
self._loop, loop_source, Quartz.kCFRunLoopDefaultMode)
Quartz.CGEventTapEnable(tap, True)
while True:
result = Quartz.CFRunLoopRunInMode(
Quartz.kCFRunLoopDefaultMode, 1, False)
if result != Quartz.kCFRunLoopRunTimedOut:
break
finally:
self._loop = None
def _stop(self):
# The base class sets the running flag to False; this will cause the
# loop around run loop invocations to terminate and set this event
Quartz.CFRunLoopStop(self._loop)
@_base.Listener._emitter
def _handler(self, proxy, event_type, event, refcon):
"""The callback registered with *Mac OSX* for mouse events.
This method will call the callbacks registered on initialisation.
"""
(x, y) = Quartz.CGEventGetLocation(event)
try:
# Quickly detect the most common event type
if event_type == Quartz.kCGEventMouseMoved:
self.on_move(x, y)
elif event_type == Quartz.kCGEventScrollWheel:
dx = Quartz.CGEventGetIntegerValueField(
event,
Quartz.kCGScrollWheelEventDeltaAxis2)
dy = Quartz.CGEventGetIntegerValueField(
event,
Quartz.kCGScrollWheelEventDeltaAxis1)
self.on_scroll(x, y, dx, dy)
else:
for button in Controller.Button:
(press, release, drag), mouse_button = button.value
# Press and release generate click events, and drag
# generates move events
if event_type in (press, release):
self.on_click(x, y, button, event_type == press)
elif event_type == drag:
self.on_move(x, y)
except self.StopException:
self.stop()
return event
PK )\HY
ds s pynput/mouse/_win32.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
import enum
from pynput._util.win32 import *
from . import _base
class Controller(_base.Controller):
class Button(enum.Enum):
"""The various buttons.
"""
left = (MOUSEINPUT.LEFTUP, MOUSEINPUT.LEFTDOWN)
middle = (MOUSEINPUT.MIDDLEUP, MOUSEINPUT.MIDDLEDOWN)
right = (MOUSEINPUT.RIGHTUP, MOUSEINPUT.RIGHTDOWN)
__GetCursorPos = windll.user32.GetCursorPos
__SetCursorPos = windll.user32.SetCursorPos
def _position_get(self):
point = wintypes.POINT()
if self.__GetCursorPos(ctypes.byref(point)):
return (point.x, point.y)
else:
return None
def _position_set(self, pos):
self.__SetCursorPos(*pos)
self._notify('on_move', *pos)
def _scroll(self, dx, dy):
if dy:
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mouse=MOUSEINPUT(
dwFlags=MOUSEINPUT.WHEEL,
mouseData=dy)))),
ctypes.sizeof(INPUT))
if dx:
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mouse=MOUSEINPUT(
dwFlags=MOUSEINPUT.HWHEEL,
mouseData=dy)))),
ctypes.sizeof(INPUT))
if dx or dy:
self._notify('on_scroll', dx, dy)
def _press(self, button):
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mouse=MOUSEINPUT(
dwFlags=button.value[0])))),
ctypes.sizeof(INPUT))
x, y = self.position
self._notify('on_click', x, y, button, True)
def _release(self, button):
SendInput(
1,
ctypes.byref(INPUT(
type=INPUT.MOUSE,
value=INPUT_union(
mouse=MOUSEINPUT(
dwFlags=button.value[1])))),
ctypes.sizeof(INPUT))
x, y = self.position
self._notify('on_click', x, y, button, False)
def _notify(self, action, *args):
"""Sends a notification to all currently running instances of
:class:`Listener`.
This method will ensure that listeners that raise
:class:`StopException` are stopped.
:param str action: The name of the notification.
:param args: The arguments to pass.
"""
stopped = []
for listener in Listener.listeners():
try:
getattr(listener, action)(*args)
except Listener.StopException:
stopped.append(listener)
for listener in stopped:
listener.stop()
class Listener(_base.Listener):
#: The Windows hook ID for low level mouse events
WH_MOUSE_LL = 14
HC_ACTION = 0
WM_LBUTTONDOWN = 0x0201
WM_LBUTTONUP = 0x0202
WM_MOUSEMOVE = 0x0200
WM_MOUSEWHEEL = 0x020A
WM_MOUSEHWHEEL = 0x020E
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WHEEL_DELTA = 120
#: A mapping from messages to button events
CLICK_BUTTONS = {
WM_LBUTTONDOWN: (Controller.Button.left, True),
WM_LBUTTONUP: (Controller.Button.left, False),
WM_RBUTTONDOWN: (Controller.Button.right, True),
WM_RBUTTONUP: (Controller.Button.right, False)}
#: A mapping from messages to scroll vectors
SCROLL_BUTTONS = {
WM_MOUSEWHEEL: (0, 1),
WM_MOUSEHWHEEL: (1, 0)}
#: The currently running listeners
__listeners = set()
#: The lock protecting access to the :attr:`_listeners`
__listener_lock = threading.Lock()
class MSLLHOOKSTRUCT(ctypes.Structure):
"""Contains information about a mouse event passed to a ``WH_MOUSE_LL``
hook procedure, ``MouseProc``.
"""
_fields_ = [
('pt', wintypes.POINT),
('mouseData', wintypes.DWORD),
('flags', wintypes.DWORD),
('time', wintypes.DWORD),
('dwExtraInfo', ctypes.c_void_p)]
#: A pointer to a :class:`MSLLHOOKSTRUCT`
LPMSLLHOOKSTRUCT = ctypes.POINTER(MSLLHOOKSTRUCT)
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._message_loop = MessageLoop()
def _run(self):
self.__add_listener(self)
try:
self._message_loop.start()
with SystemHook(self.WH_MOUSE_LL, self._handler):
# Just pump messages
for msg in self._message_loop:
if not self.running:
break
finally:
self.__remove_listener(self)
def _stop(self):
self._message_loop.stop()
@_base.Listener._emitter
def _handler(self, code, msg, lpdata):
"""The callback registered with *Windows* for mouse events.
This method will call the callbacks registered on initialisation.
"""
if code != self.HC_ACTION:
return
data = ctypes.cast(lpdata, self.LPMSLLHOOKSTRUCT).contents
if msg == self.WM_MOUSEMOVE:
self.on_move(data.pt.x, data.pt.y)
elif msg in self.CLICK_BUTTONS:
button, pressed = self.CLICK_BUTTONS[msg]
self.on_click(data.pt.x, data.pt.y, button, pressed)
elif msg in self.SCROLL_BUTTONS:
mx, my = self.SCROLL_BUTTONS[msg]
d = wintypes.SHORT(data.mouseData >> 16).value // self.WHEEL_DELTA
self.on_scroll(data.pt.x, data.pt.y, d * mx, d * my)
@classmethod
def __add_listener(self, listener):
"""Adds a listener to the set of running listeners.
:param Listener listener: The listener to add.
"""
with self.__listener_lock:
self.__listeners.add(listener)
@classmethod
def __remove_listener(self, listener):
"""Removes a listener from the set of running listeners.
:param Listener listener: The listener to remove.
"""
with self.__listener_lock:
self.__listeners.remove(listener)
@classmethod
def listeners(self):
"""Iterates over the set of running listeners.
This method will quit without acquiring the lock if the set is empty,
so there is potential for race conditions. This is an optimisation,
since :class:`Controller` will need to call this method for every
control event.
"""
if not self.__listeners:
return
with self.__listener_lock:
for listener in self.__listeners:
yield listener
PK )\Hz\L pynput/mouse/_xorg.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
import enum
import Xlib.display
import Xlib.ext
import Xlib.ext.xtest
import Xlib.X
import Xlib.protocol
from pynput._util.xorg import *
from . import _base
# Create a display to verify that we have an X connection
display = Xlib.display.Display()
display.close()
del display
class Controller(_base.Controller):
class Button(enum.Enum):
"""The various buttons.
"""
left = 1
middle = 2
right = 3
scroll_up = 4
scroll_down = 5
scroll_left = 6
scroll_right = 7
def __init__(self):
self._display = Xlib.display.Display()
def _position_get(self):
with display_manager(self._display) as d:
data = d.screen().root.query_pointer()._data
return (data["root_x"], data["root_y"])
def _position_set(self, pos):
x, y = pos
with display_manager(self._display) as d:
Xlib.ext.xtest.fake_input(d, Xlib.X.MotionNotify, x=x, y=y)
def _scroll(self, dx, dy):
if dy:
self.click(
button=self.Button.scroll_up if dy > 0
else self.Button.scroll_down,
count=abs(dy))
if dx:
self.click(
button=self.Button.scroll_right if dx > 0
else self.Button.scroll_left,
count=abs(dx))
def _press(self, button):
with display_manager(self._display) as d:
Xlib.ext.xtest.fake_input(d, Xlib.X.ButtonPress, button.value)
def _release(self, button):
with display_manager(self._display) as d:
Xlib.ext.xtest.fake_input(d, Xlib.X.ButtonRelease, button.value)
class Listener(_base.Listener):
#: A mapping from button values to scroll directions
SCROLL_BUTTONS = {
Controller.Button.scroll_up.value: (0, 1),
Controller.Button.scroll_down.value: (0, -1),
Controller.Button.scroll_right.value: (1, 0),
Controller.Button.scroll_left.value: (-1, 0)}
def __init__(self, *args, **kwargs):
super(Listener, self).__init__(*args, **kwargs)
self._display_stop = Xlib.display.Display()
self._display_record = Xlib.display.Display()
with display_manager(self._display_record) as d:
self._context = d.record_create_context(
0,
[Xlib.ext.record.AllClients],
[{
'core_requests': (0, 0),
'core_replies': (0, 0),
'ext_requests': (0, 0, 0, 0),
'ext_replies': (0, 0, 0, 0),
'delivered_events': (0, 0),
'device_events': (
Xlib.X.ButtonPressMask,
Xlib.X.ButtonReleaseMask),
'errors': (0, 0),
'client_started': False,
'client_died': False}])
def _run(self):
with display_manager(self._display_record) as d:
d.record_enable_context(
self._context, self._handler)
d.record_free_context(self._context)
def _stop(self):
self._display_stop.sync()
with display_manager(self._display_stop) as d:
d.record_disable_context(self._context)
@_base.Listener._emitter
def _handler(self, events):
"""The callback registered with *X* for mouse events.
This method will parse the response and call the callbacks registered
on initialisation.
"""
# We use this instance for parsing the binary data
e = Xlib.protocol.rq.EventField(None)
data = events.data
while len(data):
event, data = e.parse_binary_value(
data, self._display_record.display, None, None)
x = event.root_x
y = event.root_y
if event.type == Xlib.X.ButtonPress:
# Scroll events are sent as button presses with the scroll
# button codes
scroll = self.SCROLL_BUTTONS.get(event.detail, None)
if scroll:
self.on_scroll(x, y, *scroll)
else:
self.on_click(x, y, Controller.Button(event.detail), True)
elif event.type == Xlib.X.ButtonRelease:
# Send an event only if this was not a scroll event
if event.detail not in self.SCROLL_BUTTONS:
self.on_click(x, y, Controller.Button(event.detail), False)
else:
self.on_move(x, y)
PK )\HO8f pynput/mouse/_base.py# coding=utf-8
# pynput
# Copyright (C) 2015 Moses Palmér
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see .
import enum
import functools
import threading
class Controller(object):
"""A controller for sending virtual mouse events to the system.
"""
class Button(enum.Enum):
"""The various buttons.
The actual values for these items differ between platforms. Some
platforms may have additional buttons, but these are guaranteed to be
present everywhere.
"""
#: The left button
left = 1
#: The middle button
middle = 2
#: The right button
right = 3
@property
def position(self):
"""The current position of the mouse pointer.
This is the tuple ``(x, y)``.
"""
return self._position_get()
@position.setter
def position(self, pos):
self._position_set(pos)
def scroll(self, dx, dy):
"""Sends scroll events.
:param int dx: The horizontal scroll. The units of scrolling is
undefined.
:param int dy: The vertical scroll. The units of scrolling is
undefined.
"""
self._scroll(dx, dy)
def press(self, button):
"""Emits a button press event at the current position.
:param Controller.Button button: The button to press.
"""
self._press(button)
def release(self, button):
"""Emits a button release event at the current position.
:param Controller.Button button: The button to release.
"""
self._release(button)
def move(self, dx, dy):
"""Moves the mouse pointer a number of pixels from its current
position.
:param int x: The horizontal offset.
:param int dy: The vertical offset.
"""
self.position = tuple(sum(i) for i in zip(self.position, (dx, dy)))
def click(self, button, count=1):
"""Emits a button click event at the current position.
The default implementation sends a series a press and release events.
:param Controller.Button button: The button to click.
:param int count: The number of clicks to send.
"""
with self as controller:
for _ in range(count):
controller.press(button)
controller.release(button)
def __enter__(self):
"""Begins a series of clicks.
In the default :meth:`click` implementation, the return value of this
method is used for the calls to :meth:`press` and :meth:`release`
instead of ``self``.
The default implementation is a no-op.
"""
return self
def __exit__(self, type, value, traceback):
"""Ends a series of clicks.
"""
pass
def _position_get(self):
"""The implementation of the getter for :attr:`position`.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _position_set(self, pos):
"""The implementation of the setter for :attr:`position`.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _scroll(self, dx, dy):
"""The implementation of the :meth:`scroll` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _press(self, button):
"""The implementation of the :meth:`press` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _release(self, button):
"""The implementation of the :meth:`release` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
class Listener(threading.Thread):
"""A listener for mouse events.
Instances of this class can be used as context managers. This is equivalent
to the following code::
listener.start()
try:
with_statements()
finally:
listener.stop()
:param callable on_move: The callback to call when mouse move events occur.
It will be called with the arguments ``(x, y)``, which is the new
pointer position. If this callback raises :class:`StopException` or
returns ``False``, the listener is stopped.
:param callable on_click: The callback to call when a mouse button is
clicked.
It will be called with the arguments ``(x, y, button, pressed)``,
where ``(x, y)`` is the new pointer position, ``button`` is one of the
:class:`Controller.Button` values and ``pressed`` is whether the button
was pressed.
If this callback raises :class:`StopException` or returns ``False``,
the listener is stopped.
:param callable on_scroll: The callback to call when mouse scroll
events occur.
It will be called with the arguments ``(x, y, dx, dy)``, where
``(x, y)`` is the new pointer position, and ``(dx, dy)`` is the scroll
vector.
If this callback raises :class:`StopException` or returns ``False``,
the listener is stopped.
"""
class StopException(Exception):
"""If an event listener callback raises this exception, the current
listener is stopped.
Its first argument must be set to the :class:`Listener` to stop.
"""
pass
def __init__(self, on_move=None, on_click=None, on_scroll=None):
super(Listener, self).__init__()
def wrapper(f):
def inner(*args):
if f(*args) is False:
raise self.StopException(self)
return inner
self._running = False
self._thread = threading.current_thread()
self.on_move = wrapper(on_move or (lambda *a: None))
self.on_click = wrapper(on_click or (lambda *a: None))
self.on_scroll = wrapper(on_scroll or (lambda *a: None))
@property
def running(self):
"""Whether the listener is currently running.
"""
return self._running
def stop(self):
"""Stops listening for mouse events.
When this method returns, the listening thread will have stopped.
"""
if self._running:
self._running = False
self._stop()
if threading.current_thread() != self._thread:
self.join()
def __enter__(self):
self.start()
return self
def __exit__(self, type, value, traceback):
self.stop()
def run(self):
"""The thread runner method.
"""
self._running = True
self._thread = threading.current_thread()
self._run()
@classmethod
def _emitter(self, f):
"""A decorator to mark a method as the one emitting the callbacks.
This decorator will wrap the method and catch :class:`StopException`.
If this exception is caught, the listener will be stopped.
"""
@functools.wraps(f)
def inner(*args, **kwargs):
try:
f(*args, **kwargs)
except self.StopException as e:
e.args[0].stop()
return inner
def _run(self):
"""The implementation of the :meth:`start` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
def _stop(self):
"""The implementation of the :meth:`stop` method.
This is a platform dependent implementation.
"""
raise NotImplementedError()
PK IHiG G % pystray-0.3.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 - 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 IHmܵ # pystray-0.3.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\u00e9r", "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"}PK IH'8. . pystray-0.3.dist-info/pbr.json{"is_release": true, "git_version": "b7085a7"}PK IH m # pystray-0.3.dist-info/top_level.txtpystray
PK McH2 pystray-0.3.dist-info/zip-safe
PK IH}\ \ pystray-0.3.dist-info/WHEELWheel-Version: 1.0
Generator: bdist_wheel (0.26.0)
Root-Is-Purelib: true
Tag: py3-none-any
PK IHG
G
pystray-0.3.dist-info/METADATAMetadata-Version: 2.0
Name: pystray
Version: 0.3
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 - 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 IH^ ^ pystray-0.3.dist-info/RECORDpynput/__init__.py,sha256=S-QqApr9WyaH_v4ogz4lXYmcR6KU8NRq9L4EPCbY-_k,714
pynput/_info.py,sha256=Vc0pCY8TWh_0sjZpyFLO8m7edrRXxIY9QptCMSXF3aE,67
pynput/_util/__init__.py,sha256=mRlq3NQSSG2IaeLmK0UE1ILK2afNIkrfwfFrK_DTvuc,693
pynput/_util/win32.py,sha256=Q-SgfathmnGeIn34_yoeXL-RCE9MYfl-RSfaDFfN_AE,6498
pynput/_util/xorg.py,sha256=xLj5Hef35iewr4dbylhNyaPHHyZcViUwIzb4Y4SdrKw,1608
pynput/mouse/__init__.py,sha256=mS5dEaFe9bPSOoT84DqKsQfgaL6lnn9wFegJSY9f-_Y,1374
pynput/mouse/_base.py,sha256=WEeW84bPveKRq1uQWNRzhEHGnEcgwhNwj3IiazdSa00,8200
pynput/mouse/_darwin.py,sha256=VlRAgOBeujhCk-XFNs5Xo6Q5na1SGYrp2-YzmocnkNU,7405
pynput/mouse/_win32.py,sha256=E3iyIhxv7TpyUJ-vaW887iLlIXuUgLddc5xBPn8cPDY,7539
pynput/mouse/_xorg.py,sha256=5zhhSrlaMithxKlubNCAuMeBs0pw2575zDMawJv0Q0s,5298
pystray/__init__.py,sha256=Q0lc5c50n5FbOoSd4pvyhHxcA9kK3eajskFI1u5qx98,1185
pystray/_base.py,sha256=GMGfq3qu1KlWOFS0WYAXXI1-U7GO2JjXQIqqYZQMcqQ,5759
pystray/_darwin.py,sha256=BFS6vJUzjWcaprqqqnR4KAUqxIyKJl-nudrfEhQ1Uhs,4050
pystray/_info.py,sha256=SBFpzmi2a-CZHoDdUKWj8eu3taSX4T1FTnvc2WxDtQI,67
pystray/_win32.py,sha256=yqcmQn3BRELmu25joH4JhHTRLhJIBuRQWSEjE_BtyMg,10975
pystray/_xorg.py,sha256=XHWR3X2bibSKqwD4ABkqpJ4vQYLkxf5ibkeB96-B4BA,14229
pystray-0.3.dist-info/DESCRIPTION.rst,sha256=lXtnXFPHlgPVhpmpngryjDLFo68Vk0JsiZfFRIuXCFE,1863
pystray-0.3.dist-info/METADATA,sha256=sI4hq3W-t6O88iq8rxmV4K-1gz-NHuqpokFA5Aj1090,2631
pystray-0.3.dist-info/RECORD,,
pystray-0.3.dist-info/WHEEL,sha256=zX7PHtH_7K-lEzyK75et0UBa3Bj8egCBMXe1M4gc6SU,92
pystray-0.3.dist-info/metadata.json,sha256=Sm-DjARQqF9RGyo4hbpbUiffzUQ11TdVF7T_DPkMKhU,949
pystray-0.3.dist-info/pbr.json,sha256=a5cllYr171C3fPWer7T6v80Bl3ZeiJHLCqV3GNFEcKY,46
pystray-0.3.dist-info/top_level.txt,sha256=6xvZ5fFN6P3IotgMJLAHeWonXXZzDdf47BhMLsXEHlg,8
pystray-0.3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
PK k{Hz pystray/__init__.pyPK -lxHtH pystray/_darwin.pyPK HHV* * pystray/_win32.pyPK HH47 7 ? pystray/_xorg.pyPK HH5KL w pystray/_base.pyPK IH[ywC C R pystray/_info.pyPK )\H^A) Î pynput/__init__.pyPK )\H#1vC C pynput/_info.pyPK )\HY - pynput/_util/__init__.pyPK )\HIH H pynput/_util/xorg.pyPK )\Hb b pynput/_util/win32.pyPK )\HV]^ ^ ' pynput/mouse/__init__.pyPK )\Hڊ pynput/mouse/_darwin.pyPK )\HY
ds s pynput/mouse/_win32.pyPK )\Hz\L pynput/mouse/_xorg.pyPK )\HO8f i
pynput/mouse/_base.pyPK IHiG G % * pystray-0.3.dist-info/DESCRIPTION.rstPK IHmܵ # .2 pystray-0.3.dist-info/metadata.jsonPK IH'8. . $6 pystray-0.3.dist-info/pbr.jsonPK IH m # 6 pystray-0.3.dist-info/top_level.txtPK McH2 6 pystray-0.3.dist-info/zip-safePK IH}\ \ 7 pystray-0.3.dist-info/WHEELPK IHG
G
7 pystray-0.3.dist-info/METADATAPK IH^ ^ ,B pystray-0.3.dist-info/RECORDPK I