Arjen Balfoort
Arjen Balfoort

Reputation: 185

VteTerminal - Wait until command has finished

In an application I want to embed VteTerminal and let the user feed pre-defined commands.

After I run a command that takes some time I want to run another command. However, the second command is run even while the first one hasn't even started, or so it seems. The button label is instantaneously changed into "Finished" and in the terminal you can see the echo command right after the first command (before the first command echos the files).

How can I let feed_child wait until the command has finished?

Following is the example code:

#!/usr/bin/env python3

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

from gi.repository import Gtk, GLib, Vte
from os import environ


class Terminal(Vte.Terminal):
    def __init__(self):
        super(Terminal, self).__init__()
        self.connect_after('child-exited', self.on_child_exited)
        self.create_child()
        
    def create_child(self):
        self.spawn_async(
            Vte.PtyFlags.DEFAULT,
            environ['HOME'],
            ["/bin/bash"],
            [],
            GLib.SpawnFlags.DO_NOT_REAP_CHILD,
            None,
            None,
            -1,
            None,
            None,
            None
            )
    
    def feed(self, command):
        command += '\n'
        self.feed_child(command.encode())
    
    def on_child_exited(self, terminal, status):
        # Create a new child if the user ended the current one
        # with Ctrl-D or typing exit.
        self.create_child()
        
class VteTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Vte Test")
        self.connect('destroy', Gtk.main_quit)
        self.terminal = Terminal()
        self.set_default_size(600, 400)

        # GtkBox
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        # VteTerminal
        sw = Gtk.ScrolledWindow()
        sw.add(self.terminal)
        box.pack_start(sw, True, True, 2)
        # GtkButton
        self.btn = Gtk.Button(label="Run commands")
        self.btn.connect("clicked", self.on_btn_clicked)
        box.pack_start(self.btn, False, True, 2)
        # Add GtkBox to window
        self.add(box)
        
        self.show_all()
        
    def on_btn_clicked(self, widget):
        self.terminal.feed('for F in $(ls); do echo "$F"; sleep 0.2; done')
        # Button label is instantaniously changed to "Finished"
        self.btn.set_label('Working...')
        # TODO: wait until command has finished
        self.terminal.feed('echo "This shoud be the end"')
        self.btn.set_label('Finished')

VteTest()
Gtk.main()
    

Upvotes: 0

Views: 165

Answers (1)

Arjen Balfoort
Arjen Balfoort

Reputation: 185

I have come up with a less than ideal solution by creating a loop in the feed function that checks the terminal output for a new prompt sign ($ or #).

If anyone finds a more elegant solution to this problem, please let me know.

Here follows the adjusted feed function:

    def feed(self, command, wait_until_done=False):
        command += '\n'
        self.feed_child(command.encode())

        def sleep(seconds=0.1):
            time.sleep(seconds)
            # Update the parent window
            while Gtk.events_pending():
                Gtk.main_iteration()

        # Unfortunately, there is no built-in way to notify the parent
        # that a command has finished or to wait for the command 
        # until it is finished.
        if wait_until_done:
            # This won't work if the user scrolls up.
            # So, disable scrolling while running the command
            parent_is_sensitive = self.get_parent().get_sensitive()
            self.get_parent().set_sensitive(False)
            # First, wait until the last character is not a prompt sign
            while self.get_text(None, None)[0].strip()[-1:] in '$#':
                sleep()
            # Finally, the command is executing - wait until the last
            # character is a prompt sign
            while self.get_text(None, None)[0].strip()[-1:] not in '$#':
                sleep()
            # Make the terminal scrollable again if it was at the start
            if parent_is_sensitive: self.get_parent().set_sensitive(True)

Upvotes: 1

Related Questions