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 (TypeError, Exception): pass # not sure of this 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 = resource_filename(Requirement.parse("win10toast"), "win10toast/data/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: DestroyWindow(self.hwnd) sleep(duration) UnregisterClass(self.wc.lpszClassName, None) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None)
def _show_toast(self, title, msg, duration): message_map = { WM_DESTROY: self.on_destroy, } self.wc = WNDCLASS() self.hinst = self.wc.hInstance = GetModuleHandle(None) self.wc.lpszClassName = str("PythonTaskbar") self.wc.lpfnWndProc = message_map try: self.classAtom = RegisterClass(self.wc) except: pass 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) hicon = LoadIcon(0, IDI_APPLICATION) 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)) sleep(duration) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) return None
def cleanup_windows_notification(): try: from win32gui import DestroyWindow, UnregisterClass except ImportError: return else: if tool_window: DestroyWindow(tool_window) UnregisterClass("FastFlix", None)
def quit(self): """ Ends notification event loop. :returns: Nothing. """ DestroyWindow(self._window) UnregisterClass(self._window_class.lpszClassName, None) PostQuitMessage(0) self.logger.info("NotificationCenter is going to quit.")
def __init__(self, message, title): message_map = {win32con.WM_DESTROY: self.OnDestroy} # Register the Window class. wc = WNDCLASS() hinst = wc.hInstance = GetModuleHandle(None) wc.lpszClassName = "PythonTaskbar" wc.lpfnWndProc = message_map # could also specify a wndproc. classAtom = RegisterClass(wc) # Create the Window. style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU self.hwnd = CreateWindow( classAtom, "Taskbar", style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None, ) UpdateWindow(self.hwnd) iconPathName = os.path.abspath( os.path.join(sys.path[0], "balloontip.ico")) icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE try: hicon = LoadImage(hinst, iconPathName, win32con.IMAGE_ICON, 0, 0, icon_flags) except: hicon = LoadIcon(0, win32con.IDI_APPLICATION) flags = NIF_ICON | NIF_MESSAGE | NIF_TIP nid = (self.hwnd, 0, flags, win32con.WM_USER + 20, hicon, "tooltip") Shell_NotifyIcon(NIM_ADD, nid) Shell_NotifyIcon( NIM_MODIFY, ( self.hwnd, 0, NIF_INFO, win32con.WM_USER + 20, hicon, "Balloon tooltip", message, 200, title, ), ) time.sleep(10) DestroyWindow(self.hwnd) UnregisterClass(classAtom, hinst)
def _show_toast(self, title, msg, icon_path, duration): """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 """ 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("QMKGearTaskbar") # must be a string self.wc.lpfnWndProc = message_map # could also specify a wndproc. try: self.classAtom = RegisterClass(self.wc) except: pass #not sure of this 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 = resource_filename(Requirement.parse("QuietWin10Toast"), "QuietWin10Toast/data/myriad.ico") icon_path = path.realpath("QuietWin10Toast/data/myriad.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, NIIF_NOSOUND)) # take a rest then destroy sleep(duration) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) return None
def run(self): self.BuildWindow() self.started = True while self.started: PumpWaitingMessages() if self.do_update: SendMessage(self.progbar, PBM_SETPOS, int(self.position % self.max_val), 0) percentage = int(round(100.0 * self.position / self.max_val)) SendMessage(self.dialog, WM_SETTEXT, 0, self.title + ' (%d%%)' % percentage) # SendMessage(self.progbar, PBM_STEPIT, 0, 0) self.do_update = False sleep(0.1) PostQuitMessage(0) DestroyWindow(self.dialog)
def _show_toast(self, title, msg, icon_path, duration): """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 """ # Register the window class. try: self.classAtom = RegisterClass(self.wc) except: pass # not sure of this 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 = resource_filename(Requirement.parse("win10toast"), "win10toast/data/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)) # take a rest then destroy sleep(duration) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) return None
def balloon_tip(self, title, msg): style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU hwnd = CreateWindow(self.classAtom, "Taskbar", style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, self.hinst, None) UpdateWindow(hwnd) hicon = LoadIcon(0, win32con.IDI_APPLICATION) nid = (hwnd, 0, NIF_ICON | NIF_MESSAGE | NIF_TIP, win32con.WM_USER + 20, hicon, 'Tooltip') Shell_NotifyIcon(NIM_ADD, nid) nid = (hwnd, 0, NIF_INFO, win32con.WM_USER + 20, hicon, 'Balloon Tooltip', msg, 200, title, NIIF_INFO) Shell_NotifyIcon(NIM_MODIFY, nid) DestroyWindow(hwnd)
def destroy(self): """Destroys active toast""" delay = self.toast_data["delay"] while delay != None and delay > 0: sleep(0.1) delay -= 0.1 try: DestroyWindow(self.toast_data["hwnd"]) UnregisterClass(self.toast_data["wnd_class"].lpszClassName, self.toast_data["hinst"]) Shell_NotifyIcon( NIM_DELETE, (self.toast_data["hwnd"], 0) ) # Sometimes the try icon sticks around until you click it - this should stop that except WinTypesException: pass PostQuitMessage() self.active = False
def _show_toast(self, title, msg, icon_path, duration): """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 """ 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.style = self.wc.cbWndExtra = 0 # self.wc.lpfnWndProc = message_map # could also specify a wndproc. try: self.classAtom = RegisterClass(self.wc) except: pass # not sure of this style = WS_OVERLAPPED | WS_SYSMENU self.hwnd = CreateWindowEx(0, "PythonTaskbar", "555", style, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, self.hinst, None) UpdateWindow(self.hwnd) hicon = LoadIcon(0, IDI_APPLICATION) # Taskbar icon flags = NIF_ICON | NIF_TIP nid = (self.hwnd, 0, flags, WM_USER + 20, hicon, "66666") Shell_NotifyIcon(NIM_ADD, nid) Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO, WM_USER + 20, hicon, "289347293874", msg, 0, title)) # # take a rest then destroy sleep(duration) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) return None
def balloon_tip(self, title="Notification", msg="Here comes the message", duration=5, icon_path="plugins\Win10Notifications\python.ico"): """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 """ 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 icon_path = path.abspath(icon_path) 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 ({0}): {1}".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)) # take a rest then destroy sleep(duration) DestroyWindow(self.hwnd) return None
def _show_toast(self, title, msg, icon_path, duration, cbFunc=None, cbArgs=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 """ self.user_cbFunc = cbFunc self.user_cbArgs = cbArgs # Register the window class. self.wc = WNDCLASS() self.hinst = self.wc.hInstance = GetModuleHandle(None) self.wc.lpszClassName = str("PythonTaskbar" + os.urandom(24).hex()) # must be a string self.wc.lpfnWndProc = self._decorator( self.wnd_proc, self.user_cbFunc) # could also specify a wndproc. try: self.classAtom = RegisterClass(self.wc) except: pass #not sure of this 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 hicon = None if icon_path is not None: icon_path = path.realpath(icon_path) icon_flags = LR_LOADFROMFILE 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 sleep(duration) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) return None
def close_window(self, win): DestroyWindow(win.hwnd)
def _show_toast( self, title: str, msg: str = "No msg", icon_path: Path = None, duration: float = None, sound_path=None, callback_on_click: callable = None, tooltip: Optional[str] = None, ) -> None: """Notification settings. :param title: notification title :param msg: notification message :param icon_path: path to the .ico file to custom notification :param duration: delay in seconds before notification self-destruction, None for no-self-destruction :param sound_path: path to the .wav file to custom notification """ self.duration = duration def callback(): """ """ self.duration = 0 if callback_on_click is not None: callback_on_click() if tooltip is None: tooltip = PROJECT_NAME # Register the window class. self.window_class = WNDCLASS() self.instance_handle = self.window_class.hInstance = GetModuleHandle( None) self.window_class.lpszClassName = f"{PROJECT_NAME}-{title}" # must be a string self.window_class.lpfnWndProc = self._decorator( self.wnd_proc, callback) # could instead specify simple mapping try: self.classAtom = RegisterClass(self.window_class) except Exception as e: logging.error("Some trouble with classAtom (%s)", e) style = WS_OVERLAPPED | WS_SYSMENU button_style = WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON # TODO: Unused for now self.window_handle = CreateWindow( self.classAtom, "Taskbar", style, 0, 0, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, self.instance_handle, None, ) UpdateWindow(self.window_handle) # icon new_name = "" if icon_path is not None: icon_path = path.realpath(icon_path) converted = False if Image is not None and icon_path.split(".")[-1] != ".ico": img = Image.open(icon_path) new_name = f'{str(icon_path.split(".")[:-1])}.ico' img.save(new_name) icon_path = new_name converted = True else: icon_path = resource_filename( Requirement.parse(PROJECT_NAME), str(Path(PROJECT_NAME) / "data" / "python.ico"), ) converted = False try: hicon = LoadImage( self.instance_handle, icon_path, IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE, ) if Image and path.exists(new_name and converted): remove(new_name) except Exception as e: logging.error("Some trouble with the icon (%s): %s", icon_path, e) hicon = LoadIcon(0, IDI_APPLICATION) # Set the duration """ buff = create_unicode_buffer(10) windll.user32.SystemParametersInfoW(SPI_GETMESSAGEDURATION, 0, buff, 0) try: oldlength = int(buff.value.encode("unicode_escape").decode().replace("\\", "0"), 16) except ValueError: oldlength = 5 # Default notification length duration_output = windll.user32.SystemParametersInfoW(SPI_SETMESSAGEDURATION, 0, self.duration, SPIF_SENDCHANGE) windll.user32.SystemParametersInfoW(SPI_GETMESSAGEDURATION, 0, buff, 0) duration_error = False try: int(buff.value.encode("unicode_escape").decode().replace("\\", "0"), 16) except ValueError: duration_error = True if duration_output == 0 or self.duration > 255 or duration_error: windll.user32.SystemParametersInfoW(SPI_SETMESSAGEDURATION, 0, oldlength, SPIF_SENDCHANGE) self.active = False raise RuntimeError(f"Some trouble with the duration ({self.duration})" ": Invalid duration length") """ title += " " * randint(0, 63 - len(title)) msg += " " * randint(0, 128 - len(msg)) Shell_NotifyIcon( NIM_ADD, ( self.window_handle, 0, NIF_ICON | NIF_MESSAGE | NIF_TIP, WM_USER + 20, hicon, tooltip, ), ) Shell_NotifyIcon( NIM_MODIFY, ( self.window_handle, 0, NIF_INFO, WM_USER + 20, hicon, tooltip, msg, 200, title, 0 if sound_path is None else NIIF_NOSOUND, ), ) if sound_path is not None: # play the custom sound sound_path = path.realpath(sound_path) if not path.exists(sound_path): logging.error( f"Some trouble with the sound file ({sound_path}): [Errno 2] No such file" ) try: PlaySound(sound_path, SND_FILENAME) except Exception as e: logging.error( f"Some trouble with the sound file ({sound_path}): {e}") PumpMessages() """ # Put the notification duration back to normal SystemParametersInfoW(SPI_SETMESSAGEDURATION, 0, oldlength, SPIF_SENDCHANGE) """ if duration is not None: # take a rest then destroy # sleep(duration) while self.duration > 0: sleep(0.1) self.duration -= 0.1 DestroyWindow(self.window_handle) UnregisterClass(self.window_class.lpszClassName, self.instance_handle) try: # Sometimes the try icon sticks around until you click it - this should stop that Shell_NotifyIcon(NIM_DELETE, (self.window_handle, 0)) except WinTypesException: pass self.active = False
def _destroy_window(self): DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) print('destroy')
def __call__(self, summary, message="", timeout=2000, **kwargs): tip = kwargs.get("tip", "Balloon tooltip") # Register the Window class. wc = WNDCLASS() hinst = wc.hInstance = GetModuleHandle(None) wc.lpszClassName = "PythonTaskbar" wc.lpfnWndProc = self.message_map # could also specify a wndproc. classAtom = RegisterClass(wc) # Create the Window. style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU self.hwnd = CreateWindow( classAtom, "Taskbar", style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None, ) UpdateWindow(self.hwnd) # Icons managment iconPathName = os.path.abspath( os.path.join(sys.path[0], "balloontip.ico")) icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE try: hicon = LoadImage(hinst, iconPathName, win32con.IMAGE_ICON, 0, 0, icon_flags) except Exception: hicon = LoadIcon(0, win32con.IDI_APPLICATION) flags = NIF_ICON | NIF_MESSAGE | NIF_TIP nid = (self.hwnd, 0, flags, win32con.WM_USER + 20, hicon, "tooltip") # Notify Shell_NotifyIcon(NIM_ADD, nid) Shell_NotifyIcon( NIM_MODIFY, ( self.hwnd, 0, NIF_INFO, win32con.WM_USER + 20, hicon, tip, message, timeout, summary, ), ) # Destroy DestroyWindow(self.hwnd) classAtom = UnregisterClass(classAtom, hinst) return True
def cleanup_windows_notification(): from win32gui import DestroyWindow, UnregisterClass if tool_window: DestroyWindow(tool_window) UnregisterClass("FastFlix", None)
def _show_toast(self, title, msg, icon_path, duration): """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 """ 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( f"PythonTaskbar{title}") # must be a string self.wc.lpfnWndProc = message_map # could also specify a wndproc. global handler global active_toast_list try: active_toast_list except NameError: active_toast_list = [] try: self.classAtom = RegisterClass(self.wc) handler = self.classAtom except: self.classAtom = handler style = WS_OVERLAPPED | WS_SYSMENU name = msg[:msg.find(' triggered')] if name not in active_toast_list: active_toast_list.append(name) print(active_toast_list) 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 = resource_filename(Requirement.parse("win10toast"), "win10toast/data/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, "Alert!") Shell_NotifyIcon(NIM_ADD, nid) Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO, WM_USER + 20, hicon, "Balloon Tooltip", msg, 200, title)) # take a rest then destroy sleep(duration) DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None) active_toast_list.remove(name) return None
def __del__(self): Shell_NotifyIcon(NIM_DELETE, (self.handler, 0, NIF_ICON | NIF_MESSAGE | NIF_TIP, WM_USER + 20, self.icon_handler)) DestroyWindow(self.handler)
def end_toast(self): DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, None)
def _show_toast(self, title, msg, icon_path, duration, callback_on_click): '''Notification settings. :param title: notification title :param msg: notification message :param icon_path: path to the .ico file to custom notification :para mduration: 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) # must be a string self.wc.lpszClassName = str(f"PythonTaskbar{title}") # could instead specify simple mapping self.wc.lpfnWndProc = self._decorator(self.wnd_proc, callback_on_click) try: self.classAtom = RegisterClass(self.wc) except Exception as e: #logging.error('Some trouble with classAtom (%s)', e) self.classAtom = self.wc 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) converted = False if Image is not None and icon_path.split('.')[-1] != '.ico': img = Image.open(icon_path) new_name = icon_path.split('.')[:-1] + '.ico' img.save(new_name) icon_path = new_name converted = True else: icon_path = resource_filename(Requirement.parse('win10toast'), 'win10toast/data/python.ico') converted = False icon_flags = LR_LOADFROMFILE | LR_DEFAULTSIZE try: hicon = LoadImage(self.hinst, icon_path, IMAGE_ICON, 0, 0, icon_flags) if Image and path.exists(new_name and converted): remove(new_name) except Exception as e: logging.error('Some trouble with the icon (%s): %s', 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, msg, icon_path, delay, sound_path, tooltip, duration, callback_on_click, kill_without_click): """Notification settings. :title: notification title :msg: notification message :icon_path: path to the .ico file to custom notification :delay: delay in seconds before notification self-destruction, None for no-self-destruction :sound_path: path to the .wav file to custom notification :duration: how long the notification stays on the screen in seconds :callback_on_click: function to run on click :kill_without_click: Kill the tray icon after the notification goes away, even if it wasn't clicked """ self.delay = delay self.destroy_window = kill_without_click # Register the window class. self.wc = WNDCLASS() self.hinst = self.wc.hInstance = GetModuleHandle(None) self.wc.lpszClassName = str( f"PythonTaskbar - {uuid4().hex}") # 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: raise type(e)(f"Some trouble with classAtom:\n{e}") from None style = WS_OVERLAPPED | WS_SYSMENU self.hwnd = CreateWindow(self.classAtom, "Python 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 = resource_filename(Requirement.parse("win10toast"), "win10toast/data/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: raise type(e)( f"Some trouble with the icon ({icon_path}):\n{e}") from None hicon = LoadIcon(0, IDI_APPLICATION) # set the duration buff = create_unicode_buffer(10) SystemParametersInfoW(SPI_GETMESSAGEDURATION, 0, buff, 0) try: oldlength = int( buff.value.encode('unicode_escape').decode().replace( "\\", "0"), 16) except ValueError: oldlength = 5 # default notification length durationOutput = SystemParametersInfoW(SPI_SETMESSAGEDURATION, 0, duration, SPIF_SENDCHANGE) SystemParametersInfoW(SPI_GETMESSAGEDURATION, 0, buff, 0) durationError = False try: int( buff.value.encode('unicode_escape').decode().replace( "\\", "0"), 16) except ValueError: durationError = True if durationOutput == 0 or duration > 255 or durationError: SystemParametersInfoW(SPI_SETMESSAGEDURATION, 0, oldlength, SPIF_SENDCHANGE) raise RuntimeError( f"Some trouble with the duration ({duration}): Invalid duration length" ) # 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, tooltip, msg, 0, title, 0 if sound_path == None else NIIF_NOSOUND)) # play the custom sound if sound_path is not None: sound_path = path.realpath(sound_path) if not path.exists(sound_path): raise IOError( "Some trouble with the sound file ({sound_path}): [Errno 2] No such file" ) try: PlaySound(sound_path, SND_FILENAME) except Exception as e: raise type( e)(f"Some trouble with the sound file ({sound_path}): {e}" ) from None PumpMessages() # put the notification duration back to normal SystemParametersInfoW(SPI_SETMESSAGEDURATION, 0, oldlength, SPIF_SENDCHANGE) # take a rest then destroy if self.delay is not None and self.destroy_window: while self.delay > 0: sleep(0.1) self.delay -= 0.1 DestroyWindow(self.hwnd) UnregisterClass(self.wc.lpszClassName, self.hinst) try: # sometimes the tray icon sticks around until you click it - this should stop it self.remove_window(self.hwnd) except WinTypesException: pass return