Un problème récurrent avec les applications en mode texte est qu'elle n'ont pas
conscience du fait que la fenêtre qui les héberge gagne, ou perd le focus. Cette
information est pourtant bien utile car elle peut par exemple permettre sous VIM de
sauvegarder les buffers modifiés lorsque l'on laisse l'éditeur de côté (perte de focus).
Mais aussi de mettre les buffers à jour s'ils ont été modifiés à l'extérieur de VIM
lorsque l'on y retourne (gain de focus). Nous allons donc voir comment rendre une
application texte consciente de ces deux évènements.
Perte et gain de focus
Lorsqu'une fenêtre a le focus, tout ce qui est tapé au clavier lui est envoyé. La
sélection de la fenêtre qui a ce focus est réalisée par le gestionnaire de fenêtre.
Certains considèrent que le focus est là où se trouve la souris. C'est ainsi que l'on
fonctionne sous Unix depuis des lustres. Du moins jusqu'à ce que la mode windowsienne ne
prenne le pas et obligent en plus à cliquer dans la fenêtre à rendre prioritaire.
Le fait de perdre le focus est donc un bon moyen de dire à une application que l'on
passe à une autre tâche. On a donc logiquement envie, s'agissant d'un éditeur de texte,
d'en profiter pour faire une petite sauvegarde automatique.
Lorsque l'éditeur est une chose graphique (gedit, gVim), il est capable de recevoir
directement les messages perte et gain de focus, envoyés par X11. En revanche lorsqu'il
s'agit d'une application texte (Vim, Mutt, etc.) c'est plus compliqué car c'est
l'émulateur de terminal qui reçoit ces messages. Il nous faut donc faire un pont entre le
gestionnaire de fenêtre et l'application en mode texte en utilisant l'émulateur de
terminal comme intermédiaire.
Construcion d'un OSC maison
Comme vous le savez déjà, les applications en mode texte conversent avec le terminal sous
la forme de séquence d'échappement commençant par le code 27 (aka ESC, aka \\033).
Pour s'y retrouver, ces séquences ont des petits noms. Prenons par exemple les séquences ANSI
qui permet de cacher le curseur ESC[?25l. La première partie de
cette séquence ESC[ défini les commandes dites CSI (Control Sequence
Introducer). On résume donc souvent dans les documentation cette commande de masquage de
curseur CSI?25l.
Il existe de nombreuses autres classes de séquence (qui commencent cependant toujours
par ESC), comme par exemples les OSC (Operating System Control) qui elles sont de la
forme ESC] (notez le crochet dans l'autre sens). Ces séquencent permettent
généralement de modifier le comportement du terminal comme par exemple changer la
couleur du curseur (OSC 12;red BEL). BEL représente le code qui
fait beep qui s'écrit aussi \\007.
Urxvt permet à ses plugins d'intercepter n'importe quelle séquence OSC nous permettant
ainsi de composer la notre pour activer et désactiver la prise en charge du focus par le
terminal. En effet, si nous ne faisons pas cela, une console ne gérant pas le focus
serait rapidement polluée par les entrées/sorties du curseur de la souris.
- Activation du mode mode "focus" par la séquence \\033]777;focus;on\\007
- Désactivation du mode "focus" par la séquence \\033]777;focus;off\\007
Notez l'usage de 777 qui est une classe spéciale d'OSC qu'urxvt réserve aux
plugins.
Pour terminer, lorsque le mode "focus" sera activé, le terminal va lui aussi émettre des
séquences que VIM va interpréter. C'est un peu comme des touches "spéciales" :
- Focus gagné par la séquence \\033UlFocusIn.
- Focus perdu par la séquence \\033UlFocusOut.
Ces séquences ne suivent aucune normes. C'est un choix totalement arbitraire avec comme
seule contrainte de ne pas taper dans une séquence existante.
Construction du plugin URxvt
URxvt dispose d'une API en perl très bien documentée (man urxvtperl) qui
fournit une série d'évènements dont le gain et la perte du focus, mais aussi la
gestion des séquences OSC custom.
Construire un plugin pour URxvt est relativement simple. Il suffit pour cela de créer un
script perle focus que l'on place par exemple dans un dossier
~/.urxvt/perl :
#!/usr/bin/perl
# Hook invoqué au démarrage d'urxvt
sub on_start {
my($term) = @_;
// le mode "focus" est désactivé par défaut
$terme->{focus_activated} = 0;
}
# Interception des séquences "OSC 777 xxx BEL"
sub on_osc_seq_perl {
my ($term, $osc, $resp) = @_;
return unless $osc =~ s/^focus;//;
$term->{focus_activated} = $osc eq 'on'?1:0;
}
# Hook invoqué au gain de focus
sub on_focus_in {
my($term) = @_;
# Si le mode focus est activé, on envoie la notification
if ($term->{focus_activated}) {
$term->tt_write("\\033[UlFocusIn");
}
}
# Hook invoqué à la perte de focus
sub on_focus_out {
my($term) = @_;
# Si le mode focus est activé, on envoie la notification
if ($term->{focus_activated}) {
$term->tt_write("\\033[UlFocusOut");
}
}
Plugin URxvt - ~/.urxvt/perl/focus
Comme vous le voyez le plugin est assez simple. Les subs définies sont des hooks, c'est
à dire des fonctions qui sont appelées par URxvt en réaction à des évènements :
-
on_start est déclenché lorsque le plugin est instancé. On va y régler
l'état du mode "focus" par défaut (désactivé)
-
on_focus_in est déclenché lorsqu'URxvt est notifié de la prise de focus. On vérifie alors que le mode focus est activé et si c'est le cas, on écrit la séquence qui va bien dans le terminal.
-
on_focus_out est déclenché lorsqu'URxvt est notifié de la perte de focus.
-
on_osc_seq_perl est déclenché dés qu'URXVT reçoit une séquence OSC. On vérifie juste qu'il s'agit bien de la notre et on active/désactive le mode focus.
Une fois que ce plugin écrit, il nous reste à l'installer dans URxvt. Pour cela rajouter les lignes suivantes à ~.Xdefaults :
# On dit ou se trouvent nos extensions
URxvt.perl-lib : /home/gaston/.urxvt/perl
# Et on active l'extension "focus"
URxvt.perl-ext-common: focus
Nous pouvons maintenant relancer URxvt (éventuellement faire un coup de xrdb -load
~/.Xdefaults pour s'assurer que les ressources sont bien à jour) et tester
l'activation du mode "focus" :
gaston$echo -ne "\\033]777;focus;on\\007"
Si tout c'est bien passé, en survolant la fenêtre (ou en cliquant dessus selon votre gestionnaire de fenêtres) vous devriez voir des choses apparaître. Idem en perte de focus. Les choses en question sont une tentative échouée d'interprétation de nos séquences par Bash. D'où l'intérêt de pouvoir activer/désactiver ce mode à convenance.
Implémentation du mode focus dans VIM
Maintenant que notre plugin est en place, il ne reste plus qu'à l'intégrer dans VIM :
" Initialisation du mode "focus"
exe 'silent !echo -ne "\\033]777;focus;on\\007"'
" Gestion de la séquence focusin/focusout
map ^[[UlFocusIn :bufdo checktime<CR>
map ^[[UlFocusOut :wa!
map! ^[[UlFocusIn <C-O>:bufdo checktime<CR>
map! ^[[UlFocusOut <C-O>:wa!
" Désactivation du mode focus en partant
autocmd VimLeavePre * exe 'silent !echo -ne "\\033]777;focus;off\\007"'
Notez bien que l'usage que je fait ici de
map est tout sauf efficient. Il est
juste là pour simplifier le code. Il serait beaucoup plus efficace d'utiliser la méthode
décrite
ici.
En relançant votre VIM, la magie devrait fonctionner. En perte du focus on force la
sauvegarde de tous les buffers (wa!). Et lorsque l'on regagne le focus, on
demande à VIM de passer en revue tous les buffers ouverts (bufdo) pour les
recharger s'ils ont été modifiés (checktime).
Approche un peu plus sauvage
Cette technique ne marche pas mal mais elle souffre d'un défaut désagréable. Lorsque
l'on passe en mode commande par : (ou recherche par ? ou
/) et que l'on a le malheur de perdre le focus, la ligne que l'on saisissait
vient se faire pourrir par les messages de notification.
Malheureusement il n'y a pas à ma connaissance de moyen de détourner le problème. VIM ne
propose en effet pas d'évènement déclenché à l'entrée et à la sortie des modes commande.
La solution est donc d'aller hacker le code de VIM.
Pour cela il faut récupérer le code source. Le mieux est de passer par le gestionnaire
de version mercurial comme indiqué ici.
Lorsque le dossier vim est créé, il faut aller modifier le fichier
src/os_unix.c et aux environs de la ligne 3342, dans la fonction
mch_setmouse, nous allons modifier comme ceci :
# ifdef FEAT_MOUSE_URXVT
if (ttym_flags == TTYM_URXVT) {
out_str_nf((char_u *)
(on
? IF_EB("\\033[?1015h", ESC_STR "[?1015h")
: IF_EB("\\033[?1015l", ESC_STR "[?1015l")));
// ----------- LE VILAIN PATCH... {{{
out_str_nf((char_u *)
(on
? IF_EB("\\033]777;focus;on\\007", ESC_STR "]777;focus;on" (char_u *)"\\077")
: IF_EB("\\033]777;focus;off\\007", ESC_STR "]777;focus;off" (char_u *)"\\077")));
// ------------ FIN DU PATCH}}}
ison = on;
}
# endif
Ceci fait il ne reste plus qu'à configure/compiler/installer. Perso je fais comme ceci,
mais la configuration est à adapter selon vos goûts. Je vous conseille si vous vous
lancez là dedans de désinstaller proprement VIM _avant_ d'installer ce résultat de
compilation !!
make distclean
./configure \\
--prefix=/usr \\
--with-features=huge \\
--disable-selinux \\
--enable-pythoninterp \\
--enable-xim \\
--enable-float \\
--enable-gui=no \\
--disable-workshop \\
--with-x
#--enable-perlinterp \\
#--enable-cscope \\
make -j 10
sudo make install
Alors avant de lancer, comment ça marche. Le principe est assez simple. VIM au lancement
va activer la notification des évènements souris auprès du terminal (set
mouse=...). Lorsqu'il va s'arrêter, il va désactiver cette notification. Et
lorsqu'il passe en mode commande, il va aussi désactiver. L'astuce est ici donc juste de
prendre le train en marche et d'activer/désactiver le mode "focus" en même temps que le
mode "souris". Oui je sais c'est stupide, c'est moche, mais ça marche super bien ;-)
Pour que cela fonctionne, il faut supprimer du chapitre précédent tout ce qui touche à
l'activation/désactivation du mode "focus" (mais pas la gestion des notifications
évidemment) et laisser vim faire...
Conclusion
Voilà en tout cas une manière de replacer avantageusement l'auto-sauvegarde pour VIM que j'avais
réalisée précédemment. Car contrairement à la méthode que je proposais, celle-ci
fonctionne en toute circonstances et sans aucun délai.
Après cette technique peut être exploitée pour bien d'autres usages. Par exemple un
problème classique lorsque l'on bosse en console est de pouvoir copier du texte sur une
console distante (un vim en ssh) et de récupérer ce contenu en local. Par cette approche
c'est facilement réalisable en définissant une séquence OSC "presse-papier" qu'URxvt
récupérerait pour transférer le contenu dans la sélection CLIPBOARD du serveur X11
local. Quelque chose comme :
gaston$echo -en "\\033]777;CLIPBOARD;ON\\077Ceci est mon texte à copier\\033]777;CLIPBOARD;OFF\\077"
D'ailleurs l'idée est déjà une petit peu mis en place dans un plugin peu connu d'URXVT,
clipboard-osc. Sur le même principe peut être utilisé pour implémenter un protocole de
transfert de fichier type ZModem/Kermit.
Comme vous le voyez, les usages sont nombreux. Et c'est une fois de plus la preuve
qu'URxvt est un fantastiquement outil rentrant pleinement dans la catégories des "ce que
je veux, je peux".
Original post of Artisan Numérique.Votez pour ce billet sur Planet Libre.