Skip to content

Commit

Permalink
Update to use ctypes instead of win32gui so we could use unicode.
Browse files Browse the repository at this point in the history
  • Loading branch information
matham committed Mar 9, 2014
1 parent e6742ff commit 1d7fb1f
Showing 1 changed file with 100 additions and 53 deletions.
153 changes: 100 additions & 53 deletions plyer/platforms/win/libs/balloontip.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# -- coding: utf-8 --
# Original from https://gist.github.com/wontoncc/1808234
# Modified from https://gist.github.com/boppreh/4000505

import os
import sys

import time
import win_api_defs
import ctypes
from ctypes import byref

import win32gui
from win32api import GetModuleHandle

WS_OVERLAPPED = 0x00000000
WS_SYSMENU = 0x00080000
Expand All @@ -19,54 +17,103 @@

IMAGE_ICON = 1

IDI_APPLICATION = 32512

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)
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
_notify_data = None

def __init__(self, title, message, app_name, app_icon='', timeout=10):
''' app_icon if given is a icon file.
Timeout is deprecated in windows since its system managed.
'''

wnd_class_ex = win_api_defs.get_WNDCLASSEXW()
wnd_class_ex.lpszClassName = ('PlyerTaskbar' +
str(id(self))).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(byref(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
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.
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._hicon = hicon
self.notify(title, message, app_name)
if timeout:
time.sleep(timeout)

def __del__(self):
if self._notify_data is not None:
win_api_defs.Shell_NotifyIconW(NIM_DELETE, self._notify_data)
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
as long as :meth:`remove_notify` is called afterwards.
'''
if self._notify_data is not None:
win_api_defs.Shell_NotifyIconW(NIM_DELETE, self._notify_data)
# 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
icon_flag = NIIF_USER | NIIF_LARGE_ICON
notify_data = win_api_defs.get_NOTIFYICONDATAW(0, self._hwnd,
id(self), flags, 0, None, app_name.decode('utf8')[:127], 0, 0,
message.decode('utf8')[:255], NOTIFYICON_VERSION_4,
title.decode('utf8')[:63], icon_flag, win_api_defs.GUID(), hicon)

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,
byref(notify_data)):
raise Exception('Shell_NotifyIconW failed.')


def balloon_tip(**kwargs):
Expand Down

0 comments on commit 1d7fb1f

Please sign in to comment.