Callbacks with Python curses - python

I have code that looks like this:
stdscr.nodelay(1)
while True:
c = stdscr.getch()
if c != -1:
stdscr.addstr("%s was pressed\n" % c)
if time() - last > 1:
stdscr.addstr("time() == %s\n" % str(time()))
last = time()
However, I'm worried that my code is really wasteful/inefficient. Is there a callback mechanism available for curses? If not, what would be the canonical way to handle this kind of situation?

You could try Urwid instead.
Urwid provides a higher-level toolkit on top of curses and includes an event loop to handle keyboard and mouse input. It either uses it's own select-based event loop, or can hook into gevent or Twisted.
In addition to handling keyboard input efficiently you'll also have a host of options to handle user input with edit boxes, list controls and more.

How about using half-delay mode to make getch() blocking?
import time
import curses
stdscr = curses.initscr()
curses.halfdelay(10) # 10/10 = 1[s] inteval
try:
while True:
c = stdscr.getch()
if c != -1:
stdscr.addstr("%s was pressed\n" % c)
stdscr.addstr("time() == %s\n" % time.time())
finally:
curses.endwin()

Related

Interrupting a timer with spacebar

Say I want to time how long I can hold my breath, and I want to do that with Python. I have short script:
start = time()
try:
while True: pass
except KeyboardInterrupt:
print(time() - start)
This has the basic functionality I want, but it has an fatal shortcoming. After a long period of holding my breath, my mind might be a little fuzzy, and I might not find the coordination to hit Ctrl+c right away, and I might loose important data about my training.
The spacebar is much easier target to hit. Is there a simple way to make the loop stop when I press it?
EDIT: I'm on OSX
You need to put the console's keyboard (pty) driver into raw mode.
It is explained in this answer: What is the easiest way to detect key presses in python 3 on a linux machine?
Quoting liberally from that answer:
#! /usr/bin/env python3
import sys
import termios
import time
import tty
def hold_breath(fin):
orig_setting = termios.tcgetattr(fin)
tty.setraw(fin)
start = time.time()
try:
ch = fin.read(1)[0]
assert ch == ' '
finally:
print('You lasted %.03f seconds.\r' % (time.time() - start))
termios.tcsetattr(fin, termios.TCSADRAIN, orig_setting)
if __name__ == '__main__':
print('Hit space.')
hold_breath(sys.stdin)
Works fine on OS/X and Linux. If you wind up interrupting the program before it restores the original setting, then $ stty sane is your friend.
The answer depends on your OS. On Windows, this will stop on any key press, but you could look at the return value of msvcrt.getch() to determine if it was space. Now when you pass out and your face hits the keyboard it will stop on any key.
import time
import msvcrt
start = time.time()
while not msvcrt.kbhit(): # Indicates a key is waiting to be read
pass
end = time.time()
msvcrt.getch() # read and (in this case) throw away the key press.
print(end-start)
import sys
import termios
import contextlib
SPACE_BAR = 32
#contextlib.contextmanager
def raw_mode(file):
old_attrs = termios.tcgetattr(file.fileno())
new_attrs = old_attrs[:]
new_attrs[3] = new_attrs[3] & ~(termios.ECHO | termios.ICANON)
try:
termios.tcsetattr(file.fileno(), termios.TCSADRAIN, new_attrs)
yield
finally:
termios.tcsetattr(file.fileno(), termios.TCSADRAIN, old_attrs)
def main():
print 'exit with spacebar'
with raw_mode(sys.stdin):
try:
while True:
if sys.stdin.read(1) == chr(SPACE_BAR):
break
except (KeyboardInterrupt, EOFError):
pass
if __name__ == '__main__':
main()

Stop an infinite while loop repeatedly invoking os.system

Thank you guys for seeing my post.
First, the following is my code:
import os
print("You can create your own message for alarm.")
user_message = input(">> ")
print("\n<< Sample alarm sound >>")
for time in range(0, 3):
os.system('say ' + user_message) # this code makes sound.
print("\nOkay, The alarm has been set.")
"""
##### My problem is here #####
##### THIS IS NOT STOPPED #####
while True:
try:
os.system('say ' + user_message)
except KeyboardInterrupt:
print("Alarm stopped")
exit(0)
"""
My problem is that Ctrl + C does not work!
I tried changing position of try block, and making signal(SIGINT) catching function.
But those also does not work.
I have seen https://stackoverflow.com/a/8335212/5247212, https://stackoverflow.com/a/32923070/5247212, and other several answers about this problem.
I am using MAC OS(10.12.3) and python 3.5.2.
This is expected behaviour, as os.system() is a thin wrapper around the C function system(). As noted in the man page, the parent process ignores SIGINT during the execution of the command. In order to exit the loop, you have to manually check the exit code of the child process (this is also mentioned in the man page):
import os
import signal
while True:
code = os.system('sleep 1000')
if code == signal.SIGINT:
print('Awakened')
break
However, the preferred (and more pythonic) way to achieve the same result is to use the subprocess module:
import subprocess
while True:
try:
subprocess.run(('sleep', '1000'))
except KeyboardInterrupt:
print('Awakened')
break
Your code would then look like something like this:
import subprocess
print("You can create your own message for alarm.")
user_message = input(">> ")
print("\n<< Sample alarm sound >>")
for time in range(0, 3):
subprocess.run(['say', user_message]) # this code makes sound.
print("\nOkay, The alarm has been set.")
while True:
try:
subprocess.run(['say', user_message])
except KeyBoardInterrupt:
print("Alarm terminated")
exit(0)
As an added note, subprocess.run() is only available in Python 3.5+. You can use subprocess.call() to achieve the same effect in older versions of Python.
Also catch "SystemExit"
except (KeyboardInterrupt, SystemExit):
print("Alarm stopped")
The problem seems to be that Ctrl+C is captured by the subprocess you call via os.system. This subprocess reacts correspondingly, probably by terminating whatever it is doing. If so, the return value of os.system() will be not zero. You can use that to break the while loop.
Here's an example that works with me (substituting say by sleep):
import os
import sys
while True:
try:
if os.system('sleep 1 '):
raise KeyboardInterrupt
except KeyboardInterrupt:
print("Alarm stopped")
sys.exit(0)
If Ctrl-C is captured by the subprocess, which is the case here, the simplest solution is to check the return value of os.system(). For example in my case it returns value of 2 if Ctrl-C stops it, which is a SIGINT code.
import os
while True:
r = os.system(my_job)
if r == 2:
print('Stopped')
break
elif r != 0:
print('Some other error', r)

Dynamic Threading

Hello I am having an issue with Python Threads.
What I am aiming to do is create a function that launches other functions as a thread when called. Unfortunately I am running into two issues (Besides my lack of knowledge in Python.)
1: If I put quotes around: "globals()[T_Name[i]]()" it treats it as a string and executes the code normally.
2: If I omit the quotes around globals()[T_Name[i]]() it launches the first function immediately and does not process through the rest of the script to launch it as a thread.
If anyone could provide some insight I apologize for the formatting I will be bumping it up to PEP 8 standards eventually.
Code:
import threading
import time
T_Name=("Write_Done", "Write_Pin")
T_Time=[]
Tr=[]
for i, Nu in enumerate(T_Name):
Tr.append("T" + str(i))
T_Time.append("0")
def Write_Done():
while True:
print("Done")
time.sleep(5)
def Write_Pin():
while True:
print("Pin")
time.sleep(15)
def Thread_Checker():
while True:
time.sleep(5)
for i, TH in enumerate(T_Time):
if (time.time() - int(TH)) < 30:
pass
#thread is still rocking
else:
#thread has failed Time to get her done.
Tr[i] = threading.Thread(target=("globals()[T_Name[i]]()"))
print("starting" + T_Name[i])
Tr[i].daemon = True
Tr[i].start()
print("Test if alive")
if Tr[0].is_alive():
print("I LIVE!")
else:
print("I ded")
Thread_Checker()
Use a lambda function to create something that is actually callable as the target, but defers the call of what you want until the target is called.
Tr[i] = threading.Thread(target=lambda: globals()[T_Name[i]]())

Block pyhook from so-generated keystrokes?

I'm using pyhook and pyhk to map keystrokes on a windows XP machine, and it works fine except for when the keystroke (say, ctrl+z) already exists in the application. In that case, the ctrl+z passes to the application and triggers the action that has been mapped to it.
If you are familiar with autohotkey, note that autohotkey gets around this by defining hotkeys that can optionally be passed to the underlying application. Here's a bit of codes that gets at the idea. Note that I'm trying to keep track of when the ctrl key is down.
import pythoncom, pyHook
control_down = False
def OnKeyboardEvent_up(event):
global control_down
if event.Key=='Lcontrol' or event.Key=='Rcontrol':
control_down=False
return True
def OnKeyboardEvent(event,action=None,key='Z',context=None):
global control_down
if event.Key=='Lcontrol' or event.Key=='Rcontrol':
control_down=True
if control_down and event.Key==key:
print 'do something'
return False
if event.Key=='Pause':
win32gui.PostQuitMessage(1)
return False
# return True to pass the event to other handlers
return True
if __name__ == '__main__':
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.KeyUp = OnKeyboardEvent_up
hm.HookKeyboard() # set the hook
pythoncom.PumpMessages() # wait forever
Any help appreciated.
Thanks!
If you're insterested in Windows only, you can use win API, e.g. via ctypes:
>>> from ctypes import windll
>>> windll.user32.RegisterHotKey(0, -1, 0x0002, 0x5a)
After running these lines of code Ctrl (code = 0x0002) + Z (code = 0x5a) combination doesn't work any more in Python REPL.
So you should better look at what windows are those hotkey registered. More information you can find in MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646309(v=vs.85).aspx
I may totally wrong here, but from my understanding of the pyHook documentation, in order to prevent the key presses from being sent to another application you need to change the return True in def OnKeyboardEvent_up(event): and OnKeyboardEvent(event,action=None,key='Z',context=None): to return False (or anything else other than True.
In windows I noticed that if the code "Do something" takes too long than the key codes are passed to the application even if you return False in the OnKeyboardEvent handler. The solution for me was to pack the code in a thread launched by the key or key combination press. This is sufficiently fast to make the return False work as expected.
def OnKeyboardEvent(event):
if event.key == myKey:
myThred = threading.Thread(target=doSomething_Function).start()
return False

On Windows, how can I install the lowest level global keyboard hook possible?

I'm working on a custom arcade launcher in python on Windows. I want to choose system and game, then launch the emulator - and require a certain key combination to kill the emulator. All of my key hooks work when testing with random applications, but when I actually launch the emulators (e.g. Nestopia), my key hooks fail to fire. I am currently using RegisterHotKey, which gets events but not the hotkeys. Anyone have an idea how to install something low enough to actually get the event before Nestopia? Here's my code:
import ctypes
import win32con
from ctypes import wintypes
from ctypes import byref
user32 = ctypes.windll.user32
class SimpleKeyboardHook:
def getNextId(self):
SimpleKeyboardHook._id += 1
return SimpleKeyboardHook._id
# modifiers is a bitmask with win32con.[MOD_SHIFT, MOD_ALT, MOD_CONTROL, MOD_WIN]
def waitFor(self, key, modifiers):
# coerce to 0 if necessary
modifiers = modifiers or 0
id = self.getNextId()
hk = user32.RegisterHotKey(None, id, modifiers, key)
print "register hotkey: ",hk
if not hk:
print "Unable to register hotkey for key ", key
return False
print "registered id", id
try:
msg = wintypes.MSG()
while user32.GetMessageA(byref(msg), None, 0, 0) != 0:
print "got message",msg.message,"which is not",win32con.WM_HOTKEY
if msg.message == win32con.WM_HOTKEY:
print "got hotkey"
if msg.wParam == id:
print "found proper hotkey"
return True
user32.TranslateMessage(byref(msg))
user32.DispatchMessageA(byref(msg))
finally:
user32.UnregisterHotKey(None, id)
return False
SimpleKeyboardHook._id = 0
You should definitely look at SetWindowsHookEx from user32. These functions allow you to register global keyboard hooks. (Just don't forget to pass them on by calling CallNextHookEx.)
Link: http://msdn.microsoft.com/en-us/library/ms644990(v=vs.85).aspx
I have no idea how to do that from python though, sorry.
Have you tried using pyHook over on SourceForge yet? You can check DaniWeb for example usage.

Categories