Reputation: 185
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
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