winotify
What is winotify?
winotify is a python library and a command-line application to make Windows 10 toast notification.
Features
- Notification stays in action center
- Clickable notification with 5 additional buttons
- Use function as a callback when clicking the notification
Installation
Install winotify using pip
pip install winotify
Example
A simple notification with icon
from winotify import Notification
toast = Notification(app_id="example app",
title="Winotify Test Toast",
msg="New Notification!",
icon=r"C:\path\to\icon.png")
toast.show()
How to ...
... add buttons to the notification
from winotify import Notification, audio
toast = Notification(...)
toast.add_actions(label="open github",
launch="https://github.com/versa-syahptr/winotify/")
... set sound of the notification
from winotify import Notification, audio
toast = Notification(...)
toast.set_audio(audio.Mail, loop=False)
All supported audio are in the audio
module
... use callback feature
this is an advanced feature of winotify. Please follow this guide carefully
- Declare your app id, default interpreter, and script path globally
import winotify
r = winotify.Registry("app_id", winotify.PY_EXE, r"c:\abs\path\to\script.py")
notifier = winotify.Notifier(r)
- Register a function to use as a callback using
Notifier.register_callback
decorator
@notifier.register_callback
def say_hello():
print("hello")
- Create new
Notification
usingNotifier.create_notification()
then pass the registered function tolaunch
parameter,
toast = notifier.create_notification(title="a notification",
msg='a notification test with callback',
launch=say_hello)
# or pass it to `Notification.add_actions()`
toast.add_actions(label="say hello in console",
launch=say_hello)
- Start the notifier thread
if __name__ == '__main__':
notifier.start()
Command-line Application
winotify.exe ^
-id myApp ^
-t "A Title" ^
-m "A message" ^
-i "c:\path\to\icon.png" ^
--audio default ^
--open-url "http://google.com" ^
--action "open github" ^
--action_url "http://github.com"
Use
winotify-nc.exe
instead ofwinotify.exe
to hide the console window.
View Source
""" .. include:: ./documentation.md """ import queue import os import subprocess import sys import atexit from tempfile import gettempdir from typing import Callable, Union from winotify import audio from winotify._registry import Registry, format_name, PY_EXE, PYW_EXE from winotify._communication import Listener, Sender __author__ = "Versa Syahputra" __version__ = "1.1.0-dev" __all__ = ["Notifier", "Notification", "Registry", "audio"] TEMPLATE = r""" [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null [Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null $Template = @" <toast {launch} duration="{duration}"> <visual> <binding template="ToastImageAndText02"> <image id="1" src="{icon}" /> <text id="1"><![CDATA[{title}]]></text> <text id="2"><![CDATA[{msg}]]></text> </binding> </visual> <actions> {actions} </actions> {audio} </toast> "@ $SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument $SerializedXml.LoadXml($Template) $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml) $Toast.Tag = "{tag}" $Toast.Group = "{group}" $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("{app_id}") $Notifier.Show($Toast); """ tempdir = gettempdir() def _run_ps(*, file='', command=''): si = subprocess.STARTUPINFO() si.dwFlags |= subprocess.STARTF_USESHOWWINDOW cmd = ["powershell.exe", "-ExecutionPolicy", "Bypass"] if file and command: raise ValueError elif file: cmd.extend(["-file", file]) elif command: cmd.extend(['-Command', command]) else: raise ValueError subprocess.Popen( cmd, # stdin, stdout, and stderr have to be defined here, because windows tries to duplicate these if not null stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, # set to null because we don't need the output :) stderr=subprocess.DEVNULL, startupinfo=si ) class Notification(object): def __init__(self, app_id: str, title: str, msg: str = "", icon: str = "", duration: str = 'short', launch: str = ''): """ Construct a new notification Args: app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported. title: The heading of the toast. msg: The content/message of the toast. icon: An optional path to an image to display on the left of the title & message. Make sure the path is absolute. duration: How long the toast should show up for (short/long), default is short. launch: The url or callback to launch (invoked when the user clicks the notification) Notes: If you want to pass a callback to `launch` parameter, please use `create_notification` from `Notifier` object Raises: ValueError: If the duration specified is not short or long """ self.app_id = app_id self.title = title self.msg = msg self.icon = icon self.duration = duration self.launch = launch self.audio = audio.Silent self.tag = self.title self.group = self.app_id self.actions = [] self.script = "" if duration not in ("short", "long"): raise ValueError("Duration is not 'short' or 'long'") def set_audio(self, sound: audio.Sound, loop: bool): """ Set the audio for the notification Args: sound: The audio to play when the notification is showing. Choose one from `winotify.audio` module, (eg. audio.Default). The default for all notification is silent. loop: If True, the audio will play indefinitely until user click or dismis the notification. """ self.audio = '<audio src="{}" loop="{}" />'.format(sound, str(loop).lower()) def add_actions(self, label: str, launch: Union[str, Callable] = ""): """ Add buttons to the notification. Each notification can have 5 buttons max. Args: label: The label of the button launch: The url to launch when clicking the button, 'file:///' protocol is allowed. Or a registered callback function Returns: None Notes: Register a callback function using `Notifier.register_callback()` decorator before passing it here Raises: ValueError: If the callback function is not registered """ if callable(launch): if hasattr(launch, 'url'): url = launch.url else: raise ValueError(f"{launch} is not registered") else: url = launch xml = '<action activationType="protocol" content="{label}" arguments="{link}" />' if len(self.actions) < 5: self.actions.append(xml.format(label=label, link=url)) def build(self): """ This method is deprecated, call `Notification.show()` directly instead. Warnings: DeprecationWarning """ import warnings warnings.warn("build method is deprecated, call show directly instead", DeprecationWarning) return self def show(self): """ Show the toast """ if self.actions: self.actions = '\n'.join(self.actions) else: self.actions = '' if self.audio == audio.Silent: self.audio = '<audio silent="true" />' if self.launch: self.launch = 'activationType="protocol" launch="{}"'.format(self.launch) self.script = TEMPLATE.format(**self.__dict__) _run_ps(command=self.script) class Notifier: def __init__(self, registry: Registry): """ A `Notification` manager class. Args: registry: A `Registry` instance containing the `app_id`, default interpreter, and the script path. """ self.app_id = registry.app self.icon = "" pidfile = os.path.join(tempdir, f'{self.app_id}.pid') # alias for callback_to_url() self.cb_url = self.callback_to_url if self._protocol_launched(): # communicate to main process if it's alive self.func_to_call = sys.argv[1].split(':')[1] self._cb = {} # callbacks are stored here because we have no listener if os.path.isfile(pidfile): sender = Sender(self.app_id) sender.send(self.func_to_call) sys.exit() else: self.listener = Listener(self.app_id) open(pidfile, 'w').write(str(os.getpid())) # pid file atexit.register(os.unlink, pidfile) @property def callbacks(self): """ Returns: A dictionary containing all registered callbacks, with each function's name as the key """ if hasattr(self, 'listener'): return self.listener.callbacks else: return self._cb def set_icon(self, path: str): """ Set icon globally for all notification Args: path: The absolute path of the icon Returns: None """ self.icon = path def create_notification(self, title: str, msg: str = '', icon: str = '', duration: str = 'short', launch: Union[str, Callable] = '') -> Notification: """ See Also: `Notification` Notes: `launch` parameter can be a callback function here Returns: `Notification` object """ if self.icon: icon = self.icon if callable(launch): url = self.callback_to_url(launch) else: url = launch notif = Notification(self.app_id, title, msg, icon, duration, url) return notif def start(self): """ Start the listener thread. This method *must* be called first in the main function, Otherwise, all the callback function will never get called. Examples: ```python if __name__ == "__main__": notifier.start() ... ``` """ if self._protocol_launched(): # call the callback directly self.callbacks.get(self.func_to_call)() else: self.listener.callbacks.update(self.callbacks) self.listener.thread.start() def update(self): """ check for available callback function in queue then call it this method *must* be called *every time* in loop. If all callback functions don't need to run in main thread, calling this functions is *optional* Examples: ```python # the main loop while True: notifier.update() ... ``` """ if self._protocol_launched(): return q = self.listener.queue try: func = q.get_nowait() if callable(func): func() else: print(f"{func.__name__} ") except queue.Empty: pass def _protocol_launched(self) -> bool: """ check whether the app is opened directly or via notification Returns: True, if opened from notification; False if opened directly """ if len(sys.argv) > 1: arg = sys.argv[1] return format_name(self.app_id) + ':' in arg and len(arg.split(':')) > 0 else: return False def register_callback(self, func=None, *, run_in_main_thread=False): """ A decorator to register a function to be used as a callback Args: func: the function to decorate run_in_main_thread: If True, the callback function will run in main thread Examples: ```python @notifier.register_callback def foo(): ... ``` Returns: The registered function """ def inner(f): if run_in_main_thread: f.rimt = run_in_main_thread self.callbacks[f.__name__] = f f.url = self.callback_to_url(f) return f if func is None: return inner else: return inner(func) def callback_to_url(self, func: Callable) -> str: """ Translate the registered callback function `func` to url notation. Args: func: The registered callback function Returns: url-notation string eg. `my-app-id:foo`, where **my-app-id** is the app id and **foo** is the function name """ if callable(func) and func.__name__ in self.callbacks: url = format_name(self.app_id) + ":" + func.__name__ return url def clear(self): """ Clear all notification created by `Notifier` from action center """ cmd = f"""\ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null [Windows.UI.Notifications.ToastNotificationManager]::History.Clear('{self.app_id}') """ _run_ps(command=cmd)
View Source
class Notifier: def __init__(self, registry: Registry): """ A `Notification` manager class. Args: registry: A `Registry` instance containing the `app_id`, default interpreter, and the script path. """ self.app_id = registry.app self.icon = "" pidfile = os.path.join(tempdir, f'{self.app_id}.pid') # alias for callback_to_url() self.cb_url = self.callback_to_url if self._protocol_launched(): # communicate to main process if it's alive self.func_to_call = sys.argv[1].split(':')[1] self._cb = {} # callbacks are stored here because we have no listener if os.path.isfile(pidfile): sender = Sender(self.app_id) sender.send(self.func_to_call) sys.exit() else: self.listener = Listener(self.app_id) open(pidfile, 'w').write(str(os.getpid())) # pid file atexit.register(os.unlink, pidfile) @property def callbacks(self): """ Returns: A dictionary containing all registered callbacks, with each function's name as the key """ if hasattr(self, 'listener'): return self.listener.callbacks else: return self._cb def set_icon(self, path: str): """ Set icon globally for all notification Args: path: The absolute path of the icon Returns: None """ self.icon = path def create_notification(self, title: str, msg: str = '', icon: str = '', duration: str = 'short', launch: Union[str, Callable] = '') -> Notification: """ See Also: `Notification` Notes: `launch` parameter can be a callback function here Returns: `Notification` object """ if self.icon: icon = self.icon if callable(launch): url = self.callback_to_url(launch) else: url = launch notif = Notification(self.app_id, title, msg, icon, duration, url) return notif def start(self): """ Start the listener thread. This method *must* be called first in the main function, Otherwise, all the callback function will never get called. Examples: ```python if __name__ == "__main__": notifier.start() ... ``` """ if self._protocol_launched(): # call the callback directly self.callbacks.get(self.func_to_call)() else: self.listener.callbacks.update(self.callbacks) self.listener.thread.start() def update(self): """ check for available callback function in queue then call it this method *must* be called *every time* in loop. If all callback functions don't need to run in main thread, calling this functions is *optional* Examples: ```python # the main loop while True: notifier.update() ... ``` """ if self._protocol_launched(): return q = self.listener.queue try: func = q.get_nowait() if callable(func): func() else: print(f"{func.__name__} ") except queue.Empty: pass def _protocol_launched(self) -> bool: """ check whether the app is opened directly or via notification Returns: True, if opened from notification; False if opened directly """ if len(sys.argv) > 1: arg = sys.argv[1] return format_name(self.app_id) + ':' in arg and len(arg.split(':')) > 0 else: return False def register_callback(self, func=None, *, run_in_main_thread=False): """ A decorator to register a function to be used as a callback Args: func: the function to decorate run_in_main_thread: If True, the callback function will run in main thread Examples: ```python @notifier.register_callback def foo(): ... ``` Returns: The registered function """ def inner(f): if run_in_main_thread: f.rimt = run_in_main_thread self.callbacks[f.__name__] = f f.url = self.callback_to_url(f) return f if func is None: return inner else: return inner(func) def callback_to_url(self, func: Callable) -> str: """ Translate the registered callback function `func` to url notation. Args: func: The registered callback function Returns: url-notation string eg. `my-app-id:foo`, where **my-app-id** is the app id and **foo** is the function name """ if callable(func) and func.__name__ in self.callbacks: url = format_name(self.app_id) + ":" + func.__name__ return url def clear(self): """ Clear all notification created by `Notifier` from action center """ cmd = f"""\ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null [Windows.UI.Notifications.ToastNotificationManager]::History.Clear('{self.app_id}') """ _run_ps(command=cmd)
View Source
def __init__(self, registry: Registry): """ A `Notification` manager class. Args: registry: A `Registry` instance containing the `app_id`, default interpreter, and the script path. """ self.app_id = registry.app self.icon = "" pidfile = os.path.join(tempdir, f'{self.app_id}.pid') # alias for callback_to_url() self.cb_url = self.callback_to_url if self._protocol_launched(): # communicate to main process if it's alive self.func_to_call = sys.argv[1].split(':')[1] self._cb = {} # callbacks are stored here because we have no listener if os.path.isfile(pidfile): sender = Sender(self.app_id) sender.send(self.func_to_call) sys.exit() else: self.listener = Listener(self.app_id) open(pidfile, 'w').write(str(os.getpid())) # pid file atexit.register(os.unlink, pidfile)
A Notification
manager class.
Args
- registry: A
Registry
instance containing theapp_id
, default interpreter, and the script path.
Returns: A dictionary containing all registered callbacks, with each function's name as the key
View Source
def set_icon(self, path: str): """ Set icon globally for all notification Args: path: The absolute path of the icon Returns: None """ self.icon = path
Set icon globally for all notification
Args
- path: The absolute path of the icon
Returns
None
View Source
def create_notification(self, title: str, msg: str = '', icon: str = '', duration: str = 'short', launch: Union[str, Callable] = '') -> Notification: """ See Also: `Notification` Notes: `launch` parameter can be a callback function here Returns: `Notification` object """ if self.icon: icon = self.icon if callable(launch): url = self.callback_to_url(launch) else: url = launch notif = Notification(self.app_id, title, msg, icon, duration, url) return notif
View Source
def start(self): """ Start the listener thread. This method *must* be called first in the main function, Otherwise, all the callback function will never get called. Examples: ```python if __name__ == "__main__": notifier.start() ... ``` """ if self._protocol_launched(): # call the callback directly self.callbacks.get(self.func_to_call)() else: self.listener.callbacks.update(self.callbacks) self.listener.thread.start()
Start the listener thread. This method must be called first in the main function, Otherwise, all the callback function will never get called.
Examples
if __name__ == "__main__": notifier.start() ...
View Source
def update(self): """ check for available callback function in queue then call it this method *must* be called *every time* in loop. If all callback functions don't need to run in main thread, calling this functions is *optional* Examples: ```python # the main loop while True: notifier.update() ... ``` """ if self._protocol_launched(): return q = self.listener.queue try: func = q.get_nowait() if callable(func): func() else: print(f"{func.__name__} ") except queue.Empty: pass
check for available callback function in queue then call it this method must be called every time in loop.
If all callback functions don't need to run in main thread, calling this functions is optional
Examples
# the main loop while True: notifier.update() ...
View Source
def register_callback(self, func=None, *, run_in_main_thread=False): """ A decorator to register a function to be used as a callback Args: func: the function to decorate run_in_main_thread: If True, the callback function will run in main thread Examples: ```python @notifier.register_callback def foo(): ... ``` Returns: The registered function """ def inner(f): if run_in_main_thread: f.rimt = run_in_main_thread self.callbacks[f.__name__] = f f.url = self.callback_to_url(f) return f if func is None: return inner else: return inner(func)
A decorator to register a function to be used as a callback
Args
- func: the function to decorate
- run_in_main_thread: If True, the callback function will run in main thread
Examples
@notifier.register_callback def foo(): ...
Returns
The registered function
View Source
def callback_to_url(self, func: Callable) -> str: """ Translate the registered callback function `func` to url notation. Args: func: The registered callback function Returns: url-notation string eg. `my-app-id:foo`, where **my-app-id** is the app id and **foo** is the function name """ if callable(func) and func.__name__ in self.callbacks: url = format_name(self.app_id) + ":" + func.__name__ return url
Translate the registered callback function func
to url notation.
Args
- func: The registered callback function
Returns
url-notation string eg.
my-app-id:foo
, where my-app-id is the app id and foo is the function name
View Source
def clear(self): """ Clear all notification created by `Notifier` from action center """ cmd = f"""\ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null [Windows.UI.Notifications.ToastNotificationManager]::History.Clear('{self.app_id}') """ _run_ps(command=cmd)
Clear all notification created by Notifier
from action center
View Source
class Notification(object): def __init__(self, app_id: str, title: str, msg: str = "", icon: str = "", duration: str = 'short', launch: str = ''): """ Construct a new notification Args: app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported. title: The heading of the toast. msg: The content/message of the toast. icon: An optional path to an image to display on the left of the title & message. Make sure the path is absolute. duration: How long the toast should show up for (short/long), default is short. launch: The url or callback to launch (invoked when the user clicks the notification) Notes: If you want to pass a callback to `launch` parameter, please use `create_notification` from `Notifier` object Raises: ValueError: If the duration specified is not short or long """ self.app_id = app_id self.title = title self.msg = msg self.icon = icon self.duration = duration self.launch = launch self.audio = audio.Silent self.tag = self.title self.group = self.app_id self.actions = [] self.script = "" if duration not in ("short", "long"): raise ValueError("Duration is not 'short' or 'long'") def set_audio(self, sound: audio.Sound, loop: bool): """ Set the audio for the notification Args: sound: The audio to play when the notification is showing. Choose one from `winotify.audio` module, (eg. audio.Default). The default for all notification is silent. loop: If True, the audio will play indefinitely until user click or dismis the notification. """ self.audio = '<audio src="{}" loop="{}" />'.format(sound, str(loop).lower()) def add_actions(self, label: str, launch: Union[str, Callable] = ""): """ Add buttons to the notification. Each notification can have 5 buttons max. Args: label: The label of the button launch: The url to launch when clicking the button, 'file:///' protocol is allowed. Or a registered callback function Returns: None Notes: Register a callback function using `Notifier.register_callback()` decorator before passing it here Raises: ValueError: If the callback function is not registered """ if callable(launch): if hasattr(launch, 'url'): url = launch.url else: raise ValueError(f"{launch} is not registered") else: url = launch xml = '<action activationType="protocol" content="{label}" arguments="{link}" />' if len(self.actions) < 5: self.actions.append(xml.format(label=label, link=url)) def build(self): """ This method is deprecated, call `Notification.show()` directly instead. Warnings: DeprecationWarning """ import warnings warnings.warn("build method is deprecated, call show directly instead", DeprecationWarning) return self def show(self): """ Show the toast """ if self.actions: self.actions = '\n'.join(self.actions) else: self.actions = '' if self.audio == audio.Silent: self.audio = '<audio silent="true" />' if self.launch: self.launch = 'activationType="protocol" launch="{}"'.format(self.launch) self.script = TEMPLATE.format(**self.__dict__) _run_ps(command=self.script)
View Source
def __init__(self, app_id: str, title: str, msg: str = "", icon: str = "", duration: str = 'short', launch: str = ''): """ Construct a new notification Args: app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported. title: The heading of the toast. msg: The content/message of the toast. icon: An optional path to an image to display on the left of the title & message. Make sure the path is absolute. duration: How long the toast should show up for (short/long), default is short. launch: The url or callback to launch (invoked when the user clicks the notification) Notes: If you want to pass a callback to `launch` parameter, please use `create_notification` from `Notifier` object Raises: ValueError: If the duration specified is not short or long """ self.app_id = app_id self.title = title self.msg = msg self.icon = icon self.duration = duration self.launch = launch self.audio = audio.Silent self.tag = self.title self.group = self.app_id self.actions = [] self.script = "" if duration not in ("short", "long"): raise ValueError("Duration is not 'short' or 'long'")
Construct a new notification
Args
- app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported.
- title: The heading of the toast.
- msg: The content/message of the toast.
- icon: An optional path to an image to display on the left of the title & message. Make sure the path is absolute.
- duration: How long the toast should show up for (short/long), default is short.
- launch: The url or callback to launch (invoked when the user clicks the notification)
Notes
If you want to pass a callback to
launch
parameter, please usecreate_notification
fromNotifier
object
Raises
- ValueError: If the duration specified is not short or long
View Source
def set_audio(self, sound: audio.Sound, loop: bool): """ Set the audio for the notification Args: sound: The audio to play when the notification is showing. Choose one from `winotify.audio` module, (eg. audio.Default). The default for all notification is silent. loop: If True, the audio will play indefinitely until user click or dismis the notification. """ self.audio = '<audio src="{}" loop="{}" />'.format(sound, str(loop).lower())
Set the audio for the notification
Args
- sound: The audio to play when the notification is showing. Choose one from
winotify.audio
module, (eg. audio.Default). The default for all notification is silent. - loop: If True, the audio will play indefinitely until user click or dismis the notification.
View Source
def add_actions(self, label: str, launch: Union[str, Callable] = ""): """ Add buttons to the notification. Each notification can have 5 buttons max. Args: label: The label of the button launch: The url to launch when clicking the button, 'file:///' protocol is allowed. Or a registered callback function Returns: None Notes: Register a callback function using `Notifier.register_callback()` decorator before passing it here Raises: ValueError: If the callback function is not registered """ if callable(launch): if hasattr(launch, 'url'): url = launch.url else: raise ValueError(f"{launch} is not registered") else: url = launch xml = '<action activationType="protocol" content="{label}" arguments="{link}" />' if len(self.actions) < 5: self.actions.append(xml.format(label=label, link=url))
Add buttons to the notification. Each notification can have 5 buttons max.
Args
- label: The label of the button
- launch: The url to launch when clicking the button, 'file:///' protocol is allowed. Or a registered callback function
Returns: None
Notes
Register a callback function using
Notifier.register_callback()
decorator before passing it here
Raises
- ValueError: If the callback function is not registered
View Source
def build(self): """ This method is deprecated, call `Notification.show()` directly instead. Warnings: DeprecationWarning """ import warnings warnings.warn("build method is deprecated, call show directly instead", DeprecationWarning) return self
View Source
def show(self): """ Show the toast """ if self.actions: self.actions = '\n'.join(self.actions) else: self.actions = '' if self.audio == audio.Silent: self.audio = '<audio silent="true" />' if self.launch: self.launch = 'activationType="protocol" launch="{}"'.format(self.launch) self.script = TEMPLATE.format(**self.__dict__) _run_ps(command=self.script)
Show the toast
View Source
class Registry: def __init__(self, app_id: str, executable=PY_EXE, script_path: str = '', *, force_override=False): """ register app_id to Windows Registry as a protocol, eg. the app_id is "My Awesome App" can be called from browser or run.exe by typing "my-awesome-app:[Params]" Params can be a function name to call Args: app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported. executable: set the default interpreter or executable to run when a notification is clicked, default is `PY_EXE` which is python.exe. To hide cmd flashing when a notification is clicked, use `PYW_EXE`. script_path: The script path, usually `__file__`. force_override: If True, force replace the exists registry value in Windows Registry. Default is False. Set it True if you want to change default interpreter or script path. Raises: InvalidKeyStructure: If `force_override` is True but the registry value is not created by winotify or the key structure is invalid. """ self.app = format_name(app_id) self._key = SUBKEY.format(self.app) self.executable = executable self.path = script_path self._override = force_override self.reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) self._register() def _validate_structure(self): key = winreg.OpenKey(self.reg, self._key) try: winreg.OpenKey(key, SHELLKEY).Close() except WindowsError: raise InvalidKeyStructure(f'The registry from "{self.app}" was not created by winotify or the structure ' f'is invalid') def _key_exist(self) -> bool: try: winreg.OpenKey(HKEY, self._key).Close() return True except WindowsError: return False def _register(self): if self._key_exist() and self._override: self._validate_structure() # validate key = winreg.CreateKey(self.reg, self._key) with key: winreg.SetValueEx(key, '', 0, winreg.REG_SZ, f"URL:{self.app}") winreg.SetValueEx(key, 'URL Protocol', 0, winreg.REG_SZ, '') subkey = winreg.CreateKey(key, SHELLKEY) with subkey: winreg.SetValueEx(subkey, '', 0, winreg.REG_SZ, f'{self.executable} {self.path} %1')
View Source
def __init__(self, app_id: str, executable=PY_EXE, script_path: str = '', *, force_override=False): """ register app_id to Windows Registry as a protocol, eg. the app_id is "My Awesome App" can be called from browser or run.exe by typing "my-awesome-app:[Params]" Params can be a function name to call Args: app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported. executable: set the default interpreter or executable to run when a notification is clicked, default is `PY_EXE` which is python.exe. To hide cmd flashing when a notification is clicked, use `PYW_EXE`. script_path: The script path, usually `__file__`. force_override: If True, force replace the exists registry value in Windows Registry. Default is False. Set it True if you want to change default interpreter or script path. Raises: InvalidKeyStructure: If `force_override` is True but the registry value is not created by winotify or the key structure is invalid. """ self.app = format_name(app_id) self._key = SUBKEY.format(self.app) self.executable = executable self.path = script_path self._override = force_override self.reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) self._register()
register app_id to Windows Registry as a protocol, eg. the app_id is "My Awesome App" can be called from browser or run.exe by typing "my-awesome-app:[Params]" Params can be a function name to call
Args
- app_id: your app name, make it readable to your user. It can contain spaces, however special characters (eg. é) are not supported.
- executable: set the default interpreter or executable to run when a notification is clicked,
default is
PY_EXE
which is python.exe. To hide cmd flashing when a notification is clicked, usePYW_EXE
. - script_path: The script path, usually
__file__
. - force_override: If True, force replace the exists registry value in Windows Registry. Default is False. Set it True if you want to change default interpreter or script path.
Raises
- InvalidKeyStructure: If
force_override
is True but the registry value is not created by winotify or the key structure is invalid.
View Source
class Sound: def __init__(self, s): self.s = s def __str__(self): return self.s Default = Sound("ms-winsoundevent:Notification.Default") IM = Sound("ms-winsoundevent:Notification.IM") Mail = Sound("ms-winsoundevent:Notification.Mail") Reminder = Sound("ms-winsoundevent:Notification.Reminder") SMS = Sound("ms-winsoundevent:Notification.SMS") LoopingAlarm = Sound("ms-winsoundevent:Notification.Looping.Alarm") LoopingAlarm2 = Sound("ms-winsoundevent:Notification.Looping.Alarm2") LoopingAlarm3 = Sound("ms-winsoundevent:Notification.Looping.Alarm3") LoopingAlarm4 = Sound("ms-winsoundevent:Notification.Looping.Alarm4") LoopingAlarm6 = Sound("ms-winsoundevent:Notification.Looping.Alarm6") LoopingAlarm8 = Sound("ms-winsoundevent:Notification.Looping.Alarm8") LoopingAlarm9 = Sound("ms-winsoundevent:Notification.Looping.Alarm9") LoopingAlarm10 = Sound("ms-winsoundevent:Notification.Looping.Alarm10") LoopingCall = Sound("ms-winsoundevent:Notification.Looping.Call") LoopingCall2 = Sound("ms-winsoundevent:Notification.Looping.Call2") LoopingCall3 = Sound("ms-winsoundevent:Notification.Looping.Call3") LoopingCall4 = Sound("ms-winsoundevent:Notification.Looping.Call4") LoopingCall5 = Sound("ms-winsoundevent:Notification.Looping.Call5") LoopingCall6 = Sound("ms-winsoundevent:Notification.Looping.Call6") LoopingCall7 = Sound("ms-winsoundevent:Notification.Looping.Call7") LoopingCall8 = Sound("ms-winsoundevent:Notification.Looping.Call8") LoopingCall9 = Sound("ms-winsoundevent:Notification.Looping.Call9") LoopingCall10 = Sound("ms-winsoundevent:Notification.Looping.Call10") Silent = Sound("silent")