Sommaire
Intro beaucoup trop longue
Sur ma liste de choses à faire[1], j'avais marqué "apprendre dbus" il y a bien longtemps.
Sur mon PC, j'écoute de la musique via deezer, et parfois, c'est long quand je veux passer un morceau ou retourner sur le dernier --car j'aime écouter certains trucs en boucle, toi même tu sais-- :
- il faut retrouver firefox,
- il faut retrouver le bon onglet,
- il faut cliquer au bon endroit dans la page.
Bref une perte de temps.
J'ai donc commencer à me demander comment piloter firefox autrement. L'idée, c'est de faire un programme qui va bien et de le lancer via une combinaison de touches (ou dans un bash, ou même un vim).
deezer-cli permet de piloter Safari sur un Mac. L'idée est bonne : exécuter une commande js dans l'ongle deezer.
Bon, c'est pas ce que je cherche, mais l'idée me plaît.
Et pourquoi pas lancer un serveur udp depuis un script greasemonkey pour faire la même chose ?
Je me voyais déjà ajouter la fonctionnalité manquante à firefox, devenir un nouveau gourou du logiciel libre, à moi la gloire, le succès et tout le reste.
Et pis, j'ai repensé à mon vieux casque bluetooth qui est capable de faire ça, ">*/×@)!
Quelqu'un avait déjà eu l'idée. Il y avait même un touche sur mon clavier pour mettre le flux audio en pause…
Après quelque recherche[2], j'ai trouvé ce que je cherchais : Piloter une interface mediaplayer2 via dbus
Quoi
Vas plutôt lire wikipedia, mais en gros, DBUS est un bus qui permet aux applications de proposer leurs services à qui en veut.
Par exemple, on peut allumer ou éteindre le bluetooth, éteindre l'ordinateur, envoyer une notification…
Et donc, on peut piloter un lecteur multimédia, via l'interface MPRIS[3].
Comment
Un peu de lecture plus tard, je trouve ce qui m'intéresse : l'interface MediaPlayer2.Player
qui implémente --entre autres-- les méthodes
-
Next
, -
Previous
, -
PlayPause
.
Donc, c'est bon, j'ai ce qu'il faut maintenant ? Non, il manque l'objet à manipuler. Et cet objet n'a pas forcement un nom unique, il faudra donc le chercher…
Chaque objet dbus a un nom qu'il demande sur le bus, si un lecteur multimédia veut implémenter l'interface qui va bien doit avoir un nom qui commence par org.mpris.MediaPlayer2
.
Il faut donc chercher les noms sur le bus qui commence par le motif indiqué.
Pour cela, on utilise l'interface org.freedesktop.DBus
implémentée par l'objet org.freedesktop.DBus
sur laquelle on doit appeler ListNames
.
C'est bon on a tout pour commencer ??
Non, et le chemin alors ?
Le chemin (path dans la langue des Spices Girls) correspond plus au moins à la notion de classe. En simple, c'est comme l'interface, mais dont on replace .
par des /
, et que l'on fait commencer par /
.
Donc, l'interface "org.freedesktop.DBus" sera associé au chemin "/org/freedesktop/DBus".
Là, on a tout.
On va donc commencer par chercher la liste des objets dont le nom suit un certain motif.
Si cet objet est unique, on peut appeler les fonctions qui nous intéressent, comme "Next" et les autres cités plus haut.
Implémentations
Il y a plein de binding de dbus. J'ai commencé avec un test en python, pour voir si ça marchait, puis en C par ce qu'on se refait pas et en bash, parce que je savais pas si c'était possible.
python
J'aime pas python --pas le langage en lui même, mais la gestion des versions et des dépendances-- mais il faut bien lui reconnaître qu'il est facile d'accès et que sa bibliothèque dbus est bien documentée.
C'est facile et rapide, tellement que je peux mettre ici (réduit au minimum, c'est :
#! /usr/bin/env python3
import sys
from dbus import SessionBus, Interface
# On récupère le bus de session
bus = SessionBus()
# On récupère l'objet qui nom permet d'énumérer les objets sur le bus de session
session = bus.get_object( "org.freedesktop.DBus", "/org/freedesktop/DBus")
# Et on appelle ListNames sur cet objet
names = session.ListNames()
# Dont on récupère les objets dont le nom commence par ...
players = [x for x in names if 0 == x.find('org.mpris.MediaPlayer2')]
# On veut un seul objet
if 1 != len(players):
if 0 == len(players) :
sys.stderr.write('No player found\n')
else:
sys.stderr.write('More than one player found\n')
sys.exit()
# On récupère l'objet associé au nom
player = bus.get_object(players[0], '/org/mpris/MediaPlayer2')
# Ainsi que l'interface
interface = Interface(player, dbus_interface='org.mpris.MediaPlayer2.Player')
# Et on appelle la méthode qui va bien.
interface.PlayPause()
C
En C, j'ai voulu utiliser la bibliothèque officielle (libdbus), mais j'ai lu :
If you use this low-level API directly, you're signing up for some pain. —official API documentation
Donc, je suis parti sur glib, parce que c'est rigolo de cherche de la doc sur la manipulation des GString sur internet.
C'est du même tonneau, mais avec des appels moins simples :
On récupère le bus avec la fonction qui va bien :
GDBusConnection *con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
Et on appelle les méthode avec un gros pâté :
GVariant *result = g_dbus_connection_call_sync(
con, // connexion au bus
"org.freedesktop.DBus", // nom
"/org/freedesktop/DBUS", // chemin
"org.freedesktop.DBus", // interface
"ListNames", // methode
NULL, // parametre
G_VARIANT_TYPE("(as)"), // sortie attendue: tableau de chaines
G_DBUS_CALL_FLAGS_NONE, // pas de drapeau
3000, // timeout
NULL, // cancellable
NULL); // error
La partie pénible est de parcourir les résultats pour trouver l'objet qu'on veut car on doit faire attention à libérer explicitement la mémoire. Un fois l'objet voulu trouvé, on peut appeler la méthode voulue :
GVariant *result = g_dbus_connection_call_sync(
con,
player_name,
"/org/mpris/MediaPlayer2",
"org.mpris.MediaPlayer2.Player",
"PlayPause",
NULL,
NULL,
G_DBUS_CALL_FLAGS_NONE,
3000,
NULL,
NULL);
Le code était un peu plus touffu, il est lisible ici
Shell
Et pour bash, il existe dbus-send
qui fait en gros ce que je veux en un seul appel (que j'aurais pu appeler en python ou en C si j'étais pervers)
#! /usr/bin/env bash
# à la recherche du player
PLAYER=$(dbus-send --print-reply --session --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep org.mpris.MediaPlayer2 | sed s'/.*"\([^"]*\)".*/\1/')
# Meh, what if PLAYER is empty? wc -l will return 1
# should also test for PLAYER emptyness
COUNT="$(wc -w <<< "$PLAYER")"
if [ 0 -eq "$COUNT" ] ; then
echo "no player found"
exit
elif [ 1 -ne "$COUNT" ] ; then
echo "More than one player found"
exit
fi
# appel de la méthode voulue
1>&2 dbus-send --print-reply --session --dest="$PLAYER" /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause
C'est moins lisible, mais ça fait le job.
Comparaison
Avec GNU time, je me suis amusé à comparer les trois implémentations (qui font la même chose, hein)
Quoi | python | C | shell
--------------+---------+--------+------
taille code | 0.8kB | 5kB | 0.6kB
RAM | 13MB | 5MB | 3MB
Vitesse | ~30 ms | <10 ms | ~10ms
Sans surprise, python est plus lent et occupe plus en mémoire que l'implémentation en C, mais les ordres de grandeurs sont les mêmes et restent très raisonnables sur des architectures modernes (surtout comparé aux 3GB accaparés par firefox)
Je suis étonné le shell prenne si peu de RAM, faut peut être que je vérifie comment les pipes sont prises en compte par time…
La suite
Je suis content, après avoir copié les exécutables dans ~/.local/bin
, je n'ai plus à utiliser la souris pour changer de piste, et j'ai un peu appris un truc. La suite serait d'ajouter un filtre pour ne piloter que certains logiciels, soigner le rangement des sources python… mais ça sera pour la prochaine fois.
Repo git
Le code, c'est là
[1]: Aussi appelée liste des trucs que je ferai jamais
[2]: Oui, au singulier, c'était pas vraiment caché
[3]: Media Player Remote Interfacing Specification
# playerctl
Posté par impromptux . Évalué à 2 (+2/-0).
Bonjour,
Merci pour ce journal, il m'a donné plusieurs idées.
J'utilise playerctl qui fonctionne probablement comme ça. Je me suis fait un script d'une vingtaine de ligne par dessus qui me permet d'afficher les musiques en bas de mon écran et de cliquer dessus pour arrêter,relancer ou changer de musique. Je vais regarder si je ne peut pas réécrire ça avec dbus.
Envoyer un commentaire
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.