8

How can I use python to send notifications that support buttons, and stay in the action/notificaton center?

I am trying to make an app that reminds me to do stuff, and the notification will have a complete, and a snooze button. I tried using the win10toast package, but the notification didnt stay in the action center, and it did not support putting buttons on it.

The notification should look similar to this:

enter image description here

Thanks!

Alexander
  • 1,051
  • 1
  • 8
  • 21
  • That's a Toast, not just a notification. You could use a package like `win10toast` – Panagiotis Kanavos Oct 06 '20 at 16:43
  • I tried the package win10toast already. It doesnt support buttons like i need – Alexander Oct 06 '20 at 16:46
  • 1
    You forgot to mention both of those things. Edit the question and add what you actually want, what you tried and what's missing. Otherwise people waste their time trying to answer the wrong question – Panagiotis Kanavos Oct 06 '20 at 16:50
  • As for `win10toast` it cheatsToas. The Toast is displayed by the OS, not the application. If you check win10toast's code you'll see it makes some Win32 calls to display an old-style notification instead of a Toast. That's why it can't display any of the new UI elements – Panagiotis Kanavos Oct 06 '20 at 16:54
  • 1
    Looks like [someone posted a real answer](https://stackoverflow.com/a/57445886/134204) using Python/WinRT. There's a [gist here](https://gist.github.com/MarcAlx/443358d5e7167864679ffa1b7d51cd06) – Panagiotis Kanavos Oct 06 '20 at 17:00

6 Answers6

13

Maybe too late but this code should show a sample notification with buttons:

import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom

app = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe'

#create notifier
nManager = notifications.ToastNotificationManager
notifier = nManager.create_toast_notifier(app)

#define your notification as string
tString = """
  <toast>
    <visual>
      <binding template='ToastGeneric'>
        <text>Sample toast</text>
        <text>Sample content</text>
      </binding>
    </visual>
    <actions>
      <action
        content="Delete"
        arguments="action=delete"/>
      <action
        content="Dismiss"
        arguments="action=dismiss"/>
    </actions>        
  </toast>
"""

#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)

#display notification
notifier.show(notifications.ToastNotification(xDoc))
Jesús G.
  • 131
  • 1
  • 2
  • 3
    Hey, Sorry to bother but you seem to be the only person on the internet who knows how the app Id works, could you please explain this to me. I would like to replace `app = '{1AC14E77-0...` with something more relevant – Isiah May 07 '21 at 18:49
  • 5
    @Haisi You can get the Application User Model ID (AUMID) of any Windows App by launching PowerShell as admin and running the following command `get-StartApps`. This command will get all AUMIDs. If you want to get the AUMID of a specific application like Windows PowerShell run the following: `get-StartApps Windows PowerShell` – Tes3awy May 20 '21 at 06:14
5

Unfortunately, win10toast cheats and displays old-style, Windows XP notifications, not toasts. A Toast's content is specified using XML and can contain buttons, formatting, images etc.

To display a toast, one has to use WinRT. Luckily, someone wrote a real answer recently, using the Python/WinRT package.

I won't vote to close as duplicate, because the upvoted answers to that question all work with notifications, not toasts. Please go upvote that answer.

The linked answer explains how to install Python/WinRT with :

pip install winrt

Then uses it with surprisingly little code:


import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom

#create notifier
nManager = notifications.ToastNotificationManager
notifier = nManager.create_toast_notifier();

#define your notification as string
tString = """
<toast>
    <visual>
        <binding template='ToastGeneric'>
            <text>Sample toast</text>
            <text>Sample content</text>
        </binding>
    </visual>
</toast>
"""

#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)

#display notification
notifier.show(notifications.ToastNotification(xDoc))

The Toast Content article explains how the content is created, either through code or XML. Buttons are described here, eg :

    <actions>

        <action
            content="See more details"
            arguments="action=viewdetails&amp;contentId=351"
            activationType="foreground"/>

        <action
            content="Remind me later"
            arguments="action=remindlater&amp;contentId=351"
            activationType="background"/>

    </actions>

Once an action is selected, the arguments are sent to the application

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • It gives me this error when i try to run the first part: Traceback (most recent call last): File "c:/Users/Alexander/Desktop/python projects/notification/b.py", line 6, in notifier = nManager.create_toast_notifier(); RuntimeError: Element not found. – Alexander Oct 06 '20 at 17:19
  • 1
    I fixed the error by changing to notifier = nManager.create_toast_notifier("App-id"); but now no notification shows up – Alexander Oct 06 '20 at 18:38
  • 1
    Are you doing any other steps ? Because just running this script will give the errors @Alexander is mentioning. So it would be great if you can give the other (necessary) steps required for this to work. – sanders Oct 08 '20 at 10:36
4

I will use win10toast module. First use:

pip install win10toast

In cmd to install it.

Then to import it:

from win10toast import ToastNotifier

A sample notification:

toast = ToastNotifier()
toast.show_toast("Notification title","Notification body",duration=DURATION,icon_path="ICON PATH")
Wasif
  • 14,755
  • 3
  • 14
  • 34
2

I don't have enough rep to just comment a link to my other answer...but I've found a solution using WinRT that displays buttons that are usable from within your Python code (unlike the answer that only provides a "dismiss" option). I started from the other WinRT answer here but that one has errors and is missing critical steps.

I've written out more details over here, but I'll provide just the code from that for those that just want a solution. As a useful example, the following code (Python 3.9) will open your "Documents" folder in a Windows Explorer window using subprocess call in the handle_activated function.

import os,sys,time
import subprocess
import threading
import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom

# this is not called on the main thread!
def handle_activated(sender, _):
    path = os.path.expanduser("~\Documents")
    subprocess.Popen('explorer "{}"'.format(path))

def test_notification():
    #define your notification as
    tString = """
    <toast duration="short">

        <visual>
            <binding template='ToastGeneric'>
                <text>New notifications</text>
                <text>Text</text>
                <text>Second text</text>
            </binding>
        </visual>

        <actions>
            <action
                content="Test Button!"
                arguments=""
                activationType="foreground"/>
        </actions>

    </toast>
    """

    #convert notification to an XmlDocument
    xDoc = dom.XmlDocument()
    xDoc.load_xml(tString)
    notification = notifications.ToastNotification(xDoc)

    # add the activation token.
    notification.add_activated(handle_activated)

    #create notifier
    nManager = notifications.ToastNotificationManager
    #link it to your Python executable (or whatever you want I guess?)
    notifier = nManager.create_toast_notifier(sys.executable)

    #display notification
    notifier.show(notification)
    duration = 7 # "short" duration for Toast notifications

    # We have to wait for the results from the notification
    # If we don't, the program will just continue and maybe even end before a button is clicked
    thread = threading.Thread(target=lambda: time.sleep(duration))
    thread.start()
    print("We can still do things while the notification is displayed")

if __name__=="__main__":
    test_notification()
wyattg71
  • 51
  • 5
1

You could use the toast64.exe application from https://github.com/go-toast/toast. It supports Buttons, App name, Notification duration and Notification Sound. It is quite easy to use. This is far more better than other python modules like plyer, win10toast, py-notifier, pynotify etc.

This is a code snippet from the above Github page:-

C:\Users\Example\Downloads\toast64.exe \
  --app-id "Example App" \
  --title "Hello World" \
  --message "Lorem ipsum dolor sit amet, consectetur adipiscing elit." \
  --icon "C:\Users\Example\Pictures\icon.png" \
  --audio "default" --loop \
  --duration "long" \
  --activation-arg "https://google.com" \
  --action "Open maps" --action-arg "bingmaps:?q=sushi" \
  --action "Open browser" --action-arg "http://..."
1

You can use buttons in winrt like this:

import winrt.windows.ui.notifications as notifications
import winrt.windows.data.xml.dom as dom
import sys

# get python path
path = sys.executable

#create notifier
nManager = notifications.ToastNotificationManager
notifier = nManager.create_toast_notifier(path)

#define your notification as

tString = """
<toast>

    <visual>
        <binding template='ToastGeneric'>
            <text>New notifications</text>
            <text>Text</text>
            <text>Second text</text>
        </binding>
    </visual>

    <actions>
        <input id="textBox" type="text" placeHolderContent="Type a reply"/>
        <action
            content="Send"
            arguments="action=reply&amp;convId=01"
            activationType="background"
            hint-inputId="textBox"/>
            
        <action
            content="Button 1"
            arguments="action=viewdetails&amp;contentId=02"
            activationType="foreground"/>
    </actions>

</toast>
"""

#convert notification to an XmlDocument
xDoc = dom.XmlDocument()
xDoc.load_xml(tString)

# this is not called on the main thread.
def handle_activated(sender, _):
    print([sender, _])
    print('Button was pressed!')

# add the activation token.
activated_token = notification.add_activated(handle_activated)

#display notification
notifier.show(notifications.ToastNotification(xDoc))

Where I learnt about this: this issue.

N3RDIUM
  • 358
  • 5
  • 22