Direkter Weg (Linux-Magazin, März 2015)

Die Utility "Autokey" automatisiert Desktopabläufe, in dem sie auf bestimmte Textabkürzungen oder Hotkey-Kombinationen hin programmierbare Aktionen einleitet.

Vor über fünf Jahren war die Welt noch in Ordnung, da konnte man den Gnome-Window-Manager "Metacity" noch dazu überreden, einfach auf eine Tastenkombination hin eine Applikation zu starten, in dem man sie mittels der Utilty "gconf-editor" eintrug ([2]). Da ich im Laufe des Tages bestimmt hundert Terminal-Fenster öffne, verbandelte ich, um Zeit zu sparen, die Kombination "Ctrl-Alt-n" kurzerhand mit einem Shell-Skript, das ein neues Terminal mit Spezialfont und Hintergrundfarbe aufpoppte, egal in welcher Applikation sich der Tastaturfokus gerade befand. Mit dem Unity-Desktop scheint das aber nun ein Ding der Unmöglichkeit zu sein, außerdem ist es mir mittlerweile zu doof, meine Skripts dauernd dem fragwürdigen Treiben der Gnome-Community anzupassen.

Keine Gnome-Sperenzchen

Statt dessen soll sich nun ein Mittelsmann um die Schnittstelle zwischen Tastatur, dem sich gerade dieses Jahr in Mode befindlichen Windows-Manager der Gnome-Welt und einzuleitenden Aktionen kümmern, und da kommt das etwa zwei Jahre alte Projekt Autokey [3] gerade recht. Als zusätzlichen Bonus unterstützt es neben dem GTK3-Toolkit gar QT4, und damit könnte ich damit sogar eines fernen Tages theoretisch auf einen KDE-Desktop umziehen! Man soll nie nie sagen. Unter Ubuntu installiert das Kommando

    sudo apt-get install autokey-gtk

die Autokey-Utility und wer sie automatisch beim Systemstart hochfahren möchte, trägt sie in der Ubuntu-Konfiguration unter "Startup Applications" ein (Abbildung 1).

Abbildung 1: Damit Autokey automatisch startet, trägt der User die Utility auf Ubuntu unter "Start Applications" ein.

Autokey unterscheidet zwischen reiner Textersetzung ("Phrases") und Skripts, die Aktionen einleiten. Beide wirft es entweder bei getipptem Text oder beim Pressen vordefinierter Hotkey-Kombinationen an. So könnte man unter "Phrases" zum Beispiel eintragen, dass Autokey die vollständige Postaddresse rauslässt, sobald der User "myaddr" tippt, zum Beispiel in einem Textdokument in gedit oder im Inhaltsfeld einer Email eines Webmail-Providers.

Abbildung 2: Einfache Textersetzung mit Autokey: Die Abkürzung "hth" expandiert zu einer länglichen Floskel.

"Wir hoffen, es hilft"

Abbildung 2 zeigt die Definition des Kürzels "hth", das per Autokey zu der Floskel "Hope that helps, let me know ..." mutiert. Um die Aktion auszulösen, tippt der User in einer Applikation, die ihre Tastatureingaben über den Gnome-Desktop erhält, den Text "hth" und drückt anschließend auf die Return-Taste. Achtung: Es funktioniert zum Beispiel nicht in einem reinen xterm-Terminal, es muss schon das gnome-terminal sein. Und aufgepasst: Kürzel, die als Worte häufig in normalem Text vorkommen, eignen sich naturgemäß weniger als offensichtliche Kunstkonstrukte wie "hth" oder "afaik". Unter "Set Abbreviations" trägt der User das Kürzel in eine Liste zum zugehörigen Textersatz ein und kann in Zukunft mächtig Tipparbeit sparen (Abbildungen 3 und 4).

Abbildung 3: In einer Email-Applikation eines Webmail-Providers expandiert das Kürzel "hth" ...

Abbildung 4: ... zu einer höflichen Floskel am Ende einer Sendung.

Heiße Tasten

Wer auch unabhängig von Applikationen mit offenen Texteingabefeldern Aktionen auslösen möchte, zum Beispiel direkt auf dem Desktop, definiert im Konfigurations-Dialog statt einer Textabkürzung einfach einen Hotkey, also eine Kombination gleichzeitig gedrückter Tasten auf dem Keyboard.

Abbildung 5: In dieser Einstellung poppt Autokey ein neues Terminalfenster auf, falls der User Ctrl-Alt-n drückt.

Ein Mausklick im Autokey-Dialog auf den "Set"-Button unter "Hotkeys" bringt das Formular in Abbildung 5 zum Vorschein. Eine sicherlich nicht anderstweitig genutzte Kombination wie Ctrl-Alt-n wählt der User am einfachsten durch einen Klick auf den Button "Press to Set" aus, um anschließend die Tastenkombination selbst zu drücken, während Autokey die Ohren spitzt.

Mit Hotkeys leitet der Desktop-Automatisierer nicht nur Textersetzungen ein sondern kann in Autokey mittels selbstgekochter Python-Skripts auch beliebige Aktionen einleiten. Hierzu erzeugt die Autokey-UI mit "New Script" (im Gegensatz zum vorher genutzten "New Phrase") ein neues Skript und legt es nicht wie vorher im Ordner "My Phrases" sondern unter "Sample Scripts" ab. Die Unterscheidung zwischen Ersetzungen und dynamischen Skripts ist wichtig, denn ist ein Skript nicht als solches gekennzeichnet, würde Autokey im Aktionsfall einfach den Python-Code aus der Tastatur rauslassen, statt ihn auszuführen. Abbildung 5 zeigt eine Aktion, die dem Hotkey Ctrl-Alt-n zugewiesen ist und die eingangs erwähnten maßgeschneiderten Terminalfenster aufmacht, von denen ich am Tag ein viele Dutzend verbrauche.

Versioniert automatisiert

Natürlich geht es nicht an, die Kürzel und Hotkey-Aktionen auf jeder neuen Linux-Kiste von Hand in die GUI von autokey-gtk einzutragen. Auch neu hinzukommende Produktivitätshelfer sollen wenn möglich nicht von Hand auf dem Desktop sondern programmatisch in die Autokey-Konfiguration einfließen. Hierzu hilft es, Autokey über die Schulter zu schauen: Nachdem der User die entsprechenden Knöpfe zur Autokey-Konfiguration gedrückt, die Textfelder gefüllt und die Dialoge geschlossen hat (nicht vergessen den "Save"-Knopf in der Kopfzeile zu klicken!), legt Autokey die Daten im Home-Verzeichnis unter ~/config/autokey/... ab. Die Meta-Daten der definierten Tastenkombinationen liegen im JSON-Format unter dem jeweiligen Ordner, "My Phrases" für Textsnippets und "Sample Scripts" für Skripts. Eine frische Autokey-Installation erzeugt beim ersten Aufruf von autokey-gtk eine Reihe von Beispielphrasen und -skripte, die einen Eindruck davon vermitteln, was mit dem Tool alles möglich ist (Abbildung 6).

Abbildung 6: Beim ersten Aufruf erzeugt autokey-gtk einige Beispielphrasen und -skripte.

Um den Vorgang zu automatisieren, kopiert im Folgenden ein Perl-Skript das von Autokey genutzte Metadatenformat aus den bereits vorhandenen Beispielen und fügt neue Einstellungen hinzu, gemäß einer relativ kompakten YAML-Datei, die jeder nach seinem Gusto ohne viel Aufwand anpassen kann (Listing 1).

Listing 1: autokey.yaml

    01 -
    02   name:   terminal
    03   type:   script
    04   exec:   /home/mschilli/bin/xt &
    05   hotkey: ctrl-alt-n
    06 -
    07   name:   hth
    08   type:   phrase
    09   abbrv:  hth
    10   text:   Hope that helps!

Geschwätziges JSON

Die von Autokey genutzten JSON-Dateien zum Speichern von Metainformationen geben sich relativ geschwätzig, wie aus Abbildung 7 ersichtlich. In der Datei .name.json stehen die mit dem Eintrag name verknüpften Informationen, während das zughörige Python-Skript zum Auslösen der gewünschten Aktion in name.py im selben Ordner residiert.

Abbildung 7: In der Datei .terminal.json speichert Autokey die Zuordnung des Hotkeys zum Python-Skript unter terminal.py.

Schnappt sich das Skript in Listing 2 beim Aufruf

    autokeygen autokey.yaml

die Yaml-Datei in Listing 1, formt es aus den zwei dort stehenden Einträgen, nämlich einem zum Textersatz des Kürzels "hth" und einer Terminal-Aktion zur Tastenkombination "Ctrl-Alt-n", insgesamt vier zusätzliche Einträge im Konfigurationsbaum von Autokey, wie aus Abbildung 8 ersichtlich. Jeder Eintrag erzeugt eine JSON-Datei und eine Text- bzw. Python-Datei. Zu beachten ist, dass Autokey zu diesem Zeitpunkt nicht laufen darf, sonst bekäme es die Änderung seiner Konfiguration nicht mit und würde die neuen Daten später rücksichtslos wieder mit alten Einträgen überkleistern.

Abbildung 8: Das Skript autokeygen erzeugt ordnungsgemäße Autokey-Einträge gemäß der YAML-Definition in Listing 1.

Zum Lesen von Yaml- und Json-Dateien zieht Listing 2 zunächst die Module JSON und YAML vom CPAN herein. Die Zeilen 7 bis 12 definieren, wo Autokey die Konfigurationsdateien ablegt, und die for-Schleife ab Zeile 14 iteriert über die Einträgen der Yaml-Datei in Listing 1, deren Daten als Array von Hash-Elementen vorliegen. Je nach Typ des Eintrags springt der symbolische Funktionsaufruf in Zeile 18 entweder process_script() oder process_phrase() auf. Damit Perl solche gewagten Konstrukte im Strict-Modus erlaubt, muss Zeile 17 die Strenge bei der Interpretation von symbolischen Referenzen temporär auf ein geringeres Maß zurückschrauben.

Listing 2: autokeygen

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use Sysadm::Install qw(:all);
    04 use YAML qw( LoadFile );
    05 use JSON qw( from_json to_json );
    06 
    07 my $yaml_file   = "autokey.yaml";
    08 my( $base_dir ) = 
    09   glob "~/.config/autokey/data";
    10 my $script_dir  = 
    11   "$base_dir/Sample Scripts";
    12 my $phrase_dir  = "$base_dir/My Phrases";
    13 
    14 for my $entry (
    15     @{ LoadFile $yaml_file } ) {
    16   my $func = "process_$entry->{type}";
    17   no strict 'refs';
    18   $func->( $entry );
    19 }
    20 
    21 ###########################################
    22 sub process_script {
    23 ###########################################
    24   my( $entry ) = @_;
    25 
    26     # get template script
    27   my $data = 
    28     json_rw( "Insert Date", $script_dir );
    29 
    30   $data->{ hotkey } = hotkey( $entry );
    31   $data->{ description } = $entry->{ name };
    32   $data->{ modes } = [ 3 ];
    33 
    34   json_rw( $entry->{ name }, $script_dir, 
    35            $data );
    36 
    37   blurt( "system.exec_command(" .
    38       "'$entry->{exec}')\n", 
    39       "$script_dir/$entry->{ name }.py" );
    40 
    41 }
    42 
    43 ###########################################
    44 sub hotkey {
    45 ###########################################
    46   my( $entry ) = @_;
    47 
    48   my @mods = split /-/, $entry->{ hotkey };
    49   my $key = pop @mods;
    50 
    51   return { 
    52     hotKey    => $key,
    53     modifiers => [ map { "<$_>" } @mods ],
    54   };
    55 }
    56 
    57 ###########################################
    58 sub process_phrase {
    59 ###########################################
    60   my( $entry ) = @_;
    61 
    62     # get template phrase
    63   my $data = 
    64     json_rw( "Second phrase", $phrase_dir );
    65 
    66   $data->{ abbreviation }->
    67     { abbreviations }->[ 0 ] = 
    68       $entry->{ abbrv };
    69 
    70   $data->{ description } 
    71     = $entry->{ name };
    72 
    73   $data->{ modes } = [ 1 ];
    74 
    75   json_rw( $entry->{ name }, $phrase_dir,
    76            $data );
    77 
    78   blurt( "$entry->{ text }\n", 
    79     "$phrase_dir/$entry->{ name }.txt" );
    80 }
    81 
    82 ###########################################
    83 sub json_rw {
    84 ###########################################
    85   my( $name, $dir, $data ) = @_;
    86 
    87   my $path = "$dir/.$name.json";
    88 
    89   if( defined $data ) {
    90     return blurt to_json( 
    91           $data, { pretty => 1 } ), $path;
    92   }
    93 
    94   return from_json( slurp $path );
    95 }

Beispielhaft

Damit das Skript nicht den ganzen Json-Wust nachimplementiert, orientiert es sich an den bereits von Autokey erstellten Beispieldateien und für jeden Eintrag der Yaml-Datei nur explizit benötigte Felder.

Die Funktion json_rw() greift dabei sowohl lesend ("r") als auch schreibend ("w") auf Json-Dateien der Autokey-Konfiguration zu. Zeile 28 liest zum Beispiel die Test-Konfiguration zu Insert Date ein, einem Beispielskript zum dynamischen Einfügen des aktuellen Datums per Python-Skript. Anschließend modifiziert es in den Zeilen 30 bis 32 nur die Einträge hotkey, description und modes, um sie an die Anforderungen gemäß dem Yaml-Eintrag zum Öffnen des Terminalfensters zur Tastaturkombination Ctrl-Alt-n anzupassen. Mit einer Referenz auf den Datenhash als zusätzliches Argument erzeugt der Aufruf von json_rw() in Zeile 34 dann die Datei .terminal.json in der Autokey-Konfiguration. Das einzeilige Python-Skript, das mit der Funktion system.exec_command() über die Shell ein Kommando aufruft, landet mittels blurt() in Zeile 37 in der Python-Datei terminal.py.

Da die Hotkey-Definition in der Yaml-Datei kompakt als ctrl-alt-n vorliegt, Autokey aber eine kompliziertere Datenstruktur bevorzugt, bereitet die Funktion hotkey() in Zeile 44 die Yaml-Rohdaten entsprechend auf.

Das Kürzel "hth" zum Textersatz formt die Funktion process_phrase() ab Zeile 58 in das von Autokey geforderte Json-Format um. Wie auch schon in process_script() leisten die Funktionen slurp() und blurt() aus dem Fundus des CPAN-Moduls Sysadm::Install zum Lesen und Zurückschreiben von Dateien ganze Arbeit.

Wer sich übrigens beim Experimentieren mit neuen Skripts die Haare rauft, weil der Python-Code eines Skripts nicht so will, wie eigentlich angedacht, der sei auf die Datei ~/.config/autokey/autokey.log verwiesen. Dort treten alle Systemfehler zutage, und die dort registrierten Meldungen helfen meist weiter, eventuell auftretende Probleme einzukreisen. Wer einige Kürzel definiert, weiß den gewonnenen Produktivitätsgewinn schnell zu schätzen, und sollte die Yaml-Datei in einem Versionskontrollsystem wie Git pflegen.

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2015/03/Perl

[2]

"At your fingertips", Michael Schilli, Linux-Magazin 06/2008, http://www.linux-magazin.de/Ausgaben/2008/06/Schnellstart

[3]

Autokey Projektseite, https://code.google.com/p/autokey

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.