Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Nouveau Candidat au Club
    la fonction signal.alarm() a un comportement inattendu en python3/Gtk3
    Bonjour à tous,

    Il y a quelques années dans le cadre d'un projet j'ai écrit un petit chronomètre
    en python2/pygtk qui marche très bien. Maintenant je veux porter ce projet en
    python3/Gtk3 et la mon chronomètre ne marche plus du tout. Il fonctionne de
    manière classique avec le signal SIGALRM arrêté par un handler toutes les secondes.
    Curieusement en python3/Gtk3 le signal n'est pas transmis comme on s'y attendrait.
    Pour donner plus de détails sur ce problème voici la version simplifiée du programme
    en python2/pygtk qui marche parfaitement suivi du même programme en python3/Gtk3
    qui ne fonctionne pas. Comment adapter cette gestion du signal en python3 ?

    Je précise que je travaille sous Linux Debian 9 Stretch 64 bits. On voit mieux
    le fonctionnement du signal si on lance les programmes dans un terminal.

    La version qui marche en python2/pygtk
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
     
    #!/usr/bin/env python2
    # -*- coding: UTF-8
    import pygtk
    pygtk.require('2.0')
    import gtk, gobject
    import signal
     
    class MainWindow(gtk.Window):
        def __init__(self):
            gtk.Window.__init__(self)
            self.set_border_width(6)
            self.set_title("Petit Chronomètre")
            self.stop = 0
            self.seconde = 0
     
            self.vbox = gtk.VBox(homogeneous=False,spacing=5)
            self.add(self.vbox)
     
            self.button1 = gtk.Button("Lancer le chrono")
            self.button1.connect("clicked", self.Button_Chrono)
            self.vbox.pack_start(self.button1, False, True, 1)
     
            self.button2 = gtk.Button("Mettre à zéro le chronomètre")
            self.button2.connect("clicked", self.init)
            self.vbox.pack_start(self.button2, False, True, 1)
     
            self.button3 = gtk.Button("Fermer")
            self.button3.connect("clicked", self.fermer)
            self.vbox.pack_start(self.button3, False, True, 1)
     
        def Button_Chrono(self,button):
            if(self.stop == 1):
                signal.alarm(1)
                button.set_label("Arrêter le chronomètre  ( "+str(self.seconde)+" secondes )")
                self.stop = 0
            else:
                signal.alarm(0)
                button.set_label("Relancer le chronomètre ( "+str(self.seconde)+" secondes )")
                self.stop = 1
     
        def init(self,button):
            self.seconde = 0
            if(self.stop == 1):
                self.button1.set_label("Relancer le chronomètre ( "+str(self.seconde)+" secondes )")
            else:
                self.button1.set_label("Arrêter le chronomètre  ( "+str(self.seconde)+" secondes )")
     
        def fermer(self,button):
            gtk.main_quit()
     
    def gerechrono():
        def handler(signum, frame):
            print "DANS HANDLER: ",signum
            signal.signal(signal.SIGALRM, handler)
            win.button1.set_label("Arrêter le chronomètre  ( "+str(win.seconde)+" secondes )")
            win.seconde = win.seconde + 1
            signal.alarm(1)
        handler(0,0)
     
    win = MainWindow()
    win.connect("delete-event", gtk.main_quit)
    win.show_all()
    gerechrono()
    gtk.main()


    La version en python3/Gtk3
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
     
    #!/usr/bin/env python3
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    import signal
     
    class MainWindow(Gtk.Window):
        def __init__(self):
            Gtk.Window.__init__(self, title="Petit Chronomètre")
            self.set_border_width(6)
            self.stop = 0
            self.seconde = 0
     
            self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,homogeneous=False,spacing=5)
            self.add(self.vbox)
     
            self.button1 = Gtk.Button("Lancer le chronomètre")
            self.button1.connect("clicked", self.Button_Chrono)
            self.vbox.pack_start(self.button1, False, True, 1)
     
            self.button2 = Gtk.Button("Mettre à zéro le chronomètre")
            self.button2.connect("clicked", self.init)
            self.vbox.pack_start(self.button2, False, True, 1)
     
            self.button3 = Gtk.Button("Fermer")
            self.button3.connect("clicked", self.fermer)
            self.vbox.pack_start(self.button3, False, True, 1)
     
        def Button_Chrono(self,button):
            if(self.stop == 1):
                signal.alarm(1)
                button.set_label("Arrêter le chronomètre ( "+str(self.seconde)+" secondes )")
                self.stop = 0
            else:
                signal.alarm(0)
                button.set_label("Relancer le chronomètre ( "+str(self.seconde)+" secondes )")
                self.stop = 1
     
        def init(self,button):
            self.seconde = 0
            if(self.stop == 1):
                self.button1.set_label("Relancer le chronomètre ( "+str(self.seconde)+" secondes )")
            else:
                self.button1.set_label("Arrêter le chronomètre  ( "+str(self.seconde)+" secondes )")
     
        def fermer(self,button):
            Gtk.main_quit()
     
    def gerechrono():
        def handler(signum, frame):
            print("DANS HANDLER: ",signum)
            signal.signal(signal.SIGALRM, handler)
            win.button1.set_label("Arrêter le chronomètre  ( "+str(win.seconde)+" secondes )")
            win.seconde = win.seconde + 1
            signal.alarm(1)
        handler(0,0)
     
    win = MainWindow()
    win.connect("delete-event", Gtk.main_quit)
    win.show_all()
    gerechrono()
    Gtk.main()


    Avec mes remerciements pour vos conseils

  2. #2
    Nouveau Candidat au Club
    Pour revenir sur fil que j'ai initié, et pour mieux illustrer le problème on peut
    simplifier drastiquement les deux programmes ci-dessus comme suit:

    Le programme en PyGTK (Gtk+2) qui marche:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #!/usr/bin/env python
    # -*- coding: UTF-8
    import gtk
    import signal
     
    def prehandler():
        def handler(signum, frame):
            signal.signal(signal.SIGALRM, handler)
            print( 'Signal handler called with signal', signum)
            signal.alarm(1)
        handler(0,0)
     
    prehandler()
    gtk.main()


    et le même en PyGObject (Gtk+3) qui bloque le signal alarm:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #!/usr/bin/env python3
    # -*- coding: UTF-8
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk
    import signal
     
    def prehandler():
        def handler(signum, frame):
            signal.signal(signal.SIGALRM, handler)
            print( 'Signal handler called with signal', signum)
            signal.alarm(1)
        handler(0,0)
     
    prehandler()
    Gtk.main()

    Je n'ai pas avancé pour comprendre le blocage du signal, MAIS en creusant un peu
    (même beaucoup) et pour en revenir à mon projet initial de chronomètre en Gtk+3
    si on descend au niveau de la Glib il y a un timer (GLib.timeout_add) qui permet
    de gérer des interruptions au millième de seconde, ce qui résout de manière plus
    élégante mon problème de chronomètre.
    Pour ceux qui seraient interessés voici le petit chronomètre au centième de seconde
    en Gtk+3 et qui marche:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
     
    #! /usr/bin/env python3
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, GLib
     
    class MainWindow(Gtk.Window):
        def __init__(self):
            Gtk.Window.__init__(self, title="Petit Chronomètre")
            self.set_border_width(6)
            self.stop = 0
            self.seconde = 0
            self.timer = None
     
            self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,homogeneous=False,spacing=5)
            self.add(self.vbox)
     
            self.label = Gtk.Label("0 seconde ")
            self.vbox.pack_start(self.label, False, True, 1)
            self.button1 = Gtk.Button("Arrêter le chronomètre")
            self.button1.connect("clicked", self.Button_Chrono)
            self.vbox.pack_start(self.button1, False, True, 1)
     
            self.button2 = Gtk.Button("Mettre à zéro le chronomètre")
            self.button2.connect("clicked", self.init)
            self.vbox.pack_start(self.button2, False, True, 1)
     
            self.button3 = Gtk.Button("Fermer")
            self.button3.connect("clicked", self.fermer)
            self.vbox.pack_start(self.button3, False, True, 1)
     
        def Button_Chrono(self,button):
            if(self.stop == 1):
                self.gerechrono()
                button.set_label("Arrêter le chronomètre")
                self.stop = 0
            else:
                button.set_label("Relancer le chronomètre")
                self.stop = 1
     
        def init(self,widget):
            self.seconde = 0
            self.label.set_label(str(self.seconde))
     
        def fermer(self,widget):
            Gtk.main_quit()
     
        def horo(self):
            self.seconde = self.seconde + 1
            self.label.set_label(str(self.seconde/100))
            if(self.stop):
                return(False)
            return(True)
     
        def gerechrono(self):
            self.timer = GLib.timeout_add(10,self.horo)
     
    win = MainWindow()
    win.connect("delete-event", Gtk.main_quit)
    win.show_all()
    win.gerechrono()
    Gtk.main()


    Merci de m'avoir lu

  3. #3
    Modérateur

    Bonjour,

    ton chronomètre ne fonctionnera pas sur des longues durées, g_timeout_add n'est pas fait pour ça. Il ne te garantit pas l'exactitude du temps réellement écoulé entre 2 appels. Cela veut dire que plus tu laisses ton chronomètre tourner, et plus il risque de dériver, et en le laissant tourner longtemps tu peux potentiellement te retrouver avec plusieurs secondes d'écart avec la réalité.

    Utilise plutôt un GTimer. Tu peux ainsi contrôler le démarrage, l'arrêt et la pause de ton chronomètre. Ensuite l'affichage se fait via g_idle_add, qui fonctionne un peu comme g_timeout_add, à la différence que ta callback ne sera appelée que quand GTK+ n'aura rien de mieux à faire. Ainsi la charge du système ne risque pas de t'afficher des chiffres faux. Dans le pire des cas, tu auras juste un peu plus de temps entre 2 raffraichissements, mais ce sera (sauf cas extrême) invisible pour toi.

    Pour récupérer la valeur du GTimer, pas bsoin de l'arrêter, il faut juste appeler g_timer_elapsed. Bon, là je t'ai donné les noms de l'API C de la GLib, il te faudra trouver la correspondance dans l'API python.
    Documentation officielle GTK+ 3:
    GTK en C, GTK en Python

    Tutoriels GTK+ 3:
    GTK en C, GTK en Python

    Tutoriels par l'exemple (platform-demos):
    GTK (tous langages)

  4. #4
    Modérateur

    Autre chose: connecte toi à "destroy" et pas "delete-event" pour quitter ton application. C'est "destroy" qui est émis quand on quitte réellement l'application.
    Documentation officielle GTK+ 3:
    GTK en C, GTK en Python

    Tutoriels GTK+ 3:
    GTK en C, GTK en Python

    Tutoriels par l'exemple (platform-demos):
    GTK (tous langages)