=head1 Rxvt-Unicode: jetzt mit Mega-Perl! =head2 Zusammenfassung Seit Anfang des Jahres besitzt der X11 Terminal-Emulator "Rxvt-Unicode" einen eingebetteten Perl-Interpreter. Dieser wird jetzt schon benutzt, um z.B. die Selection intelligenter zu gestalten, um mehrere Terminals durch Reiter zu kombinieren, Popup-Menüs zu implementieren, Text zu formatieren, den Scrollback-Puffer durchsuchbar zu machen, OnTheSpot-Editing für Eingabemethoden (XIM, X Input Methods) zu implementieren und vieles, vieles mehr. Dies erfüllt meinen lang gehegten Traum, meine Hauptschnittstelle zum Rechner noch besser anpassen zu können. =head1 Einführung Rxvt-Unicode ist Ende 2003 aus dem Frust heraus entstanden, daß es keine guten und Unicode-fähigen Terminals gab. Mlterm war der beste Kandidat (sehr schlank, sehr gute Rendering-Fähigkeiten, gutes internes Design), aber mein Problem der gleichzeitigen Darstellung von Umlauten und Kanji hat es leider nicht gelöst: Dafür braucht man zwei Schriftarten, und mlterm konnte dies nur für die Kombination ASCII/sonstiges. Aber philosophische Gedanken beiseite: Als erstes wurde rxvt in C++ "übersetzt" und die richtige Interpretation von Locales beigebracht: Das "Unicode" bezieht sich auf die interne Speicherung der Zeichen, nicht auf die Festlegung von z.B. UTF-8 als Ein-/Ausgabeencoding, da hält sich Rxvt-Unicode an die vom Benutzer eingestellte Locale (wie auch bei Dingen wie die Breite der Zeichen, aber das geht schon wieder in Richtung Philosophie). Lange Jahre hindurch wünschte ich mir aber mehr Eingriffsmöglichkeiten: Ich dachte immer, wie schön es wäre, wenn man z.B. durch einen externen Prozess intelligentere Steuerungen oder Feedback-Möglichkeiten hätte. Erst Anfang des Jahres kam mir die Idee, nicht eine aufwendige sprachenunabhängige Schnittstelle zu implementieren, sondern einfach Perl einzubetten, womit ich mehr Erfahrung hatte und was auch eine einfachere Implementierung zuließ. Ein wichtiger Punkt ist der erhöhte Speicherbedarf, da Rxvt-Unicode den Ruf hat, sehr ressourcenschonend zu sein: Statt 3MB für ein full-featured Terminal sind es nun eher 7MB, dank Perl-Speicherverbrauchstechnologie. Aber da I Rxvt-Unicode Prozess beliebig viele Fenster öffnen kann (wobei die Kosten von Perl auf diese verteilt werden), ein Xft-Font mit 10MB+ und gängige Terminals wie KDEs Konsole durchaus mal mit 60MB+ zuschlagen können, dachte ich "no big deal", und es war eine gute Entscheidung :). =head1 Das "Wie" der Perl-Erweiterungen Rxvt-Unicode kommt von Hause aus mit einer Menge Perl-Erweiterugen. Hat man das Paket heruntergeladen, kann man sich diese in F anschauen (sie sind zur Zeit alle in der urxvtperl(7)-Manpage beschrieben). Ist das Paket schon installiert, muss man im F<../lib/urxvt/perl>-Verzeichnis nachschauen. Eigene Erweiterungen kann man ganz einfach erstellen und benutzen: mkdir extensions vi extentions/myext urxvt --perl-lib $PWD/extensions -pe myext Die Perl-Erweiterung (Methode etc.) ist I in der urxvtperl(7)-Manpage dokumentiert. =head1 Beispiele, Beispiele... Beispiele! =head2 Pseudo-Transparenz (C<-pe automove-background>) Viele Leute wünschen sich pseudotransparente Terminals. Ich nicht. Daher hatte ich keinen Bock, eine häufig gewünschte Erweiterung zu implementieren (schon garnicht in C++): da es stets Probleme macht, das aktuelle Hintergrundbild zu identifizieren, könnte man da nicht ein normales Terminal-Hintergrundbild angeben, dieses aber je nach Position des Fensters auf dme Schirm verschieben? Das hätte sogar den Vorteil, viel schneller zu sein, da man Effekte wie Schatten u.ä. vorher in seinem Malprogramm erreichen kann. Der erste Versuch in Perl war schnell geschreiben: sub on_configure_notify { my ($self, $event) = @_; $self->cmd_parse ("\033]20;;=+$event->{x}+$event->{y}\007"); } Ja, das ist die komplette Erweiterung: Jede Erweiterung ist (intern) eine Perl-Klasse, und für jedes Terminal-Fenster wird ein neues Objekt für jede daran gebundene Erweiterung erzeugt, das C<$self> ist also ein echtes Erweiterungs-"Objekt", daß von der C-Klasse (indirekt) abgeleitet ist. Rxvt-Unicode selbst ruft nur Methoden mit Namen auf, die mit C beginnen, und das immer bei bestimmten Ereignissen. Im obigen Beispiel, wenn ein B-Event eintrifft, der bei Größenänderungen und Verschiebungen des Fensters aufgerufen wird. Danach wird eine Escape-Sequenz generiert, die das Hintergrundbild verschiebt (alle Escape-Sequenzen sind in der urxvt(7)-Manpage dokumentiert) und dem Terminal per C eingegeben, wodurch sie so interpretiert werden, als ob das im Terminal laufende Programm sie ausgegeben hätte. Das klappte schon so halb, leider stimmen die Koordinaten im ConfigureNotify-event nicht immer, daher muss man explizit den server fragen, wo das Fenster denn nun gerade ist: sub on_configure_notify { my ($self, $event) = @_; my ($x, $y) = $self->XTranslateCoordinates ( $self->vt, $self->DefaultRootWindow, 0, 0 ); $self->cmd_parse ("\033]20;;=+$x+$y\007"); } Das klappt in allen Fällen. =head2 Eine Digitaluhr im Terminal (C<-pe digital-clock>) Klar, braucht jeder. Umso erstaunlicher ist es, daß dies die erste Erweiterung war, die existierte, weil sie recht viel Funktionalität testet. Tatsächlich soll es Leute geben, die sie benutzen. Hier ist sie: sub on_start { my ($self) = @_; $self->{overlay} = $self->overlay (-1, 0, 8, 1, urxvt::OVERLAY_RSTYLE, 0); $self->{timer} = urxvt::timer ->new ->start (1 + int urxvt::NOW) # make sure we update "on" the second ->interval (1) ->cb (sub { $self->{overlay}->set (0, 0, sprintf "%2d:%02d:%02d", (localtime urxvt::NOW)[2,1,0]); }); () } Die C-Methode wird aufgerufen, nachdem das Terminal komplett initialisiert wurde und daher komplett funktionsfähig ist. Die obige C-Methode erzeugt zuerst ein I. Das ist ein rechteckiger Kasten, der "über" dem normalen Terminal-Inhalt liegt und für Menüs und andere Dinge genutzt werden kann. Die Position ist C<-1, 0> (rechts, oben), die Größe ist C<8, 1> (8 Spalten, eine Zeile), und die initiale Farbgebung ist C, schwarz auf grellgelb. Danach wird ein Timer erzeugt, der einmal pro Sekunde am Sekundenanfang feuert. Der Callback ersetzt den Text im Overlay durch eine einfache Digitalanzeige. Wichtig ist, daß Overlays, Timer usw. direkt in C<%$self> gespeichert werden. Der Grund ist nicht sofort ersichtlich: Solange noch irgendwelche Event-Watcher (Zeitgeber, Datei-Watcher, Prozess-Watcher usf.) aktiv sind, endet der Prozess nicht. Merkt man sich den Timer in einer C-Variable, wird er beim Löschen des Fensters vielleicht nicht freigegeben und läuft weiter, und er Benutzer wundert sich, warum Rxvt-Unicode weiterläuft, obwohl er das Fenster geschlossen hat. C<%$self> wird dagegen automatisch geleert, was Timer und andere Objekte automatisch zerstört. Im Prinzip kann man Rxvt-Unicode als Perl-Daemon betrachten, der I Terminal-Fenster aufmachen kann. Ach ja, er unterstützt natürlich auch C, allerdings nur non-blocking (wäre doof, wenn das Terminal Minuten oder auch nur Sekunden "steht"). Näheres im Beitrag "Ereignisgesteuerte Programmierung". =head2 URLs markieren und anklickbar gestalten (C<-pe mark-urls>) Noch ein Feature, daß nur existiert weil es so simpel zu implementieren war, ist die C Erweiterung: Sie unterstreicht alle URLs (naja, zumindest viele) im Terminal-Fenster. Wenn man draufklickt, startet sie einen Browser. Zuerst zu dem Teil, der die Sache mit der Unterstreichung regelt: my $url = qr{( (?:https?|ftp|news|mailto|file)://[ab-zA-Z0-9\-\@;\/?:&=%\$_.+!*\x27(),~]+ [ab-zA-Z0-9\-\@;\/?:&=%\$_+!*\x27()~] # exclude some trailing characters (heuristic) )}x; Diese horrende Regex versucht, "übliche" URLs zu matchen. Muss man nicht lesen können, klappt aber gut. sub on_line_update { my ($self, $row) = @_; Die C-Methode wird (meistens) aufgerufen, wenn eine Zeile dargestellt wird, die bisher noch nicht durch Perl gefiltert wurde. Das bedeutet schlicht, daß man jeden angezeigten Text filtern kann. Wird eine Zeile verändert, wird die Methode nochmals aufgerufen. Man muss nur aufpassen, daß die Zeile eventuell schon Änderungen von vorherigen Aufrufen enthält, so daß man zwar Unterstreichung gefahrlos anschalten, aber nicht Togglen darf. Das "langsame" Perl behindert übrigens nicht das Scrolling: Obwohl Perl so "langsam" ist, hat es erstaunlich wenig Einfluß auf die Geschwindigkeit, obwohl es pro Bildschirmrefresh 30 mal oder mehr aufgerufen wird (je nach Zeilenzahl). Außerdem ruft Rxvt-Unicode Perl nicht auf, wenn es an der Grenze des technisch Möglichen (Last!) scrollt. Anders als andere Terminals (die kleinen Wichtel, äh Gnome, kommen mir da in den Sinn) gibt es im Normalfall aber auch keine Zeitverzögerung, d.h. die URLs erscheinen, sofern die Last es zuläßt, sofort unterstrichen und nicht erst nach kurzer Verzögerung. Eine "kleine" Bemerkung zum Begriff "Zeile" (line): Ein Terminal ist ein rechteckiges Raster aus vielen, immer gleich großen, "Zellen", die einzelne Zeichen (oder Teile derselben) enthalten können. Diese werden in "Reihen" (eigentlich Zeilen) ("rows") und Spalten ("columns") eingeteilt. Darüberhinaus gibt es aber Absätze, die aus langen Zeilen bestehen, die in mehrere "Reihen" aufgespalten wurden. Diese Absätze werden "Zeilen" (lines) genannt, wobei die Unterscheidung von Zeilen vs. Reihen (lines vs. rows) im Deutschen etwas künstlich ist: korrekt wäre es, von Absätzen zu sprechen, wenn man potentiell lange, mehrzeilige "Zeilen" meint, aber ich werfe das immer durcheinander: Zeilen kann also mal Absatz, mal Einzelzeile/Reihe bedeutet. C wirds jedenfalls pro Absatz, nicht pro Terminal-Zeile aufgerufen. Zurück zum Quelltext, der als allererstes die entsprechende Zeile ausliest: # fetch the line that has changed my $line = $self->line ($row); my $text = $line->t; C<$row> ist immer die oberste Zeile, und C<< $self->line >> erzeugt ein "Absatz-Objekt", daß man manipulieren kann. Nun müssen noch alle URLs in der Zeile gefunden werden: # find all urls (if any) while ($text =~ /$url/g) { Und unterstrichen werden: my $rend = $line->r; $_ |= urxvt::RS_Uline for @{$rend}[ $-[1] .. $+[1] - 1]; $line->r ($rend); } Zum Unterstreichen werden die Zeichenattribute ("rendition") der Zeile gelesen, für die entsprechenden Zeichen wird das C-Attribut (naja, "Bit") gesetzt, und wieder geschrieben. Ergebnis: URL wird unterstrichen. Fertig: () } Das C<()> sorgt dafür, daß nicht aus Versehen etwas zurückgegeben wird, was nicht zurückgegeben werden soll: Alle C*-Methoden sollten nichts zurückliefern, sofern sie nicht das Ereignis "fressen": Wenn z.B. ein Mausklick verarbeitet wird, entscheidet der Rückgabewert darüber, ob Rxvt-Unicode ihn Intern weiterverarbeitet (C<()>) oder ignoriert (C<1>). Die URL zusätzlich anklickbar zu machen, erfordert einen gedanklichen Spagat und etwas Vorsicht, ist aber prinzipiell der gleiche Vorgang: sub on_start { my ($self) = @_; $self->{browser} = $self->x_resource ("urlLauncher") || "x-www-browser"; () } Zuerst schaut die Erweiterung nach der C-Resource. Auf diese Weise kann man konfigurieren, welchen "Browser" man denn gerne hätte: URxvt.urlLauncher: links -g Dies sollte man unbedingt in C machen, da es nur eine Resourcedatenbank für alle Terminals gibt und diese nur beim Start aktuell ist. Keinsfalls sollte man globale Variablen verwenden, da es durchaus Benutzer gibt, die verschiedene Terminal-Fenster verschieden konfigurieren, und globale Variablen sind für alle Terminal-Fenster eines Prozesses gleich. Für den "Klick" muss man noch die C-Methode implementieren: sub on_button_release { my ($self, $event) = @_; my $mask = $self->ModLevel3Mask | $self->ModMetaMask | urxvt::ShiftMask | urxvt::ControlMask; if ($event->{button} == 2 && ($event->{state} & $mask) == 0) { Das C sorgt dafür, daß nur bei "normalen" Klicks (keine wichtigen Modifer-Tasten wie Ctrl gedrückt), und nur mit der mittleren Maustaste angeklickt werden kann (ich finde das nicht intuitiv mit der mittleren Taste, aber mir wurde gesagt, gnome-terminal würde es ebenfalls so machen, und gnome-terminal hat bei solchen Features per Definition recht, solange es sich an das hält, was KDEs Konsole auch tut und es I hilfreich beim Arbeiten ist. Hmmja). In C<($row,$col)> steht die Koordinate des Klicks. Doch welche URL befindet sich darunter? Da man (noch) keine Daten mit Zeilen verknüpfen kann, muss man - wie beim Markieren - auch alle URLs suchen und diejenige finden, die sich unterhalb des Maus-Cursors befindet: my $row = $event->{row}; my $col = $event->{col}; my $line = $self->line ($row); my $text = $line->t; while ($text =~ /$url/g) { if ($-[1] <= $col && $+[1] >= $col) { $self->exec_async ($self->{browser}, $1); return 1; } } } () } Die C-Methode startet ein externes Programm, hier den Browser. Sie ist besser als ein einfaches C, daß sie das richtige %ENV setzt (wie schon bemerkt, kann ein einzelner Prozess viele Fenster mit unterschiedlichen Environment-Variablen haben). =head2 Blockgrafik-nach-ASCII (C<-pe block-graphics-to-ascii>) Dies ist ein einfacher Filter, um alle Blockgrafikzeichen durch ASCII-Zeichen zu ersetzen: sub on_add_lines { my ($self, $str) = @_; $str =~ s/([\x{2500}-\x{259f}])/substr $rep_unicode, (ord $1) - 0x2500, 1/ge; $self->scr_add_lines ($str); 1 } (In Wahrheit etwas komplexer, da es in Vor-Unicode-Zeichen etwas anders gemacht wurde). Eine Erklärung ist fast nicht notwendig: C wird aufgerufen, wenn Text ausgegeben werden soll. In diesem werden Unicode-Blockgrafikzeichen durch ASCII-Äquivalente in C<$rep_unicode> (Definition steht nur in der Quelldatei, da der Perlworkshop noch nicht Unicode-fähig ist) ersetzt und ausgegeben. Die C<1> am Ende sorgt dafür, daß Rxvt-Unicode den Text nicht mehr ausgibt. =head1 Weitere Erweiterungen Es gibt noch eine Menge weiterer Erweiterungen, die z.B. asynchrone Kommunikation mit Pipes benutzen, um eine Art Distributed Shared Clipboard zu implementieren (C<-pe remote-clipboard>), Popups implementieren (C<-pe selection-popup>), die übrigens nichts anderes als kleine Terminal-Fenster sind, inkrementelles Suchen im Scrollback-Buffer ermöglichen (C<-pe scrollback-search>), "Tabbed Terminals" (C<-pe tabbed>), Cursor-Movement per Mausklick (C<-pe readline>) und einiges andere mehr. Natürlich gibt es auch einfach nur "Nützliches", wie die intelligentere Selection, die auch bei hartnäckigen Fällen korrekt Shell-Argumente parsed... cerebro ~/src/rxvt-unicode/src# ls da"t ei"\ .txt ^^^^^^^^^^^^^^ oder eine vom Benutzer getätige Selection sofort transformiert: syntax error at /opt/rxvt/lib/urxvt/urxvt.pm line 957, near "_exit 255" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Selektiert man das Unterstrichene, landet bei mir folgendes in der Selection: :e \/opt\/rxvt\/lib\/urxvt\/urxvt\.pm :957 Das muss ich nur noch in meinen VI[M] pasten und bin sofort an der richtigen Stelle. Ich hoffe, dem Ein- oder Anderen habe ich etwas Lust gemacht, sein bevorzugtes Man-Machine-Interface an die eigenen Bedürfnisse anzupassen und noch intelligenter zu gestalten. =head1 Danke... ... an Ralf Valerien für seine Kommentare und Korrekturen.