I'm trying to write a simple macro recorder in Python for OSX - something which can capture mouse and key events as the script runs in the background and replay them. I can use autopy for the latter, is there a similarly simple library for the former?
I ran across a few solutions to this problem today and figured I'd circle back around and share here so others could save the search time.
A nifty cross platform solution for simulating keyboard and mouse input: http://www.autopy.org/
I also found a brief but working (As of Mountain Lion) example of how to globally log key strokes. The only caveat is that you have to use Python2.6 as 2.7 doesn't seem to have the objc modules available.
#!/usr/bin/python2.6
"""PyObjC keylogger for Python
by ljos https://github.com/ljos
"""
from Cocoa import *
import time
from Foundation import *
from PyObjCTools import AppHelper
class AppDelegate(NSObject):
def applicationDidFinishLaunching_(self, aNotification):
NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, handler)
def handler(event):
NSLog(u"%#", event)
def main():
app = NSApplication.sharedApplication()
delegate = AppDelegate.alloc().init()
NSApp().setDelegate_(delegate)
AppHelper.runEventLoop()
if __name__ == '__main__':
main()
For mouse input, simply replace NSKeyDownMask with the relevant mask from the list available here: http://developer.apple.com/library/mac/#documentation/cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/Reference/Reference.html#//apple_ref/occ/clm/NSEvent/addGlobalMonitorForEventsMatchingMask:handler:
For example, NSMouseMovedMask works for tracking mouse movements.
From there, you can access event.locationInWindow() or other attributes.
Here's a solution without using curses:
http://docs.python.org/faq/library.html#how-do-i-get-a-single-keypress-at-a-time
This question was asked some time back here - Python cross-platform listening for keypresses?
You might find the sample code there helpful!
I know that you can use curses for capturing key input, but im not sure about mouse input. Not only that but if im not mistaken it is included in the std library with 2.7.2.
There doesn't seem to be a way of doing this in Python on OSX.
Calvin Cheng, Thank you. your suggestion works on OS X 10.8.5.
Code from http://docs.python.org/faq/library.html#how-do-i-get-a-single-keypress-at-a-time
#!/usr/bin/python
import termios, fcntl, sys, os
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while 1:
try:
c = sys.stdin.read(1)
print "Got character", repr(c)
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
One more solution
Key Listeners in python?
Related
How do I make my python script wait until the user presses any key?
In Python 3, use input():
input("Press Enter to continue...")
In Python 2, use raw_input():
raw_input("Press Enter to continue...")
This only waits for the user to press enter though.
On Windows/DOS, one might want to use msvcrt. The msvcrt module gives you access to a number of functions in the Microsoft Visual C/C++ Runtime Library (MSVCRT):
import msvcrt as m
def wait():
m.getch()
This should wait for a key press.
Notes:
In Python 3, raw_input() does not exist.
In Python 2, input(prompt) is equivalent to eval(raw_input(prompt)).
In Python 3, use input():
input("Press Enter to continue...")
In Python 2, use raw_input():
raw_input("Press Enter to continue...")
On my linux box, I use the following code. This is similar to code I've seen elsewhere (in the old python FAQs for instance) but that code spins in a tight loop where this code doesn't and there are lots of odd corner cases that code doesn't account for that this code does.
def read_single_keypress():
"""Waits for a single keypress on stdin.
This is a silly function to call if you need to do it a lot because it has
to store stdin's current setup, setup stdin for reading single keystrokes
then read the single keystroke then revert stdin back after reading the
keystroke.
Returns a tuple of characters of the key that was pressed - on Linux,
pressing keys like up arrow results in a sequence of characters. Returns
('\x03',) on KeyboardInterrupt which can happen when a signal gets
handled.
"""
import termios, fcntl, sys, os
fd = sys.stdin.fileno()
# save old state
flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
attrs_save = termios.tcgetattr(fd)
# make raw - the way to do this comes from the termios(3) man page.
attrs = list(attrs_save) # copy the stored version to update
# iflag
attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
| termios.ISTRIP | termios.INLCR | termios. IGNCR
| termios.ICRNL | termios.IXON )
# oflag
attrs[1] &= ~termios.OPOST
# cflag
attrs[2] &= ~(termios.CSIZE | termios. PARENB)
attrs[2] |= termios.CS8
# lflag
attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
| termios.ISIG | termios.IEXTEN)
termios.tcsetattr(fd, termios.TCSANOW, attrs)
# turn off non-blocking
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
# read a single keystroke
ret = []
try:
ret.append(sys.stdin.read(1)) # returns a single character
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save | os.O_NONBLOCK)
c = sys.stdin.read(1) # returns a single character
while len(c) > 0:
ret.append(c)
c = sys.stdin.read(1)
except KeyboardInterrupt:
ret.append('\x03')
finally:
# restore old state
termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
return tuple(ret)
If you are ok with depending on system commands you can use:
from __future__ import print_function
import os
import platform
if platform.system() == "Windows":
os.system("pause")
else:
os.system("/bin/bash -c 'read -s -n 1 -p \"Press any key to continue...\"'")
print()
It has been verified to work with Python 2 and 3 on Windows, Linux and Mac OS X.
Simply using
input("Press Enter to continue...")
will cause the following error when using Python 2:
SyntaxError: expected EOF while parsing.
Simple fix for the code to work on both Python 2 and Python 3 is to use:
try:
input("Press enter to continue")
except SyntaxError:
pass
Cross Platform, Python 2/3 code:
# import sys, os
def wait_key():
''' Wait for a key press on the console and return it. '''
result = None
if os.name == 'nt':
import msvcrt
result = msvcrt.getwch()
else:
import termios
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
try:
result = sys.stdin.read(1)
except IOError:
pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
return result
I removed the fctl/non-blocking stuff because it was giving IOErrors and I didn't need it. I'm using this code specifically because I want it to block. ;)
Addendum:
I implemented this in a package on PyPI with a lot of other goodies called console:
>>> from console.utils import wait_key
>>> wait_key()
'h'
The python manual provides the following:
import termios, fcntl, sys, os
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while 1:
try:
c = sys.stdin.read(1)
print "Got character", repr(c)
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
which can be rolled into your use case.
I don't know of a platform independent way of doing it, but under Windows, if you use the msvcrt module, you can use its getch function:
import msvcrt
c = msvcrt.getch()
print 'you entered', c
mscvcrt also includes the non-blocking kbhit() function to see if a key was pressed without waiting (not sure if there's a corresponding curses function). Under UNIX, there is the curses package, but not sure if you can use it without using it for all of the screen output. This code works under UNIX:
import curses
stdscr = curses.initscr()
c = stdscr.getch()
print 'you entered', chr(c)
curses.endwin()
Note that curses.getch() returns the ordinal of the key pressed so to make it have the same output I had to cast it.
I am new to python and I was already thinking I am too stupid to reproduce the simplest suggestions made here.
It turns out, there's a pitfall one should know:
When a python-script is executed from IDLE, some IO-commands seem to behave completely different (as there is actually no terminal window).
Eg. msvcrt.getch is non-blocking and always returns $ff.
This has already been reported long ago (see e.g. https://bugs.python.org/issue9290 ) - and it's marked as fixed, somehow the problem seems to persist in current versions of python/IDLE.
So if any of the code posted above doesn't work for you, try running the script manually, and NOT from IDLE.
If you want to wait for enter (so the user knocking the keyboard does not cause something un-intended to happen) use
sys.stdin.readline()
You could use the keyboard library:
import keyboard
keyboard.wait('space')
print('space was pressed, continuing...')
os.system seems to always invoke sh, which does not recognize the s and n options for read. However the read command can be passed to bash:
os.system("""bash -c 'read -s -n 1 -p "Press any key to continue..."'""")
If you want to see if they pressed a exact key (like say 'b') Do this:
while True:
choice = raw_input("> ")
if choice == 'b' :
print "You win"
input("yay")
break
At the moment I'm using AutoHotKey for triggering scripts via keyboard shortcuts. I like programming in Python much more than dealing with AutoHotKey and every time I touch my AutoHotKey scripts I wish I could simply write clean AutoHotkey code.
Let's take the simply AutoHotKey script that prints hello world in whatever window I am when I press the insert key:
foo(){
send, "hello world"
}
Insert:: foo()
How would I do the same thing in Python3 on Windows?
You will have to hook into the gizzards of windows to achieve this. You'd probably have to do that via the ctypes or CFFI module, because the necessary API's don't seem to exist in pywin32.
According to this page, you will need to use three windows API calls:
SetWindowsHookEx, using the idHook WH_KEYBOARD_LL to set up a keyboard hook; a function that peeks at keyboard events.
UnhookWindowsHookEx to eventually remove the hook.
And if you're not interested in a particular keypress, CallNextHookEx to pass it on to the next hook.
You can combine two answers from StackOverflow to (almost) solve this issue.
Use this answer (by tehvan) to create a getch() like method to read in one character from the user without the need for a \n. (repeated below from the answer)
Use the Python3 version of this answer (by Barafu Albino) to call the previously defined _Getch() class in a separate process.
Please note that the following code works for Python3 only and uses any key to stop the process, not just the insert key.
# This code is a combination of two StackOverflow answers
# (links given in the answer)
# ------- Answer 1 by tehvan -----------------------------------
class _Getch:
"""Gets a single character from standard input.
Does not echo to the screen."""
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
getch = _Getch()
# -------- Answer 2 by Barafu Albino (modified) -------
import _thread
def input_thread(a_list):
_Getch().__call__()
a_list.append(True)
def do_stuff():
a_list = []
_thread.start_new_thread(input_thread, (a_list,))
print('Press any key to stop.')
while not a_list:
pass
# This is where you can put the stuff
# you want to do until the key is pressed
print('Stopped.')
do_stuff()
How do I make my python script wait until the user presses any key?
In Python 3, use input():
input("Press Enter to continue...")
In Python 2, use raw_input():
raw_input("Press Enter to continue...")
This only waits for the user to press enter though.
On Windows/DOS, one might want to use msvcrt. The msvcrt module gives you access to a number of functions in the Microsoft Visual C/C++ Runtime Library (MSVCRT):
import msvcrt as m
def wait():
m.getch()
This should wait for a key press.
Notes:
In Python 3, raw_input() does not exist.
In Python 2, input(prompt) is equivalent to eval(raw_input(prompt)).
In Python 3, use input():
input("Press Enter to continue...")
In Python 2, use raw_input():
raw_input("Press Enter to continue...")
On my linux box, I use the following code. This is similar to code I've seen elsewhere (in the old python FAQs for instance) but that code spins in a tight loop where this code doesn't and there are lots of odd corner cases that code doesn't account for that this code does.
def read_single_keypress():
"""Waits for a single keypress on stdin.
This is a silly function to call if you need to do it a lot because it has
to store stdin's current setup, setup stdin for reading single keystrokes
then read the single keystroke then revert stdin back after reading the
keystroke.
Returns a tuple of characters of the key that was pressed - on Linux,
pressing keys like up arrow results in a sequence of characters. Returns
('\x03',) on KeyboardInterrupt which can happen when a signal gets
handled.
"""
import termios, fcntl, sys, os
fd = sys.stdin.fileno()
# save old state
flags_save = fcntl.fcntl(fd, fcntl.F_GETFL)
attrs_save = termios.tcgetattr(fd)
# make raw - the way to do this comes from the termios(3) man page.
attrs = list(attrs_save) # copy the stored version to update
# iflag
attrs[0] &= ~(termios.IGNBRK | termios.BRKINT | termios.PARMRK
| termios.ISTRIP | termios.INLCR | termios. IGNCR
| termios.ICRNL | termios.IXON )
# oflag
attrs[1] &= ~termios.OPOST
# cflag
attrs[2] &= ~(termios.CSIZE | termios. PARENB)
attrs[2] |= termios.CS8
# lflag
attrs[3] &= ~(termios.ECHONL | termios.ECHO | termios.ICANON
| termios.ISIG | termios.IEXTEN)
termios.tcsetattr(fd, termios.TCSANOW, attrs)
# turn off non-blocking
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save & ~os.O_NONBLOCK)
# read a single keystroke
ret = []
try:
ret.append(sys.stdin.read(1)) # returns a single character
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save | os.O_NONBLOCK)
c = sys.stdin.read(1) # returns a single character
while len(c) > 0:
ret.append(c)
c = sys.stdin.read(1)
except KeyboardInterrupt:
ret.append('\x03')
finally:
# restore old state
termios.tcsetattr(fd, termios.TCSAFLUSH, attrs_save)
fcntl.fcntl(fd, fcntl.F_SETFL, flags_save)
return tuple(ret)
If you are ok with depending on system commands you can use:
from __future__ import print_function
import os
import platform
if platform.system() == "Windows":
os.system("pause")
else:
os.system("/bin/bash -c 'read -s -n 1 -p \"Press any key to continue...\"'")
print()
It has been verified to work with Python 2 and 3 on Windows, Linux and Mac OS X.
Simply using
input("Press Enter to continue...")
will cause the following error when using Python 2:
SyntaxError: expected EOF while parsing.
Simple fix for the code to work on both Python 2 and Python 3 is to use:
try:
input("Press enter to continue")
except SyntaxError:
pass
Cross Platform, Python 2/3 code:
# import sys, os
def wait_key():
''' Wait for a key press on the console and return it. '''
result = None
if os.name == 'nt':
import msvcrt
result = msvcrt.getwch()
else:
import termios
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
try:
result = sys.stdin.read(1)
except IOError:
pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
return result
I removed the fctl/non-blocking stuff because it was giving IOErrors and I didn't need it. I'm using this code specifically because I want it to block. ;)
Addendum:
I implemented this in a package on PyPI with a lot of other goodies called console:
>>> from console.utils import wait_key
>>> wait_key()
'h'
The python manual provides the following:
import termios, fcntl, sys, os
fd = sys.stdin.fileno()
oldterm = termios.tcgetattr(fd)
newattr = termios.tcgetattr(fd)
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSANOW, newattr)
oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
try:
while 1:
try:
c = sys.stdin.read(1)
print "Got character", repr(c)
except IOError: pass
finally:
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
which can be rolled into your use case.
I don't know of a platform independent way of doing it, but under Windows, if you use the msvcrt module, you can use its getch function:
import msvcrt
c = msvcrt.getch()
print 'you entered', c
mscvcrt also includes the non-blocking kbhit() function to see if a key was pressed without waiting (not sure if there's a corresponding curses function). Under UNIX, there is the curses package, but not sure if you can use it without using it for all of the screen output. This code works under UNIX:
import curses
stdscr = curses.initscr()
c = stdscr.getch()
print 'you entered', chr(c)
curses.endwin()
Note that curses.getch() returns the ordinal of the key pressed so to make it have the same output I had to cast it.
I am new to python and I was already thinking I am too stupid to reproduce the simplest suggestions made here.
It turns out, there's a pitfall one should know:
When a python-script is executed from IDLE, some IO-commands seem to behave completely different (as there is actually no terminal window).
Eg. msvcrt.getch is non-blocking and always returns $ff.
This has already been reported long ago (see e.g. https://bugs.python.org/issue9290 ) - and it's marked as fixed, somehow the problem seems to persist in current versions of python/IDLE.
So if any of the code posted above doesn't work for you, try running the script manually, and NOT from IDLE.
If you want to wait for enter (so the user knocking the keyboard does not cause something un-intended to happen) use
sys.stdin.readline()
You could use the keyboard library:
import keyboard
keyboard.wait('space')
print('space was pressed, continuing...')
os.system seems to always invoke sh, which does not recognize the s and n options for read. However the read command can be passed to bash:
os.system("""bash -c 'read -s -n 1 -p "Press any key to continue..."'""")
If you want to see if they pressed a exact key (like say 'b') Do this:
while True:
choice = raw_input("> ")
if choice == 'b' :
print "You win"
input("yay")
break
I made a console game in python, and I would like to disable the console when it prints the story. It looks like this:
print("First line of story")
time.sleep(2)
print("Second line of story")
time.sleep(2)
And so on...
So my problem is that the player can type and mess up with the console while it's writing the story. Can I disable the typing somehow?
If you are on Unix, you can disable echoing like this:
import sys
import termios
import time
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
new = termios.tcgetattr(fd)
new[3] &= ~termios.ECHO
termios.tcsetattr(fd, termios.TCSADRAIN, new)
print("First line of story")
time.sleep(2)
print("Second line of story")
time.sleep(2)
termios.tcsetattr(fd, termios.TCSADRAIN, old)
If you don't want the suppressed input to be echoed after the last tcsetattr call, you can substitute the last TCSADRAIN with TCSAFLUSH.
Documentation for the termios module can be found here, which is also where the example is taken from.
I have been trying to write a keyboard listener without installing any packages. What I wanted was to create a non-blocking way of reading only one character of user input. So I created another thread besides the main one. Here is my code:
import sys, os
import thread
import time
try:
from msvcrt import getch
except ImportError:
def getch():
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
char = None;
def key_listener():
global char;
while True:
char = getch()
# escape key to exit
if ord(char) == 27:
break
#print char #comment this line for test
thread.start_new_thread(key_listener, ())
while True:
print("Whatever")
time.sleep(1);
And the printed strings are a bit weird:
Yinans-MacBook-Pro:anaconda game Yinan$ python inputtest.py
Whatever
Whatever
Whatever
Whatever
Whatever
Whatever
See those indents? I never expected to have that. And I have been trying a whole afternoon to solve it but failed. Does anybody know how to solve this? I will be so grateful. (btw I'm using a macbook pro.)
Putting STDIN in raw mode put STDOUT in raw mode as well, so the normal \n is not expanded to a CRLF. You will need to print a \r at the end of your string in order to return the cursor to the first column.