I was writing code using python and opencv. If no face is detected it should notify the windows10 user. I used win10toast
import time
from win10toast import ToastNotifier
notif = ToastNotifier()
notif.show_toast(title= "Nusrat", msg= "One baby is missing")
time.sleep(10)
but it stops the code when notification is being shown. Is there any way I can show notification using gui or anything but that will not stop the code?
The library's Github repo shows how to avoid blocking in the landing page example :
from win10toast import ToastNotifier
import time
toaster = ToastNotifier()
toaster.show_toast("Example two",
"This notification is in it's own thread!",
icon_path=None,
duration=5,
threaded=True)
# Wait for threaded notification to finish
while toaster.notification_active(): time.sleep(0.1)
You need to add the threaded=True parameter. Sleeping is only needed if you want to check whether the notification is still active.
Displaying notification is not a blocking operation. The library's code forces a sleep equal to duration before closing the window :
# take a rest then destroy
sleep(duration)
DestroyWindow(self.hwnd)
UnregisterClass(self.wc.lpszClassName, None)
threaded=True will execute the show/sleep/destroy flow in a separate thread. Frankly, there are cleaner ways to do that, eg using a one-off timer.
Related
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:
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?
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.
i am currently trying to write a short script that will rickroll (open a youtube link) while the user is watching and can't interfere.
I have managed to open insert the link slowly letter by letter and am now trying to block user inputs.
I have tried using the ctypes import to block all inputs, run the script and then unblock again, but it somehow won't block the input. I'm just receiving my RuntimeError message.
How do i fix it, so the inputs get blocked?
Thanks in advance!
Heres the code:
import subprocess
import pyautogui
import time
import ctypes
from ctypes import wintypes
BlockInput = ctypes.windll.user32.BlockInput
BlockInput.argtypes = [wintypes.BOOL]
BlockInput.restype = wintypes.BOOL
blocked = BlockInput(True)
if blocked:
try:
subprocess.Popen(["C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",])
time.sleep(3)
pyautogui.write('www.youtube.com/watch?v=DLzxrzFCyOs', interval= 0.5)
pyautogui.hotkey('enter')
finally:
unblocked = BlockInput(False)
else:
raise RuntimeError('Input is already blocked by another thread')
You can use the keyboard module to block all keyboard inputs and the mouse module to constantly move the mouse, preventing the user from moving it.
See these links for more details:
https://github.com/boppreh/keyboard
https://github.com/boppreh/mouse
This blocks all the keys on the keyboard (the 150 is large enough to ensure all keys are blocked).
#### Blocking Keyboard ####
import keyboard
#blocks all keys of keyboard
for i in range(150):
keyboard.block_key(i)
This effectively blocks mouse-movement by constantly moving the mouse to position (1,0).
#### Blocking Mouse-movement ####
import threading
import mouse
import time
global executing
executing = True
def move_mouse():
#until executing is False, move mouse to (1,0)
global executing
while executing:
mouse.move(1,0, absolute=True, duration=0)
def stop_infinite_mouse_control():
#stops infinite control of mouse after 10 seconds if program fails to execute
global executing
time.sleep(10)
executing = False
threading.Thread(target=move_mouse).start()
threading.Thread(target=stop_infinite_mouse_control).start()
#^failsafe^
And then your original code here (the if statement and try/catch block are no longer necessary).
#### opening the video ####
import subprocess
import pyautogui
import time
subprocess.Popen(["C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",])
time.sleep(3)
pyautogui.write('www.youtube.com/watch?v=DLzxrzFCyOs', interval = 0.5)
pyautogui.hotkey('enter')
#### stops moving mouse to (1,0) after video has been opened
executing = False
Just a few notes:
The mouse-moving is hard to stop from outside of the program (it's basically impossible to close the program when it is executing, especially as the keyboard is also being blocked), that's why I put in the failsafe, which stops moving the mouse to (1,0) after 10 seconds.
(On Windows) Control-Alt-Delete does allow Task Manager to be opened and then the program can be force-stopped from there.
This doesn't stop the user from clicking the mouse, which can sometimes prevent the YouTube link from being typed in full (i.e. a new tab can be opened)
See a full version of the code here:
https://pastebin.com/WUygDqbG
you could do something like this to block both keyboard and mouse input
from ctypes import windll
from time import sleep
windll.user32.BlockInput(True) #this will block the keyboard input
sleep(15) #input will be blocked for 15 seconds
windll.user32.BlockInput(False) #now the keyboard will be unblocked
Here's a function for blocking keyboard and mouse input. You can pass a number to the blockMouseAndKeys function to adjust the timeout period:
import os
import time
import pyautogui
from threading import Thread
from keyboard import block_key
def blockMouseAndKeys(timeout=5):
global blocking
blockStartTime = time.time()
pyautogui.FAILSAFE = False
blocking = True
try: float(timeout)
except: timeout = 5
def blockKeys(timeout):
global blocking
while blocking:
if timeout:
if time.time()-blockStartTime > timeout:
print(f'Keyboard block timed out after {timeout}s.')
return
for i in range(150):
try: block_key(i)
except: pass
def blockMouse(timeout):
global blocking
while blocking:
def resetMouse(): pyautogui.moveTo(5,5)
Thread(target=resetMouse).start()
if timeout:
if time.time()-blockStartTime > timeout:
print(f'Mouse block timed out after {timeout}s.')
return
def blockTimeout(timeout):
global blocking
time.sleep(timeout)
blocking = False
pyautogui.FAILSAFE = False
print('Done blocking inputs!')
print('Blocking inputs...')
Thread(target=blockKeys, args=[timeout]).start()
Thread(target=blockMouse, args=[timeout]).start()
Thread(target=blockTimeout, args=[timeout]).start()
blockMouseAndKeys(timeout=10)
os.startfile('https://www.youtube.com/watch?v=DLzxrzFCyOs')
I am having some difficulty finding out how to send local notifications on Catalina using pyobjc.
The closes example I have seen is this:
PyObjC "Notifications are not allowed for this application"
Edit (June 27, 2020): I've created a package which has functionality to display notifications on Mac OS here. It will use PyObjC to create and display notifications. If It does not work for whatever reason, it will fallback to AppleScript notifications with osascript. I did some testing and found that the PyObjC notifications work on some devices but don't on some.
Answer:
I have also been searching for this answer, so I'd like to share what I've found:
The first thing you'll notice is that the function notify() defines a class, then returns an instance of it. You might be wondering why you can't directly call Notification.send(params). I tried it, but I was getting an error with the PyObjC, which I am unfortunately unable to fix:
# Error
class Notification(NSObject):
objc.BadPrototypeError: Objective-C expects 1 arguments, Python argument has 2 arguments for <unbound selector send of Notification at 0x10e410180>
Now onto the code:
# vscode may show the error: "No name '...' in module 'Foundation'; you can ignore it"
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject, NSDate
from PyObjCTools import AppHelper
def notify(
title='Notification',
subtitle=None, text=None,
delay=0,
action_button_title=None,
action_button_callback=None,
other_button_title=None,
other_button_callback=None,
reply_placeholder=None,
reply_callback=None
):
class Notification(NSObject):
def send(self):
notif = NSUserNotification.alloc().init()
if title is not None:
notif.setTitle_(title)
if subtitle is not None:
notif.setSubtitle_(subtitle)
if text is not None:
notif.setInformativeText_(text)
# notification buttons (main action button and other button)
if action_button_title:
notif.setActionButtonTitle_(action_button_title)
notif.set_showsButtons_(True)
if other_button_title:
notif.setOtherButtonTitle_(other_button_title)
notif.set_showsButtons_(True)
# reply button
if reply_callback:
notif.setHasReplyButton_(True)
if reply_placeholder:
notif.setResponsePlaceholder_(reply_placeholder)
NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
# setting delivery date as current date + delay (in seconds)
notif.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(delay, NSDate.date()))
# schedule the notification send
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notif)
# on if any of the callbacks are provided, start the event loop (this will keep the program from stopping)
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
def userNotificationCenter_didDeliverNotification_(self, center, notif):
print('delivered notification')
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
reply_callback(reply_text)
AppHelper.stopEventLoop()
# create the new notification
new_notif = Notification.alloc().init()
# return notification
return new_notif
def main():
n = notify(
title='Notification',
delay=0,
action_button_title='Action',
action_button_callback=lambda: print('Action'),
# other_button_title='Other',
# other_button_callback=lambda: print('Other'),
reply_placeholder='Enter your reply please',
reply_callback=lambda reply: print('Replied: ', reply),
)
n.send()
if __name__ == '__main__':
main()
Explanation
The notify() function takes in quite a few parameters (they are self-explanatory). The delay is how many seconds later the notification will appear. Note that if you set a delay that's longer than the execution of the program, the notification will be sent ever after the program is being executed.
You'll see the button parameters. There are three types of buttons:
Action button: the dominant action
Other button: the secondary action
Reply button: the button that opens a text field and takes a user input. This is commonly seen in messaging apps like iMessage.
All those if statements are setting the buttons appropriately and self explanatory. For instance, if the parameters for the other button are not provided, a Other button will not be shown.
One thing you'll notice is that if there are buttons, we are starting the console event loop:
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
This is a part of Python Objective-C. This is not a good explanation, but it basically keeps program "on" (I hope someone cane give a better explanation).
Basically, if you specify that you want a button, the program will continue to be "on" until AppHelper.stopEventLoop() (more about this later).
Now there are some "hook" functions:
userNotificationCenter_didDeliverNotification_(self, notification_center, notification): called when the notification is delivered
userNotificationCenter_didActivateNotification_(self, notification_center, notification): called when the user interacts with the notification (clicks, clicks action button, or reply) (documentation)
There surely are more, but I do not think there is a hook for the notification being dismissed or ignored, unfortunately.
With userNotificationCenter_didActivateNotification_, we can define some callbacks:
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
# action button callback
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
# reply button callback
reply_callback(reply_text)
AppHelper.stopEventLoop()
There are different activation types for the types of actions. The text from the reply action can also be retrieved as shown.
You'll also notice the AppHelper.stopEventLoop() at the end. This means to "end" the program from executing, since the notification has been dealt with by the user.
Now let's address all the problems with this solution.
Problems
The program will never stop if the user does not interact with the notification. The notification will slide away into the notification center and may or may never be interacted with. As I stated before, there's no hook for notification ignored or notification dismissed, so we cannot call AppHelper.stopEventLoop() at times like this.
Because AppHelper.stopEventLoop() is being run after interaction, it is not possible to send multiple notifications with callbacks, as the program will stop executing after the first notification is interacted with.
Although I can show the Other button (and give it text), I couldn't find a way to give it a callback. This is why I haven't addressed it in the above code block. I can give it text, but it's essentially a dummy button as it cannot do anything.
Should I still use this solution?
If you want notifications with callbacks, you probably should not, because of the problems I addressed.
If you only want to show notifications to alert the user on something, yes.
Other solutions
PYNC is a wrapper around terminal-notifier. However, both received their last commit in 2018. Alerter seems to be a successor to terminal-notifier, but there is not Python wrapper.
You can also try running applescript to send notifications, but you cannot set callbacks, nor can you change the icon.
I hope this answer has helped you. I am also trying to find out how to reliably send notifications with callbacks on Mac OS. I've figured out how to send notifications, but callbacks is the issue.
I have a Raspberry Pi running the Flask framework to make a very application-specific http server. Doing an HTTP GET of a particular page on the Pi server triggers a GPIO action (e.g.: check if button is pressed, LED on, LED off and so on). Because I will poll the /status page to see if the button is pressed at 250ms intervals, I do not want the polling of the /unlock page to use timer.sleep(0.25) to close a relay for 250ms.
I want to set up an action that when called, will wait 250ms, turn the LED off then quietly go away. I tried both threading and sched. Threading works ONCE and throws an error when I pass through the code a second time (raise RuntimeError("threads can only be started once")).
#
# lots of other defines and includes
#
import threading
# set up function to turn de-energize relay (and Green LED)
def LED_Off_Wait():
GPIO.output(GREEN, False)
# set up timed thread
LED_Off = threading.Timer(0.25, LED_Off_Wait)
#
# flask framework page definitions go here
#
# unlock page where we energize and de-energize a lock
#app.route('/unlock')
def thing_unlock():
GPIO.output(GREEN, True) # energize the relay (and green LED)
# time.sleep(0.25) # don't want to do this
# GPIO.output(GREEN, False) #don't want to do this
LED_Off.start() # trigger the non-blocking thread to turn off
# the GPIO pin in 0.25s
# more flask stuff...
response = make_response(render_template('body.html', response='unlocked'))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
I tried sched as well and it does not work for me, although it may be because I'm programming from the inside-out, learning by doing:
import sched
def LED_Off():
GPIO.output(GREEN, False)
# set up scheduled action, as nearly as I can determine from my reading
LED_ON_Delay = sched.scheduler(time.time, time.sleep) # set up scheduler (no sleep!)
LED_ON_Delay.enter( 1, 1, LED_Off,()) # schedule LED_Off for 0.25s in the future
#
# again, more Flask stuff here
#
#app.route('/unlock')
def thing_unlock():
GPIO.output(GREEN, True)
# time.sleep(0.25) # don't want to do this
# GPIO.output(GREEN, False) # don't want to do this
LED_ON_Delay.run() #trigger "non-blocking sleep"
response = make_response(render_template('body.html', response='unlocked'))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
# more Flask stuff...
Bottom Line: how do I spin off a task that will turn the GPIO off after a certain time (or do anything else for that matter) without an error if I try to do it twice?
With the help of a friend, the answer was simpler than I expected: Lambda functions. First, import threading then declare the function that will do what you want (turn off the LED) after a time interval using threading.Timer():
def LED_Off_Wait( wait_time,IO_port ):
threading.Timer(wait_time, lambda: GPIO.output(IO_port, False)).start()
In this case, I need to use it in more than one place so I pass it the delay time and the GPIO pin it needs to act on.
When I need to turn the LED off after a delay inside another function, I call it like this:
#app.route('/unlock')
def prop_unlock():
GPIO.output(GREEN, True)
LED_Off_Wait( 0.5, GREEN )
response = make_response(render_template('body.html', response='unlocked'))
response.headers['Access-Control-Allow-Origin'] = '*'
return response
For completeness, GREEN has been previously defined like this:
GREEN = 24
# Use GPIO pin numbers
GPIO.setmode(GPIO.BCM)
GPIO.setup(GREEN, GPIO.OUT)
Works like a charm.
In my gevent-based program, I've got a thread somewhere which is suck in a loop something like:
while True:
gevent.sleep(0)
How can I figure out which thread this is? Is it possible to list (and get stack traces for) the running threads?
method 1. TimeOut
I use this in my code to keep track greenlets that potentially block. A NodeTaskTimeout is raised when this happens. Just wrap your jobs in a Timeout or provide them with a TimeOut object.
with Timeout(90, False):
task_jobs.join()
if task_jobs:
print 'task jobs killed', task_jobs
task_jobs.kill()
if settings.DEBUG:
raise NodeTaskTimeout
This print's out the task if it hangs/blocks/takes to long.
Especially nasty are those jobs that depend on each other and cause a deadlock
job1 /thread -> job2/thread2 -> job3/thread3 and job/thread3 only finishes when job1 is done wich will never happen because job2 is not done and job2 is not done because of job3 is not done.. you get the idea ;)
Mehtod 2 Settrace
http://www.rfk.id.au/blog/entry/detect-gevent-blocking-with-greenlet-settrace/
but you also need to put code that you suspect to be "spinning" in a with block.
There is an official API for checking the block, it will pinpoint the exact line which caused the block, check code example below:
# gevent 1.3.7
# greenlet 0.4.15
# zope.event 4.4
import gevent
from gevent import config, get_hub
from gevent.events import IEventLoopBlocked
import logging
from pprint import pformat
import time
import zope.event
# initial logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
# setup gevent config
# enable the monitor thread
config.monitor_thread = True
config.max_blocking_time = 4
# start the monitor thread
hub = get_hub()
monitor = hub.start_periodic_monitoring_thread()
# register the event to logging system
def g(event):
log.error('Greenlet: {}, exceed the max blocking time: {}'.format(event.greenlet, event.blocking_time))
log.error(pformat(event.info))
event = IEventLoopBlocked()
zope.event.subscribers.append(g)
# you can also create you own monitoring function
# def check(hub):
# print('< periodic check in monitoring thread >')
#
# monitor.add_monitoring_function(check, 1)
def gl1():
# use time.sleep to trigger block
log.info('block at gl1 for 2 seconds')
time.sleep(2)
log.info('leave gl1 now')
def gl2():
# use time.sleep to trigger block
log.info('block at gl2 for 6 seconds should be detected')
time.sleep(6)
log.info('leave gl2 now')
def gl3():
# gevent.sleep will not block
log.info('gl3 will not block since it use gevent.sleep')
gevent.sleep(8)
log.info('leave gl3 now')
gevent.joinall([
gevent.spawn(gl3),
gevent.spawn(gl1),
gevent.spawn(gl2),
])
I hope it helps!