I'm creating an application that uses curses to build a simple user interface. It also uses the subprocess module to run external text editor so a user can type some text, then close the editor and go back to the main program.
The problem is that when the editor is console-based such as Vim or Nano, curses doesn't de-initialize properly. Which means if the color mode is used (curses.start_color()), terminal stays colored after the program is finished.
Here's a test script that has this issue (at least for me, I use Ubuntu and gnome-terminal):
import curses
import subprocess
screen = curses.initscr()
curses.noecho()
curses.cbreak()
screen.keypad(1)
try:
curses.curs_set(0)
except curses.error:
pass
curses.start_color()
screen.addstr(0, 0, 'hello, world!')
screen.refresh()
while 1:
c = screen.getch()
if c == ord('q'):
break
if c == ord('r'):
subprocess.call('vim', shell=False)
screen.clear()
screen.addstr(0, 0, 'hello, world!')
screen.refresh()
curses.nocbreak()
screen.keypad(0)
curses.echo()
curses.curs_set(1)
curses.endwin()
(Press r to enter Vim, then q to exit.)
Is there a way to fix this?
Would modifying your code to:
if c == ord('q'):
subprocess.call('reset', shell=False)
break
be enough for you? Or is there some additional behaviour in your real script that is not in the code you pasted here, that makes this workaround unsuitable to your goal?
Related
I want to write a python program (run.py) that always runs and only stops running when Ctr-C is pressed. This is how I implement it:
wrapper.py:
import subprocess
import signal
def sig_handler(signum, frame):
res = input("Ctrl-c was pressed. Do you really want to exit? y/n ")
if res == 'y':
exit(1)
signal.signal(signal.SIGINT, sig_handler)
while(True):
p = None
p = subprocess.Popen("python run.py", shell=True)
stdout, stderr = p.communicate()
run.py:
print('aaaaa')
print('bbbbb')
However, when I hold left-mouse and select text in the terminal that is running wrapper.py, this event is understood incorrectly as Ctr-C then the wrapper.py stop running run.py. My question is how to prevent reading mouse events as KeyboardInterrupt in python (Unix). Thanks!
Terminal
Instead of using a module like signal to achieve this you could opt to use exceptions since it is a pretty exceptional case that your program will receive a keyboard interrupt.
#!/usr/bin/env python3
import sys
def main() -> int:
try:
while True:
print(sys.argv)
except KeyboardInterrupt as e:
return 0
if __name__ == '__main__':
try:
sys.exit(main())
except Exception as e:
print(f'Error: {e}', file=sys.stderr)
sys.exit(1)
The source code has no problem. The problem is caused by dictionary software. The software has a feature of selecting word to translate. By somehow it converts the mouse event (selecting word) to Ctr-C then the program above exits. When I turn off the dictionary software, the problem disappears. I will close the thread here
My project is to make a program that you can run while you are playing games or other programs in the background.
When you press a certain key, your notepad should open and also close after you press the same key again.
I have managed to open notepad with subprocess and that works fine but I have no idea to make it open only when a certain key is pressed.
Thanks for any help!
EDIT:
What I tried already:
import subprocess
import keyboard
if keyboard.is_pressed('k'):
subprocess.Popen('C:\\Windows\\System32\\notepad.exe')
input()
here it just doesn't detect any keyboard input, the input() at the end makes the program not close instantly
import subprocess
import keyboard
keyboard.add_hotkey('ctrl+k', print,args=("hello", "test"))
input()
Here if I press "ctrl+k it" will print hello test that means the hotkey works fine. When I switch this part "print,args=("hello", "test")" to "subprocess.Popen('C:\Windows\System32\notepad.exe')"(it should open the program instead of printing hello test) the notepad opens instantly after I run the program and when I press "ctrl+k" I get a big error.
A more complex, but still working example could be the following. With this code your program will be always listening the keyboard, not only when you are focused on the input, so may be mre practical in your case
from pynput import keyboard
import subprocess
import threading
class MyException(Exception): pass
class Listening:
"""Is allways waiting for the keyboard input"""
def __init__(self):
self.notepad_open = False # to know the state
with keyboard.Listener(
on_press=self.on_press) as listener:
try:
listener.join()
except:
pass
def on_press(self, key):
try:
if key.char == "k":
if not self.notepad_open:
self.subprocess = \
subprocess.Popen('C:\\Windows\\System32\\notepad.exe')
self.notepad_open = True # update state
else:
self.subprocess.kill()
self.notepad_open = False # update state
except: # special key was pressed
pass
thread = threading.Thread(target=lambda: Listening())
thread.start()
The problem is that you check for the key 'k' only once at the beginning. If you want the program to correctly work then you should try this:
import time
import subprocess
import keyboard
while True:
if keyboard.is_pressed('k'):
subprocess.Popen('C:\\Windows\\System32\\notepad.exe')
time.sleep(5)
-I used the time so that you can only open the program once 5 seconds(If you're curious, see what happens without it)-
I want to detect when the XF86Launch1 key is pressed on my keyboard, using Python.
I have a headless server with a Bluetooth connected keyboard. I'd like to launch a command-line program whenever a specific multimedia key is pressed.
At the moment, I'm using:
import sys
import tty, termios
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
print getch()
But it won't detect multimedia keys. Nothing prints out when I press them.
Is there a way to detect these keys on a headless Ubuntu box - or a better way to launch a program on keypress?
Rather than trying to read stdin of the tty, you can use linux's input device api to read them.
On linux, all input devices show up as a file under /dev/input (such as /dev/input/event0) and when one read()s from these files, structured data indicating when keys are pressed and released is returned.
In C, the libevdev library provides a wrapper around those. In python, you could use the python-evdev library. It should also be possible to read directly from the input device (though you may need to carefully read the kernel documentation & source to handle that properly).
I think that your problem is that multimedia keys do not map to terminal input.
It's possible that you could make progress by running xev to trap the key and xmodmap to map the key to a different input.
Alternatively, use something like TKinter and see if a graphical program doesn't collect the keypresses.
from Tkinter import *
root = Tk()
def key(event):
print "pressed", repr(event.char)
def callback(event):
frame.focus_set()
frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
Another possibility is to map to an F key instead of a multimedia key. (i.e. F9)
Edit: Further research into this resulted in these two links:
Extra Keyboard Keys
Extra Keyboard Keys in Console
The console itself does not support multimedia keys. But it does support custom F keys. F30-F246 are always free. Rather than map to XF86Launch1, map to F70. Then map F70 to keyboard input in your keymap, or use the Python script you already wrote to handle it.
pycopia may be an option. I am using it with this bluetooth button and it seems to work fairly well. I am still working on getting it to reconnect to the button when the button goes to sleep and then comes back. Here's part of the script that I'm using:
keyboard.py:
from pycopia.OS.Linux import Input
from pycopia.OS.Linux import event
class Satechi(Input.EventDevice):
DEVNAME = 'Satechi'
def register_callback(self, cb):
self._callback = cb
def poll(self):
while 1:
ev = self.read()
if ev.evtype == event.EV_KEY:
self._callback(ev)
read_handler = poll
button.py
from keyboard import Satechi
def callback(event):
pass #Do something fun
if __name__ == '__main__':
pm = Satechi()
pm.find()
pm.register_callback(callback)
while 1:
try:
pm.poll()
except OSError:
pm = Satechi()
while True:
try:
pm.find()
pm.register_callback(callback)
break
except IOError:
pass
pm.close()
Where DEVNAME is the devices name in /proc/bus/input/devices.
You can print event in the callback to figure out what the code and value is for the button you are looking for
Try to read xinput test <id> stdout in a loop and catch the events you need.
Here is some example in Bash:
#!/bin/bash
keyboard_id=9 # use xinput to find your keyboard id
xinput test $keyboard_id | while read line ; do
case $line in
"key press 44") echo -e "\n == j pressed ==" ;;
"key press 45") echo -e "\n == k pressed ==" ;;
esac
done
I am testing out the curses module for python and I encountered this error while trying a simple script:
NameError: global name 'addstr' is not defined
Here is my code:
#!/usr/bin/env python
import curses, sys
from curses import *
def main():
stdscr = initscr()
addstr("Hello")
endwin()
if __name__ == "__main__":
main()
I don't know what Newbie mistake I made, I am following a guide for curses on python.
Thanks in advance.
You need to call addstr() on stdscr, since that is the window object. Here's an example of a curses application:
import curses
import time
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.addstr("Hello World")
stdscr.refresh()
try:
while True:
time.sleep(0.001)
except KeyboardInterrupt:
pass
curses.nocbreak()
curses.echo()
curses.endwin()
Note that the while loop is simply so the curses terminal is displayed until you hit Ctrl-C, otherwise you won't really see much.
Let's say I have a python program that is spitting out lines of text, such as:
while 1:
print "This is a line"
What's the easiest way to allow one to press a key on the keyboard to pause the loop, then to resume if pressed again---but if nothing is pressed it should just continue on automatically?
I'm hoping I don't have to go into something like curses to get this!
You could try this implementation for Linux / Mac (and possible other Unices) (code attribution: found on ActiveState Code Recipes).
On Windows you should check out msvcrt.
import sys, termios, atexit
from select import select
# save the terminal settings
fd = sys.stdin.fileno()
new_term = termios.tcgetattr(fd)
old_term = termios.tcgetattr(fd)
# new terminal setting unbuffered
new_term[3] = (new_term[3] & ~termios.ICANON & ~termios.ECHO)
# switch to normal terminal
def set_normal_term():
termios.tcsetattr(fd, termios.TCSAFLUSH, old_term)
# switch to unbuffered terminal
def set_curses_term():
termios.tcsetattr(fd, termios.TCSAFLUSH, new_term)
def putch(ch):
sys.stdout.write(ch)
def getch():
return sys.stdin.read(1)
def getche():
ch = getch()
putch(ch)
return ch
def kbhit():
dr,dw,de = select([sys.stdin], [], [], 0)
return dr <> []
Implementing what you're looking for would then become something like this:
atexit.register(set_normal_term)
set_curses_term()
while True:
print "myline"
if kbhit():
print "paused..."
ch = getch()
while True
if kbhit():
print "unpaused..."
ch = getch()
break
The easiest way for me, assuming I was working in bash, would be to hit Control-Z to suspend the job, then use the 'fg' command to restore it when I was ready. But since I don't know what platform you're using, I'll have to go with using ChristopheD's solution as your best starting point.
When you press Ctrl+C, a KeyboardInterrupt exception gets raised in your program. You can catch that exception to create the behavior you want - for instance, to pause the program and resume after 5s:
import time
while True:
try:
# This is where you're doing whatever you're doing
print("This is a line")
except KeyboardInterrupt:
print("Paused! Ctrl+C again within 5 seconds to kill program")
# A second KeyboardInterrupt that happens during this sleep will
# not be caught, so it will terminate the program
time.sleep(5)
print("Continuing...")
Or to pause the program indefinitely until the user hits 'enter':
while True:
try:
# This is where you're doing whatever you're doing
print("This is a line")
except KeyboardInterrupt:
print("Interrupted!")
input("Press enter to continue, or Ctrl+C to terminate.")
print("Continuing...")
If you want to catch a second KeyboardInterrupt as well and do something fancy with that, you can do so by nesting try/except blocks, though I wouldn't really recommend that - it's a good idea to allow a string of KeyboardInterrupts to terminate the program.