From 1451a61dfa28c9863bc4aba91185357853ee0bc7 Mon Sep 17 00:00:00 2001 From: Arterialist Date: Sun, 7 Oct 2018 18:12:04 +0300 Subject: [PATCH 1/2] added infinite toast duration --- win10toast/__init__.py | 28 +++++++++++++++------------- win10toast/__main__.py | 14 ++++++++------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/win10toast/__init__.py b/win10toast/__init__.py index 8ed1bc5..50d6d22 100644 --- a/win10toast/__init__.py +++ b/win10toast/__init__.py @@ -4,7 +4,7 @@ __all__ = ['ToastNotifier'] -# ############################################################################# +# ################################## # ########## Libraries ############# # ################################## # standard library @@ -44,7 +44,8 @@ from win32gui import UpdateWindow from win32gui import WNDCLASS -# ############################################################################ + +# ################################## # ########### Classes ############## # ################################## @@ -66,7 +67,7 @@ def _show_toast(self, title, msg, :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, } @@ -78,7 +79,7 @@ def _show_toast(self, title, msg, try: self.classAtom = RegisterClass(self.wc) except: - pass #not sure of this + pass # not sure of this style = WS_OVERLAPPED | WS_SYSMENU self.hwnd = CreateWindow(self.classAtom, "Taskbar", style, 0, 0, CW_USEDEFAULT, @@ -90,7 +91,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,19 +110,21 @@ 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): """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) @@ -136,12 +139,12 @@ def show_toast(self, title="Notification", msg="Here comes the message", 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): + def on_destroy(self, _, _, _, _): """Clean after notification ended. :hwnd: @@ -154,4 +157,3 @@ def on_destroy(self, hwnd, msg, wparam, lparam): 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) From c95c34561149182464dfc0dacc4e0f15cc8034fb Mon Sep 17 00:00:00 2001 From: Arterialist Date: Sun, 7 Oct 2018 18:16:48 +0300 Subject: [PATCH 2/2] added on_click callback --- win10toast/__init__.py | 52 ++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/win10toast/__init__.py b/win10toast/__init__.py index 50d6d22..0637af6 100644 --- a/win10toast/__init__.py +++ b/win10toast/__init__.py @@ -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,6 +43,9 @@ from win32gui import UpdateWindow from win32gui import WNDCLASS +PARAM_DESTROY = 1028 +PARAM_CLICKED = 1029 + # ################################## # ########### Classes ############## @@ -56,12 +58,27 @@ 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 @@ -69,16 +86,15 @@ def _show_toast(self, title, msg, :icon_path: path to the .ico file to custom notification :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: + except (TypeError, Exception): pass # not sure of this style = WS_OVERLAPPED | WS_SYSMENU self.hwnd = CreateWindow(self.classAtom, "Taskbar", style, @@ -118,7 +134,7 @@ def _show_toast(self, title, msg, 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 @@ -127,13 +143,15 @@ def show_toast(self, title="Notification", msg="Here comes the message", :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 @@ -144,14 +162,18 @@ def notification_active(self): return True return False - def on_destroy(self, _, _, _, _): - """Clean after notification ended. + 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) - :hwnd: - :msg: - :wparam: - :lparam: - """ + def on_destroy(self, _, _, _, _): + """Clean after notification ended.""" nid = (self.hwnd, 0) Shell_NotifyIcon(NIM_DELETE, nid) PostQuitMessage(0)