2012-05-09 3 views
1

J'essaie d'utiliser un objet gobject pour permettre la communication entre un processus Popen et une interface graphique GTK.gobject et subprocess.Popen pour communiquer dans une interface graphique GTK

Inspiré par ceci: https://pygabriel.wordpress.com/2009/07/27/redirecting-the-stdout-on-a-gtk-textview/#comment-156

Je mis en œuvre quelque chose de similaire à ceci:

http://hartree.altervista.org/files/command-textview.py

mais je remarquai que la gobject utilise beaucoup de cycle CPU, même une fois le processus Popen est mis fin. Il suffit de lancer le script ci-dessus et regarder le moniteur système Ubuntu.

Après quelques travaux avec "pty" je suis venu avec ceci:

import gtk,pygtk 
import subprocess 
import gobject 
import pty, os, time 

class CommandTextView(gtk.TextView): 
    def __init__(self): 
     super(CommandTextView,self).__init__() 
     self.master, self.slave = pty.openpty() 
     gobject.io_add_watch(os.fdopen(self.master), gobject.IO_IN, self.write_to_buffer) 
     self.proc = None 

    def run(self, w, cmd): 
     if self.proc == None or self.proc.poll() != None: # poll()=None means still running 
      self.proc = subprocess.Popen(cmd.split(), shell=True, stdout=self.slave, stderr=self.slave) 

    def stop(self,w): 
     if type(self.proc) is subprocess.Popen: 
      self.proc.kill() 
      while self.proc.poll() == None: 
       time.sleep(0.1) 
      self.proc = None 

    def write_to_buffer(self, fd, condition): 
     if condition == gobject.IO_IN: 
      char = fd.readline() 
      print 'adding:',char  
      buf = self.get_buffer() 
      buf.insert_at_cursor(char) 
      return True 
     else: 
      return False 

def test(): 
    win=gtk.Window() 
    vbox = gtk.VBox(False, 0) 
    win.set_size_request(300,300) 
    win.connect('delete-event',lambda w,e : gtk.main_quit()) 
    ctv=CommandTextView() 
    bt1 = gtk.Button('Run') 
    bt2 = gtk.Button('Stop') 
    vbox.pack_start(ctv) 
    vbox.pack_end(bt2,False,False) 
    vbox.pack_end(bt1,False,False) 
    win.add(vbox) 

    bt1.connect("clicked", ctv.run, 'ls -la') 
    bt2.connect("clicked", ctv.stop) 
    win.show_all() 
    gtk.main() 

if __name__=='__main__': test() 

Les questions que je sont:

  • est pty une bonne idée? Peut-il être utilisé pour Windows aussi?

  • est-il possible d'éviter d'utiliser pty et juste utiliser stdout et ne pas avoir le problème d'utilisation de l'unité centrale élevée?

  • Si vous exécutez ce script la première fois, il semble mettre en tampon la sortie txt et donner une sortie incomplète.

Nous vous remercions de l'aide

Répondre

0

Utilisez la lecture UDIMM os.read, il faut un descripteur de fichier réel. Votre fd n'est pas un vrai descripteur de fichier, c'est un objet de fichier; généralement appelé f.

Si vous voulez être sûr que le processus est mort, utilisez os.kill.

+0

Pouvez-vous nous en dire un peu plus sur ce que devrait être la solution? Je pense en fait que la commande self.proc.kill() ne tue pas le processus parce que j'utilise shell = True. Possible? – Fabrizio

+0

Tout est possible. –

+0

Cet exemple ne semble pas vraiment fonctionner si cmd = 'ls -R /' par exemple. Et pour le faire fonctionner avec lui, vous aurez peut-être besoin shell = False dans ce cas, le bouton d'arrêt ne fonctionnera pas. Bottom line, pas un si bel exemple de communication gui processus pygtk. – Fabrizio

1

Ceci est pour les personnes qui ont trébuche à ce poste après 2016 et tentait de le réécrire en gtk3.

#!/usr/bin/env python3 

import gi 
gi.require_version('Gtk', '3.0') 

from gi.repository import Gtk 
from gi.repository import GObject 

import os 
import fcntl 
import subprocess 

def unblock_fd(stream): 
    fd = stream.fileno() 
    fl = fcntl.fcntl(fd, fcntl.F_GETFL) 
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) 


class StreamTextBuffer(Gtk.TextBuffer): 
    '''TextBuffer read command output syncronously''' 
    def __init__(self): 
     Gtk.TextBuffer.__init__(self) 
     self.IO_WATCH_ID = tuple() 


    def bind_subprocess(self, proc): 
     unblock_fd(proc.stdout) 
     watch_id_stdout = GObject.io_add_watch(
      channel = proc.stdout, 
      priority_ = GObject.IO_IN, 
      condition = self.buffer_update, 
      # func  = lambda *a: print("func") # when the condition is satisfied 
      # user_data = # user data to pass to func 
     ) 

     unblock_fd(proc.stderr) 
     watch_id_stderr = GObject.io_add_watch(
      channel = proc.stderr, 
      priority_ = GObject.IO_IN, 
      condition = self.buffer_update, 
      # func  = lambda *a: print("func") # when the condition is satisfied 
      # user_data = # user data to pass to func 
     ) 

     self.IO_WATCH_ID = (watch_id_stdout, watch_id_stderr) 
     return self.IO_WATCH_ID 


    def buffer_update(self, stream, condition): 
     self.insert_at_cursor(stream.read()) 
     return True # otherwise isn't recalled 


def sample(): 
    root = Gtk.Window() 
    root.set_default_size(400, 260) 
    root.connect("destroy", Gtk.main_quit) 
    root.connect(# quit when Esc is pressed 
     'key_release_event', 
     lambda w, e: Gtk.main_quit() if e.keyval == 65307 else None 
    ) 
    layout = Gtk.Box(orientation=1) 
    scroll = Gtk.ScrolledWindow() 
    layout.pack_start(scroll, expand=1, fill=1, padding=0) 

    buff = StreamTextBuffer() 
    textview = Gtk.TextView.new_with_buffer(buff) 
    scroll.add(textview) 

    button_start = Gtk.Button("Execute Command") 
    layout.add(button_start) 

    def on_click(widget): 
     if len(buff.IO_WATCH_ID): 
      for id_ in buff.IO_WATCH_ID: 
       # remove subprocess io_watch if not removed will 
       # creates lots of cpu cycles, when process dies 
       GObject.source_remove(id_) 
      buff.IO_WATCH_ID = tuple() 
      on_click.proc.terminate() # send SIGTERM 
      widget.set_label("Execute Command") 
      return 

     on_click.proc = subprocess.Popen(
      [ 'ping', '-c', '3', 'localhost' ], 
      stdout = subprocess.PIPE, 
      stderr = subprocess.PIPE, 
      universal_newlines=True, 
     ) 
     buff.bind_subprocess(on_click.proc) 
     widget.set_label("STOP!") 

    button_start.connect("clicked", on_click) 
    root.add(layout) 
    root.show_all() 


if __name__ == "__main__": 
    sample() 
    Gtk.main() 
Questions connexes