In Python, I'd like to write a terminal program using both cmd and curses together, ie. use cmd to accept and decode full input lines, but position the output with curses.
Mashing together examples of both curses and cmd like this :
import curses
import cmd
class HelloWorld(cmd.Cmd):
"""Simple command processor example."""
def do_greet(self, line):
screen.clear()
screen.addstr(1,1,"hello "+line)
screen.addstr(0,1,">")
screen.refresh()
def do_q(self, line):
curses.endwin()
return True
if __name__ == '__main__':
screen = curses.initscr()
HelloWorld().cmdloop()
I find that I'm not seeing anything when I type. curses is presumably waiting for a refresh before displaying anything on the screen. I could switch to using getch() but then I'd lose the value of cmd.
Is there a way to make these work together?
The question seems to be very old... But it attracted enough of attention so i thought to leave my answer behind.. You can check out the site here and clear your doubts..
Update : this answer links to the gist from the original questioner. Just to save someone having to follow the link ... here's the code in full :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import curses
import curses.textpad
import cmd
def maketextbox(h,w,y,x,value="",deco=None,textColorpair=0,decoColorpair=0):
# thanks to http://stackoverflow.com/a/5326195/8482 for this
nw = curses.newwin(h,w,y,x)
txtbox = curses.textpad.Textbox(nw,insert_mode=True)
if deco=="frame":
screen.attron(decoColorpair)
curses.textpad.rectangle(screen,y-1,x-1,y+h,x+w)
screen.attroff(decoColorpair)
elif deco=="underline":
screen.hline(y+1,x,underlineChr,w,decoColorpair)
nw.addstr(0,0,value,textColorpair)
nw.attron(textColorpair)
screen.refresh()
return nw,txtbox
class Commands(cmd.Cmd):
"""Simple command processor example."""
def __init__(self):
cmd.Cmd.__init__(self)
self.prompt = "> "
self.intro = "Welcome to console!" ## defaults to None
def do_greet(self, line):
self.write("hello "+line)
def default(self,line) :
self.write("Don't understand '" + line + "'")
def do_quit(self, line):
curses.endwin()
return True
def write(self,text) :
screen.clear()
textwin.clear()
screen.addstr(3,0,text)
screen.refresh()
if __name__ == '__main__':
screen = curses.initscr()
curses.noecho()
textwin,textbox = maketextbox(1,40, 1,1,"")
flag = False
while not flag :
text = textbox.edit()
curses.beep()
flag = Commands().onecmd(text)
Related
I am trying to detect the keyboard presses just when I am in the cmd window running a ROS2 node.
I have already written this using pynput, but it detects all keys pressed even if I am in another window.
import sys
import os
import signal
import time
from pynput import keyboard
import rclpy
from rclpy.parameter import Parameter
import std_msgs.msg
class KeystrokeListen:
def __init__(self, name=None):
self.node = rclpy.create_node(name or type(self).__name__)
self.pub_glyph = self.node.create_publisher(std_msgs.msg.String, 'glyphkey_pressed', 10)
self.pub_code = self.node.create_publisher(std_msgs.msg.UInt32, 'key_pressed', 10)
def spin(self):
with keyboard.Listener(on_press=self.on_press, on_release=self.on_release) as listener:
while rclpy.ok() and listener.running:
rclpy.spin_once(self.node, timeout_sec=0.1)
#property
def logger(self):
return self.node.get_logger()
def on_release(self, key):
pass
def on_press(self, key):
try:
char = getattr(key, 'char', None)
if isinstance(char, str):
self.logger.info('pressed ' + char)
self.pub_glyph.publish(self.pub_glyph.msg_type(data=char))
else:
try:
# known keys like spacebar, ctrl
name = key.name
vk = key.value.vk
except AttributeError:
# unknown keys like headphones skip song button
name = 'UNKNOWN'
vk = key.vk
self.logger.info('pressed {} ({})'.format(name, vk))
# todo: These values are not cross-platform. When ROS2 supports Enums, use them instead
self.pub_code.publish(self.pub_code.msg_type(data=vk))
except Exception as e:
self.logger.error(str(e))
raise
if key == keyboard.Key.esc:
self.logger.info('stopping listener')
raise keyboard.Listener.StopException
os.kill(os.getpid(), signal.SIGINT)
def main(args=None):
rclpy.init(args=args)
KeystrokeListen().spin()
if __name__ == '__main__':
main()
I have found here How to detect key presses? something related with win32gui, as getting the name of the current window and then the name of the window we want the key presses to be detected:
from win32gui import GetWindowText, GetForegroundWindow
current_window = (GetWindowText(GetForegroundWindow()))
desired_window_name = "Stopwatch"
However, I just want for my program to work whenever I am in the cmd where I launched it.
Any idea??
Thank you in advance!
Indeed, this module might help you out.
Here is an example of how I would do it:
from win32gui import GetForegroundWindow, GetWindowText
if all(win_name in GetWindowText(GetForegroundWindow()) for win_name in ["cmd.exe", __file__]):
print("this program's cmd is focused!")
I want to make let's say 5 subprocesses to work in exact same time. Problem is cmd window that appears for a split second so I am unable to see anything. How can I pause it or make anything else to have that window on sight after finished code?
I am opening it with such approach:
client = subprocess.Popen(f'python file.py {arg1} {arg2}', creationflags=CREATE_NEW_CONSOLE)
You could do it by creating Windows batch files that executed your Python script and then pause before ending.
import atexit
import msvcrt
import os
import subprocess
import sys
from tempfile import NamedTemporaryFile
import time
import textwrap
def create_batchfile(proc_no, py_script_filename, *script_args):
"""Create a batch file to execute a Python script and pass it the specified
arguments then pause and wait for a key to be pressed before allowing the
console window to close.
"""
with NamedTemporaryFile('w', delete=False, newline='', suffix='.bat',
) as batchfile:
cmd = (f'"{sys.executable}" "{py_script_filename}" ' +
' '.join(f'"{sarg}"' for sarg in script_args))
print(f'{cmd}') # Display subprocess being executed. (optional)
batchfile.write(textwrap.dedent(f"""\
#echo off
title Subprocess #{proc_no}
{cmd}
echo {py_script_filename} has finished execution
pause
"""))
return batchfile.name
def clean_up(filenames):
"""Remove list of files."""
for filename in filenames:
os.remove(filename)
temp_files = [] # List of files to delete when script finishes.
py_script_filename = 'my_file.py'
atexit.register(clean_up, temp_files)
for i in range(1, 4):
batch_filename = create_batchfile(i, 'my_file.py', i, 'arg 2')
temp_files.append(batch_filename)
subprocess.Popen(batch_filename, creationflags=subprocess.CREATE_NEW_CONSOLE)
print('Press any key to quit: ', end='', flush=True)
while True: # Wait for keypress.
if msvcrt.kbhit():
ch = msvcrt.getch()
if ch in b'\x00\xe0': # Arrow or function key prefix?
ch = msvcrt.getch() # Second call returns the actual key code.
break
time.sleep(0.1)
How can I poll the keyboard from a console python app? Specifically, I would like to do something akin to this in the midst of a lot of other I/O activities (socket selects, serial port access, etc.):
while True:
# doing amazing pythonic embedded stuff
# ...
# periodically do a non-blocking check to see if
# we are being told to do something else
x = keyboard.read(1000, timeout = 0)
if len(x):
# ok, some key got pressed
# do something
What is the correct pythonic way to do this on Windows? Also, portability to Linux wouldn't be bad, though it's not required.
The standard approach is to use the select module.
However, this doesn't work on Windows. For that, you can use the msvcrt module's keyboard polling.
Often, this is done with multiple threads -- one per device being "watched" plus the background processes that might need to be interrupted by the device.
A solution using the curses module. Printing a numeric value corresponding to each key pressed:
import curses
def main(stdscr):
# do not wait for input when calling getch
stdscr.nodelay(1)
while True:
# get keyboard input, returns -1 if none available
c = stdscr.getch()
if c != -1:
# print numeric value
stdscr.addstr(str(c) + ' ')
stdscr.refresh()
# return curser to start position
stdscr.move(0, 0)
if __name__ == '__main__':
curses.wrapper(main)
Ok, since my attempt to post my solution in a comment failed, here's what I was trying to say. I could do exactly what I wanted from native Python (on Windows, not anywhere else though) with the following code:
import msvcrt
def kbfunc():
x = msvcrt.kbhit()
if x:
ret = ord(msvcrt.getch())
else:
ret = 0
return ret
None of these answers worked well for me. This package, pynput, does exactly what I need.
https://pypi.python.org/pypi/pynput
from pynput.keyboard import Key, Listener
def on_press(key):
print('{0} pressed'.format(
key))
def on_release(key):
print('{0} release'.format(
key))
if key == Key.esc:
# Stop listener
return False
# Collect events until released
with Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()
import sys
import select
def heardEnter():
i,o,e = select.select([sys.stdin],[],[],0.0001)
for s in i:
if s == sys.stdin:
input = sys.stdin.readline()
return True
return False
From the comments:
import msvcrt # built-in module
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
Thanks for the help. I ended up writing a C DLL called PyKeyboardAccess.dll and accessing the crt conio functions, exporting this routine:
#include <conio.h>
int kb_inkey () {
int rc;
int key;
key = _kbhit();
if (key == 0) {
rc = 0;
} else {
rc = _getch();
}
return rc;
}
And I access it in python using the ctypes module (built into python 2.5):
import ctypes
import time
# first, load the DLL
try:
kblib = ctypes.CDLL("PyKeyboardAccess.dll")
except:
raise ("Error Loading PyKeyboardAccess.dll")
# now, find our function
try:
kbfunc = kblib.kb_inkey
except:
raise ("Could not find the kb_inkey function in the dll!")
# Ok, now let's demo the capability
while True:
x = kbfunc()
if x != 0:
print "Got key: %d" % x
else:
time.sleep(.01)
I've come across a cross-platform implementation of kbhit at http://home.wlu.edu/~levys/software/kbhit.py (made edits to remove irrelevant code):
import os
if os.name == 'nt':
import msvcrt
else:
import sys, select
def kbhit():
''' Returns True if a keypress is waiting to be read in stdin, False otherwise.
'''
if os.name == 'nt':
return msvcrt.kbhit()
else:
dr,dw,de = select.select([sys.stdin], [], [], 0)
return dr != []
Make sure to read() the waiting character(s) -- the function will keep returning True until you do!
You might look at how pygame handles this to steal some ideas.
I am using this for checking for key presses, can't get much simpler:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import curses, time
def main(stdscr):
"""checking for keypress"""
stdscr.nodelay(True) # do not wait for input when calling getch
return stdscr.getch()
while True:
print("key:", curses.wrapper(main)) # prints: 'key: 97' for 'a' pressed
# '-1' on no presses
time.sleep(1)
While curses is not working on windows, there is a 'unicurses' version, supposedly working on Linux, Windows, Mac but I could not get this to work
One more option would be to use sshkeyboard library to enable reacting to key presses instead of polling them periodically, and potentially missing the key press:
from sshkeyboard import listen_keyboard, stop_listening
def press(key):
print(f"'{key}' pressed")
if key == "z":
stop_listening()
listen_keyboard(on_press=press)
Simply pip install sshkeyboard to use it.
This can be done using 'pynput' module in python,
You press a key and it gets printed It's that easy!
PIP Install the module in command prompt, write following text and press enter
pip install pynput
Run the following code:
from pynput.keyboard import Key, Listener
def pressed(key):
print('Pressed:',key)
def released(key):
print('Released:',key)
if key == Key.enter:
# Stop detecting when enter key is pressed
return False
# Below loop for Detcting keys runs until enter key is pressed
with Listener(on_press=pressed, on_release=released) as detector:
detector.join()
You can end the loop with any key you want by changing Key.enter to some other key in the 8th line of the code.
If you combine time.sleep, threading.Thread, and sys.stdin.read you can easily wait for a specified amount of time for input and then continue,
also this should be cross-platform compatible.
t = threading.Thread(target=sys.stdin.read(1) args=(1,))
t.start()
time.sleep(5)
t.join()
You could also place this into a function like so
def timed_getch(self, bytes=1, timeout=1):
t = threading.Thread(target=sys.stdin.read, args=(bytes,))
t.start()
time.sleep(timeout)
t.join()
del t
Although this will not return anything so instead you should use the multiprocessing pool module you can find that here: how to get the return value from a thread in python?
I'm trying to create a basic PyGTK app to embed MPlayer inside a window (since it otherwise doesn't work well with tiling WM's, which I like).
I'll put my code so far at the end of this post, but basically my setup currently involves a Window containing a DrawingArea which I embed MPlayer into using the `-wid' command-line option.
The problem I'm having is that, when resizing, I get the following sorts of visual artifacts (see inside the red box):
I've tried calling queue_draw() on the DrawingArea when a configure-event happens, but this seems to have no effect. Anyone have any ideas?
My complete code is as follows: (command-line usage is `$0 [ vid ]')
#!/usr/bin/env python2
import sys
import os
import subprocess
import time
import string
import gtk
import gobject
import pygtk
pygtk.require('2.0')
class MPlayer:
def __init__(self, path, draw, show_output=True):
self.path = path
self.draw = draw
self.fifo = "/tmp/%s.%d" % (os.path.basename(__file__), time.time())
# Start mplayer in draw
cmd = string.split("mplayer -slave -wid %d -input file=%s" % \
(self.draw.window.xid, self.fifo))
cmd.append(self.path)
if show_output:
process = subprocess.Popen(cmd)
else:
self.devnull = open(os.devnull)
process = subprocess.Popen(cmd, stdout=self.devnull, \
stderr=self.devnull)
self.pid = process.pid
def __enter__(self):
os.mkfifo(self.fifo)
return self
def __exit__(self, ext_type, exc_value, traceback):
if hasattr(self, "devnull"):
self.devnull.close()
os.unlink(self.fifo)
# Send cmd to mplayer via fifo
def exe(self, cmd, *args):
if not self.pid: return
full_cmd = "%s %s\n" % (cmd, string.join([str(arg) for arg in args]))
with open(self.fifo, "w+") as fifo:
fifo.write(full_cmd)
fifo.flush()
class MPlayerWrapper:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.draw = gtk.DrawingArea()
self.mplayer = None
self.setup_widgets()
def setup_widgets(self):
self.window.connect("destroy", gtk.main_quit)
self.window.connect("key_press_event", self.key_press_event)
self.draw.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
self.draw.connect("configure_event", self.redraw)
self.window.add(self.draw)
self.window.show_all()
def mplayer_exe(self, cmd, *args):
if self.mplayer:
self.mplayer.exe(cmd, *args)
def key_press_event(self, widget, event, data=None):
self.mplayer_exe("key_down_event", event.keyval)
def redraw(self, draw, event, data=None):
self.draw.queue_draw()
def play(self, path):
with MPlayer(path, self.draw, True) as self.mplayer:
gobject.child_watch_add(self.mplayer.pid, gtk.main_quit)
gtk.main()
if __name__ == "__main__":
wrapper = MPlayerWrapper()
wrapper.play(sys.argv[1])
Solved it -- the solution was to add
-vo gl
to the call to mplayer. That is, I replaced
cmd = string.split("mplayer -slave -wid %d -input file=%s" % \
(self.draw.window.xid, self.fifo))
with
cmd = string.split("mplayer -slave -vo gl -wid %d -input file=%s" % \
(self.draw.window.xid, self.fifo))
in the above code, and now it resizes correctly.
That command line option has actually removed the whole reason I wanted to create this wrapper script in the first place (i.e. to embed mplayer into a window with black borders taking up the space not required by the movie itself, in order to use it in dwm), but I imagine this fix would still be useful for other people wanting to embed mplayer for other reasons.
Is there a way that I can run a script from another while getting output as readily as I get by executing it by itself.
For example:
I use the os.popen3 command to execute abc.py, but I am unable to get output from abc.py as readily as I would with doing python abc.py; it seems that I need to wait for os.popen3 command to finish:
fin, fout, ferr=os.popen3("abc.py")
out = fout.read()
err = ferr.read()
fo.write(out)
fe.write(err)
print out
print err
[EDIT]:fo and fe here are file handles to the output and error logs, respectively.
Also, what widget do I use to populate the output in, in pygtk?
import subprocess
pro = subprocess.Popen('abc.py')
Is a much better way of fiddling with another scripts ins, outs, and errors.
The subprocess module is the option, but the tricky part is to follow the output un parallel with your main loop of gtk, to accomplish that goal you must have to consider the platform that you are dealing, if you are in linux you can easily run another thread and use gtk.gdk.threads_init to use threads in pygtk, but if you are planing to run your application on windows, then you should use generators and gobject.idle_add.
About the widget, use gtk.TextBuffer associated with a gtk.TextView
Here is an example with threads
import gtk
import subprocess
import threading
gtk.gdk.threads_init()
class FollowProcess(threading.Thread):
def __init__(self, cmd, text_buffer):
self.tb = text_buffer
self.child = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
super(FollowProcess, self).__init__()
def run(self):
while not self.child.poll():
out = self.child.stdout.read(1)
if out != '':
gtk.gdk.threads_enter()
self.tb.insert_at_cursor(out)
gtk.gdk.threads_leave()
def destroy(w, cmd):
cmd.child.terminate()
gtk.main_quit()
i = 0
def click_count(btn):
global i
message.set_text('Calling button %d' %i)
i += 1
other_command = 'python extranger.py'
w = gtk.Window()
w.resize(400, 400)
message = gtk.Label('Nothing')
tb = gtk.TextBuffer()
tv = gtk.TextView(tb)
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroll.add(tv)
box = gtk.VBox()
button = gtk.Button('Active button')
cmd = FollowProcess('python extranger.py', tb)
button.connect('clicked', click_count )
w.connect('destroy', destroy, cmd)
box.pack_start(button, False)
box.pack_start(message, False)
box.pack_start(scroll)
w.add(box)
w.show_all()
cmd.start()
gtk.main()
And in extranger.py
import time
import sys
i = 0
while True:
print 'some output %d' % i
sys.stdout.flush() # you need this to see the output
time.sleep(.5)
i += 1
Note how the button stills responsive even with the update in parallel.
You mentioned PyGtk but you can give a try to PyQt and particularly QProcess class wich has some nice signals like :
readyReadStandardError
readyReadStandardOutput
Look for a similar tool with PyGtk.