Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Duration-less notifications and on click callback support #111

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 51 additions & 27 deletions win10toast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

__all__ = ['ToastNotifier']

# #############################################################################
# ##################################
# ########## Libraries #############
# ##################################
# standard library
Expand All @@ -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
Expand All @@ -44,7 +43,11 @@
from win32gui import UpdateWindow
from win32gui import WNDCLASS

# ############################################################################
PARAM_DESTROY = 1028
PARAM_CLICKED = 1029


# ##################################
# ########### Classes ##############
# ##################################

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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

14 changes: 8 additions & 6 deletions win10toast/__main__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)