Skip to content

Commit

Permalink
Merge pull request #18 from matham/ctypes-notify-window
Browse files Browse the repository at this point in the history
Changes notify to use ctypes instead of win32gui so we could use unicode.
  • Loading branch information
brousch committed Mar 14, 2014
2 parents 53865e7 + c56c9f0 commit bd2bc34
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 52 deletions.
174 changes: 122 additions & 52 deletions plyer/platforms/win/libs/balloontip.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# -- coding: utf-8 --
# Original from https://gist.github.com/wontoncc/1808234
# Modified from https://gist.github.com/boppreh/4000505

import os
import sys
__all__ = ('WindowsBalloonTip', 'balloon_tip')


import time
import ctypes
import win_api_defs
from threading import RLock

import win32gui
from win32api import GetModuleHandle

WS_OVERLAPPED = 0x00000000
WS_SYSMENU = 0x00080000
Expand All @@ -16,57 +16,127 @@

LR_LOADFROMFILE = 16
LR_DEFAULTSIZE = 0x0040

IDI_APPLICATION = 32512
IMAGE_ICON = 1

IDI_APPLICATION = 32512
NOTIFYICON_VERSION_4 = 4
NIM_ADD = 0
NIM_MODIFY = 1
NIM_DELETE = 2
NIM_SETVERSION = 4
NIF_MESSAGE = 1
NIF_ICON = 2
NIF_TIP = 4
NIF_INFO = 0x10
NIIF_USER = 4
NIIF_LARGE_ICON = 0x20


class WindowsBalloonTip(object):

_class_atom = 0
_wnd_class_ex = None
_hwnd = None
_hicon = None
_balloon_icon = None
_notify_data = None
_count = 0
_lock = RLock()

WM_USER = 1024

class WindowsBalloonTip:

def __init__(self, title, message, app_name, app_icon, timeout=10):
message_map = {WM_DESTROY: self.OnDestroy, }
# Register the Window class.
wc = win32gui.WNDCLASS()
hinst = wc.hInstance = GetModuleHandle(None)
wc.lpszClassName = "PythonTaskbar"
wc.lpfnWndProc = message_map # could also specify a wndproc.
class_atom = win32gui.RegisterClass(wc)
# Create the Window.
style = WS_OVERLAPPED | WS_SYSMENU
self.hwnd = win32gui.CreateWindow(class_atom, "Taskbar", style,
0, 0, CW_USEDEFAULT,
CW_USEDEFAULT, 0, 0,
hinst, None)
win32gui.UpdateWindow(self.hwnd)
@staticmethod
def _get_unique_id():
WindowsBalloonTip._lock.acquire()
val = WindowsBalloonTip._count
WindowsBalloonTip._count += 1
WindowsBalloonTip._lock.release()
return val

def __init__(self, title, message, app_name, app_icon='', timeout=10):
''' app_icon if given is a icon file.
'''

wnd_class_ex = win_api_defs.get_WNDCLASSEXW()
wnd_class_ex.lpszClassName = ('PlyerTaskbar' +
str(WindowsBalloonTip._get_unique_id())).decode('utf8')
# keep ref to it as long as window is alive
wnd_class_ex.lpfnWndProc =\
win_api_defs.WindowProc(win_api_defs.DefWindowProcW)
wnd_class_ex.hInstance = win_api_defs.GetModuleHandleW(None)
if wnd_class_ex.hInstance == None:
raise Exception('Could not get windows module instance.')
class_atom = win_api_defs.RegisterClassExW(wnd_class_ex)
if class_atom == 0:
raise Exception('Could not register the PlyerTaskbar class.')
self._class_atom = class_atom
self._wnd_class_ex = wnd_class_ex

# create window
self._hwnd = win_api_defs.CreateWindowExW(0, class_atom,
'', WS_OVERLAPPED, 0, 0, CW_USEDEFAULT,
CW_USEDEFAULT, None, None, wnd_class_ex.hInstance, None)
if self._hwnd == None:
raise Exception('Could not get create window.')
win_api_defs.UpdateWindow(self._hwnd)

# load icon
if app_icon:
icon_path_name = app_icon
icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
hicon = win_api_defs.LoadImageW(None, app_icon.decode('utf8'),
IMAGE_ICON, 0, 0, icon_flags)
if hicon is None:
raise Exception('Could not load icon {}'.
format(icon_path_name).decode('utf8'))
self._balloon_icon = self._hicon = hicon
else:
icon_path_name = os.path.abspath(os.path.join(sys.path[0],
"balloontip.ico"))
icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
try:
hicon = win32gui.LoadImage(hinst, icon_path_name,
IMAGE_ICON, 0, 0, icon_flags)
except:
hicon = win32gui.LoadIcon(0, IDI_APPLICATION)
flags = win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP
nid = (self.hwnd, 0, flags, WM_USER+20, hicon, "tooltip")
win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, nid)
win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY,
(self.hwnd, 0, win32gui.NIF_INFO,
WM_USER+20, hicon,
"Balloon tooltip", message, 200, title))
# self.show_balloon(title, msg)
time.sleep(timeout)
win32gui.DestroyWindow(self.hwnd)
win32gui.UnregisterClass(class_atom, hinst)

def OnDestroy(self, hwnd, msg, wparam, lparam):
nid = (self.hwnd, 0)
win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid)
win32gui.PostQuitMessage(0) # Terminate the app.
self._hicon = win_api_defs.LoadIconW(None,
ctypes.cast(IDI_APPLICATION, win_api_defs.LPCWSTR))
self.notify(title, message, app_name)
if timeout:
time.sleep(timeout)

def __del__(self):
self.remove_notify()
if self._hicon is not None:
win_api_defs.DestroyIcon(self._hicon)
if self._wnd_class_ex is not None:
win_api_defs.UnregisterClassW(self._class_atom,
self._wnd_class_ex.hInstance)
if self._hwnd is not None:
win_api_defs.DestroyWindow(self._hwnd)

def notify(self, title, message, app_name):
''' Displays a balloon in the systray. Can be called multiple times
with different parameter values.
'''
self.remove_notify()
# add icon and messages to window
hicon = self._hicon
flags = NIF_TIP | NIF_INFO
icon_flag = 0
if hicon is not None:
flags |= NIF_ICON
# if icon is default app's one, don't display it in message
if self._balloon_icon is not None:
icon_flag = NIIF_USER | NIIF_LARGE_ICON
notify_data = win_api_defs.get_NOTIFYICONDATAW(0, self._hwnd,
id(self), flags, 0, hicon, app_name.decode('utf8')[:127], 0, 0,
message.decode('utf8')[:255], NOTIFYICON_VERSION_4,
title.decode('utf8')[:63], icon_flag, win_api_defs.GUID(),
self._balloon_icon)

self._notify_data = notify_data
if not win_api_defs.Shell_NotifyIconW(NIM_ADD, notify_data):
raise Exception('Shell_NotifyIconW failed.')
if not win_api_defs.Shell_NotifyIconW(NIM_SETVERSION,
notify_data):
raise Exception('Shell_NotifyIconW failed.')

def remove_notify(self):
'''Removes the notify balloon, if displayed.
'''
if self._notify_data is not None:
win_api_defs.Shell_NotifyIconW(NIM_DELETE, self._notify_data)
self._notify_data = None


def balloon_tip(**kwargs):
Expand Down
178 changes: 178 additions & 0 deletions plyer/platforms/win/libs/win_api_defs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
''' Defines ctypes windows api.
'''

__all__ = ('GUID', 'get_DLLVERSIONINFO', 'MAKEDLLVERULL',
'get_NOTIFYICONDATAW', 'CreateWindowExW', 'WindowProc',
'DefWindowProcW', 'get_WNDCLASSEXW', 'GetModuleHandleW',
'RegisterClassExW', 'UpdateWindow', 'LoadImageW',
'Shell_NotifyIconW', 'DestroyIcon', 'UnregisterClassW',
'DestroyWindow', 'LoadIconW')

import ctypes
from ctypes import Structure, windll, sizeof, byref, POINTER, memset,\
WINFUNCTYPE
from ctypes.wintypes import DWORD, HICON, HWND, UINT, WCHAR, WORD, BYTE,\
HRESULT, LPCWSTR, LPWSTR, INT, LPVOID, HINSTANCE, HMENU, LPARAM, WPARAM,\
HBRUSH, HMODULE, ATOM, BOOL, HANDLE, LONG, HHOOK
LRESULT = LPARAM
HCURSOR = HICON



class GUID(Structure):
_fields_ = [
('Data1', DWORD),
('Data2', WORD),
('Data3', WORD),
('Data4', BYTE * 8)
]


class DLLVERSIONINFO(Structure):
_fields_ = [
('cbSize', DWORD),
('dwMajorVersion', DWORD),
('dwMinorVersion', DWORD),
('dwBuildNumber', DWORD),
('dwPlatformID', DWORD),
]

def get_DLLVERSIONINFO(*largs):
version_info = DLLVERSIONINFO(*largs)
version_info.cbSize = sizeof(DLLVERSIONINFO)
return version_info

def MAKEDLLVERULL(major, minor, build, sp):
return (major << 48) | (minor << 32) | (build << 16) | sp


NOTIFYICONDATAW_fields = [
("cbSize", DWORD),
("hWnd", HWND),
("uID", UINT),
("uFlags", UINT),
("uCallbackMessage", UINT),
("hIcon", HICON),
("szTip", WCHAR * 128),
("dwState", DWORD),
("dwStateMask", DWORD),
("szInfo", WCHAR * 256),
("uVersion", UINT),
("szInfoTitle", WCHAR * 64),
("dwInfoFlags", DWORD),
("guidItem", GUID),
("hBalloonIcon", HICON),
]

class NOTIFYICONDATAW(Structure):
_fields_ = NOTIFYICONDATAW_fields[:]


class NOTIFYICONDATAW_V3(Structure):
_fields_ = NOTIFYICONDATAW_fields[:-1]


class NOTIFYICONDATAW_V2(Structure):
_fields_ = NOTIFYICONDATAW_fields[:-2]


class NOTIFYICONDATAW_V1(Structure):
_fields_ = NOTIFYICONDATAW_fields[:6]

NOTIFYICONDATA_V3_SIZE = sizeof(NOTIFYICONDATAW_V3)
NOTIFYICONDATA_V2_SIZE = sizeof(NOTIFYICONDATAW_V2)
NOTIFYICONDATA_V1_SIZE = sizeof(NOTIFYICONDATAW_V1)

def get_NOTIFYICONDATAW(*largs):
notify_data = NOTIFYICONDATAW(*largs)

# get shell32 version to find correct NOTIFYICONDATAW size
DllGetVersion = windll.Shell32.DllGetVersion
DllGetVersion.argtypes = [POINTER(DLLVERSIONINFO)]
DllGetVersion.restype = HRESULT

version = get_DLLVERSIONINFO()
if DllGetVersion(version):
raise Exception('Cannot get Windows version numbers.')
v = MAKEDLLVERULL(version.dwMajorVersion, version.dwMinorVersion,
version.dwBuildNumber, version.dwPlatformID)

# from the version info find the NOTIFYICONDATA size
if v >= MAKEDLLVERULL(6, 0, 6, 0):
notify_data.cbSize = sizeof(NOTIFYICONDATAW)
elif v >= MAKEDLLVERULL(6, 0, 0, 0):
notify_data.cbSize = NOTIFYICONDATA_V3_SIZE
elif v >= MAKEDLLVERULL(5, 0, 0, 0):
notify_data.cbSize = NOTIFYICONDATA_V2_SIZE
else:
notify_data.cbSize = NOTIFYICONDATA_V1_SIZE
return notify_data


CreateWindowExW = windll.User32.CreateWindowExW
CreateWindowExW.argtypes = [DWORD, ATOM, LPCWSTR, DWORD, INT, INT, INT, INT,
HWND, HMENU, HINSTANCE, LPVOID]
CreateWindowExW.restype = HWND

GetModuleHandleW = windll.Kernel32.GetModuleHandleW
GetModuleHandleW.argtypes = [LPCWSTR]
GetModuleHandleW.restype = HMODULE

WindowProc = WINFUNCTYPE(LRESULT, HWND, UINT, WPARAM, LPARAM)
DefWindowProcW = windll.User32.DefWindowProcW
DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM]
DefWindowProcW.restype = LRESULT


class WNDCLASSEXW(Structure):
_fields_ = [
('cbSize', UINT),
('style', UINT),
('lpfnWndProc', WindowProc),
('cbClsExtra', INT),
('cbWndExtra', INT),
('hInstance', HINSTANCE),
('hIcon', HICON),
('hCursor', HCURSOR),
('hbrBackground', HBRUSH),
('lpszMenuName', LPCWSTR),
('lpszClassName', LPCWSTR),
('hIconSm', HICON),
]

def get_WNDCLASSEXW(*largs):
wnd_class = WNDCLASSEXW(*largs)
wnd_class.cbSize = sizeof(WNDCLASSEXW)
return wnd_class

RegisterClassExW = windll.User32.RegisterClassExW
RegisterClassExW.argtypes = [POINTER(WNDCLASSEXW)]
RegisterClassExW.restype = ATOM

UpdateWindow = windll.User32.UpdateWindow
UpdateWindow.argtypes = [HWND]
UpdateWindow.restype = BOOL

LoadImageW = windll.User32.LoadImageW
LoadImageW.argtypes = [HINSTANCE, LPCWSTR, UINT, INT, INT, UINT]
LoadImageW.restype = HANDLE

Shell_NotifyIconW = windll.Shell32.Shell_NotifyIconW
Shell_NotifyIconW.argtypes = [DWORD, POINTER(NOTIFYICONDATAW)]
Shell_NotifyIconW.restype = BOOL

DestroyIcon = windll.User32.DestroyIcon
DestroyIcon.argtypes = [HICON]
DestroyIcon.restype = BOOL

UnregisterClassW = windll.User32.UnregisterClassW
UnregisterClassW.argtypes = [ATOM, HINSTANCE]
UnregisterClassW.restype = BOOL

DestroyWindow = windll.User32.DestroyWindow
DestroyWindow.argtypes = [HWND]
DestroyWindow.restype = BOOL

LoadIconW = windll.User32.LoadIconW
LoadIconW.argtypes = [HINSTANCE, LPCWSTR]
LoadIconW.restype = HICON

0 comments on commit bd2bc34

Please sign in to comment.