Bonjour,
Peut-être avez-vous constaté comme moi qu'il n'est pas toujours possible de faire fonctionner correctement le bouton "Annuler" d'une boîte de dialogue zenity --progress lorsqu’on veut interrompre un traitement lancé sous la forme :
traitement | zenity --progress ....
Si "traitement" n'écrit pas ou plus sur stdout, "Annuler" n'annule rien. L'option --auto-kill de zenity ne résoud le problème que dans un nombre très limité de cas. Une analyse du fonctionnement de "Annuler" est détaillée
dans ce post.
Pour faire fonctionner à tous les coups la touche "Annuler" des boîtes de dialogue zenity --progress, c'est-à-dire avertir le traitement en cours, quel qu'il soit, de l'action de l'utilisateur, j'ai réalisé une procédure shell, start_and_monitor qui lance le traitement dans un processus P, lui associe une boîte de dialogue de type zenity --progress et, en cas d'appui sur le bouton "Annuler" de la boîte, prévient le processus P en lui envoyant le signal SIGUSR1.
Un exemple d'utilisation simple est :
start_and_monitor --witdth=350 --title="Titre de ma boîte" --pulsate traitement param1 param2
Voici le code de cette procédure start_and_monitor :
#!/bin/bash
# Version 0.1
#
# Procedure start_and_monitor
# Procedure permettant de déclencher l'exécution d'une procédure ou commande donnée en paramètre et d'en contrôler
# le déroulement grâce à une interface de type fenêtre de contrôle zenity --progress.
#
# La procédure contrôlée est lancée dans un processus séparé et peut interagir avec la fenêtre de contrôle zenity
# en lui envoyant des messages via la procédure send_to_sam (synopsis ci-dessous) ou directement en écrivant
# sur le file descripteur 3.
# Ces messages sont lues par zenity et, suivant leur contenu, peuvent modifier l'affichage du message dans la boîte
# de dialogue ou celui de la barre de progression (voir man zenity).
#
# Par ailleurs, la boîte de dialogue est dotée d'une touche "Annuler" qui, lorsqu'elle est cliquée par l'utilisateur
# provoque l'envoi du signal SIGUSR1 à la procédure contrôlée, procédure qui s'exécute dans un processus séparé.
#
# La procédure contrôlée peut prévoir dans son corps le handler récupérant et traitant le signal SIGUSR1.
# Par défaut, la procédure contrôlée est arrêtée avec un code de sortie égal à 128+SIGUSR1 et un message du type
# "Signal utilisateur 1 reçu" est affiché sur stderr.
#
# Synopsis :
# start_and_monitor [option [option]...] action [param [param]...]
#
# où option est l'une des options suivantes d'affichage de la fenêtre zenity (voir man zenity)
# --width=INT
# --pulsate
# --auto-close
# --percentage=INT
# --title=STRING
#
# où action désigne la commande ou procédure à exécuter sous contrôle
# et où chaque param désigne un paramètre de action.
#
# Retour :
# le code de sortie de la procédure contrôlée action
# et, en cas d'erreur détectée par start_and_monitor :
# 30 : en cas de paramètre erroné
# 31 ou 32 : si l'un des processus nécessaire à l'implantation du mécanisme n'a pu être créé.
#
# Synopsis :
# send_to_sam message
#
# où message est une chaîne de caractère interprétée par zenity (voir man zenity)
#
# Exemples :
# Voir fichier séparé (sam_exemples)
# Voir notamment exemples 3 et 4 pour le contrôle de tâches lourdes.
#
# Copyright : erpiu 2011
# License : GPL V3
# Contact : erpiu sur forum www.ubuntu-fr.org
# Note d'implantation:
# start_and_monitor lance deux processus et leur fournit un canal de communication.
# La procédure contrôlée est lancée dans un processus ctr_proc.
# La fenêtre zenity est affichée par un processus zen_proc.
# Les deux processus peuvent communiquer via un fifo accessible :
# - via le file descriptor 0 (stdin) pour zen_proc,
# - via le file descriptor 3 pour ctr_proc.
# Le fifo est entièrement géré, créé, puis détruit par la procédure start_and_monitor
# Ainsi, si la procédure contrôlé n'est pas un shell script, la procédure contrôlée peut
# envoyer des messages à la fenêtre zenity en écrivant sur le fichier correspondant au file descripteur 3.
# Procedure send_to_sam
# Utilisée depuis la procédure contrôlée
# Permet d'envoyer un message texte au processus zen_proc
# Paramètre :
# $1 : texte du message
function send_to_sam ()
{
echo "$1" >&3
}
function start_and_monitor ()
{
local -i ctr_proc_pid=0 # pid du processus ctr_proc
local -i zen_proc_pid=0 # pid du processus zenity
local -i ires
local -i base_err=29 # toutes les erreurs transmises seront de code > à 29
local fifo_name signal_name
fifo_name="/tmp/start_and_mon$$_$(date +%H%M%S%N)" # Fifo de communication avec le processus zenity
# Nom du fifo en fonction de l'identité du process shell et de l'instant de création ==> unicité
signal_name="USR1"
# =========== Vérification des paramètres ===========
local -i fl_err fl_done
local -a zen_tab # tableau des options pour zenity
local -i zen_tab_ix=0 # et index courant
local cur_par
IFS=$'\n'
fl_err=0
fl_done=0
if test $# -eq 0; then fl_err=1; fi
while test $# -gt 0 -a $fl_done -eq 0 -a $fl_err -eq 0; do
cur_par=$1
case $cur_par in
--* ) case $cur_par in
--width=* | --pulsate | --auto-close | --percentage=* | --title=*)
zen_tab[$zen_tab_ix]="$cur_par"
((zen_tab_ix++)) ;; # Une option de plus dans le tableau
*) fl_err=2 ;;
esac
shift ;;
*) fl_done=1 ;;
esac
done
if test $fl_done -eq 0; then
fl_err=3
fi
if test $fl_err -ne 0; then
case $fl_err in
1) echo "Erreur : au moins un paramètre nécessaire" >&2 ;;
2) echo "Erreur : paramètre \"$cur_par\" non reconnu" >&2 ;;
3) echo "Erreur : action à lancer non spécifée" >&2 ;;
esac
return $((base_err+1)) # Paramètres erronés : ne pas aller plus loin
fi
# Ici $@ désigne la commande à exécuter avec l'ensemble de ses paramètres
# et les paramètres pour zenity sont dans zen_tab
# =========== Traitement proprement dit ===========
mkfifo "$fifo_name" # Création du fifo
# Création du process zenity
zenity --progress "${zen_tab[@]}" < "$fifo_name" &
ires=$?
if test $ires -eq 0 ; then
zen_proc_pid=$!
# Puis lancement de la procédure paramètre
{
# Au cas où on est en mode pulsate et si jamais la procédure contrôlée n'envoie rien à zenity
# forcer l'affichage d'un message vide, ce qui aura au moins pour effet de démarrer le mode
# pulsate
send_to_sam "#"
# Lancement proprement dit dans un sous-processus
"$@" &
} 3> "$fifo_name"
# le tout en ouvrant le fd 3 sur le fifo
ires=$?
if test $ires -eq 0 ; then
ctr_proc_pid=$!
else
# Tuer zen_proc car on n'a pas réussi à créer ctr_proc
kill -TERM $zen_proc_pid
ires=$((base_err+2))
fi
else
# On n'a pas réussi à créer zen_proc
ires=$((base_err+3))
fi
if test $ires -eq 0 ; then
# Attendre la fin du processus zenity
wait $zen_proc_pid
ires=$?
if test $ires -ne 0 ; then
# Cas du cancel par l'utilisateur : envoyer le signal à ctr_proc pour le prévenir
kill -$signal_name $ctr_proc_pid
fi
# Attendre la fin du process ctr_proc
wait $ctr_proc_pid
ires=$?
fi
# Nettoyer
rm -f "$fifo_name"
return $ires
}
Pour mieux comprendre l'utilisation de start_and_monitor, voici un script d'exemples (le nom du fichier paramètre de la commande source est à adapter) :
#!/bin/bash
#
# Exemples d'utilisation de start_and_monitor
# Script à lancer dans un terminal pour en observer les effets sur stderr ou stdout
#
source sam_src #Fichier contenant le code de start_and_monitor (nom à adapter si nécessaire)
exemple_suivant() {
zenity --question --title="Start_and_monitor" --width=400 --text="Passage à exemple $1?"
if [ $? -ne 0 ]; then exit 1; fi
}
# Exemple 1 : Monitoring d'un processus affichant régulièrement son état d'avancement
# dans la boîte de dialogue
# Note : c'est un exemple tout à fait implémentable avec une construction directe
# du type "traitement | zenity --progress"
traitement1() {
# Paramètres : $1 = % d'avancement initial
# $2 = Message à afficher
local -i i
do_on_usr1() {
echo "Traitement 1 interrompu à $i% d'avancement" >&2
exit 1 # Abandon (fin du processus)
}
trap do_on_usr1 USR1
for ((i=$1; i<=100;i=i+10)); do
send_to_sam "$i"
send_to_sam "#$2 : $i%"
sleep 1
done
send_to_sam "100"; send_to_sam "#Fin de traitement"
}
start_and_monitor --width=500 --title="Exemple 1" traitement1 5 "Progression de T1"
echo "== Exemple 1 - Code de sortie : $?" >&2
exemple_suivant 2
# Exemple 2 : Monitoring d'un processus n'affichant rien dans la boîte de dialogue
# (mode pulsate par exemple). Les affichages se font uniquement sur stderr.
#
traitement2() {
# Paramètres : $1 = % d'avancement initial
# $2 = Message à afficher
local -i i
do_on_usr1() {
echo "Traitement 2 interrompu à $i% d'avancement" >&2
exit 1 # Abandon (fin du processus)
}
trap do_on_usr1 USR1
for ((i=$1; i<=100;i=i+10)); do
echo "$2 : $i%" >&2
sleep 1
done
echo "Fin de traitement" >&2
}
start_and_monitor --width=500 --title="Exemple 2" --pulsate traitement2 10 "Avancement de T2"
echo "== Exemple 2 - Code de sortie : $?" >&2
exemple_suivant "2 (sans start_and_monitor)"
# Note : avec une construction directe "traitement | zenity --progress --pulsate"
# un tel traitement ne peut être annulé par un clic sur le bouton "Annuler"
#
traitement2 10 "Avancement" | zenity --progress --pulsate --width=400 --title="Exemple 2 sans start_and_monitor"
exemple_suivant 3
# Exemple 3 : Monitoring d'un processus consommateur de CPU ou d'affichage,
# non prévu pour être monitoré, mais pouvant tout de même être stoppé
# par un clic sur "Annuler".
start_and_monitor --width=300 --title="Exemple 3" --pulsate find $HOME -name '*a*'
echo "== Exemple 3 - Code de sortie : $?" >&2
exemple_suivant 4
# Exemple 4 : Monitoring d'un processus consommateur de CPU ou d'affichage
# mais prévu pour récupérer une interruption suite à un clic sur "Annuler"
#
traitement4() {
local -i pid
do_on_usr1() {
kill -TERM $pid
echo "Traitement 4 interrompu - Process find stoppé" >&2
return # Retour (derrière wait)
}
trap do_on_usr1 USR1
# Lancement de la commande find (supposée durer longtemps) dans un sous-process
# Note importante : si find était exécuté directement dans le même process, comme le
# trap do_on_usr1 n'est éxécuté par le shell qu'à la fin de la commande en cours,
# on ne pourrait traiter le signal d'Annulation qu'à la fin de l'exécution de
# la commande, ce qui serait sans intérêt.
find $HOME -name '*a*' &
pid=$!
wait $pid
}
start_and_monitor --width=300 --pulsate --title="Exemple 4" traitement4
echo "== Exemple 4 - Code de sortie : $?" >&2
Peut-être cette procédure peut-elle servir aussi à certains d'entre vous.
En tous cas, merci d'avance de vos commentaires, suggestions et ... rapports de bugs!