About a week ago while working on propensity, I was trying to add an impeded terminal into the program instead of just sending commands to an xterm. I quickly found out about the VTE Terminal Widget, this is the same widget used by the gnome-terminal. Now while I was able to find a great API, I was working in python not C and while I love API’s when I’m using a new feature I also find examples to be one of the best ways for me to learn, but even with all my google searching I found it really hard to find a simple python example of the VTE Terminal Widget. The good news is I now have a simple working example, its a basic gui that lets you enter a command and then that command will be run in a separate terminal window (I’m not exactly sure why I used two windows now that I think of it.)
An so without further ado here is the code (all GPL). Hope this helps someone in the future. You can download the code here, or view it below.
VirtualTerminal.py (This code could be used in your program)
#!/usr/bin/env python
#
# VirtualTerminal.py
#
# Copyright 2007 Edward Andrew Robinson <earobinson@gmail>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Imports
import os
import vte
import gtk
import time
class VirtualTerminal(vte.Terminal):
def __init__(self, log_file = None, history_length = 5, prompt_watch = {}, prompt_auto_reply = True, icon = None):
# Set up terminal
vte.Terminal.__init__(self)
self.history = []
self.history_length = history_length
self.icon = icon
self.last_row_logged = 0
self.log_file = log_file
self.prompt_auto_reply = prompt_auto_reply
self.prompt_watch = prompt_watch
self.connect('eof', self.run_command_done_callback)
self.connect('child-exited', self.run_command_done_callback)
self.connect('cursor-moved', self.contents_changed_callback)
if False:
self.connect('char-size-changed', self.activate_action, 'char-size-changed')
#self.connect('child-exited', self.activate_action, 'child-exited')
self.connect('commit', self.activate_action, 'commit')
self.connect('contents-changed', self.activate_action, 'contents-changed')
#self.connect('cursor-moved', self.activate_action, 'cursor-moved')
self.connect('decrease-font-size', self.activate_action, 'decrease-font-size')
self.connect('deiconify-window', self.activate_action, 'deiconify-window')
self.connect('emulation-changed', self.activate_action, 'emulation-changed')
self.connect('encoding-changed', self.activate_action, 'encoding-changed')
#self.connect('eof', self.activate_action, 'eof')
self.connect('icon-title-changed', self.activate_action, 'icon-title-changed')
self.connect('iconify-window', self.activate_action, 'iconify-window')
self.connect('increase-font-size', self.activate_action, 'increase-font-size')
self.connect('lower-window', self.activate_action, 'lower-window')
self.connect('maximize-window', self.activate_action, 'maximize-window')
self.connect('move-window', self.activate_action, 'move-window')
self.connect('raise-window', self.activate_action, 'raise-window')
self.connect('refresh-window', self.activate_action, 'refresh-window')
self.connect('resize-window', self.activate_action, 'resize-window')
self.connect('restore-window', self.activate_action, 'restore-window')
self.connect('selection-changed', self.activate_action, 'selection-changed')
self.connect('status-line-changed', self.activate_action, 'status-line-changed')
self.connect('text-deleted', self.activate_action, 'text-deleted')
self.connect('text-inserted', self.activate_action, 'text-inserted')
self.connect('text-modified', self.activate_action, 'text-modified')
self.connect('text-scrolled', self.activate_action, 'text-scrolled')
self.connect('window-title-changed', self.activate_action, 'window-title-changed')
def activate_action(self, action, string):
print 'Action ' + action.get_name() + ' activated ' + str(string)
def capture_text(self,text,text2,text3,text4):
return True
def contents_changed_callback(self, terminal):
'''Gets the last line printed to the terminal, it will log
this line using self.log() (if the logger is on, and it will
also prompt this line using self.prompt() if the line needs
prompting'''
column,row = self.get_cursor_position()
if self.last_row_logged != row:
off = row-self.last_row_logged
text = self.get_text_range(row-off,0,row-1,-1,self.capture_text)
self.last_row_logged=row
text = text.strip()
# Log
self.log(text)
# Prompter
self.prompter()
def get_last_line(self):
terminal_text = self.get_text(self.capture_text)
terminal_text = terminal_text.split('\\\\n')
ii = len(terminal_text) - 1
while terminal_text[ii] == '':
ii = ii - 1
terminal_text = terminal_text[ii]
return terminal_text
def log(self, text):
'''if self.log_file is not None the the line will be logged to
self.log_file. This function also stors the info in self.histoy
if self.history_lenght > 0'''
if self.log_file != None:
date_string = time.strftime('[%d %b %Y %H:%M:%S] ', time.localtime())
file = open(self.log_file, 'a')
file.write(date_string + text + '\\\\n')
file.close
# Append to internal history
if self.history_length != 0:
if len(self.history) >= self.history_length:
self.history.pop(0)
self.history.append(text)
def prompter(self):
last_line = self.get_last_line()
if last_line in self.prompt_watch:
if self.prompt_auto_reply == False:
message = ''
for ii in self.prompt_watch[last_line]:
message = message + self.history[self.history_length - 1 - ii]
if self.yes_no_question(message):
self.feed_child('Yes\\\\n')
# TODO not sure why this is needed twice
self.feed_child('Yes\\\\n')
else:
self.feed_child('No\\\\n')
else:
self.feed_child('Yes\\\\n')
def run_command(self, command_string):
'''run_command runs the command_string in the terminal. This
function will only return when self.thred_running is set to
True, this is done by run_command_done_callback'''
self.thread_running = True
spaces = ''
for ii in range(80 - len(command_string) - 2):
spaces = spaces + ' '
self.feed('$ ' + str(command_string) + spaces)
self.log('$ ' + str(command_string) + spaces)
command = command_string.split(' ')
pid = self.fork_command(command=command[0], argv=command, directory=os.getcwd())
while self.thread_running:
#time.sleep(.01)
gtk.main_iteration()
def run_command_done_callback(self, terminal):
'''When called this function sets the thread as done allowing
the run_command function to exit'''
#print 'child done'
self.thread_running = False
def yes_no_question(self, message):
message = message.replace('\\\\n\\\\n', '[NEWLINE][NEWLINE]').replace('\\\\n', '').replace('[NEWLINE]', '\\\\n')
if message.find('?') == -1:
message = message + '\\\\n\\\\nDo you want to continue?'
type=gtk.MESSAGE_QUESTION
if message.lower().find('warning') != -1:
type=gtk.MESSAGE_WARNING
dialog = gtk.MessageDialog(parent=None, flags=0, type=type, buttons=gtk.BUTTONS_YES_NO, message_format=message)
dialog.set_icon(self.icon)
dialog.show_all()
responce = dialog.run()
dialog.destroy()
# Responce == yes
return responce == -8
TestVirtualTerminal.py Used to actually make the GUIs, this is the program you should run.
#!/usr/bin/env python
#
# TestVirtualTerminal.py
#
# Copyright 2007 Edward Andrew Robinson <earobinson@gmail>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
import gtk
from VirtualTerminal import VirtualTerminal
class mainWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.connect('destroy', lambda w: gtk.main_quit())
self.set_default_size(400, 400)
self.button = gtk.Button('press me')
self.button.connect("clicked", self.pressed_callback)
self.command_entry = gtk.Entry()
self.command_entry.set_text('python count.py')
#self.command_entry.set_text('sudo aptitude install gaim-encryption')
vbox = gtk.VBox()
self.r = 0
self.add(vbox)
vbox.pack_start(self.command_entry, False)
vbox.pack_start(self.button, True)
self.myTerminal = terminal()
self.show_all()
gtk.main()
def pressed_callback(self, button):
print 'presed'
column,row = self.myTerminal.terminal.get_cursor_position()
if self.r != row:
off = row-self.r
text = self.myTerminal.terminal.get_text_range(row-off,0,row-1,-1,self.capture_text)
self.r=row
text = text.strip()
print text
self.button.set_sensitive(False)
self.myTerminal.terminal.run_command(self.command_entry.get_text())
self.button.set_sensitive(True)
print 'done'
def capture_text(self,text,text2,text3,text4):
return True
class terminal(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
#self.set_title(self.settings.application_name)
self.connect('destroy', lambda w: gtk.main_quit())
self.terminal = VirtualTerminal()
#self.child_pid = self.terminal.fork_command()
self.add(self.terminal)
self.show_all()
mainWindow()
count.py A little test program but you can run any command you want
#!/usr/bin/python
import time
print '0'
for ii in range (10):
time.sleep(1)
print ii + 1
A special thanks to Alberto Milone for all the help he provided me with for this.


[…] Source Code to Wordpress.com The other day I tried to blog about the vte.terminal, and I had huge problems posting the source code. My problem was that despite how much I searched, […]
[…] other day I tried to blog about the vte.terminal, and I had huge problems posting the source code. My problem was that despite how much I searched, […]
you’re welcome ;)
[…] think this is my turn to thank Edward A Robinson since I used (part of) his VirtualTerminal.py. When I helped him on ubuntuforums he told me that he […]
This is very interesting stuff. I’m working with python-vte myself (http://www.tenshu.net/terminator/) and I’m having some serious problems getting the vte widgets to resize smaller – they will only ever allow themselves to get larger.
Is this something either you or Alberto have encountered?
[…] other day I tried to blog about the vte.terminal, and I had huge problems posting the source code. My problem was that despite how much I searched, […]
[…] other day I tried to blog about the vte.terminal, and I had huge problems posting the source code. My problem was that despite how much I searched, […]
[…] ทดสอบ มกราคม 22, 2008 The other day I tried to blog about the vte.terminal, and I had huge problems posting the source code. My problem was that despite how much I searched, […]