Das ``Perlpanel'' ersetzt die Bodenpanels von Gnome und KDE, ist in Perl geschrieben, und erlaubt auch selbstgeschriebene Perl-Widgets.
Jeder ausgewachsene Desktop, ob Gnome oder KDE, bietet sogenannte
Panels an. Sie schmiegen sich entweder oben oder unten an den
Desktop an, beherbergen Menüs und Icons zum Starten von Programmen,
zeigen laufende Applikationen mit Task-Bars an und schalten zwischen
virtuellen Desktops hin und her. Ziel des alternativen Pakets
perlpanel
ist es, ein plattformunabhängiges Panel bereitzustellen,
das der Anwender mit ein paar schnell zusammengeklopften Perlskripts
um eigene Widgets erweitern kann. Unter Ubuntu installiert der User das
Panel mit apt-get install perlpanel
und startet es probeweise mit dem
Aufruf perlpanel
, einem Perlskript in /usr/bin
. Abbildung 1 zeigt die
GUI am unteren Rand des Desktops.
Abbildung 1: Das PerlPanel bietet die üblichen Funktionen wie virtuelle Desktops und Task-Bars, erlaubt aber auch selbstgeschriebene Widgets wie einen Aktienticker. |
Ein eventuell schon an dieser Stelle liegendes Panel des Window-Managers kann man entweder zur Seite ziehen und es den Window-Manager vertikal am rechten oder linken Rand darstellen lassen, oder gleich ganz löschen, falls man die Brücken abbrennen möchte.
Wer gerne mit Aktien spekuliert, möchte vielleicht die Kurse interessanter
Werte stets im Blick behalten und keine Applikation, deren Fenster
eventuell durch andere verdeckt werden, ist dazu geeignet. Was liegt also
näher, als ein Applet für das PerlPanel zu entwickeln, das alle 5 Minuten
alle in der Konfigurationsdatei ~/.ticker-rc
im Homeverzeichnis abgelegten
Aktienwerte von Yahoo Finance abholt und anzeigt? Wie in Abbildung 2
gezeigt, ignoriert das Applet Kommentar- und Leerzeilen und erwartet
pro Zeile ein Aktienkürzel. Abbildung 1 zeigt, wie es die vier ausgewählten
Kurse im Panel auf den Desktop bringt.
Abbildung 2: Die Kurse der in ~/.ticker-rc eingetragenen Aktien holt das Applet in regelmäßigen Abständen vom Yahoo-Server. |
Der eigentliche Applet-Code ist in Listing Ticker.pm
zu sehen, das
Einholen der Kurse vom Yahoo-Server erledigt das Skript in Listing
getquote
, das
auf der Kommandozeile eine Reihe von Aktienkürzeln entgegennimmt und
deren letzte Kurswerte in einem Spaltenformat zeilenweise ausgibt. Das
Applet liest einfach die Konfigurationsdatei ein, ruft
im 5-Minuten-Rhythmus das Skript getquote
auf, und frischt mit den
zurückkommenden Werten seine Anzeige im PerlPanel auf.
Falls der User mit der Maus auf das Applet klickt, ist er offensichtlich
ungeduldig und möchte die neuesten Kurse, also führt das Applet sofort
einen weiteren Request an den Yahoo-Server durch und frischt die Anzeige
im Panel gleich wieder auf.
Die an getquote
auf der Kommandozeile übergebenen Aktienkürzel liegen
im Skript wie in Perl üblich im Array @ARGV
vor. Das Modul
Finance::YahooQuote holt mit der Funktion getcustomquote
die Kurse
vom Yahoo-Server und gibt an, dass es nur an den Spalten "Symbol"
(das vorher bei der Anfrage angegebene Aktienkürzel) und
"Last Trade (Price Only)"
(dem letzten Börsenkurs) interessiert ist.
Yahoo liefert sonst mit der Funktion getquote()
einen ganzen
Rattenschwanz an Daten zurück, den das Applet aber nicht braucht und
deswegen von vorneherein ablehnt. Passiert ein Fehler bei der Übertragung,
kommt ein einzelner Wert mit einer Fehlermeldung zurück, während Yahoo im
Erfolgsfall einen Array zurückliefert, dessen Einträge wiederum Referenzen
auf Arrays mit den Einträgen Aktiensymbol und Kurs sind. Zeile 13 prüft
deswegen, ob tatsächlich ein zweispaltiger Eintrag zurückkam und bricht
andernfalls sofort ab. Zeile 5 setzt noch einen Timeout von 60 Sekunden,
der bei Netzwerkstörungen dafür sorgt, dass das Skript nicht ewig
herumhängt.
Über die Leitung eingetrudelte Kurswerte gibt die
anschließene for
-Schleife ab Zeile
17 im Format ``Kürzel Kurs'' zeilenweise auf der Standardausgabe aus.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Finance::YahooQuote; 04 05 $Finance::YahooQuote::TIMEOUT = 60; 06 07 exit 0 unless @ARGV; 08 09 my @quotes = getcustomquote( 10 [@ARGV], 11 ["Symbol", "Last Trade (Price Only)"]); 12 13 if(! exists $quotes[0][1]) { 14 die "Fetching quote failed\n"; 15 } 16 17 for my $quote (@quotes) { 18 print "@$quote\n"; 19 }
Listing Ticker.pm
enthält den Applet-Code und muss den Richtlinien des
PerlPanels genügen. Dazu gehört ein Modul im Namensraum
PerlPanel::Applet::Name, ein Konstruktor new()
sowie eine vom
Panel anfänglich angesprungene Initial-Funktion configure()
.
expand()
und fill()
geben an, wie sich das Widget verändert,
wenn der verfügbare Platz im Panel größer wird. widget()
muss eine
Referenz auf oberste Gtk-Widget des Applets zurückgeben und
get_default_config()
liefert normalerweise eine Datenstruktur, die das
Applet konfiguriert. Da aber im vorliegenden Fall die Konfiguration extern
in einer Datei liegt, weil Änderungen seitens des Benutzers keinen Neustart
des Applets erfordern sollen, gibt die Funktion hier nur undef
zurück.
Damit ist der API des PerlPanels Genüge getan und es fehlt nur noch der
eigentliche Applikationscode. In configure()
baut das Applet seine
GUI auf, die aus einem Label mit den Kursangaben besteht und einem damit
verbundenen
Button, der Klicks des Benutzers verarbeitet. Zeile 33 definiert dies
mit der Methode signal_connect()
des Widgets, das dem Event ``clicked''
eine anonyme Subroutine zuordnet, die die Applet-Methode stocks_update()
aufruft. Sind alle Widgets definiert, zeichnet show_all()
sie in
das Panel ein. Zeile 42 ruft das erste Mal stocks_update()
auf, bevor
das Programm mit add_timeout()
. ein alle 5 Minuten
(5 * 60 * 1000 Millisekunden) wiederkehrendes Ereignis definiert, das
ebenfalls stocks_update()
aufruft.
Die Funktion ruft ihrerseits jeweils symbols()
auf, die die Datei
~/.ticker-rc
ausliest, also kurzfristige Kürzelupdates seitens
des Users dort sofort mitbekommt. Kommentarzeilen, Leerzeilen, und alles,
was nicht nach Aktienkürzel aussieht, verwirft sie, denn später werden
diese Kürzel auf der Kommandozeile an getquotes
übergeben und dann
sollen keine bösen Überraschungen passieren.
stocks_update()
erledigt dies mit dem Aufruf von open()
mit einer
Pipe, damit getquote
im Hintergrund startet. Nun heißt es Aufpassen:
Verbisse sich der Code gleich in die Ausgabe des extern aufgerufenen
Prozesses, müsste er eventuell ein paar Sekunden warten, bis die Ergebnisse
über das Internet eintreffen, und das ist bei einer GUI, die stets
flink auf Usereingaben reagieren muss, keine gute Idee. Stattdessen nutzt
stocks_update()
die Gtk2::Helper-Funktion add_watch()
, die einen
Filedeskriptor entgegennimmt (fileno()
generiert aus einem
Perl-Filehandle einen Deskriptor) und springt bei eintreffenden Daten
eine anschließend angegebene Callback-Funktion an. Dies bedeutet, dass
des Programm in der Zwischenzeit weiterläuft, stocks_update()
beendet und in die Haupteventschleife des Applets springt, wo Usereingaben
und andere externe Ereignisse verzugslos abgearbeitet werden.
Schickt get_quote
endlich Daten, hat aber noch mehr auf Lager, ist
eof(COMMAND)
falsch und der else-Zweig ab Zeile 106 schnappt
die bisher vorliegenden Ergebnisse auf. Ist get_quote
hingegen
fertigt, kommt der if
-Zweig an die Reihe, das File-Handle wird mit
close()
abgeräumt und der Watch auf den zugehörigen Deskriptor
mit remove_watch()
beendet. Zeile 103 zieht das Spaltenformat dann
einfach zu einer Zeile im Format ``Kürzel Kurs Kürzel Kurs ...'' zusammen
und schickt es mit der Methode set_markup()
an das Label-Widget,
das den Text auf dem Panel anzeigt.
Abbildung 3: In der Datei /tmp/ticker.log protokolliert das Perl-Applet mit, woran es gerade arbeitet. |
Damit der Entwickler weiß, was das Applet treibt, initialisiert
Ticker.pm
anfangs Log4perl, um die in den Code eingebetteten
Log-Statements in die Datei /tmp/ticker.log
umzuleiten. In
Abbildung 3 sind einige Zeilen daraus zu sehen. Wer auf die zusätzlichen
Daten verzichten kann, kommentiert einfach den Aufruf von easy_init()
im Code aus.
001 package PerlPanel::Applet::Ticker; 002 use strict; 003 use Log::Log4perl qw(:easy); 004 005 my $REFRESH = 5 * 30_000; 006 my($CFG_FILE)= glob "~/.ticker-rc"; 007 my $GETQUOTE = "/usr/bin/getquote"; 008 009 Log::Log4perl->easy_init({ 010 level => $DEBUG, 011 file => ">>/tmp/ticker.log", 012 }); 013 014 ########################################### 015 sub new { 016 ########################################### 017 my($package) = @_; 018 019 my $self = {}; 020 bless($self, $package); 021 return $self; 022 } 023 024 ########################################### 025 sub configure { 026 ########################################### 027 my($self) = @_; 028 029 $self->{label} = 030 Gtk2::Label->new("Ticker"); 031 032 $self->{widget} = Gtk2::Button->new(); 033 $self->{widget}->signal_connect( 034 'clicked', sub { 035 $self->stocks_update() }); 036 037 $self->{widget}->set_relief('none'); 038 $self->{widget}->add($self->{label}); 039 040 $self->{widget}->show_all; 041 042 $self->stocks_update(); 043 044 PerlPanel::add_timeout(5 * 60_000, 045 sub { $self->stocks_update() ; 046 return 1 }); 047 048 return 1; 049 } 050 051 ########################################### 052 sub symbols { 053 ########################################### 054 my @symbols = (); 055 056 if(! open(FILE, "<$CFG_FILE")) { 057 ERROR "Cannot open $CFG_FILE"; 058 return (); 059 } 060 061 while(<FILE>) { 062 s/#.*//g; 063 s/[^\w\.]//g; 064 next if /^\s*$/; 065 chomp; 066 push @symbols, $_; 067 } 068 069 return @symbols; 070 } 071 072 ########################################### 073 sub stocks_update { 074 ########################################### 075 my ($self) = @_; 076 077 my($tag, $buffer); 078 my $symbols = join " ", symbols(); 079 080 DEBUG "Updating '$symbols'"; 081 082 if($symbols eq "") { 083 $self->{label}->set_markup( 084 "No symbols defined"); 085 return undef; 086 } 087 088 if (!open(COMMAND, 089 "$GETQUOTE $symbols |")) { 090 $self->{label}->set_markup( 091 "Fetch failed ($!)"); 092 ERROR "Fetch failed ($!)"; 093 return undef; 094 } 095 096 $tag = Gtk2::Helper->add_watch( 097 fileno(COMMAND), 'in', 098 sub { 099 if (eof(COMMAND)) { 100 DEBUG "Received data: $buffer"; 101 close(COMMAND); 102 Gtk2::Helper->remove_watch($tag); 103 $buffer =~ s/\n/ /g; 104 $self->{label}->set_markup( 105 $buffer); 106 } else { 107 $buffer .= <COMMAND>; 108 } 109 }); 110 111 return 1; 112 } 113 114 ########################################### 115 sub expand { return 0; } 116 sub fill { return 0; } 117 sub widget { return $_[0]->{widget} } 118 sub get_default_config { return undef; } 119 120 1;
Unter Ubuntu installiert der Befehl sudo apt-get install perlpanel
das
Perl-Panel und bringt gleich alle abhängigen Perl-Module mit. Diese stammen
aus dem Ubuntu-Package-Repository und deshalb geht dies ganz ohne CPAN-Shell.
Das Skript getquote
wird in /usr/bin
installiert und
benötigt das Modul Finance::YahooQuote, das
ebenfalls per Ubuntu-Paket (libfinance-yahooquote-perl
) erhältlich
ist.
Damit das Perlpanel um das neue Ticker-Applet weiß, sind zwei Schritte
erforderlich: Erstens wird die Datei Ticker.pm
ins Verzeichnis
/usr/share/perlpanel/PerlPanel
kopiert, wo auch noch weitere Applets,
mit denen PerlPanel ausgeliefert wird, liegen. Zweitens wird die
Applet-Registry in der Datei /usr/share/perlpanel/applet.registry
um die Zeile
Ticker:Stock Ticker:Utilities
erweitert. Sie gibt an, dass das Panel das Widget ``Ticker'' im
Appletverzeichnis unter dem Namen Ticker.pm
findet, dass es sich
um einen ``Stock Ticker'' handelt und dass dieser im Bereich ``Utiltities''
zu finden ist.
Nun weiß Perlpanel nach einem Neustart
um die Existenz des Applets, stellt es aber noch nicht dar. Hierzu
muss es der User erst dem Panel hinzufügen, und das geht, indem
er auf den ``Actions''-Button (Abbildung 1) des Panels klickt und
den Menüpunkt ``Configure'' auswählt. In der
der aufpoppenden Dialogbox (Abbildung 4) wird anschließend der
Knopf ``+Add'' gedrückt.
Zum Vorschein kommt die Auswahlbox nach Abbildung 5, die eine Vielzahl
von bereits fertigen Applets anbietet. Unter ihnen befindet sich auch
``Ticker'', das erst ausgewählt und dann mit +Install Applet
installiert
wird. Anschließend erscheint es wieder in der Box nach Abbildung 4, wo
der User es nach oben oder unten verschieben kann, um seine horizontale
Lage auf dem Panel zu verändern.
Abbildung 4: Ein Klick auf den "Action"-Knopf des Panels und anschließende Wahl des Menüpunkts "Configure" bringt diesen Dialog zum Vorschein, mit der der User weitere Applets zum Panel hinzufügen kann. |
Abbildung 5: Das neugeschriebene Perl-Applet "Ticker" steht zur Auswahl bereit und kann per Mausklick zum Panel hinzugefügt werden. |
Damit das Panel automatisch beim Einloggen in eine Session des
Window-Managers startet, wird das Programm /usr/bin/perlpanel
in
den Session-Start-Dialog eingefügt, unter Gnome geht dies wie in Abbildung
6 gezeigt in der Dialogbox, die hochkommt, wenn der User den Eintrag
``System->Preferences->Sessions'' im Hauptmenü anklickt.
Abbildung 6: Mit dieser Konfiguration startet der Gnome Session Manager das Perl Panel jeweils beim Einloggen. |
Weitere Informationen zum Bauen eigener Perl-Applets können
experimentierfreudige Panelnutzer der Datei
perlpanel-applet-howto.pod
entnehmen, die zwar nicht dem Ubuntu-Paket,
aber dem in CVS eingecheckten Source-Code des Perlpanel-Projektes
auf [2] beiliegt. Alle Funktionen des Panels, inklusive der virtuellen
Desktopschalttafel, dem Taskbar und den Dialogfenstern zum Hinzufügen
neuer Applets und deren Konfiguration sind in Perl geschrieben und
geben einen Vorgeschmack darauf, was mit PerlPanel
alles möglich ist.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2009/03/Perl
PerlPanel-Projekt: http://savannah.nongnu.org/projects/perlpanel
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |