metalex
metalex

Reputation: 131

windows toast notifications with action using python winrt module

I've been trying to get this working for a long time now and i always get stuck at detecting button presses. I made a toast notification that looks like this:

enter image description here

Here's my code :

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>New notifications</text>
           <text>Text</text>
           <text>Second text</text>
       </binding>
   </visual>

   <actions>   
       <action
           content="test1"
           arguments="test1"
           activationType="backround"/>
       <action
           content="test2"
           arguments="test2"
           activationType="backround"/>
   </actions>

</toast>
"""
print(type(notifier.update))

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

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

I don't know how to detect button presses the only thing i figured out is that if i change the argument of the buttons to a link like this:

arguments="https://google.com"

then it will open it Is there any way i could implement this? or is there documentation for this XML format these toast notifications use. That explains how arguments work?

Upvotes: 4

Views: 1638

Answers (1)

wyattg71
wyattg71

Reputation: 51

Alright so I know It's been a while, but I was trying to figure out the same thing and I couldn't find a good, conclusive answer anywhere. I've finally gotten something to work with WinRT in Python 3.9 so I wanted there to be an answer somewhere that people could find!

So to start, I'm not intimately familiar with how the 'arguments' attribute works, but it doesn't seem to be important for at least simple use cases. Most of what I know came from the Windows Toast docs. Here's some code that should produce a notification and open your Documents folder when you click the button. I got a headstart from an answer in this thread but it was missing some very important steps.

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()

The key thing to note here is that you need to find a way to wait for the response to the notification, since the notification is handled by a different thread than the program that produces it. This is why your "www.google.com" example worked while others didn't, because it didn't have anything to do with the Python program.

There's likely a more elegant solution, but a quick and easy way is to just create a Python thread and wait there for a duration. This way it doesn't interfere with the rest of your program in case you need to be doing something else. If you want your program to wait for a response, use time.sleep(duration) without all the threading code to pause the whole program.

I'm not sure how it works exactly, but it seems like the add_activated function just assigns a callback handler to the next available block in the XML. So if you wanted to add another button, my guess is that you can just do add_activated with another callback handler in the same order as you've listed your buttons.

Edit: I played around with it some and it turns out this lets you click anywhere, not just on the button. Not sure where to go from there but it's worth a heads up.

Upvotes: 3

Related Questions