read raw input from keyboard in python - python

I'm trying to get the raw input of my keyboard in python. I have a Logitech gaming keyboard with programmable keys, but Logitech doesn't provide drivers for Linux. So i thought i could (try) to write my own driver for this. In think the solution could be something like:
with open('/dev/keyboard', 'rb') as keyboard:
while True:
inp = keyboard.read()
-do something-
English isn't my native language. If you find errors, please correct it.

Two input methods that rely on the OS handling the keyboard
import sys
for line in sys.stdin.readlines():
print line
This is one "simple" solution to your problem considering it reads sys.stdin you'll probably need a driver and if the OS strips stuff along the way it will probably break anyways.
This is another solution (linux only afaik):
import sys, select, tty, termios
class NonBlockingConsole(object):
def __enter__(self):
self.old_settings = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin.fileno())
return self
def __exit__(self, type, value, traceback):
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
def get_data(self):
try:
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
return sys.stdin.read(1)
except:
return '[CTRL-C]'
return False
data = ''
printed = ''
last = ''
with NonBlockingConsole() as nbc:
while 1:
c = nbc.get_data()
if c:
if c == '\x1b': # x1b is ESC
break
elif c == '\x7f': # backspace
data = data[:-1]
printed = data[:-1]
last = ''
sys.stdout.write('\b')
elif c == '[CTRL-C]':
data = ''
last = ''
sys.stdout.write('\n')
elif c == '\n': # it's RETURN
sys.stdout.write('\n')
# parse data here
data = ''
else:
data += (c)
last = c
sys.stdout.write(c)
Driver issue?
If none of the above work, you woun't be able to get the keys within Python.
Most likely you'll need an actual driver that can parse the data sent from the keyboard that is not a normal keyboard event on the USB stack, meaning.. This is way to low-level for Python and you're out of luck... unless you know how to build linux drivers.
Anyway, have a look at: http://ubuntuforums.org/showthread.php?t=1490385
Looks like more people have tried to do something about it.
Trying PyUSB
http://pyusb.sourceforge.net/docs/1.0/tutorial.html
You could try a PyUSB solution and fetch raw data from the USB socket, but again.. if the G-keys are not registered as "traditional" USB data it might get dropped and you won't recieve it.
Hooking on to the input pipes in Linux
Another untested method, but might work //Hackaday:

Logitech doesn't provide drivers for Linux. So i thought i could (try) to write my own driver for this.
Linux drivers are written in C; it's very low-level code and runs in kernel space.

Related

Serial communication drops information on Linux, but not on Windows

The problem is, that I use a device (ISDT CM1620) for charging my batteries.
I want to automate the start of the charging via a simple button.
The device offers a simple protocol described on their page https://www.isdt.co/support?lang=en at "Open source"
I tried Python with "pyserial" and Kotlin with "jSerialComm" and got the same result.
If I run it on a Windows machine it works without any problem, but as soon as I switch to my raspberry with raspbian or to my PC with ubuntu it no longer works reliable.
As soon as I receive longer payloads all or parts of the message I receive are dropped.
What I have found out is that the device automatically detects the baud rate and windows using the "usbser" driver to handle the communication. On my raspberry pi, I just plugged it in and used the "/dev/ttyACM0" file for communication.
I also tried flow control (xonxoff, rtscts, dsrdtr and none), but it did not solve the problem.
Have anyone an idea what I am missing?
Here is a part of my python implementation:
import serial
import argparse
import io
import sys
def isNL(str):
return str == "\n" or str == "\r"
class Charger:
def __init__(self, port):
self.ser = serial.Serial(port, 2560000, timeout = 0.2)
self.sio = io.TextIOWrapper(io.BufferedRWPair(self.ser, self.ser), "utf-8")
def readFrame(self):
text = ""
byte = ""
last = ""
while not(isNL(byte) and isNL(last)):
text += byte
last = byte
byte = self.sio.read(1)
if byte == "":
return []
if text == "":
return []
return text.strip().split("\n")
def sendCommand(self, line):
encoded_cmd = "#" + line + "\r\n"
self.sio.write(encoded_cmd)
self.sio.flush()
def request(self, line):
for i in range(300):
self.sendCommand(line)
frame = self.readFrame()
if len(frame) > 0:
return frame
print("retry")
sys.exit(-1)
def login(self):
block = self.request("login null")
return block[1:]
def hello(self):
block = self.request("hello")
return block[1:]
def print_serial(name):
global ser
print(name)
charger = Charger(name)
status = charger.hello() #waits indefinitely because it waits for a message that has been dropped and tries again after a timeout and so on.
print(status)
ap = argparse.ArgumentParser()
ap.add_argument("-p","--port",required = True, help = "Enter Port Name")
args = vars(ap.parse_args())
PORT = args['port']
print_serial(PORT)
EDIT:
Due to the fact, that the software works find on Windows i have installed windows 10 on my raspberry.
But it seems it does not depend on the operating system, because the same effects of dropping bytes and missing frames appear there as well. Until now the implementation and shipped software from the manufacturer only worked on that one windows laptop, which I can not use as my server.

Finding the width of the terminal? [duplicate]

Is there a way in python to programmatically determine the width of the console? I mean the number of characters that fits in one line without wrapping, not the pixel width of the window.
Edit
Looking for a solution that works on Linux
Not sure why it is in the module shutil, but it landed there in Python 3.3. See:
Querying the size of the output terminal
>>> import shutil
>>> shutil.get_terminal_size((80, 20)) # pass fallback
os.terminal_size(columns=87, lines=23) # returns a named-tuple
A low-level implementation is in the os module. Cross-platform—works under Linux, Mac OS, and Windows, probably other Unix-likes. There's a backport as well, though no longer relevant.
import os
rows, columns = os.popen('stty size', 'r').read().split()
uses the 'stty size' command which according to a thread on the python mailing list is reasonably universal on linux. It opens the 'stty size' command as a file, 'reads' from it, and uses a simple string split to separate the coordinates.
Unlike the os.environ["COLUMNS"] value (which I can't access in spite of using bash as my standard shell) the data will also be up-to-date whereas I believe the os.environ["COLUMNS"] value would only be valid for the time of the launch of the python interpreter (suppose the user resized the window since then).
(See answer by #GringoSuave on how to do this on python 3.3+)
use
import console
(width, height) = console.getTerminalSize()
print "Your terminal's width is: %d" % width
EDIT: oh, I'm sorry. That's not a python standard lib one, here's the source of console.py (I don't know where it's from).
The module seems to work like that: It checks if termcap is available, when yes. It uses that; if no it checks whether the terminal supports a special ioctl call and that does not work, too, it checks for the environment variables some shells export for that.
This will probably work on UNIX only.
def getTerminalSize():
import os
env = os.environ
def ioctl_GWINSZ(fd):
try:
import fcntl, termios, struct, os
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
'1234'))
except:
return
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
### Use get(key[, default]) instead of a try/catch
#try:
# cr = (env['LINES'], env['COLUMNS'])
#except:
# cr = (25, 80)
return int(cr[1]), int(cr[0])
Code above didn't return correct result on my linux because winsize-struct has 4 unsigned shorts, not 2 signed shorts:
def terminal_size():
import fcntl, termios, struct
h, w, hp, wp = struct.unpack('HHHH',
fcntl.ioctl(0, termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0)))
return w, h
hp and hp should contain pixel width and height, but don't.
It's either:
import os
columns, rows = os.get_terminal_size(0)
# or
import shutil
columns, rows = shutil.get_terminal_size()
The shutil function is just a wrapper around os one that catches some errors and set up a fallback, however it has one huge caveat - it breaks when piping!, which is a pretty huge deal.
To get terminal size when piping use os.get_terminal_size(0) instead.
First argument 0 is an argument indicating that stdin file descriptor should be used instead of default stdout. We want to use stdin because stdout detaches itself when it is being piped which in this case raises an error.
I've tried to figure out when would it makes sense to use stdout instead of stdin argument and have no idea why it's a default here.
I searched around and found a solution for windows at :
http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/
and a solution for linux here.
So here is a version which works both on linux, os x and windows/cygwin :
""" getTerminalSize()
- get width and height of console
- works on linux,os x,windows,cygwin(windows)
"""
__all__=['getTerminalSize']
def getTerminalSize():
import platform
current_os = platform.system()
tuple_xy=None
if current_os == 'Windows':
tuple_xy = _getTerminalSize_windows()
if tuple_xy is None:
tuple_xy = _getTerminalSize_tput()
# needed for window's python in cygwin's xterm!
if current_os == 'Linux' or current_os == 'Darwin' or current_os.startswith('CYGWIN'):
tuple_xy = _getTerminalSize_linux()
if tuple_xy is None:
print "default"
tuple_xy = (80, 25) # default value
return tuple_xy
def _getTerminalSize_windows():
res=None
try:
from ctypes import windll, create_string_buffer
# stdin handle is -10
# stdout handle is -11
# stderr handle is -12
h = windll.kernel32.GetStdHandle(-12)
csbi = create_string_buffer(22)
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
except:
return None
if res:
import struct
(bufx, bufy, curx, cury, wattr,
left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
sizex = right - left + 1
sizey = bottom - top + 1
return sizex, sizey
else:
return None
def _getTerminalSize_tput():
# get terminal width
# src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
try:
import subprocess
proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
output=proc.communicate(input=None)
cols=int(output[0])
proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
output=proc.communicate(input=None)
rows=int(output[0])
return (cols,rows)
except:
return None
def _getTerminalSize_linux():
def ioctl_GWINSZ(fd):
try:
import fcntl, termios, struct, os
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
except:
return None
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
try:
cr = (env['LINES'], env['COLUMNS'])
except:
return None
return int(cr[1]), int(cr[0])
if __name__ == "__main__":
sizex,sizey=getTerminalSize()
print 'width =',sizex,'height =',sizey
Starting at Python 3.3 it is straight forward:
https://docs.python.org/3/library/os.html#querying-the-size-of-a-terminal
>>> import os
>>> ts = os.get_terminal_size()
>>> ts.lines
24
>>> ts.columns
80
It looks like there are some problems with that code, Johannes:
getTerminalSize needs to import os
what is env? looks like os.environ.
Also, why switch lines and cols before returning? If TIOCGWINSZ and stty both say lines then cols, I say leave it that way. This confused me for a good 10 minutes before I noticed the inconsistency.
Sridhar, I didn't get that error when I piped output. I'm pretty sure it's being caught properly in the try-except.
pascal, "HHHH" doesn't work on my machine, but "hh" does. I had trouble finding documentation for that function. It looks like it's platform dependent.
chochem, incorporated.
Here's my version:
def getTerminalSize():
"""
returns (lines:int, cols:int)
"""
import os, struct
def ioctl_GWINSZ(fd):
import fcntl, termios
return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234"))
# try stdin, stdout, stderr
for fd in (0, 1, 2):
try:
return ioctl_GWINSZ(fd)
except:
pass
# try os.ctermid()
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
try:
return ioctl_GWINSZ(fd)
finally:
os.close(fd)
except:
pass
# try `stty size`
try:
return tuple(int(x) for x in os.popen("stty size", "r").read().split())
except:
pass
# try environment variables
try:
return tuple(int(os.getenv(var)) for var in ("LINES", "COLUMNS"))
except:
pass
# i give up. return default.
return (25, 80)
Many of the Python 2 implementations here will fail if there is no controlling terminal when you call this script. You can check sys.stdout.isatty() to determine if this is in fact a terminal, but that will exclude a bunch of cases, so I believe the most pythonic way to figure out the terminal size is to use the builtin curses package.
import curses
w = curses.initscr()
height, width = w.getmaxyx()
Try "blessings"
I was looking for the very same thing. It is very easy to use and offers tools for coloring, styling and positioning in the terminal. What you need is as easy as:
from blessings import Terminal
t = Terminal()
w = t.width
h = t.height
Works like a charm in Linux. (I'm not sure about MacOSX and Windows)
Download and documentation here
or you can install it with pip:
pip install blessings
I was trying the solution from here that calls out to stty size:
columns = int(subprocess.check_output(['stty', 'size']).split()[1])
However this failed for me because I was working on a script that expects redirected input on stdin, and stty would complain that "stdin isn't a terminal" in that case.
I was able to make it work like this:
with open('/dev/tty') as tty:
height, width = subprocess.check_output(['stty', 'size'], stdin=tty).split()
If you're using Python 3.3 or above, I'd recommend the built-in get_terminal_size() as already recommended. However if you are stuck with an older version and want a simple, cross-platform way of doing this, you could use asciimatics. This package supports versions of Python back to 2.7 and uses similar options to those suggested above to get the current terminal/console size.
Simply construct your Screen class and use the dimensions property to get the height and width. This has been proven to work on Linux, OSX and Windows.
Oh - and full disclosure here: I am the author, so please feel free to open a new issue if you have any problems getting this to work.
#reannual's answer works well, but there's an issue with it: os.popen is now deprecated. The subprocess module should be used instead, so here's a version of #reannual's code that uses subprocess and directly answers the question (by giving the column width directly as an int:
import subprocess
columns = int(subprocess.check_output(['stty', 'size']).split()[1])
Tested on OS X 10.9
Here is an version that should be Linux and Solaris compatible. Based on the posts and commments from madchine. Requires the subprocess module.
def termsize():
import shlex, subprocess, re
output = subprocess.check_output(shlex.split('/bin/stty -a'))
m = re.search('rows\D+(?P\d+); columns\D+(?P\d+);', output)
if m:
return m.group('rows'), m.group('columns')
raise OSError('Bad response: %s' % (output))
>>> termsize()
('40', '100')

Prevent Command Line Usage During Function Run in Python/Terminal?

I'm using Terminal to run a python script with a series of print statements, separated by the time.sleep function.
If I'm printing various items over a period of 10 seconds, I would like to be able to prevent the user from inputting new commands into the command line during this time.
Is this possible in Terminal? Is there a work-around?
My goal here is to be able to provide the user with a lot of print statements, then have them answer a question only after the question is asked.
Because I don't want to overwhelm the user, I want to time delay the print statements so it appears more manageable (well, it's really for theatrical effect).
ie
for i in range(10):
print "Eating cheeseburger..."
time.sleep(1)
response = raw_input("What is your favorite color?")
if response == "blue":
blah blah blah etc.
Right now, the user can input a response before the question is asked, and while the cheeseburger is still being eaten. I want to prevent this.
The question is a platform specific one, as different operating systems handle standard input and output differently. I will attempt to answer your question for Linux:
You can use os.system to access the linux command stty -echo to make any text entered on the terminal invisible, and stty echo to make it visible again.
The next thing you want to achieve is to clear the stdin buffer when user input is asked. This can be achieved through the termios function tcflush that can be used to flush all input that has been received but not read by the terminal yet.
import os
import time
import termios
import sys
os.system("stty -echo")
for i in range(10):
print(i)
time.sleep(1)
os.system("stty echo")
termios.tcflush(sys.stdin, termios.TCIOFLUSH)
print(raw_input("Answer now:"))
The following is a version of Saurabh Shirodkar's answer written for the Windows console using ctypes.
import sys
import msvcrt
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
ENABLE_ECHO_INPUT = 0x0004
def _check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
kernel32.GetConsoleMode.errcheck = _check_bool
kernel32.GetConsoleMode.argtypes = (ctypes.c_void_p,
ctypes.POINTER(ctypes.c_ulong))
kernel32.SetConsoleMode.errcheck = _check_bool
kernel32.SetConsoleMode.argtypes = (ctypes.c_void_p, ctypes.c_ulong)
kernel32.FlushConsoleInputBuffer.errcheck = _check_bool
kernel32.FlushConsoleInputBuffer.argtypes = (ctypes.c_void_p,)
def echo_input(enable=True, conin=sys.stdin):
h = msvcrt.get_osfhandle(conin.fileno())
mode = ctypes.c_ulong()
kernel32.GetConsoleMode(h, ctypes.byref(mode))
if enable:
mode.value |= ENABLE_ECHO_INPUT
else:
mode.value &= ~ENABLE_ECHO_INPUT
kernel32.SetConsoleMode(h, mode)
def flush_input(conin=sys.stdin):
h = msvcrt.get_osfhandle(conin.fileno())
kernel32.FlushConsoleInputBuffer(h)
if __name__ == '__main__':
import time
if sys.version_info[0] == 2:
input = raw_input
echo_input(False)
for i in range(10):
print(i)
time.sleep(1)
echo_input(True)
flush_input()
print(input("Answer now: "))

Print dashes across full width of screen [duplicate]

Is there a way in python to programmatically determine the width of the console? I mean the number of characters that fits in one line without wrapping, not the pixel width of the window.
Edit
Looking for a solution that works on Linux
Not sure why it is in the module shutil, but it landed there in Python 3.3. See:
Querying the size of the output terminal
>>> import shutil
>>> shutil.get_terminal_size((80, 20)) # pass fallback
os.terminal_size(columns=87, lines=23) # returns a named-tuple
A low-level implementation is in the os module. Cross-platform—works under Linux, Mac OS, and Windows, probably other Unix-likes. There's a backport as well, though no longer relevant.
import os
rows, columns = os.popen('stty size', 'r').read().split()
uses the 'stty size' command which according to a thread on the python mailing list is reasonably universal on linux. It opens the 'stty size' command as a file, 'reads' from it, and uses a simple string split to separate the coordinates.
Unlike the os.environ["COLUMNS"] value (which I can't access in spite of using bash as my standard shell) the data will also be up-to-date whereas I believe the os.environ["COLUMNS"] value would only be valid for the time of the launch of the python interpreter (suppose the user resized the window since then).
(See answer by #GringoSuave on how to do this on python 3.3+)
use
import console
(width, height) = console.getTerminalSize()
print "Your terminal's width is: %d" % width
EDIT: oh, I'm sorry. That's not a python standard lib one, here's the source of console.py (I don't know where it's from).
The module seems to work like that: It checks if termcap is available, when yes. It uses that; if no it checks whether the terminal supports a special ioctl call and that does not work, too, it checks for the environment variables some shells export for that.
This will probably work on UNIX only.
def getTerminalSize():
import os
env = os.environ
def ioctl_GWINSZ(fd):
try:
import fcntl, termios, struct, os
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,
'1234'))
except:
return
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
cr = (env.get('LINES', 25), env.get('COLUMNS', 80))
### Use get(key[, default]) instead of a try/catch
#try:
# cr = (env['LINES'], env['COLUMNS'])
#except:
# cr = (25, 80)
return int(cr[1]), int(cr[0])
Code above didn't return correct result on my linux because winsize-struct has 4 unsigned shorts, not 2 signed shorts:
def terminal_size():
import fcntl, termios, struct
h, w, hp, wp = struct.unpack('HHHH',
fcntl.ioctl(0, termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0)))
return w, h
hp and hp should contain pixel width and height, but don't.
It's either:
import os
columns, rows = os.get_terminal_size(0)
# or
import shutil
columns, rows = shutil.get_terminal_size()
The shutil function is just a wrapper around os one that catches some errors and set up a fallback, however it has one huge caveat - it breaks when piping!, which is a pretty huge deal.
To get terminal size when piping use os.get_terminal_size(0) instead.
First argument 0 is an argument indicating that stdin file descriptor should be used instead of default stdout. We want to use stdin because stdout detaches itself when it is being piped which in this case raises an error.
I've tried to figure out when would it makes sense to use stdout instead of stdin argument and have no idea why it's a default here.
I searched around and found a solution for windows at :
http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/
and a solution for linux here.
So here is a version which works both on linux, os x and windows/cygwin :
""" getTerminalSize()
- get width and height of console
- works on linux,os x,windows,cygwin(windows)
"""
__all__=['getTerminalSize']
def getTerminalSize():
import platform
current_os = platform.system()
tuple_xy=None
if current_os == 'Windows':
tuple_xy = _getTerminalSize_windows()
if tuple_xy is None:
tuple_xy = _getTerminalSize_tput()
# needed for window's python in cygwin's xterm!
if current_os == 'Linux' or current_os == 'Darwin' or current_os.startswith('CYGWIN'):
tuple_xy = _getTerminalSize_linux()
if tuple_xy is None:
print "default"
tuple_xy = (80, 25) # default value
return tuple_xy
def _getTerminalSize_windows():
res=None
try:
from ctypes import windll, create_string_buffer
# stdin handle is -10
# stdout handle is -11
# stderr handle is -12
h = windll.kernel32.GetStdHandle(-12)
csbi = create_string_buffer(22)
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
except:
return None
if res:
import struct
(bufx, bufy, curx, cury, wattr,
left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
sizex = right - left + 1
sizey = bottom - top + 1
return sizex, sizey
else:
return None
def _getTerminalSize_tput():
# get terminal width
# src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window
try:
import subprocess
proc=subprocess.Popen(["tput", "cols"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
output=proc.communicate(input=None)
cols=int(output[0])
proc=subprocess.Popen(["tput", "lines"],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
output=proc.communicate(input=None)
rows=int(output[0])
return (cols,rows)
except:
return None
def _getTerminalSize_linux():
def ioctl_GWINSZ(fd):
try:
import fcntl, termios, struct, os
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'1234'))
except:
return None
return cr
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
if not cr:
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
cr = ioctl_GWINSZ(fd)
os.close(fd)
except:
pass
if not cr:
try:
cr = (env['LINES'], env['COLUMNS'])
except:
return None
return int(cr[1]), int(cr[0])
if __name__ == "__main__":
sizex,sizey=getTerminalSize()
print 'width =',sizex,'height =',sizey
Starting at Python 3.3 it is straight forward:
https://docs.python.org/3/library/os.html#querying-the-size-of-a-terminal
>>> import os
>>> ts = os.get_terminal_size()
>>> ts.lines
24
>>> ts.columns
80
It looks like there are some problems with that code, Johannes:
getTerminalSize needs to import os
what is env? looks like os.environ.
Also, why switch lines and cols before returning? If TIOCGWINSZ and stty both say lines then cols, I say leave it that way. This confused me for a good 10 minutes before I noticed the inconsistency.
Sridhar, I didn't get that error when I piped output. I'm pretty sure it's being caught properly in the try-except.
pascal, "HHHH" doesn't work on my machine, but "hh" does. I had trouble finding documentation for that function. It looks like it's platform dependent.
chochem, incorporated.
Here's my version:
def getTerminalSize():
"""
returns (lines:int, cols:int)
"""
import os, struct
def ioctl_GWINSZ(fd):
import fcntl, termios
return struct.unpack("hh", fcntl.ioctl(fd, termios.TIOCGWINSZ, "1234"))
# try stdin, stdout, stderr
for fd in (0, 1, 2):
try:
return ioctl_GWINSZ(fd)
except:
pass
# try os.ctermid()
try:
fd = os.open(os.ctermid(), os.O_RDONLY)
try:
return ioctl_GWINSZ(fd)
finally:
os.close(fd)
except:
pass
# try `stty size`
try:
return tuple(int(x) for x in os.popen("stty size", "r").read().split())
except:
pass
# try environment variables
try:
return tuple(int(os.getenv(var)) for var in ("LINES", "COLUMNS"))
except:
pass
# i give up. return default.
return (25, 80)
Many of the Python 2 implementations here will fail if there is no controlling terminal when you call this script. You can check sys.stdout.isatty() to determine if this is in fact a terminal, but that will exclude a bunch of cases, so I believe the most pythonic way to figure out the terminal size is to use the builtin curses package.
import curses
w = curses.initscr()
height, width = w.getmaxyx()
Try "blessings"
I was looking for the very same thing. It is very easy to use and offers tools for coloring, styling and positioning in the terminal. What you need is as easy as:
from blessings import Terminal
t = Terminal()
w = t.width
h = t.height
Works like a charm in Linux. (I'm not sure about MacOSX and Windows)
Download and documentation here
or you can install it with pip:
pip install blessings
I was trying the solution from here that calls out to stty size:
columns = int(subprocess.check_output(['stty', 'size']).split()[1])
However this failed for me because I was working on a script that expects redirected input on stdin, and stty would complain that "stdin isn't a terminal" in that case.
I was able to make it work like this:
with open('/dev/tty') as tty:
height, width = subprocess.check_output(['stty', 'size'], stdin=tty).split()
If you're using Python 3.3 or above, I'd recommend the built-in get_terminal_size() as already recommended. However if you are stuck with an older version and want a simple, cross-platform way of doing this, you could use asciimatics. This package supports versions of Python back to 2.7 and uses similar options to those suggested above to get the current terminal/console size.
Simply construct your Screen class and use the dimensions property to get the height and width. This has been proven to work on Linux, OSX and Windows.
Oh - and full disclosure here: I am the author, so please feel free to open a new issue if you have any problems getting this to work.
#reannual's answer works well, but there's an issue with it: os.popen is now deprecated. The subprocess module should be used instead, so here's a version of #reannual's code that uses subprocess and directly answers the question (by giving the column width directly as an int:
import subprocess
columns = int(subprocess.check_output(['stty', 'size']).split()[1])
Tested on OS X 10.9
Here is an version that should be Linux and Solaris compatible. Based on the posts and commments from madchine. Requires the subprocess module.
def termsize():
import shlex, subprocess, re
output = subprocess.check_output(shlex.split('/bin/stty -a'))
m = re.search('rows\D+(?P\d+); columns\D+(?P\d+);', output)
if m:
return m.group('rows'), m.group('columns')
raise OSError('Bad response: %s' % (output))
>>> termsize()
('40', '100')

Python: how to tell if input file has input pending without waiting

Normally, you process a file line by line in Python using a loop like:
import sys
for s in sys.stdin:
# do something with the line in s
or
import sys
while True:
line = sys,stdin.readline()
if len(line) == 0: break
# process input line
Of course, you can also use raw_input() in soemthing like this:
try:
while True:
s = raw_input()
# process input line
except EOFError:
# there's EOF.
Of course in all these cases, if there's no input ready to be read, the underlying read() operation suspends waiting for I/O.
What I want to do is see if there is input pending without suspending, so I can read until input is exhausted and then go do something else. That is, I'd like to be able to do something like
while "there is input pending":
#get the input
but when no more input is pending, break the loop.
If you are using some variant of Unix, and your standard input is a pipe and not a file, you can use the select module to check to see whether there is waiting input. At a minimum, the code might look like:
import select
rlist, wlist, elist = select.select([sys.stdin], [], [])
if rlist:
s = raw_input()
else:
pass # no input ready right now
Okay, here's something that works well on UNIX:
import sys
import select
import tty
import termios
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
i = 0
while 1:
print i
i += 1
if isData():
c = sys.stdin.read(1)
if c == '\x1b': # x1b is ESC
break
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
I'll modify/extend this answer when I have a chance to make a somewhat better test program. I'm (so far) unclear on how well tty and termios work on Windows.
Update: Grmph. This depends on select. There are reasons I don't like Windows.

Categories

Resources