Artisan Numérique

/cli/terminal/urxvt/ Ajouter la notification à urxvt

Il y a quelques temps, Hobbestigrou proposait une méthode client/serveur offrant à weechat une notification par popup utilisable à distance. Dans la discussion, j'avais proposé une autre approche, directement intégrée à UrXVT et donc compatible avec toute application texte capable de faire un "echo" dans la console. Alors histoire d'arrêter de pourrir ses commentaires, j'ai mis tout cela ici :)

Principes

Comme nous l'avons vu avec dans cet article, toute application texte peut facilement communiquer avec son terminal par le biais de séquence d'échappement. Pour rappeler rapidement le principe, le terminal "écoute" le flux de caractères émis par l'application. On peut donc écrire dans cette application des séquences de caractères spéciales qui seront comprises par le terminal comme des ordres. C'est la base même du standard ANSI qui permet à une application de mettre son texte en rouge, effacer l'écran, positionner le curseur, etc.

L'avantage de cette approche c'est qu'elle est dés-facto transparente pour le réseau. En effet, si on lance dans un terminal un tunnel SSH, et que dans ce tunnel on exécute une application comme weechat, le rendu visuel se ferra en local et les traitement eux resterons distants. Il est donc possible d'appliquer le même principe non plus pour altérer le contenu du terminal, mais pour exécuter une commande externe de notification.

De la sorte, une séquence spéciale sera écrite par l'application distante, elle remontera le tunnel SSH et sera comprise par le terminal qui lui est local. Il pourra alors faire apparaître une popup graphique de notification, toujours en local.

A noter cependant que l'approche d'Hibbestigrou reste plus efficace si l'on cherche à ce qu'une application continue à remonter des alertes même si elle n'est plus rattachée à un terminal. L'approche décrite ici est donc adaptée à des applications locales ou distantes, mais toutes rattachées à un terminal bien visible.

Ring a Bell

En réalité le principe consistant à faire passer un signal de l'application au terminal existe depuis longtemps avec la commande BELL. Lorsque cette commande, qui a comme code ASCII 7 est émise par une application, le terminal l'interprète comme une alerte. Il va alors changer le statut de sa fenêtre graphique (X11) pour passer en mode "urgent". Selon les gestionnaires de fenêtre ce mode sera répercuté par un "flash" sur la barre de titre, par un surlignage du tag où se trouve la fenêtre, etc.

Un tel comportement s'active avec UrXVT par un paramétrage dans ~/.Xdefaults :

URxvt.urgentOnBell: true

Une fois le paramétrage changé, en prenant soin de recharger les ressources (xrdb -load ~/.Xdefaults), vous pouvez ouvrir un nouveau terminal et tester :

gastonsleep 3 ; echo -ne "\007"
Ring the bell...

Allez maintenant sur une autre fenêtre et attendez 3 secondes, lorsque le BELL est émis, la fenêtre où vous étiez devrait "flasher".

Cette approche est pratique car très simple à mettre en œuvre et globalement standard. Par exemple pour mutt, il suffit de rajouter le code suivant pour faire ainsi beeper le terminal à l'arrivée de nouveaux courriels

set mail_check=5
set beep=yes
set beep_new=yes
Activation de BELL avec mutt

libnotify

L'approche BELL ne permet cependant pas de remonter la raison de l'alerte. Pour cela il nous faut utiliser la librairie libnotify. libnotify est un système client/serveur basé sur le protocole DBUS et largement présent sur les bureaux et même gestionnaires de fenêtre. Par exemple Awesome implémente un serveur libnotify en standard, c'est le module naughty (ils vont au bout de leur délire ;-) qui s'active simplement en rajoutant ceci à votre rc.lua :

-- Ajout de naughty
require("naughty")

-- Un peu de réglages
naughty.config.presets.normal.border_color="#712900"
naughty.config.default_preset.bg="#262626"
naughty.config.default_preset.fg="#8A825A"
naughty.config.default_preset.border_width=2
naughty.config.default_preset.font="Meslo LG S DZ 13"
naughty.config.default_preset.screen=1
naughty.config.default_preset.width = 400
activation de naughty

Pour tester le serveur, nous devons installer la partie cliente de libnotify via le paquet libnotify-bin et la commande notify-send :

gastonnotify-send World Hello
Envoie d'une notification

Si tout c'est bien passé, une petite pop-up a du apparaître quelque part avec "Hello" en corps et "World" en titre.

Maintenant que la théorie est prête, voyons le plugin à réaliser pour urxvt.

Plugin Notify

Comme pour le focus, nous allons ajouter un plugin à urxvt, écrit en Perl.

#!/usr/bin/perl

sub on_osc_seq_perl {
  my ($term, $osc, $resp) = @_;
  if ($osc =~ /^notify;(\S+);(.*)$/) {
    system("notify-send '$1' '$2'");
    system("aplay environnement/share/skype/sounds/ChatIncoming.wav")
  }
}

Super complexe non ? :) Ici on utilise le hook urxvt on_osc_seq_perl. Ce hook est déclenché à la réception par le terminal d'une séquence de la forme ESC]777;XXXXXBELL. L'avantage de cette forme est que l'on est certain de ne pas rentrer en conflit avec des séquences propres à URXVT.

La convention veut que l'on sépare les arguments de ces séquences par des ;. Dans notre cas nous allons donc interpréter une séquence de notification de la forme ESC]777;notify;TITRE;MESSAGEBELL

La chaîne XXX est récupérée dans le paramètre $osc de l'implémentation du hook. Un petit coup de regexp nous permet 1/ de vérifier que la première partie est bien notify; 2/ que la seconde partie soit le "titre" de la notification, et le reste le message.

Ensuite un simple appel à notify-send et le tour est joué. J'avais fait une tentative sans passer par une commande externe, via la librairie perl GTK2:Notify mais elle est sévèrement buggée.

Petite cerise sur le gâteau, on agrémente la notification d'un petit signal audio (le chemin vers un autre fichier son est à adapter dans votre cas).

Maintenant il ne reste plus qu'à ajouter notre nouveau plugin à Urxvt (via ~/.Xdefaults), de recharger xrdb et de lancer un nouveau terminal pour tester cela : $$ echo -ne "\033]777;notify;Moi;Hello World\007\007" `

Si tout s'est passé correctement, vous devez avoir 1/ la popup 2/ le signal audio 3/ la mise en urgence de la fenêtre (c'est pour cela que j'ai doublé le /007).

Il ne reste maintenant plus qu'à faire le même test mais en distant cette fois, ce qui devrait marcher sans problème :-)

Intégration à weechat

Pour ceux qui ne le connaissent pas, weechat est un excellent client IRC en mode texte. Elle dispose de l'une des interfaces texte les plus efficace que j'ai peu touché jusqu'à maintenant. En plus elle est totalement paramétrable et extensible avec des plugins en python, perl, lua, etc. J'ai longtemps utilisé IRSSI (un autre très bon client) mais Weechat le surclasse clairement. Ajoutez à cela un petit serveur Bitlbee et vous avez un même outil qui gère IRC, Jabbel, Twitter, etc.

Pour mettre en place la notification, il nous suffit donc d'écrire un petit plugin, en python cette fois, à placer dans le dossier ~/.weechat/python/autoload :

import weechat, string, subprocess,sys
weechat.register("osc_notify", "ulhume", "0.1.1", "GPL3", "osc_notify - Terminal OSC notification", "", "")

settings = {
  "notify_highlights"       : "on",
  "notify_private_messages" : "on",
}

for option, default_value in settings.items():
  if weechat.config_get_plugin(option) == "":
    weechat.config_set_plugin(option, default_value)

weechat.hook_print("", "irc_privmsg", "", 1, "oscn_irc_privmsg", "")

def oscn_irc_privmsg(data, buffer, date, tags, displayed, highlight, prefix, message):
  name = (weechat.buffer_get_string(buffer, "short_name") or weechat.buffer_get_string(buffer, "name"))
  if ( (weechat.buffer_get_string(buffer, "localvar_type") == "private" and
        weechat.config_get_plugin('notify_private_messages') == "on" and name==prefix) or
       (highlight == "1" and weechat.config_get_plugin('notify_highlights') == "on") ):
    subprocess.call('echo -n "\033]777;notify;'+name+';'+message+'\007\007" > /dev/stderr', shell=True)
  return weechat.WEECHAT_RC_OK

Rien de bien compliqué pour peu de lire la très bonne documentation (en français!!) de l'API des plugins. La seule chose un peu désagréable est de devoir passer par subprocess.call plutôt que de faire un bête sys.stderr.write('...''). La raison en est que Weechat semble filtrer les canaux stdout/stderr des plugins empêchant une impression directe.

Lorsque le plugin est écrit et sauvegardé à sa place, il suffit dans weechat de taper /plugin reload python et d'attendre que quelqu'un vous parle :-)

Intégration à mutt

Mutt n'a étrangement aucun moyen direct de spécifier une commande pour être notifié de l'arrivée des nouveaux courrier. Autant ce mailler est de loin le meilleur que j'ai pu utiliser jusqu'à maintenant, autant il faudrait un jour trouver le courage d'en dépoussiérer furieusement le code...

La solution de contournement consiste à modifier la variable status_format qui autorise la prise en charge du formattage de la barre de status par un une commande externe. Et comme dans les données en entrée on peut injecter le nombre de nouveaux courriels (via %n, Voir la documentation),cela permet la "triche" :

set status_format="/home/gaston/.config/mutt/status.py '%n'|"

Ensuite le code python pour effectuer le formattage :

#!/usr/bin/python

import sys

# On récupère le nombre de nouveaux messages
new = int(sys.argv[1])

# On regarde quel nombre précédent nous avions
fileName="/tmp/mutt-new-messages"
f = open (fileName,"r")
last = int(f.read())
f.close()

# Si y'a du neuf...
if (new!=last):
  # Et que ce neuf n'est pas rien
  if (new!=0):
    # on injecte la notification
    message = str(new)+' nouveau(x) message(s)'
    name= "Courrier"
    sys.stderr.write('\033]777;notify;'+name+';'+message+'\007\007')

  # et on met à jour le dernier nombre de courriels
  file = open(fileName, "w")
  file.write(str(new))
  file.close
/home/gaston/.config/mutt/status.py

Voilà, c'est terminé. Il faut relancer mutt (ou sourcer sa config) pour que cela fonctionne. La barre de statut est mise à jour à l'arrivée d'un nouveau courriel, ou lorsque vous revenez sur le pager.

Pour compléter la solution on apprécierait d'avoir dans les options de formattage le titre et l'auteur du dernier message reçu mais mutt n'offre pas cela. Donc si on veut aller plus loin, il nous faudra passer par des utilitaires comme mailcheck.

Conclusion

Pour ceux qui utilisent Urxvt comme interface visuelle standard pour à peu prés tout leur bureau, UrxVT montre une fois de plus sa capacité à s'adapter à tous les besoins. Maintenant on a le focus, la notification, il ne reste plus qu'à gérer le presse-papier distant et l'envoi de fichier, et la solution sera complète ;-)