-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathToastNotifier.py
177 lines (158 loc) · 6.29 KB
/
ToastNotifier.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
__all__ = ['ToastNotifier']
# #############################################################################
# ########## Libraries #############
# ##################################
# standard library
import logging
import threading
from os import path
from pkg_resources import Requirement
from pkg_resources import resource_filename
from time import sleep
# 3rd party modules
from win32api import GetModuleHandle
from win32api import PostQuitMessage
from win32con import CW_USEDEFAULT
from win32con import IDI_APPLICATION
from win32con import IMAGE_ICON
from win32con import LR_DEFAULTSIZE
from win32con import LR_LOADFROMFILE
from win32con import WM_USER
from win32con import WS_OVERLAPPED
from win32con import WS_SYSMENU
from win32gui import CreateWindow
from win32gui import DestroyWindow
from win32gui import LoadIcon
from win32gui import LoadImage
from win32gui import NIF_ICON
from win32gui import NIF_INFO
from win32gui import NIF_MESSAGE
from win32gui import NIF_TIP
from win32gui import NIM_ADD
from win32gui import NIM_DELETE
from win32gui import NIM_MODIFY
from win32gui import RegisterClass
from win32gui import UnregisterClass
from win32gui import Shell_NotifyIcon
from win32gui import UpdateWindow
from win32gui import WNDCLASS
from win32gui import PumpMessages
# Magic constants
PARAM_DESTROY = 1028
PARAM_CLICKED = 1029
# ############################################################################
# ########### Classes ##############
# ##################################
class ToastNotifier(object):
"""Create a Windows 10 toast notification.
from: https://github.com/jithurjacob/Windows-10-Toast-Notifications
"""
def __init__(self):
"""Initialize."""
self._thread = None
@staticmethod
def _decorator(func, callback=None):
"""
:param func: callable to decorate
:param callback: callable to run on mouse click within notification window
:return: callable
"""
def inner(*args, **kwargs):
kwargs.update({'callback': callback})
func(*args, **kwargs)
return inner
def _show_toast(self, title, msg,
icon_path, duration,
callback_on_click):
"""Notification settings.
:title: notification title
:msg: notification message
:icon_path: path to the .ico file to custom notification
:duration: delay in seconds before notification self-destruction, None for no-self-destruction
"""
# Register the window class.
self.wc = WNDCLASS()
self.hinst = self.wc.hInstance = GetModuleHandle(None)
self.wc.lpszClassName = str("PythonTaskbar") # must be a string
self.wc.lpfnWndProc = self._decorator(self.wnd_proc, callback_on_click) # could instead specify simple mapping
try:
self.classAtom = RegisterClass(self.wc)
except Exception as e:
logging.error("Some trouble with classAtom ({})".format(e))
style = WS_OVERLAPPED | WS_SYSMENU
self.hwnd = CreateWindow(self.classAtom, "Taskbar", style,
0, 0, CW_USEDEFAULT,
CW_USEDEFAULT,
0, 0, self.hinst, None)
UpdateWindow(self.hwnd)
# icon
if icon_path is not None:
icon_path = path.realpath(icon_path)
else:
icon_path = path.realpath("python.ico")
icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE
try:
hicon = LoadImage(self.hinst, icon_path,
IMAGE_ICON, 0, 0, icon_flags)
except Exception as e:
logging.error("Some trouble with the icon ({}): {}"
.format(icon_path, e))
hicon = LoadIcon(0, IDI_APPLICATION)
# Taskbar icon
flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
nid = (self.hwnd, 0, flags, WM_USER + 20, hicon, "Tooltip")
Shell_NotifyIcon(NIM_ADD, nid)
Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO,
WM_USER + 20,
hicon, "Balloon Tooltip", msg, 200,
title))
PumpMessages()
# take a rest then destroy
if duration is not None:
sleep(duration)
DestroyWindow(self.hwnd)
UnregisterClass(self.wc.lpszClassName, None)
return None
def show_toast(self, title="Notification", msg="Here comes the message",
icon_path=None, duration=5, threaded=False, callback_on_click=None):
"""Notification settings.
:title: notification title
:msg: notification message
:icon_path: path to the .ico file to custom notification
:duration: delay in seconds before notification self-destruction, None for no-self-destruction
"""
if not threaded:
self._show_toast(title, msg, icon_path, duration, callback_on_click)
else:
if self.notification_active():
# We have an active notification, let is finish so we don't spam them
return False
self._thread = threading.Thread(target=self._show_toast, args=(
title, msg, icon_path, duration, callback_on_click
))
self._thread.start()
return True
def notification_active(self):
"""See if we have an active notification showing"""
if self._thread != None and self._thread.is_alive():
# We have an active notification, let is finish we don't spam them
return True
return False
def wnd_proc(self, hwnd, msg, wparam, lparam, **kwargs):
"""Messages handler method"""
if lparam == PARAM_CLICKED:
# callback goes here
if kwargs.get('callback'):
kwargs.pop('callback')()
self.on_destroy(hwnd, msg, wparam, lparam)
elif lparam == PARAM_DESTROY:
self.on_destroy(hwnd, msg, wparam, lparam)
def on_destroy(self, hwnd, msg, wparam, lparam):
"""Clean after notification ended."""
nid = (self.hwnd, 0)
Shell_NotifyIcon(NIM_DELETE, nid)
PostQuitMessage(0)
return None