diff --git a/win10toast/__init__.py b/win10toast/__init__.py index 8ed1bc5..0637af6 100644 --- a/win10toast/__init__.py +++ b/win10toast/__init__.py @@ -4,7 +4,7 @@ __all__ = ['ToastNotifier'] -# ############################################################################# +# ################################## # ########## Libraries ############# # ################################## # standard library @@ -23,7 +23,6 @@ from win32con import IMAGE_ICON from win32con import LR_DEFAULTSIZE from win32con import LR_LOADFROMFILE -from win32con import WM_DESTROY from win32con import WM_USER from win32con import WS_OVERLAPPED from win32con import WS_SYSMENU @@ -44,7 +43,11 @@ from win32gui import UpdateWindow from win32gui import WNDCLASS -# ############################################################################ +PARAM_DESTROY = 1028 +PARAM_CLICKED = 1029 + + +# ################################## # ########### Classes ############## # ################################## @@ -55,30 +58,44 @@ class ToastNotifier(object): from: https://github.com/jithurjacob/Windows-10-Toast-Notifications """ + @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 __init__(self): """Initialize.""" self._thread = None def _show_toast(self, title, msg, - icon_path, duration): + 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 + :duration: delay in seconds before notification self-destruction, None for no-self-destruction """ - message_map = {WM_DESTROY: self.on_destroy, } # 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 = message_map # could also specify a wndproc. + self.wc.lpfnWndProc = self._decorator(self.wnd_proc, callback_on_click) # could instead specify simple mapping try: self.classAtom = RegisterClass(self.wc) - except: - pass #not sure of this + except (TypeError, Exception): + pass # not sure of this style = WS_OVERLAPPED | WS_SYSMENU self.hwnd = CreateWindow(self.classAtom, "Taskbar", style, 0, 0, CW_USEDEFAULT, @@ -90,7 +107,7 @@ def _show_toast(self, title, msg, if icon_path is not None: icon_path = path.realpath(icon_path) else: - icon_path = resource_filename(Requirement.parse("win10toast"), "win10toast/data/python.ico") + icon_path = resource_filename(Requirement.parse("win10toast"), "win10toast/data/python.ico") icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE try: hicon = LoadImage(self.hinst, icon_path, @@ -109,49 +126,56 @@ def _show_toast(self, title, msg, hicon, "Balloon Tooltip", msg, 200, title)) # take a rest then destroy - sleep(duration) - DestroyWindow(self.hwnd) - UnregisterClass(self.wc.lpszClassName, None) + if duration: + 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): + 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 + :duration: delay in seconds before notification self-destruction, None for no-self-destruction """ if not threaded: - self._show_toast(title, msg, icon_path, duration) + 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)) + 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(): + if self._thread and self._thread.is_alive(): # We have an active notification, let is finish we don't spam them return True return False - def on_destroy(self, hwnd, msg, wparam, lparam): - """Clean after notification ended. - - :hwnd: - :msg: - :wparam: - :lparam: - """ + 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, _, _, _, _): + """Clean after notification ended.""" nid = (self.hwnd, 0) Shell_NotifyIcon(NIM_DELETE, nid) PostQuitMessage(0) return None - diff --git a/win10toast/__main__.py b/win10toast/__main__.py index a962b7a..31b59c2 100644 --- a/win10toast/__main__.py +++ b/win10toast/__main__.py @@ -1,8 +1,9 @@ -from win10toast import ToastNotifier -import time +import time -# ############################################################################# -# ###### Stand alone program ######## +from win10toast import ToastNotifier + +# ################################### +# ###### Standalone program ######### # ################################### if __name__ == "__main__": # Example @@ -17,6 +18,7 @@ icon_path=None, duration=5, threaded=True - ) + ) # Wait for threaded notification to finish - while toaster.notification_active(): time.sleep(0.1) + while toaster.notification_active(): + time.sleep(0.1)