Skip to content

Commit

Permalink
Custom sounds, app IDs and more (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
ysfchn authored Mar 21, 2024
1 parent b60e6ba commit 462e2d1
Show file tree
Hide file tree
Showing 17 changed files with 2,156 additions and 901 deletions.
Binary file modified .github/assets/preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 17 additions & 22 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,28 @@ name: Upload Python Package
on:
release:
types: [published]
push:
branches:
- dev
workflow_dispatch: {}

permissions:
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install poetry
uses: abatilo/actions-poetry@v2
uses: actions/setup-python@v3
with:
poetry-version: 1.2.1
- name: Publish package
if: github.event_name == 'release'
python-version: '3.x'
- name: Install dependencies
run: |
poetry publish --build -u __token__ -p ${PYPI_API_TOKEN}
env:
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
- name: Publish to TestPyPI
if: github.event_name == 'push'
run: |
poetry config repositories.testpypi https://test.pypi.org/legacy/
poetry publish --build -r testpypi -u __token__ -p ${TEST_PYPI_API_TOKEN}
env:
TEST_PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
requirements*.lock

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
118 changes: 82 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,104 @@
# toasted

Toast notifications library for Windows. Unlike other Windows toast libraries, Toasted is the one of the most comprehensive toast libraries. Because it supports all elements (images, progresses, texts, inputs, buttons...) that you probably never seen on a toast. Not only that, it also includes useful features found in the Notifications API.
Yet another notifications library for Windows, written in Python, built using Windows Runtime APIs (WinRT).

> Struggling with making a GUI for your script? Say no more.
Compared to other toast libraries, Toasted supports all notification elements provided by Windows, such as; inputs, selects, buttons, images, different text styles... so you are not just limited to single line of text.

Since Windows **restricts use of some features of toast notifications in unpackaged non-UWP apps**, Toasted also handles this cases _in the best way possible_ to mimic the behaviour of UWP apps, for example; HTTP(S) URIs are first downloaded to a temporary directory and then used as a local file, Windows URIs like `ms-appx` and `ms-appdata` are set relative to special directories [and so on.](#special-behaviours)

![](.github/assets/preview.png)

It works on Windows 10 and up, though outdated/initial builds of Windows 10 may not work as it doesn't include all Notification APIs used in the library.

> I'm using Linux in my daily life, and the reason why I created this library even I use Linux is that I started working on this library before my switch to Linux. So I currently use a separate Windows device to develop Toasted on, which may affect my development time. Therefore, while I'm trying to do my best and keep the library updated, do not expect regular updates.
If other systems would provide APIs and features to create rich libraries as on Windows, I would have love to add support for other systems, but as the library is focusing/relying on Windows APIs more in each update, it is not possible to make it cross-platform at the moment.

Toast notifications can also be preferred to develop rapid GUIs for your Windows-only Python projects.

## Install

```
python -m pip install toasted
```

## Example
## Usage

See [`showcase.py`](./examples/showcase.py) for an introduction to the library with detailed example toast notification configurations.

## Special behaviours

Windows API restricts use of some features of toast notifications for non-UWP/non-packaged apps, Toasted contains bunch of conveinence features to mimic the behaviour of UWP apps and get the most of the toast features of Windows.

### Remote images (HTTP URIs as image sources)

Normally, Windows restricts the use of HTTP images and only allows local file paths on non-UWP applications. But to overcome the limitation, Toasted downloads HTTP images to `%TEMP%` and replaces the link with downloaded local file before showing the toast. Downloaded files **are deleted** once toast has dismissed/clicked to not leave traces on the system.

Also, to comply with Windows API, you can enable sending system theme information (such as `ms-lang`, `ms-theme`, `ms-contrast`) to given URLs as query parameters by setting `add_query_params` property, so if you are serving files from your web server, you can serve different images based on the system theme.

### Application icon and name (app IDs)

Notifications in Windows must be bound to an **application registered in system.** So, when you tell Windows that you are sending notifications from Python executable, it will send the toast **on behalf of the given application,** thus the application's own name and icon will appear in the toast title.

If you don't set a custom `app_id`, it will use the path where Python has installed. (`sys.executable`) But note that this won't work with virtual environments as there won't be a Python that registered on the system.

However Toasted provides a `register_app_id()` method to register a new "App ID" (or AUMID) in `Foo.Bar.Example` format to the Windows Registry, so you can use your own app ID in your toasts and Windows will show on behalf of your app ID, so you can set the application icon and name as you wish.

Registering an app ID doesn't require a reboot on the system, so you can register an app ID just before sending a toast, however, **you need to make sure that all notifications sent by Toasted are cleared from Action Center** to make Windows to use the updated application icon and name.

```py
import asyncio
from typing import Dict
from toasted import Toast, Text

# Create Toast with Toast(),
# see docstring for all available parameters.
mytoast = Toast()

# Add elements.
mytoast += Text("Hello world!") # Using += operator.
mytoast.data.append(Text("Hello world!")) # Or access the inner list with Toast.data.

# Set up a handler.
# This handler will be executed when toasted has clicked or dismissed.
@mytoast.handler
def myhandler(arguments : str, user_input : Dict[str, str], dismiss_reason : int):
# dismiss_reason will set to a value higher than or equals to 0 when dismissed,
# -1 means a toast or button click.
if dismiss_reason == -1:
print("Got arguments:", arguments)
else:
print("Toast has dismissed:", dismiss_reason)

# Run show() async function.
asyncio.run(mytoast.show())
# Applications can be registered outside of Python since it just adds registry keys to Windows.
# This is just a shortcut method to do that.
# https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/send-local-toast-other-apps
app_id = Toast.register_app_id("MyOrg.MyDomain.MyPhone", "My Phone App")

# Setting app_id property causes Python to tell Windows that the currently running process is running
# under the specified application ID (MyOrg.MyDomain.MyPhone) instead of Python executable.
# If an app_id has not set, the default app_id is used which is sys.executable.
mytoast = Toast(app_id = app_id)

# Or you can change app_id later instead of constructor.
mytoast.app_id = app_id
```

## Highlights
> [!WARNING]
> Since these app ID registrations are made in Windows Registry, this will leave traces in system even after your Python program is no longer running. You can unregister the application by `unregister_app_id()` method.
### Custom sounds

If an custom sound has provided, toast's own sound will be muted and Python's `winsound` module will be used instead. Also, sounds from HTTP sources are supported too instead of just file paths.

### Use images of Windows system icons

Toasted can create images of Unicode characters in Windows icon fonts ([Segoe Fluent](https://learn.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font) and [Segoe MDL2](https://learn.microsoft.com/en-us/windows/apps/design/style/segoe-ui-symbol-font)) with `get_icon_from_font()` utility method.

A custom URI has also been added for convenience, for example if you set `icon://E706` as a source in image element, it will show a screen brightness icon (`U+E706`) from "Segoe MDL2" (pre-installed on Windows 10 and up) or "Segoe Fluent" (pre-installed on Windows 11 and up) font. Toasted will prefer using MDL2 font if it is running on Windows 10, or Fluent, if running on Windows 11.

## Advanced

### Update toast content (Data binding)

* **Remote (HTTP) images support**
<br>Normally, Windows restricts the use of HTTP images and only allows local file paths on non-UWP applications. But to overcome the limitation, Toasted downloads HTTP images to %TEMP%, so you can now use images from web without any configuration! Downloaded images are deleted once toast has dismissed / clicked. Also, to comply with Windows API, you can enable sending system information (such as `ms-lang`, `ms-theme`, `ms-contrast`) to remote sources as query parameters by setting `add_query_params` property.
Properties in toast elements can have a binding value, which is done by simply putting a key surrounded with curly braces like, `{myProgress}`. Then, you can set a new value for `myProgress` key with `show()` to set its initial value and with `update()` to update toast in-place without showing a new toast.

This is useful for updating toast progress bars without displaying a new toast for each step. Since data binding is a feature provided by Windows API itself, not all elements may support this feature, so Toasted has no control over this.

## Unimplemented features

My initial goal was to bring most if not all toast features to Python, but unfortunately there are some notification features that are not supported/implemented on this library. Below is a non extensive list of these:

* [Collections](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-collections)
* [Pending updates & background events](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-pending-update?tabs=builder-syntax), seems to require a COM server.

## Building

```
python -m pip wheel .
```

* **Update toast content (Data binding)**
<br>Properties in toast elements can have a binding/dynamic/reference value, which is done by simply putting a key surrounded with curly braces like, `{myProgress}`. Then, you can set a new value for `myProgress` key before showing toast with `show()`, and with `update()` to update toast in-place without showing a new toast.
Or if you use [rye](https://rye-up.com/), `rye build`.

* **Import from JSON**
<br>Notification elements and their properties can be imported with dictionaries (JSON-accepted types) with `Toast.from_json()`, so you can add more than one element by calling a single method. See example JSON configurations [here.](examples)

## Notes
## License

* As you can see from screenshot, it is not possible to change "Python" title in normal ways, since Windows requires a "source application" to show notifications from. However, [Toast collections](https://docs.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-collections) allows to override app icon, but I'm not sure how I can implement this (or even, is it possible for a non-UWP app?), so still working on it.
Source code is licensed under MIT license. You must include the license notice in all copies or substantial uses of the work.
38 changes: 0 additions & 38 deletions examples/call.json

This file was deleted.

33 changes: 0 additions & 33 deletions examples/image.json

This file was deleted.

53 changes: 0 additions & 53 deletions examples/input.json

This file was deleted.

Loading

0 comments on commit 462e2d1

Please sign in to comment.