xterm Rückgabe einlesen...???

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
BlackJack

@mutetella: Die Gefahr mit dem dazwischen funken dürfte eigentlich immer bestehen. Es reicht ja, wenn man auf dem Terminal vorher ein anderes Programm asynchron gestartet hat, welches jederzeit etwas in das Terminal schreiben könnte. Oder Nachrichten vom System oder vom Administrator die in allen Terminals erscheinen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Lässt sich die Cursorposition eigentlich nicht auch über 'termios' abfragen. Ähnlich wie 'TIOCGWINSZ'? Hab' schon 'termbits.h' und 'termios.h' durchgeschaut, kann aber nichts finden, was darauf hinweist. Ich bin da jetzt wirklich auch a bisl zu schwach auf der Brust... :wink:

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack:
Wobei diese Dinge aber nicht in 'stdin' landen und demnach auch keine Probleme bereiten.
Nur: Warum landen die eigentlich nicht in 'stdin'? Werden die vom OS automatisch gekapselt oder so?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Wenn Du belauschst was auf dem Terminal ausgegeben wird, dann müsstest Du diese Ausgaben auch mitbekommen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@BlackJack:
Also vielleicht ist ja meine Vorgehensweise nicht richtig:

Code: Alles auswählen

#terminal '/dev/pts/1':
>>> old_settings = termios.tcgetattr(sys.stdin)
>>> byte = ''
>>> try:
...     tty.setraw(sys.stdin)
...     while not byte == 'q':        
...         byte = sys.stdin.read(1)
... finally:
...     termios.tcsetattr(sys.stdin, termios.TCSAFLUSH, old_settings)
... 

Code: Alles auswählen

#terminal '/dev/pts/2':
>>> other_terminal = os.open('/dev/pts/1', os.O_RDWR)
>>> os.write(other_terminal, 'q')
>>>
Nachdem ich also von Terminal 2 aus ein 'q' ins Terminal 1 geschrieben habe erscheint dieses zwar an der aktuellen Cursorposition, wird aber von 'sys.stdin.read(1)' nicht erfasst.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

mutetella hat geschrieben:Also vielleicht ist ja meine Vorgehensweise nicht richtig:
Du kannst schlichtweg die Eingabe ins Terminal (ich meine hier STDIN des Masters) nicht am STDIN des Programmes lesen. Nochmal:

Code: Alles auswählen

Pseudoterminalpaar mit xterm und Python:
xterm ---- #master out: -----> in: #slave ---- Python (Terminalausgabe wird zu Programmeingabe)
                    in: <----- out:                   (Programmausgabe wird zu Terminaleingabe)
#master und #slave sind 2 Dateiobjekte, die Dir z.B. von openpty() zurückgegeben werden. Dabei wird im neueren Unix98-Gewand der Master nicht mehr als eigenständiger Gerätekoten unter /dev vorgehalten, sondern bei open('/dev/ptmx') ein Klon ohne Geräteknoten erstellt (Master liegt nur als filedescriptor vor). Der Master unterliegt desweiteren der Beschränkung, nur einmal geöffnet zu werden für ein pty-Paar, während das slave-Gerät beliebig häufig geöffnet werden kann.

Jetzt schreibst Du das 'q' in '/dev/pts/1', also dem slave, ergo landet es im STDIN des Master (dem xterm-Prozess). Da der Master ohne Eingriff in den Kernel nur einmal geöffnet sein darf, kommst Du an diese Daten von aussen nicht mehr ran. (Das Mitlesen war früher hier noch einfacher möglich.)

Zu sys.stdout.write('\033[18t'):
Hier habe ich mich getäuscht - xterm schreibt den Rückgabewert tatsächlich nochmal raus, und dieser landet dann in STDIN des Programmes. Daher Dein Erfolg mit STDIN.

Zur Cursorposition:
Unter xterm funktioniert z.B. '\E[6n'.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

jerch hat geschrieben:Jetzt schreibst Du das 'q' in '/dev/pts/1', also dem slave, ergo landet es im STDIN des Master (dem xterm-Prozess). (...)
Allerdings lese ich in meinem Beispiel doch nicht den master, sondern den slave aus. Ich lese '/dev/pts/1' aus und schreibe vom anderen Terminal aus in dieselbe Datei hinein.

Es müsste doch folgendes passieren:

Code: Alles auswählen

S1 = '/dev/pts/1'
S2 = '/dev/pts/2'
MA = '/dev/ptmx'

S2_out -> in_MA_out -> in_S1_out -> in_MA_out -> screen
An welcher Stelle liege ich verkehrt?

2 Dinge, die ich in diesem Zusammenhang ebenfalls noch nicht wirklich verstanden habe:
  • Wie kann zwischen 'STDIN', 'STDOUT' und 'STDERR' unterschieden werden, nachdem doch alle 3 auf dieselbe Datei verweisen? Die nötigen flags vorausgesetzt konnte ich noch keinen Unterschied feststellen, ob ich descriptor 0 oder 1 zur Ein- oder Ausgabe verwende.
  • Weshalb bleiben die restlichen bytes bei Zeichen, die > 1 byte sind bei der Eingabe via 'sys.stdin.read(1)' im Puffer erhalten, wohingegen bei der Eingabe via 'os.read(slave, 1)' die restlichen bytes aus dem Puffer ('/dev/pts/..' ?) verschwinden. Beispiel:

    Code: Alles auswählen

    def raw_read(manner):
        if manner == 'os':
            slave = os.open(os.ttyname(0), os.O_RDONLY)
            read_ = functools.partial(os.read, slave)
        else:
            read_ = sys.stdin.read
        try:
            old = termios.tcgetattr(sys.stdin)
            tty.setraw(sys.stdin)
            return read_(1)
        finally:
            termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old)
    

    Code: Alles auswählen

    >>> raw_read('os')  #Pfeiltaste links ('\x1b[D') tippen
    '\x1b'              #erstes byte wird gelesen
    >>> [D              #restliche bytes landen in stdout
    >>> raw_read('sys') #Pfeiltaste links ('\x1b[D') tippen
    '\x1b'              #erstes byte wird gelesen
    >>> raw_read('sys')
    '['                 #zweites byte wird gelesen
    >>> raw-read('sys')
    'D'                 #drittes byte wird gelesen
Vielen herzlichen Dank für die Mühe, die ihr euch mit mir macht!

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Etwas ausführlicher, was in Deinem obigen Bsp. passiert:

Code: Alles auswählen

   Prozess am Master                        Pseudoterminal                          Prozess am slave

    -------       read                                                     write     ----------------
   |       |  <-----------                      <----                    <--------  | STDOUT         |
   | xterm |      write       master(/dev/ptmx)       slave(/dev/pts/1)    read     |         Python |
   |       |  ------------>                     ---->           |        -------->  | STDIN          |
    -------                              Terminal-Processing    |                    ----------------
                                                                |
    ---> Output (Display über X)                                |
    <--- Input (Keyboard über X)                                |
                                                                |     zweiter Python-Prozess an /dev/pts/2 <----> anderer Master
                                                                |     --------------------                        (zB. 2. xterm)
                                                                 --- | öffnet /dev/pts/1  |
                                                                     |  und schreibt 'q'  |
                                                                      --------------------
Die Schreibaktion des zweiten Python-Prozesses landet im Master und wird dort von xterm gelesen und am Bildschirm ausgegeben. Um etwas am STDIN des Slave-Prozesses zu sehen, muss es vom Master-Prozess in Master geschrieben werden. Das passiert z.B. bei Keyboard-Input, hier schreibt xterm die Zeichen in master. Je nach Terminaleinstellung werden diese Zeichen vom Pseudoterminal nur nach slave geleitet (echo off) oder nochmal nach master gespiegelt (echo on). Das ist Teil des Terminalprocessings (termios und Konsorten), welches sicherstellt, dass der slave-Prozess eine Terminalumgebung zu sehen bekommt.

Die Auftrennung von STDIN, STDOUT ist historisch bedingt, als die Terminal Ein-/Ausgabe physisch getrennt waren. Hier gibts ein Paar Hintergrund-Infos zu TTYs - http://www.linusakesson.net/programming/tty/index.php
Am Linux-Textterminal ist diese Trennung auch noch gut nachvollziehbar, da sind Ein-/Ausgabe noch sehr nah am Keyboard-/Displaytreiber.
Auch ermöglicht diese Auftrennung in Sende- und Empfangskanal sehr elegante Pipe-Umleitungen, ohne die Bash-Skripting ziemlich alt aussehen würde.

Zu Deinem sys.stdin.read vs os.read Problem - da müsste man mal schauen, wie sys.stdin.read implementiert ist und wie es mit dem Byteparameter umgeht. os.read(fd, n) liest 1 bis n Bytes, keinesfalls mehr, während sys.stdin.read(n) auf n bytes wartet. Wahrscheinlich konsumiert sys.stdin.read mehr Bytes vom fd (und puffert diese), wodurch der Input geleert wird. Im Falle von os.read verbleiben diese im fd und werden mit dem Umschalten des Terminals in `finally` an den Master-Prozess gespiegelt und von diesem dargestellt (die Zeichen landen dabei nicht in STDOUT).
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@jerch:
Ich bin dabei, zu verstehen, was mit meinem 'q' und all dem anderen Getippe passiert... :wink: Danke für Deine Hilfe!

Um nochmal auf das eigentliche Thema zurückzukommen: Du verwendest in Deinem 'winsize()'-Beispiel ein poll-Objekt, um zu überprüfen, ob etwas auf STDIN zum Auslesen wartet. Dazu überprüfst Du, ob ein POLLOUT-event stattfindet. Damit hab' ich ein wenig herumgespielt und gesehen, dass egal, ob Daten vorhanden sind oder nicht, 'poll_obj.poll(10)' immer denselben Zustand, dass Daten zum Auslesen vorhanden seien, meldet.
Warum ist das so? Verkürztes Beispiel:

Code: Alles auswählen

def raw_read(length, default=''):
    with jerch_terminal.raw_terminal():
        result = ''
        f = os.open(os.ttyname(0), os.O_RDWR)
        poll_ = select.poll()
        poll_.register(f, select.POLLOUT)
        sys.stdout.write(default)
        sys.stdout.flush()
        first = poll_.poll(10)
        result = os.read(f, length)
        poll_.modify(f, select.POLLOUT)
        last = poll_.poll(10)
        os.close(f)
        return first, last, result

Code: Alles auswählen

>>> raw_read(32, '\033[?6n')
([(28, 4)], [(28, 4)], '\x1b[?26;1R')
>>> raw_read(3)
([(28, 4)], [(28, 4)], 'a')
mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

POLLOUT testet auf nicht blockierendes Schreiben. Was Du suchst ist POLLIN - es sind Daten zum Lesen vorhanden.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

jerch hat geschrieben:Zu Deinem sys.stdin.read vs os.read Problem - da müsste man mal schauen, wie sys.stdin.read implementiert ist und wie es mit dem Byteparameter umgeht. os.read(fd, n) liest 1 bis n Bytes, keinesfalls mehr, während sys.stdin.read(n) auf n bytes wartet. Wahrscheinlich konsumiert sys.stdin.read mehr Bytes vom fd (und puffert diese), wodurch der Input geleert wird. Im Falle von os.read verbleiben diese im fd und werden mit dem Umschalten des Terminals in `finally` an den Master-Prozess gespiegelt und von diesem dargestellt (die Zeichen landen dabei nicht in STDOUT).
Genau so verhält es sich offensichtlich. Bin gerade auf diese Antwort gestoßen, die Deine Vermutung bestätigt.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten