Das Mac-Utility Spotlight führt selbst hartgesottene Apple-Fanboys von der Maus wieder zurück zur Tastatur. Ein kurzes Perl-Skript implementiert das praktische Utility für den Linux-Desktop nach.
Wer schon einmal auf einem zugekleisterten Desktop nach dem Icon einer bestimmten Applikation gesucht hat, fragt sich vielleicht: Wer ist nur auf die Idee gekommen, Applikationen mit der Maus auszuwählen? Denn wer weiß, wie die Applikation heißt, hat doch keinen Grund, zehn Sekunden damit zu verplempern, das entsprechende Icon aus Dutzenden auf dem Desktop herauszusuchen oder sich durch verschachtelte Dropdown-Menüs zu hangeln, um das mühsam gefundene dann endlich per Mausklick zu starten.
Lernfähiger Starter
Beim in Perl geschriebenen Spotty sorgt stattdessen ein selbst definierter Hotkey dafür, dass sofort rechts oben im Desktop ein Fenster aufpoppt (Abbildung 1). Dessen Textfeld hat nun bereits den Tastaturfokus. Schon nach den ersten dort eingetippten Buchstaben »fi« weiß Spotty, dass es Firefox aufrufen soll, und pumpt den Namen in der Auswahlliste ganz nach oben. Der Benutzer drückt die [Tab]-Taste, um den Vorschlag zu übernehmen und die Applikation auch gleich zu starten. Verstrichene Zeit: nicht mal zwei Sekunden. Das Ganze funktioniert ganz ähnlich wie [Alt]+[F2] unter KDE oder Gnome, nur dass man in den eigenen Quellcode noch nach Belieben Sonderfunktionen einbauen kann wie etwa den Start von Sudo-Programmen.
![Abbildung 1: Der Benutzer hat Spotty mit dem Hotkey [Strg]+[U] aufgerufen und »gi« ins Eingabefeld getippt. Spotty erkennt, dass es sich bei der gewünschten Applikation nur um den Gimp handeln kann. Die [Tab]-Taste komplettiert zu »gimp« und startet das Programm.](https://www.linux-magazin.de/wp-content/uploads/2008/05/abb1_jpg1-300x92.jpg)
Abbildung 1: Der Benutzer hat Spotty mit dem Hotkey [Strg]+[U] aufgerufen und »gi« ins Eingabefeld getippt. Spotty erkennt, dass es sich bei der gewünschten Applikation nur um den Gimp handeln kann. Die [Tab]-Taste komplettiert zu »gimp« und startet das Programm.
Spotty lernt aus erfolgreichen Starts und merkt sich die gefundenen Programmnamen in einer persistenten Datenbank. Beim ersten Aufruf tippt der Benutzer noch »firefox« ein und schickt den String mit der [Enter]-Taste ab. Daraufhin sucht Spotty in sämtlichen Verzeichnissen der Umgebungsvariablen »$PATH« nach dem Programm und führt es aus.
Schon beim nächsten Aufruf versucht das Tool, die Eingabe des Benutzers mit den bereits gelernten Programmnamen zur Deckung zu bringen, und zeigt Treffer rechts vom Eingabefeld an. Sieht der Benutzer, dass Spotty das gewünschte Programm ans Kopfende der Trefferliste verschoben hat, muss er nur die [Tab]-Taste drücken, damit Spotty die gewünschte Applikation sofort startet.
Spotty führt diese Applikation mit »exec« aus. Sie überlädt den aktuellen Prozess (also das Perl-Skript) mit der externen Applikation, woraus folgt, dass Spotty aus der Prozesstabelle verschwindet und dort nur die gestartete Applikation übrig bleibt. Die »exec«-Zeile 110 (Listing 1) in der Funktion »launch« ist also das Ende des Skripts, da der Prozess aus ihr nicht mehr zurückkehrt.
Die Datenbank für die gemerkten Programmnamen ist ein persistenter Hash, der mit dem CPAN-Modul DB_File mit einer Berkely-DB-Datei verknüpft ist. Das Skript frischt die Datenbank auf, sobald es den mit »tie« verbundenen Hash verändert. Damit beim Programmschluss auch alles ordentlich herunterfährt, setzt Zeile 106 kurz vor dem Exec-Kommando noch ein »untie«-Kommando ab, um den Hash von der Datenbankdatei loszueisen und alle Änderungen zu speichern.
Malen mit Tk
Wie Listing 1 zeigt, nutzt Spotty das Tk-Modul vom CPAN, um das Applikationsfenster mit dem Eingabefeld zu zeichnen. Damit dies nicht irgendwo auf dem Desktop landet, sondern genau in der rechten oberen Ecke, ruft es danach die Methode »geometry()« mit dem Parameter »-0+0« auf. »-0« steht hierbei für die am weitesten rechts liegende x-Koordinate, »+0« für die oberste y-Koordinate.
Das Hauptfenster mit dem Namen »$top« ist vom Typ »MainWindow« und enthält zwei so genannte Widgets: Links ein Eingabefeld vom Typ »Entry« und rechts davon eine Anzeige vom Typ »Label«. Am Eingabefeld-Widget »$entry« hängt eine Textvariable »$input«, in der Tk den vom Benutzer eingetippten Text ablegt und nach jedem Tastendruck auffrischt. Da die Option »-validate« den Wert »key« aufweist, springt Tk bei jedem Tastendruck im Eingabefeld auch noch die Funktion »validate()« (definiert ab Zeile 63) an, deren Referenz das Widget in der Option »-validatecommand« bekam.
Diese Funktion validiert hier aber nichts, denn sie gibt immer »1« zurück, ist also mit allem einverstanden. Sie dient nur dazu, nach jedem Tastendruck des Benutzers einen Callback aufzurufen, der in der Datenbank nach Treffern schaut. Das Label-Widget rechts vom Entry-Widget hingegen überwacht eine Textvariable »$label_text«, sobald sich deren Wert ändert, frischt der Tk-Manager die Anzeige auf. Findet die Funktion »validate()« also mit »matches()« (Zeile 71) Treffer zum gerade eingegebenen Wort, fügt sie diese durch Zeilenumbrüche getrennt zu einem String zusammen und pflanzt ihn in »$label_text« ein. Daraufhin zeigt Spotty den Treffer rechts vom Eingabefenster an, ohne dass hierzu weitere Programmierschritte notwendig wären.
Der Packer (Zeilen 44, 45) packt beide Widgets mit der Option »-side => “left”« in das Containerobjekt, das Hauptfenster »$top«. Wandern mehrere Objekte mit »left« in den Container, reiht der Packer sie von links nach rechts auf. Die Vorgabe »left« bedeutet für den Packer nämlich, dass er neue Widgets an den linken Rand des noch verfügbaren Platzes klebt. Ist links schon ein Widget, wandert das nächste also an den linken Rand des rechts verbliebenen freien Raums.
Spotty reagiert auf die [Enter]- und die [Tab]-Taste. [Enter] übernimmt den bisher eingegebenen String und [Tab] schnappt sich das erste Element aus der Vorschlagsliste. Perl-Tk bindet die Tasten durch »bind«-Aufrufe in den Zeilen 48 und 50 an die Funktionen »launch()« (definiert ab Zeile 94) und »complete()« (ab Zeile 86). Letztere setzt lediglich die Variable des Entry-Widgets auf den obersten Treffer, den »$first_match« speichert, und ruft anschließend »launch()« auf, damit der Benutzer nicht mal mehr [Enter] betätigen muss, um die gefundene Applikation zu starten. Der Bind-Eintrag in Zeile 47 legt fest, dass Spotty abbricht, falls jemand [Strg]+[Q] drückt und somit aussteigen möchte, ohne eine Applikation zu suchen.
Sofort aufnahmebereit
Der Aufruf der Methode »focus()« in Zeile 52 setzt den Tastaturfokus auf das Entry-Widget. Das ist wichtig, denn sonst müsste der Benutzer erst mühsam mit der Maus ins Eingabefeld klicken, damit das Widget Tastatureingaben verarbeitet, aber das Einsparen von Mausklicks war ja der Sinn der Übung.
Ist alles definiert, startet »MainLoop« in Zeile 53 das GUI, es läuft, bis eine Applikation startet oder der Benutzer sich dazu entschließt mit [Strg]+[Q] das Programm abzubrechen. In diesem Fall oder in Fehlerfällen hilft die Funktion »bail()« (Zeile 56) beim Aufräumen. Sie ruft die »destroy«-Methode des Top-Fensters auf und faltet damit das GUI zusammen.
Heiße Tasten
Für den Desktop-Hotkey, der Spotty startet, empfiehlt es sich, eine Tastaturkombination zu wählen, die kein Anwendungsprogramm benutzt, wenn es den Tastaturfokus erhält. Bei Tastenschluckern wie »vim« ist das nicht einfach. Ich habe [Strg]+[U] gewählt, weil das einfach zu tippen ist und nicht zu meinen oft ausgeführten Vim-Kommandos gehört. (Vim hat die Kombination natürlich belegt und scrollt damit den editierten Text nach oben, aber ich verwende für diesen Zweck [Strg]+[B]).
Der Gnome-Desktop meiner Ubuntu-Installation meint aber leider, er wisse alles besser, und erlaubt nur, einen ausgewählten Fundus von Applikationen an Hotkeys zu binden, jedoch nicht beliebige Programme. Aber ein beherztes Aufrufen von »gconf-editor« (Abbildung 2) löst das Problem. Der Befehl »sudo apt-get install gconf-editor« installiert das Paket, falls es fehlt.
![Abbildung 2: Das Utility »gconf-editor« definiert unter »Apps/Metacity/global_keybindings« den Eintrag »run_command_1« mit dem Hotkey [Strg]+[U]. Auch andere freie Tastenkombinationen sind möglich.](https://www.linux-magazin.de/wp-content/uploads/2008/05/abb2_jpg1-300x236.jpg)
Abbildung 2: Das Utility »gconf-editor« definiert unter »Apps/Metacity/global_keybindings« den Eintrag »run_command_1« mit dem Hotkey [Strg]+[U]. Auch andere freie Tastenkombinationen sind möglich.
Unter »Apps« bietet es im Metacity-Eintrag (Metacity ist der Windowmanager unter Gnome) die Einträge »global_keybindings« und »keybindings_commands« an. Unter »global_keybindings« setze ich dann »run_command_1« auf die gewünschte Hotkey-Kombination (beispielsweise [Strg]+[U]) und stelle anschließend unter »keybindings_commands« als Wert den Pfad zu Spotty ein (Abbildung 3).

Abbildung 3: Der Eintrag unter »command_1« im Verzeichnis »keybinding_commands« versieht den zuvor definierten Hotkey mit einem benutzerdefinierten Kommando.
Sudo-Erweiterung
Falls das auszuführende Programm Root-Rechte verlangt, also mit »sudo« aufzurufen ist, entsteht das kleine Problem, dass der Benutzer zunächst sein Passwort eingeben muss. Der Ubuntu-Package-Manager Synaptic ist so ein Beispiel. Er läuft zwar auch ohne Root-Rechte, kann dann aber nur Pakete abfragen und keine neuen installieren.
Spotty hilft sich mit einem in Zeile 7 definierten Hash »%sudo_programs« weiter. Gibt der Benutzer eines der dort gelisteten Programme ein, findet in Zeile 108 nicht nur ein »exec« statt, sondern Spotty startet ein Xterm-Terminal, das seinerseits das gewünschte Programm mit »sudo« aufruft. Der Effekt: Im aufpoppenden »xterm« fragt die Shell zuerst das Passwort ab und startet, falls es richtig ist, das gewünschte Programm tatsächlich mit Root-Rechten.
Wer außer in den in »$PATH« voreingestellten Pfaden noch in weiteren Verzeichnissen nach Kommandos suchen möchte, kann zum Array »@misc_paths« in Zeile 10 noch weitere Einträge hinzufügen. »path_search()« findet Programme dann automatisch auch im erweiterten Verzeichnisfundus. Wer statt Buchstaben lieber mit Cursortasten manövriert, kann rechts vom Eingabefeld eine Listbox zeichnen, die mit Treffern gefüllt ist und deren Selektion erlaubt.
In jedem Fall: Wer weiß, was er sucht, für den ist nun Schluss mit dem Herumirren auf dem Desktop. Fortan ist jedes gewünschte Programm nur noch ein paar Tastendrücke entfernt. (jcb)
|
Listing 1: |
|---|
001 #!/usr/local/bin/perl -w
002 use strict;
003 use Log::Log4perl qw(:easy);
004 use DB_File;
005 use Tk;
006
007 my %sudo_programs = map { $_ => 1 }
008 qw(synaptic);
009
010 my @misc_paths = qw(/usr/sbin);
011
012 my($home) = glob "~";
013 my $spotty_dir = "$home/.spotty";
014
015 #Log::Log4perl->easy_init();
016
017 if(! -d $spotty_dir) {
018 mkdir $spotty_dir, 0755 or
019 LOGDIE "Cannot mkdir $spotty_dir ($!)";
020 }
021
022 # Init database
023 my %DB_FILE;
024 tie %DB_FILE,
025 "DB_File", "$spotty_dir/db_file.dat"
026 or LOGDIE "$!";
027
028 # Application window
029 my $top = MainWindow->new();
030 $top->geometry("-0+0");
031
032 my($input, $first_match, $label_text);
033
034 my $label = $top->Label(
035 -textvariable => $label_text,
036 -width => 20 );
037
038 my $entry = $top->Entry(
039 -textvariable => $input,
040 -validatecommand => &validate,
041 -validate => "key",
042 );
043
044 $entry->pack( -side => "left" );
045 $label->pack( -side => "left" );
046
047 $entry->bind("<Control-Key-q>", &bail);
048 $entry->bind("<Return>",
049 sub { launch($input) });
050 $entry->bind("<Tab>", &complete);
051
052 $entry->focus();
053 MainLoop;
054
055 ###########################################
056 sub bail {
057 ###########################################
058
059 $top->destroy();
060 }
061
062 ###########################################
063 sub validate {
064 ###########################################
065 my($got) = @_;
066 $label_text = join "n", matches($got);
067 return 1;
068 }
069
070 ###########################################
071 sub matches {
072 ###########################################
073 my($got) = @_;
074
075 my @all = sort keys %DB_FILE;
076 my @matches = grep { /^$got/ } @all;
077 if(@matches) {
078 $first_match = $matches[0];
079 } else {
080 $first_match = undef;
081 }
082 return @matches;
083 }
084
085 ###########################################
086 sub complete {
087 ###########################################
088
089 $input = $first_match;
090 launch($input);
091 }
092
093 ###########################################
094 sub launch {
095 ###########################################
096 my($program) = @_;
097
098 my $path = path_search( $program );
099
100 LOGDIE "$program not found ",
101 "in path ($ENV{PATH})" unless
102 defined $path;
103
104 $DB_FILE{ $program }++ if defined $path;
105 DEBUG "Launching $path";
106 untie %DB_FILE;
107 if(exists $sudo_programs{ $program } ) {
108 exec "xterm", "-e", "sudo", "$path";
109 } else {
110 exec $path;
111 }
112 LOGDIE "exec $path failed: $!";
113 }
114
115 ###########################################
116 sub path_search {
117 ###########################################
118 my($program) = @_;
119
120 DEBUG "PATH is $ENV{PATH}";
121
122 for my $path ( split(/:/, $ENV{PATH}),
123 @misc_paths ) {
124 if(-x "$path/$program") {
125 DEBUG "$program found in $path";
126 return "$path/$program";
127 }
128 }
129
130 ERROR "$program not found";
131 return undef;
132 }
|
|
Infos |
|---|
|
[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2008/06/Perl] |
|
Der Autor |
|---|
|
|






