I spent few hours trying to find the solution for them, I did find one, but it's not portable and not good for me.
The final code is
#!/usr/bin/env python
import os
import select
import signal
import sys
import termios
import time
import tty
def ttywidth():
#f = os.popen('stty size', 'r')
# Should return 'HHH WWW'
#width = int(f.read().split(' ')[1])
f = os.popen('tput cols', 'r')
width = int(f.read())
f.close()
return width
def getch():
return sys.stdin.read(1)
def update_width(signum, frame):
global width
width = ttywidth()
sys.stdout.write(str(width) + '\r\n')
width = ttywidth()
# Use signal to be ackknowledged of window change event
signal.signal(signal.SIGWINCH, update_width)
# Get stdin file descriptor
fd = sys.stdin.fileno()
# Backup, important!
old_settings = termios.tcgetattr(fd)
tty.setraw(sys.stdin.fileno())
p = select.poll()
# Register for data-in
p.register(sys.stdin, select.POLLIN)
while True:
# If do not need the width of terminal, then this catch might not be
# necessary.
try:
# Wait for 1ms, if still not char in, then return.
if p.poll(1):
ch = getch()
if ch == "\x03":
# Ctrl+C
break
if ch == "\x0d":
# Entry key
sys.stdout.write("\033[97;101m" + " " * width + "\r\n\033[39;49m\r\n")
break
except select.error:
# Conflict with signal
# select.error: (4, 'Interrupted system call') on p.poll(1)
pass
# Must restore the setting, or stdin still not echo after exit this program.
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
For first part of problem, terminal's width (columns), it calls external program, which is what I don't like. There also is a way to use environment variable
COLUMNS, but it's not a good way because most of people would not
export COLUMNS.
The second part of problem, non-blocking
stdin without echoing, it uses
select.poll to do non-blocking and
tty to do no echoing. In addition, it uses
termios to backup and to restore terminal attributes, which is very important. If it doesn't do, then the
stdin of shell will be still not echoing after program exits.
Unless you are in console, or you would change terminal's geometry sometimes, therefore
signal comes to catch the event
SIGWINCH. That will slightly affect
select.poll, but should be no harm, you can see the try except for that.
One more thing to note, after
setraw(), it needs to use
"\r\n"(
"\x0d\x0a") not just
"\n" for a newline (this I don't know the reason).
All I want from the code is when user hits enter, then it prints out a horizontal bar with same width of terminal as seperator.
This code is put in Public Domain. If you know of a portable code, and it does rely on additional library, please share with me. Please also comment on the code, explain some things that I didn't mention above, those must be the things that I didn't know. It should be able to merge the code inside main loop into
getch(), make it
return None when no data available, I leave you that to finish.
Updated on 5/26: If you don't want to show cursor, you can do:
# Hide cursor
sys.stdout.write('\033[?25l')
# Show cursor
sys.stdout.write('\033[?25h')
If you don't like change original code for
"\r\n", you can do
class STDOUT_R:
@staticmethod
def write(s):
s = s.replace('\n', '\r\n')
sys.__stdout__.write(s.encode('utf-8'))
@staticmethod
def flush():
return sys.__stdout__.flush()
class STDERR_R:
@staticmethod
def write(s):
s = s.replace('\n', '\r\n')
sys.__stderr__.write(s.encode('utf-8'))
@staticmethod
def flush():
return sys.__stderr__.flush()