From d20a13157cdbac84ac808c142b8266a4ed161671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A2=A8=E5=A4=A7=E5=AE=9D?= Date: Mon, 16 May 2022 14:41:26 +0800 Subject: [PATCH] 0.1.0 add events add docs --- README.md | 159 +++--------------- docs/Makefile | 20 +++ docs/make.bat | 35 ++++ docs/source/Examples/events.md | 94 +++++++++++ docs/source/Examples/index.rst | 9 + docs/source/Examples/minimal.md | 43 +++++ docs/source/Examples/pic_and_button.md | 66 ++++++++ .../pics}/example_for_control_freak.gif | Bin .../source/Examples/pics/minimal.gif | Bin .../source/Examples/pics/simple.gif | Bin docs/source/conf.py | 56 ++++++ docs/source/index.rst | 21 +++ example/custom_handle.py | 42 +++++ example/minimal.py | 27 +-- example/simple.py | 57 ++++--- src/winsdk_toast/__init__.py | 2 +- src/winsdk_toast/event.py | 57 +++++++ src/winsdk_toast/notifier.py | 60 ++++++- src/winsdk_toast/toast.py | 2 + 19 files changed, 578 insertions(+), 172 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/Examples/events.md create mode 100644 docs/source/Examples/index.rst create mode 100644 docs/source/Examples/minimal.md create mode 100644 docs/source/Examples/pic_and_button.md rename {doc/pic => docs/source/Examples/pics}/example_for_control_freak.gif (100%) rename doc/pic/minimal_example.gif => docs/source/Examples/pics/minimal.gif (100%) rename doc/pic/simple_example.gif => docs/source/Examples/pics/simple.gif (100%) create mode 100644 docs/source/conf.py create mode 100644 docs/source/index.rst create mode 100644 example/custom_handle.py create mode 100644 src/winsdk_toast/event.py diff --git a/README.md b/README.md index 524f831..610dcb8 100644 --- a/README.md +++ b/README.md @@ -8,156 +8,39 @@ or suddenly realize the script has stopped for a long while. It'll be reassuring that the script can stop with a friendly gesture. - -## Usage +## Features + +winsdk_toast can pop up Windows Toast Notifications that contain + +- [x] Text +- [x] Image +- [x] Input + - [x] Text + - [x] Selection +- [x] Audio + - [x] Buit-in + - [ ] Custom +- [x] Progress + - [x] Still + - [ ] Live +- [x] Group +- [x] Event + +## Minimal Example ```python -from os.path import abspath from winsdk_toast import Notifier, Toast -path_pic = abspath('./example/resource/python.ico') notifier = Notifier('程序名 applicationId') - -# %% minimal example toast = Toast() toast.add_text('第一行 1st line') notifier.show(toast) -# %% which is equivalent to -xml = """ - - - - 第一行 1st line - - - -""" -toast = Toast(xml) -notifier.show(toast) - - -# %% simple example -toast = Toast() -toast.add_text('第一行 1st line', hint_align='center', hint_style='caption') -toast.add_text('第二行 2nd line') -toast.add_text('第三行 3rd line', placement='attribution') -toast.add_image(path_pic, placement='appLogoOverride') -toast.add_action('关闭 Close') -toast.set_audio(silent='true') -notifier.show(toast) -# %% which is equivalent to -xml = f""" - - - - 第一行 1st line - 第二行 2nd line - 第三行 3rd line - - - - - - -""" -toast = Toast(xml) -notifier.show(toast) - -# %% example for control freak -toast = Toast() -element_toast = toast.set_toast( - launch='blah', duration='long', displayTimeStamp='2022-04-01T12:00:00Z', scenario='default', - useButtonStyle='false', activationType='background' -) -element_visual = toast.set_visual( - version='1', lang='zh-CN', baseUri='ms-appx:///', branding='none', addImageQuery='false' -) -element_binding = toast.set_binding( - template='ToastGeneric', fallback='2ndtemplate', lang='zh-CN', addImageQuery='false', - baseUri='ms-appx:///', branding='none' -) -element_text = toast.add_text( - text='第一行 1st line for control freak', id_='1', lang='zh-CN', placement=None, - hint_maxLines='1', hint_style='title', hint_align='center', hint_wrap='false', - element_parent=element_binding -) -element_group = toast.add_group() -element_subgroup_left = toast.add_subgroup(element_parent=element_group) -element_text = toast.add_text( - text='第二行 2nd line for control freak', id_='2', lang='zh-CN', placement=None, - hint_maxLines='1', hint_style='captionSubtle ', hint_align='left', hint_wrap='false', - element_parent=element_subgroup_left -) -element_subgroup_right = toast.add_subgroup(element_parent=element_group) -element_text = toast.add_text( - text='第三行 3rd line for control freak', id_='3', lang='zh-CN', placement='attribution', - hint_maxLines='1', hint_style='captionSubtle', hint_align='left', hint_wrap='false', - element_parent=element_subgroup_right -) -toast.add_image( - path_pic, id_=None, alt='', addImageQuery='false', - placement='appLogoOverride', hint_crop='circle' -) -toast.set_actions() -toast.add_action( - '关闭 Close', arguments='dismiss', activationType='system', placement=None, - imageUri=None, hint_inputId=None, hint_buttonStyle=None, hint_toolTip='tip close' -) -notifier.show(toast) -# %% which is equivalent to -xml = f""" - - - - 第一行 1st line for control freak - - - 第二行 2nd line for control freak - - - 第三行 3rd line for control freak - - - - - - - - - -""" -toast = Toast(xml) -notifier.show(toast) ``` -The corresponding effects are like: - -![minimal_example.gif](doc/pic/minimal_example.gif) - -![simple_example.gif](doc/pic/simple_example.gif) - -![example_for_control_freak.gif](doc/pic/example_for_control_freak.gif) - - -## Todo - -- Events and callbacks. -- Documentation. -- Costume audio. According to [Microsoft Docs], this might be tricky. -- ... - -[Microsoft Docs]: https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/custom-audio-on-toasts -## else +![minimal_example.gif](docs/source/Examples/pics/minimal.gif) -When I almost make it work, I found another package [windows_toast] -which has the same dependency and more features. -Luckily our 'styles' are quite different, and I'm on vacation, -so I decide to finish it any way. +More examples and documents are on the way. -If you need more features now, please use [windows_toast] instead, -maybe give this one a try later. [winsdk]: https://pypi.org/project/winsdk -[windows_toast]: https://github.com/DatGuy1/Windows-Toasts diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/Examples/events.md b/docs/source/Examples/events.md new file mode 100644 index 0000000..ccf041b --- /dev/null +++ b/docs/source/Examples/events.md @@ -0,0 +1,94 @@ +# Events + +A Toast Notification ends up with an event among `Activated`, `Dissmissed` and `Failed`. + +By default, `Notifier` handles `Activated` by printing the argument of the Action(button) and the inputs of Inputs, handes `Dissmissed` by printing the reason of `Dissmissed`, handles `Failed` by raising error_code. + +```python +from winsdk_toast import Notifier, Toast + + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +toast = Toast() +toast.add_text('第一行 1st line', hint_align='center', hint_style='caption') +toast.add_input(type_='text', id_='input_name') +toast.add_action('关闭 Close') +toast.set_audio(silent='true') # Mute + +# Step3. Show the Toast +notifier.show(toast) +``` + +If click the `X` at the top-right, `Dissmissed` event happens and prints + +``` +Dismissed reason: UserCanceled +``` + +If wait till it disappears, `Dissmissed` event happens and prints + +``` +Dismissed reason: TimeOut +``` + +If click the `关闭 Close` button, `Activated` event happens and prints + +``` +Activated with + argument: dismiss + inputs: {'input_name': ''} +``` + +If type `test input` in the input box and click on `关闭 Close` button, `Activated` event happens and prints + +``` +Activated with + argument: dismiss + inputs: {'input_name': 'test input'} +``` + +I haven't figured out how the `activationType` and `arguments` of `action` work, so just leave them to dismiss the Toast Notification. + +## Custom `handle` + +Just define the functions that handle the `EventArgsActivated`, `EventArgsDismissed`, `EventArgsFailed` respectively, and pass them to `notifier.show`. + +```python +from winsdk_toast import Notifier, Toast +from winsdk_toast.event import EventArgsActivated, EventArgsDismissed, EventArgsFailed + + +def handle_activated(event_args_activated: EventArgsActivated): + print('activated') + print('inputs:', event_args_activated.inputs) + print('argument:', event_args_activated.argument) + + +def handle_dismissed(event_args_dismissed: EventArgsDismissed): + print('dismissed') + print('reason:', event_args_dismissed.reason) + + +def handle_failed(event_args_failed: EventArgsFailed): + print('failed') + print('error_code:', event_args_failed.error_code) + + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +toast = Toast() +toast.add_text('第一行 1st line', hint_align='center', hint_style='caption') +toast.add_input(type_='text', id_='input_name') +toast.add_action('关闭 Close') +toast.set_audio(silent='true') # Mute + +# Step3. Show the Toast +notifier.show( + toast, handle_activated=handle_activated, handle_dismissed=handle_dismissed, handle_failed=handle_failed +) +``` \ No newline at end of file diff --git a/docs/source/Examples/index.rst b/docs/source/Examples/index.rst new file mode 100644 index 0000000..7c01504 --- /dev/null +++ b/docs/source/Examples/index.rst @@ -0,0 +1,9 @@ +Examples +======================================== + +.. toctree:: + :maxdepth: 2 + + minimal.md + pic_and_button.md + events.md diff --git a/docs/source/Examples/minimal.md b/docs/source/Examples/minimal.md new file mode 100644 index 0000000..41b90d4 --- /dev/null +++ b/docs/source/Examples/minimal.md @@ -0,0 +1,43 @@ +# Minimal + +```python +from winsdk_toast import Notifier, Toast + + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +toast = Toast() +toast.add_text('第一行 1st line') + +# Step3. Show the Toast +notifier.show(toast) +``` + +![minimal.gif](pics/minimal.gif) + +Which can also be achieved by + +```python +from winsdk_toast import Notifier, Toast + + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +xml = """ + + + + 第一行 1st line + + + +""" +toast = Toast(xml) + +# Step3. Show the Toast +notifier.show(toast) +``` diff --git a/docs/source/Examples/pic_and_button.md b/docs/source/Examples/pic_and_button.md new file mode 100644 index 0000000..0b3d7a5 --- /dev/null +++ b/docs/source/Examples/pic_and_button.md @@ -0,0 +1,66 @@ +# pic and button + +```python +from os.path import abspath + +from winsdk_toast import Notifier, Toast + + +# absolute path of the picture to be shown +path_pic = abspath('./resource/python.ico') + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +toast = Toast() +toast.add_text('第一行 1st line', hint_align='center', hint_style='caption') +toast.add_text('第二行 2nd line') +toast.add_text('第三行 3rd line', placement='attribution') +toast.add_image(path_pic, placement='appLogoOverride') +toast.add_action('关闭 Close') +toast.set_audio(silent='true') # Mute + +# Step3. Show the Toast +notifier.show(toast) +``` + +![simple.gif](pics/simple.gif) + +Which can also be achieved by + +```python +from os.path import abspath + +from winsdk_toast import Notifier, Toast + + +# absolute path of the picture to be shown +path_pic = abspath('./resource/python.ico') + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +xml = f""" + + + + 第一行 1st line + 第二行 2nd line + 第三行 3rd line + + + + + + +""" +toast = Toast(xml) + +# Step3. Show the Toast +notifier.show(toast) +``` + + diff --git a/doc/pic/example_for_control_freak.gif b/docs/source/Examples/pics/example_for_control_freak.gif similarity index 100% rename from doc/pic/example_for_control_freak.gif rename to docs/source/Examples/pics/example_for_control_freak.gif diff --git a/doc/pic/minimal_example.gif b/docs/source/Examples/pics/minimal.gif similarity index 100% rename from doc/pic/minimal_example.gif rename to docs/source/Examples/pics/minimal.gif diff --git a/doc/pic/simple_example.gif b/docs/source/Examples/pics/simple.gif similarity index 100% rename from doc/pic/simple_example.gif rename to docs/source/Examples/pics/simple.gif diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..04d622e --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,56 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'winsdk_toast' +copyright = '2022, modabao' +author = 'modabao' + +# The full version, including alpha/beta/rc tags +release = '0.1.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'myst_parser' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' # 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..463ab2f --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,21 @@ +.. winsdk_toast documentation master file, created by + sphinx-quickstart on Sat May 14 17:43:29 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to winsdk_toast's documentation! +======================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + Examples/index + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/example/custom_handle.py b/example/custom_handle.py new file mode 100644 index 0000000..b725403 --- /dev/null +++ b/example/custom_handle.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +simple example + +@Author: modabao +@Time: 2022/5/3 10:33 +""" + +from winsdk_toast import Notifier, Toast +from winsdk_toast.event import EventArgsActivated, EventArgsDismissed, EventArgsFailed + + +def handle_activated(event_args_activated: EventArgsActivated): + print('activated') + print('inputs:', event_args_activated.inputs) + print('argument:', event_args_activated.argument) + + +def handle_dismissed(event_args_dismissed: EventArgsDismissed): + print('dismissed') + print('reason:', event_args_dismissed.reason) + + +def handle_failed(event_args_failed: EventArgsFailed): + print('failed') + print('error_code:', event_args_failed.error_code) + + +# Step1. Create Notifier with applicationId +notifier = Notifier('程序名 applicationId') + +# Step2. Create Toast which contains the message to be shown +toast = Toast() +toast.add_text('第一行 1st line', hint_align='center', hint_style='caption') +toast.add_input(type_='text', id_='input_name') +toast.add_action('关闭 Close') +toast.set_audio(silent='true') # Mute + +# Step3. Show the Toast +notifier.show( + toast, handle_activated=handle_activated, handle_dismissed=handle_dismissed, handle_failed=handle_failed +) diff --git a/example/minimal.py b/example/minimal.py index da593ce..b2ca7e4 100644 --- a/example/minimal.py +++ b/example/minimal.py @@ -9,22 +9,25 @@ from winsdk_toast import Notifier, Toast +# Step1. Create Notifier with applicationId notifier = Notifier('程序名 applicationId') -# %% minimal example +# Step2. Create Toast which contains the message to be shown toast = Toast() toast.add_text('第一行 1st line') + +# Step3. Show the Toast notifier.show(toast) # %% which is equivalent to -xml = """ - - - - 第一行 1st line - - - -""" -toast = Toast(xml) -notifier.show(toast) +# xml = """ +# +# +# +# 第一行 1st line +# +# +# +# """ +# toast = Toast(xml) +# notifier.show(toast) diff --git a/example/simple.py b/example/simple.py index a997feb..313fe76 100644 --- a/example/simple.py +++ b/example/simple.py @@ -10,35 +10,54 @@ from winsdk_toast import Notifier, Toast + +# absolute path of the picture to be shown path_pic = abspath('./resource/python.ico') +# Step1. Create Notifier with applicationId notifier = Notifier('程序名 applicationId') -# %% simple example +# Step2. Create Toast which contains the message to be shown toast = Toast() toast.add_text('第一行 1st line', hint_align='center', hint_style='caption') toast.add_text('第二行 2nd line') toast.add_text('第三行 3rd line', placement='attribution') toast.add_image(path_pic, placement='appLogoOverride') toast.add_action('关闭 Close') -toast.set_audio(silent='true') +toast.set_audio(silent='true') # Mute + +# Step3. Show the Toast notifier.show(toast) + # %% which is equivalent to -xml = f""" - - - - 第一行 1st line - 第二行 2nd line - 第三行 3rd line - - - - - - -""" -toast = Toast(xml) -notifier.show(toast) +# +# from os.path import abspath +# +# from winsdk_toast import Notifier, Toast +# +# path_pic = abspath('./resource/python.ico') +# +# # Step1. Create Notifier with applicationId +# notifier = Notifier('程序名 applicationId') +# +# # Step2. Create Toast which contains the message to be shown +# xml = f""" +# +# +# +# 第一行 1st line +# 第二行 2nd line +# 第三行 3rd line +# +# +# +# +# +# +# """ +# toast = Toast(xml) +# +# # Step3. Show the Toast +# notifier.show(toast) diff --git a/src/winsdk_toast/__init__.py b/src/winsdk_toast/__init__.py index 666f8b0..30aba7f 100644 --- a/src/winsdk_toast/__init__.py +++ b/src/winsdk_toast/__init__.py @@ -9,4 +9,4 @@ from winsdk_toast.notifier import Notifier __all__ = ['Toast', 'Notifier'] -__version__ = '0.0.2' +__version__ = '0.1.0' diff --git a/src/winsdk_toast/event.py b/src/winsdk_toast/event.py new file mode 100644 index 0000000..585f492 --- /dev/null +++ b/src/winsdk_toast/event.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +""" + +@Author: modabao +@Time: 2022/5/8 14:57 +""" + +from winsdk.windows.foundation import IPropertyValue +from winsdk.windows.ui.notifications import ToastActivatedEventArgs, ToastDismissedEventArgs, ToastFailedEventArgs + + +class EventArgsActivated(object): + """ + https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.toastactivatedeventargs?view=winrt-22000 + """ + def __init__(self, sender, event_args, input_ids): + self.sender = sender + event_args = ToastActivatedEventArgs._from(event_args) + self.argument = event_args.arguments + self.inputs = { + input_id: IPropertyValue._from(event_args.user_input.lookup(input_id)).get_string() + for input_id in input_ids + } + + +class EventArgsDismissed(object): + """ + https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.toastdismissedeventargs?view=winrt-22000 + """ + def __init__(self, sender, event_args): + self.sender = sender + event_args = ToastDismissedEventArgs._from(event_args) + self.reason = ['UserCanceled', 'ApplicationHidden', 'TimeOut'][event_args.reason] + + +class EventArgsFailed(object): + """ + https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.toastdismissedeventargs?view=winrt-22000 + """ + def __init__(self, sender, event_args): + self.sender = sender + event_args = ToastFailedEventArgs._from(event_args) + self.error_code = event_args.error_code + + +def handle_activated(event_args_activated: EventArgsActivated): + print(f'Activated with') + print(f'\targument: {event_args_activated.argument}') + print(f'\tinputs: {event_args_activated.inputs}') + + +def handle_dismissed(event_args_dismissed: EventArgsDismissed): + print(f'Dismissed reason: {event_args_dismissed.reason}') + + +def handle_failed(event_args_failed: EventArgsFailed): + raise RuntimeError(event_args_failed.error_code) diff --git a/src/winsdk_toast/notifier.py b/src/winsdk_toast/notifier.py index 72e8d8a..334b1ca 100644 --- a/src/winsdk_toast/notifier.py +++ b/src/winsdk_toast/notifier.py @@ -5,9 +5,63 @@ @Time: 2022/5/1 11:52 """ +import asyncio + from winsdk.windows.ui.notifications import ToastNotificationManager from winsdk_toast.toast import Toast +from winsdk_toast.event import ( + EventArgsActivated, EventArgsDismissed, EventArgsFailed, + handle_activated, handle_dismissed, handle_failed +) + + +async def show( + toast_notifier, toast: Toast, + handle_activated=handle_activated, handle_dismissed=handle_dismissed, handle_failed=handle_failed +): + toast_notification = toast.suit_up() + event_loop = asyncio.get_running_loop() + aws = [] + tokens = {} + + future_activated = event_loop.create_future() + token_activated = toast_notification.add_activated( + lambda sender, event_args: event_loop.call_soon_threadsafe( + future_activated.set_result, handle_activated(EventArgsActivated(sender, event_args, toast.input_ids)) + ) + ) + aws.append(future_activated) + tokens['activated'] = token_activated + + future_dismissed = event_loop.create_future() + token_dismissed = toast_notification.add_dismissed( + lambda sender, event_args: event_loop.call_soon_threadsafe( + future_dismissed.set_result, handle_dismissed(EventArgsDismissed(sender, event_args))) + ) + aws.append(future_dismissed) + tokens['dismissed'] = token_dismissed + + future_failed = event_loop.create_future() + token_failed = toast_notification.add_failed( + lambda sender, event_args: event_loop.call_soon_threadsafe( + future_failed.set_result, handle_failed(EventArgsFailed(sender, event_args))) + ) + aws.append(future_failed) + tokens['failed'] = token_failed + + toast_notifier.show(toast_notification) + try: + [done], pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED) + for p in pending: + p.cancel() + finally: + if (token_activated := tokens['activated']) is not None: + toast_notification.remove_activated(token_activated) + if (token_dismissed := tokens['dismissed']) is not None: + toast_notification.remove_dismissed(token_dismissed) + if (token_failed := tokens['failed']) is not None: + toast_notification.remove_failed(token_failed) class Notifier(object): @@ -19,5 +73,7 @@ def __init__(self, applicationId): """ self.toast_notifier = ToastNotificationManager.create_toast_notifier(applicationId) - def show(self, toast: Toast): - self.toast_notifier.show(toast.suit_up()) + def show(self, toast: Toast, + handle_activated=handle_activated, handle_dismissed=handle_dismissed, handle_failed=handle_failed): + asyncio.run(show(self.toast_notifier, toast, handle_activated, handle_dismissed, handle_failed)) + diff --git a/src/winsdk_toast/toast.py b/src/winsdk_toast/toast.py index b8809b9..9d2cba2 100644 --- a/src/winsdk_toast/toast.py +++ b/src/winsdk_toast/toast.py @@ -22,6 +22,7 @@ def __init__(self, xml=None): self.xml_document = XmlDocument() if xml is not None: self.xml_document.load_xml(xml) + self.input_ids = [] def suit_up(self): """Suit up to face notifier @@ -314,6 +315,7 @@ def add_input(self, type_, id_=None, placeHolderContent=None, element_parent=Non 'placeHolderContent': placeHolderContent } set_attributes(element_input, attributes) + self.input_ids.append(id_) return element_input def add_selection(self, id_=None, content=None, element_parent=None):