Hier mal eine "plattformunabhängige" getch(), getwch() und kbhit()-Implementation.
Wie sich unixnahe Exoten wie RiscOS etc. verhalten, weiß ich nicht, getestet ist es nur unter Linux.
Vielleicht hat auch jemand einen besseren Vorschlag für die Unicode-Dekodiererei, das gefällt mir so garnicht, scheint aber erstmal zu funktionieren. Auch der Umweg über einen extra Puffer ist nicht das Gelbe vom Ei, gibt es da eine Möglichkeit, die Daten im Lesepuffer des Terminals zu halten?
Code
getch() und kbhit()
Falls Python2.6 kannst du mal das versuchen:jerch hat geschrieben: Vielleicht hat auch jemand einen besseren Vorschlag für die Unicode-Dekodiererei, das gefällt mir so garnicht, scheint aber erstmal zu funktionieren.
Code: Alles auswählen
from __future__ import unicode_literals
@ice2k3: Und was soll das bringen!? Ich sehe da keine Unicode-*Literale* in dem Quelltext.
-
- User
- Beiträge: 996
- Registriert: Mittwoch 9. Januar 2008, 13:48
Ist auf Windows-Systemen aber ungünstig, weil die Posix-Klasse trotzdem abgearbeitet wird. Das ist unnötig, mach das doch alle auf der Ebene des `except`s.
Gruß
Gruß
@Dauerbaustelle:
Naja, mir gings weniger um Modul-Schönheit und wenn ich nicht den Import des sys-Moduls im Windowsteil vergessen hätte, würde es auch funktionieren
Für eine echte Umsetzung würde ich wahrscheinlich eher den Posixkram auslagern und über einen Import analog zum Windowsimport einbinden.
Hab auch grad gesehen, dass im getwch() Fehler drin sind, werde es bei Gelegenheit nochmal überarbeiten.
Naja, mir gings weniger um Modul-Schönheit und wenn ich nicht den Import des sys-Moduls im Windowsteil vergessen hätte, würde es auch funktionieren

Für eine echte Umsetzung würde ich wahrscheinlich eher den Posixkram auslagern und über einen Import analog zum Windowsimport einbinden.
Hab auch grad gesehen, dass im getwch() Fehler drin sind, werde es bei Gelegenheit nochmal überarbeiten.
Hier eine etwas bereinigte Version.
__init__.py
posix_term.py
Funktioniert jetzt auch wieder unter Windows 
getwch() ist nach wie vor unglücklich, nur wußte ich auf die Schnelle keinen besseren Weg, die variable Bytelänge der verschiedenen Kodierungen zu verarbeiten. Ein komplexerer Ansatz mit Auswertung der Kodierungseigenschaften etc. wäre hier wahrscheinlich robuster, liegt allerdings jenseits meines Interesses bezüglich dieses kleinen Helferleins.
Edit: Rückgabe von kbhit() liefert nicht True/False sondern 1/0 - verbessert.
__init__.py
Code: Alles auswählen
try:
from msvcrt import getch, getwch, kbhit
except ImportError:
from posix_term import _PosixTerm
_posix_term = _PosixTerm()
getch = _posix_term.getch
getwch = _posix_term.getwch
kbhit = _posix_term.kbhit
Code: Alles auswählen
import sys, os, tty, termios, fcntl
class _PosixTerm(object):
def __init__(self):
self.peek_buffer = {}
def _read_blocking(self, fd):
term_settings = termios.tcgetattr(fd)
try:
tty.setcbreak(fd, termios.TCSANOW)
char = os.read(fd, 1)
finally:
termios.tcsetattr(fd, termios.TCSANOW, term_settings)
return char
def _read_nonblocking(self, fd):
term_settings = termios.tcgetattr(fd)
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
try:
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
tty.setcbreak(fd, termios.TCSANOW)
char = os.read(fd, 1)
except OSError: char = ""
finally:
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
termios.tcsetattr(fd, termios.TCSANOW, term_settings)
return char
def kbhit(self, fd=sys.stdin.fileno()):
if self.peek_buffer.get(fd):
return 1
char = self._read_nonblocking(fd)
self.peek_buffer[fd] = char
return int(bool(char))
def getch(self, fd=sys.stdin.fileno()):
if self.peek_buffer.get(fd):
char = self.peek_buffer[fd][0]
self.peek_buffer[fd] = self.peek_buffer[fd][1:]
return char
return self._read_blocking(fd)
def getwch(self, fd=sys.stdin.fileno(), encoding=sys.stdin.encoding):
raw_str = ""
for i in xrange(4):
if self.peek_buffer.get(fd):
raw_str += self.peek_buffer[fd][0]
self.peek_buffer[fd] = self.peek_buffer[fd][1:]
else:
if raw_str:
raw_char = self._read_nonblocking(fd)
if not raw_char: break
raw_str += raw_char
else:
raw_str = self._read_blocking(fd)
try:
u_char = unicode(raw_str, encoding)
return u_char
except UnicodeDecodeError: continue
return unicode(raw_str, encoding)

getwch() ist nach wie vor unglücklich, nur wußte ich auf die Schnelle keinen besseren Weg, die variable Bytelänge der verschiedenen Kodierungen zu verarbeiten. Ein komplexerer Ansatz mit Auswertung der Kodierungseigenschaften etc. wäre hier wahrscheinlich robuster, liegt allerdings jenseits meines Interesses bezüglich dieses kleinen Helferleins.
Edit: Rückgabe von kbhit() liefert nicht True/False sondern 1/0 - verbessert.
Eine weitere Version, diesmal mit völlig anderem Ansatz. kbhit() verwendet nun poll(), dadurch fällt der Puffer weg, die Terminalbehandlung erfolgt über einen Kontextmanager wodurch ein einfacher Kontextwechsel zwischen raw terminal und line terminal mittels with möglich werden soll.
Mein aktuelles Problem ist, daß ich nicht weiß, wie man an die systemweite Grundkonfiguration für den kanonischen Terminalmodus kommt, ohne gleich das Terminal zurückzusetzen (also die Info, die 'reset' bzw. 'tput reset' auswertet), weshalb ich im Moment davon ausgehe, das der erste Aufruf von TerminalHandler() das Terminal im Ausgangszustand vorfindet, was ja nicht unbedingt stimmen muß.
Prinzipiell besser wäre es, das Terminal nach dem with-Block in den vorherigen Zustand zurückzusetzen, aber auch hierfür fehlt mir die Information für den line-Modus (bzw. habe ich nur obige). Für den raw-Modus ist das kein Problem, da das tty-Modul hierfür vorgefertigte Funktionen anbietet.
Hat da vielleicht jemand eine Lösung?
Hier der Code (Achtung experimenteller Bastelcode mit wirren Paradigmensprüngen
)
Mein aktuelles Problem ist, daß ich nicht weiß, wie man an die systemweite Grundkonfiguration für den kanonischen Terminalmodus kommt, ohne gleich das Terminal zurückzusetzen (also die Info, die 'reset' bzw. 'tput reset' auswertet), weshalb ich im Moment davon ausgehe, das der erste Aufruf von TerminalHandler() das Terminal im Ausgangszustand vorfindet, was ja nicht unbedingt stimmen muß.
Prinzipiell besser wäre es, das Terminal nach dem with-Block in den vorherigen Zustand zurückzusetzen, aber auch hierfür fehlt mir die Information für den line-Modus (bzw. habe ich nur obige). Für den raw-Modus ist das kein Problem, da das tty-Modul hierfür vorgefertigte Funktionen anbietet.
Hat da vielleicht jemand eine Lösung?
Hier der Code (Achtung experimenteller Bastelcode mit wirren Paradigmensprüngen

Ich würde die beiden Modi als Konstanten auf Modulebene definieren.
Beim Encoding würde ich auch Strings vermeiden. Man könnte die Konstanten in [mod]codecs[/mod] mappen. Oder verwechsel ich hier gerade was?
Beim Encoding würde ich auch Strings vermeiden. Man könnte die Konstanten in [mod]codecs[/mod] mappen. Oder verwechsel ich hier gerade was?
Naja, das geht leider nicht, da ja erst zur "Laufzeit" der file descriptor bekannt ist, welcher z.B. auch eine serielle Konsole oder ähnliches sein kann:snafu hat geschrieben:Ich würde die beiden Modi als Konstanten auf Modulebene definieren.
Code: Alles auswählen
with raw_terminal(<nicht stdin-terminal-fd>):
# usw.
Ja da kommt wieder stdin.encoding rein.snafu hat geschrieben: Beim Encoding würde ich auch Strings vermeiden. Man könnte die Konstanten in [mod]codecs[/mod] mappen. Oder verwechsel ich hier gerade was?
@lunar:
Ich nehme an, snafu meint, die kanonischen Einstellungen einfach als Konstanten vorbelegen lassen.
@Lunar:
Ist aber was anderes, oder?The module also provides the following constants which are useful for reading and writing to platform dependent files:
codecs.BOM
codecs.BOM_BE
codecs.BOM_LE
codecs.BOM_UTF8
codecs.BOM_UTF16
codecs.BOM_UTF16_BE
codecs.BOM_UTF16_LE
codecs.BOM_UTF32
codecs.BOM_UTF32_BE
codecs.BOM_UTF32_LE
These constants define various encodings of the Unicode byte order mark (BOM) used in UTF-16 and UTF-32 data streams to indicate the byte order used in the stream or file and in UTF-8 as a Unicode signature. BOM_UTF16 is either BOM_UTF16_BE or BOM_UTF16_LE depending on the platform’s native byte order, BOM is an alias for BOM_UTF16, BOM_LE for BOM_UTF16_LE and BOM_BE for BOM_UTF16_BE. The others represent the BOM in UTF-8 and UTF-32 encodings.
Ja, die meinte ich. Ich dachte auf dem ersten Blick nur, die gibt es im `codecs`-Modul, damit man die Möglichkeiten nicht einzeln definieren muss.jerch hat geschrieben:@lunar:
Ich nehme an, snafu meint, die kanonischen Einstellungen einfach als Konstanten vorbelegen lassen.
Zuletzt geändert von snafu am Freitag 23. Oktober 2009, 17:21, insgesamt 1-mal geändert.
Update:
Ich habe versucht, nach dem Vorbild der tty-Funktionen eine eigene Funktion für den normalen Zeilenmodus zu basteln:
Die Werte sind größtenteils von meiner Standardbitmaske abgeschrieben. Für einzelne Werte ist mir die Bedeutung nicht klar, daher ist das Ganze ziemlich fehlerträchtig, vielleicht kann da nochmal jemand mit mehr Terminalerfahrung drüber schauen.
Mit Überarbeitung des Kontextmanagers sieht es jetzt so aus: Code
(Das hard-reset fliegt wieder raus)
Ich habe versucht, nach dem Vorbild der tty-Funktionen eine eigene Funktion für den normalen Zeilenmodus zu basteln:
Code: Alles auswählen
from termios import *
IFLAG = 0
OFLAG = 1
CFLAG = 2
LFLAG = 3
ISPEED = 4
OSPEED = 5
def setcooked(fd, when=TCSAFLUSH):
"""Put terminal into cooked mode."""
mode = tcgetattr(fd)
mode[IFLAG] = BRKINT | ICRNL | INPCK | ISTRIP | IXON | IGNPAR
mode[OFLAG] = OPOST | ONLCR
mode[CFLAG] = mode[CFLAG] | CS8
mode[LFLAG] = ECHOKE | ECHOCTL | ECHOK | ECHOE | ECHO | ICANON | IEXTEN | ISIG
tcsetattr(fd, when, mode)
Mit Überarbeitung des Kontextmanagers sieht es jetzt so aus: Code
(Das hard-reset fliegt wieder raus)
Hier mal eine etwas entrümpelte Posix-Version (setcooked() siehe oben):
Der Kontextmanager stellt nun die Terminalmodi cooked, cbreak und raw bereit (als shortcuts cooked_terminal(), cbreak_terminal() und raw_terminal()). Bei Verlassen des with-Blockes wird jetzt der vorherige Zustand restauriert, mit reset_terminal() gibt es noch die Möglichkeit, das Terminal direkt auf den Ausgangszustand zurückzusetzen.
Code: Alles auswählen
import os
import sys
from termios import TCSANOW, TCSADRAIN, TCSAFLUSH, tcsetattr, tcgetattr
from functools import partial
from select import poll, POLLIN
from tty import setcbreak, setraw
from tty_extension import setcooked
# TODO: comments
TERM_MODES = {'cooked': setcooked, 'cbreak': setcbreak, 'raw': setraw}
class TerminalHandler(object):
_cooked_settings = {}
def __new__(cls, *args, **kwargs):
instance = object.__new__(cls)
instance.cooked_settings = cls._cooked_settings
return instance
def __init__(self,
mode='cbreak',
fd=sys.stdin.fileno(),
when=TCSADRAIN,
when_exit=TCSAFLUSH):
self.mode = mode
self.fd = fd
self.when = when
self.when_exit = when_exit
self.old_settings = tcgetattr(self.fd)
self.cooked_settings.setdefault(self.fd, self.old_settings)
def __enter__(self):
TERM_MODES[self.mode](self.fd, self.when)
return self.fd
def __exit__(self, type_, value, traceback):
tcsetattr(self.fd, self.when_exit, self.old_settings)
@classmethod
def reset(cls, fd=sys.stdin.fileno()):
if cls._cooked_settings.get(fd):
tcsetattr(fd, TCSAFLUSH, cls._cooked_settings[fd])
cooked_terminal = partial(TerminalHandler, 'cooked')
cbreak_terminal = partial(TerminalHandler, 'cbreak')
rare_terminal = cbreak_terminal
raw_terminal = partial(TerminalHandler, 'raw')
reset_terminal = TerminalHandler.reset
def _kbhit():
poll_obj = poll()
def wrapped(fd=sys.stdin.fileno(), timeout=0):
poll_obj.register(fd, POLLIN)
with cbreak_terminal(fd, when_exit=TCSADRAIN):
for i in poll_obj.poll(timeout):
if i[0] == fd and i[1] & POLLIN:
return 1
return 0
return wrapped
kbhit = _kbhit()
def getch(fd=sys.stdin.fileno()):
with cbreak_terminal(fd, when_exit=TCSADRAIN) as rterm:
return os.read(rterm, 1)
def getwch(fd=sys.stdin.fileno(), encoding=sys.stdin.encoding):
raw_str = ""
for i in xrange(4):
if raw_str:
if kbhit():
raw_str += getch(fd)
else: break
else:
raw_str = getch(fd)
try:
return unicode(raw_str, encoding)
except UnicodeDecodeError: continue
return unicode(raw_str, encoding)