Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWade Brainerd <wadetb@gmail.com>2009-03-04 15:01:57 (GMT)
committer Wade Brainerd <wadetb@gmail.com>2009-03-04 15:01:57 (GMT)
commitee6d1e00d55dc3b2bc0b373c1fd3b2d87cafb35b (patch)
treee705fc0159b1937d4a89c60df11fb77f9326bc77
parent04e80aa2eba514389d65c65f82964dd8a56a69a3 (diff)
Added support for multiple tabs. Reworked toolbox a bit. WIP on save/resume shell state from Journal.
-rw-r--r--terminal.py383
1 files changed, 225 insertions, 158 deletions
diff --git a/terminal.py b/terminal.py
index 6c88a9d..2d027c0 100644
--- a/terminal.py
+++ b/terminal.py
@@ -17,12 +17,18 @@
import os
-import logging
from gettext import gettext as _
+# Initialize logging.
+import logging
+log = logging.getLogger('Terminal')
+log.setLevel(logging.DEBUG)
+logging.basicConfig()
+
import gtk
import dbus
+import sugar.graphics.toolbutton
from sugar.activity import activity
from sugar import env
from sugar.graphics.toolbutton import ToolButton
@@ -33,201 +39,262 @@ import os.path
import vte
import pango
+import simplejson
+
class TerminalActivity(activity.Activity):
def __init__(self, handle):
activity.Activity.__init__(self, handle)
- logging.debug('Starting the Terminal activity')
+ self.data_file = None
self.set_title(_('Terminal Activity'))
- self.connect('key-press-event', self.__key_press_cb)
+ self.connect('key-press-event', self._key_press_cb)
toolbox = activity.ActivityToolbox(self)
- self._edit_toolbar = activity.EditToolbar()
- toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
- self._edit_toolbar.show()
- self._edit_toolbar.undo.props.visible = False
- self._edit_toolbar.redo.props.visible = False
- self._edit_toolbar.separator.props.visible = False
- self._edit_toolbar.copy.connect('clicked', self._copy_cb)
- self._edit_toolbar.paste.connect('clicked', self._paste_cb)
+ editbar = activity.EditToolbar()
+ toolbox.add_toolbar(_('Edit'), editbar)
+ editbar.show()
+ editbar.undo.props.visible = False
+ editbar.redo.props.visible = False
+ editbar.separator.props.visible = False
+ editbar.copy.connect('clicked', self._copy_cb)
+ editbar.copy.props.accelerator = '<Ctrl><Shift>C'
+ editbar.paste.connect('clicked', self._paste_cb)
+ editbar.paste.props.accelerator = '<Ctrl><Shift>V'
- activity_toolbar = toolbox.get_activity_toolbar()
- # free up keyboard accelerators per #4646
- activity_toolbar.stop.props.accelerator = None
+ newtabbtn = sugar.graphics.toolbutton.ToolButton('list-add')
+ newtabbtn.set_tooltip(_("Open New Tab"))
+ newtabbtn.props.accelerator = '<Ctrl><Shift>T'
+ newtabbtn.connect('clicked', self._open_tab_cb)
+
+ deltabbtn = sugar.graphics.toolbutton.ToolButton('list-remove')
+ deltabbtn.set_tooltip(_("Close Tab"))
+ deltabbtn.props.accelerator = '<Ctrl><Shift>X'
+ deltabbtn.connect('clicked', self._close_tab_cb)
- # unneeded buttons (also frees up keyboard accelerators per #4646)
- activity_toolbar.remove(activity_toolbar.share)
- activity_toolbar.share = None
- activity_toolbar.remove(activity_toolbar.keep)
- activity_toolbar.keep = None
+ tabsep = gtk.SeparatorToolItem()
+ tabsep.set_expand(True)
+ tabsep.set_draw(False)
# Add a button that will be used to become root easily.
- activity_toolbar.become_root = ToolButton('activity-become-root')
- activity_toolbar.become_root.set_tooltip(_('Become root'))
- activity_toolbar.become_root.connect('clicked',
- self._become_root_cb)
- activity_toolbar.insert(activity_toolbar.become_root, 2)
- activity_toolbar.become_root.show()
+ rootbtn = ToolButton('activity-become-root')
+ rootbtn.set_tooltip(_('Become root'))
+ rootbtn.connect('clicked', self._become_root_cb)
+
+ prevtabbtn = sugar.graphics.toolbutton.ToolButton('go-previous')
+ prevtabbtn.set_tooltip(_("Previous Tab"))
+ prevtabbtn.props.accelerator = '<Ctrl><Shift>Left'
+ prevtabbtn.connect('clicked', self._prev_tab_cb)
+
+ nexttabbtn = sugar.graphics.toolbutton.ToolButton('go-next')
+ nexttabbtn.set_tooltip(_("Next Tab"))
+ nexttabbtn.props.accelerator = '<Ctrl><Shift>Right'
+ nexttabbtn.connect('clicked', self._next_tab_cb)
+ tabbar = gtk.Toolbar()
+ tabbar.insert(newtabbtn, -1)
+ tabbar.insert(deltabbtn, -1)
+ tabbar.insert(tabsep, -1)
+ tabbar.insert(rootbtn, -1)
+ tabbar.insert(prevtabbtn, -1)
+ tabbar.insert(nexttabbtn, -1)
+ tabbar.show_all()
+
+ toolbox.add_toolbar(_('Tab'), tabbar)
+
+ activity_toolbar = toolbox.get_activity_toolbar()
+ activity_toolbar.share.props.visible = False
+ activity_toolbar.keep.props.visible = False
+
+ fullscreenbtn = sugar.graphics.toolbutton.ToolButton('view-fullscreen')
+ fullscreenbtn.set_tooltip(_("Fullscreen"))
+ fullscreenbtn.props.accelerator = '<Alt>Enter'
+ fullscreenbtn.connect('clicked', self._fullscreen_cb)
+ activity_toolbar.insert(fullscreenbtn, 2)
+ fullscreenbtn.show()
+
self.set_toolbox(toolbox)
toolbox.show()
- box = gtk.HBox(False, 4)
+ self.notebook = gtk.Notebook()
+ self.notebook.set_property("tab-pos", gtk.POS_BOTTOM)
+ self.notebook.set_scrollable(True)
+ self.notebook.show()
+
+ self.set_canvas(self.notebook)
+
+ self._create_tab(None)
+
+ def _open_tab_cb(self, btn):
+ index = self._create_tab(None)
+ self.notebook.page = index
+
+ def _close_tab_cb(self, btn):
+ self._close_tab(self.notebook.props.page)
+
+ def _prev_tab_cb(self, btn):
+ if self.notebook.props.page == 0:
+ self.notebook.props.page = self.notebook.get_n_pages() - 1
+ else:
+ self.notebook.props.page = self.notebook.props.page - 1
+ vt = self.notebook.get_nth_page(self.notebook.get_current_page()).vt
+ vt.grab_focus()
+
+ def _next_tab_cb(self, btn):
+ if self.notebook.props.page == self.notebook.get_n_pages() - 1:
+ self.notebook.props.page = 0
+ else:
+ self.notebook.props.page = self.notebook.props.page + 1
+ vt = self.notebook.get_nth_page(self.notebook.get_current_page()).vt
+ vt.grab_focus()
+
+ def _restore_shell(self, vt, tab_idx, data, data_file):
+ tab_state = data['tabs'][tab_idx]
+
+ # Restore the scrollback buffer.
+ for l in tab_state['scrollback']:
+ vt.feed(l + '\r\n')
+
+ # Launch the shell using the wrapper script to restore the environment.
+ bundle_path = activity.get_bundle_path()
+ os.chdir(bundle_path)
+ pid = vt.fork_command(bundle_path + '/shell.py', \
+ '--state', data_file,
+ '--tab', tab_idx)
+
+ # TODO: Figure out how to grab the environment from the PID when closing.
+ log.debug(pid)
+
+ def _launch_shell(self, vt, tab_state):
+ # Launch the default shell in the HOME directory.
+ if tab_state:
+ # TODO: Need to save cwd on exit.
+ #os.chdir(tab_data['cwd'])
+ os.chdir(os.environ["HOME"])
+
+ # Restore the scrollback buffer.
+ for l in tab_state['scrollback']:
+ vt.feed(l + '\r\n')
+
+ else:
+ os.chdir(os.environ["HOME"])
+
+ vt.fork_command()
+
+ def _close_tab(self, index):
+ self.notebook.remove_page(index)
+ if self.notebook.get_n_pages() == 0:
+ self.close()
+
+ def _tab_child_exited_cb(self, vt):
+ for i in range(self.notebook.get_n_pages()):
+ if self.notebook.get_nth_page(i).vt == vt:
+ self._close_tab(i)
+ return
+
+ def _tab_title_changed_cb(self, vt):
+ for i in range(self.notebook.get_n_pages()):
+ if self.notebook.get_nth_page(i).vt == vt:
+ label = self.notebook.get_nth_page(i).label
+ label.set_text(vt.get_window_title())
+ return
+
+ def _create_tab(self, tab_state):
+ vt = vte.Terminal()
+ vt.set_scroll_on_keystroke(True)
+ vt.connect("child-exited", self._tab_child_exited_cb)
+ vt.connect("window-title-changed", self._tab_title_changed_cb)
+ vt.show()
- self._vte = VTE()
- self._vte.set_scroll_on_keystroke(True)
- self._vte.connect("child-exited", lambda term: self.close())
- self._vte.show()
+ label = gtk.Label()
- scrollbar = gtk.VScrollbar(self._vte.get_adjustment())
+ scrollbar = gtk.VScrollbar(vt.get_adjustment())
scrollbar.show()
- box.pack_start(self._vte)
- box.pack_start(scrollbar, False, False, 0)
-
- self.set_canvas(box)
- box.show()
+ box = gtk.HBox()
+ box.pack_start(vt)
+ box.pack_start(scrollbar)
+
+ box.vt = vt
+ box.label = label
- self._vte.grab_focus()
+ index = self.notebook.append_page(box, label)
+ self.notebook.show_all()
+ self.notebook.props.show_tabs = self.notebook.get_n_pages() > 1
+
+ self._launch_shell(vt, tab_state)
+
+ self.notebook.props.page = index
+ vt.grab_focus()
+
+ return index
def _copy_cb(self, button):
- if self._vte.get_has_selection():
- self._vte.copy_clipboard()
+ vt = self.notebook.get_nth_page(self.notebook.get_current_page()).vt
+ if vt.get_has_selection():
+ vt.copy_clipboard()
def _paste_cb(self, button):
- self._vte.paste_clipboard()
+ vt = self.notebook.get_nth_page(self.notebook.get_current_page()).vt
+ vt.paste_clipboard()
def _become_root_cb(self, button):
- self._vte.fork_command("/bin/su", ('/bin/su', '-'))
+ vt = self.notebook.get_nth_page(self.notebook.get_current_page()).vt
+ vt.feed('\r\n')
+ vt.fork_command("/bin/su", ('/bin/su', '-'))
- def __key_press_cb(self, window, event):
- if event.state & gtk.gdk.CONTROL_MASK and event.state & gtk.gdk.SHIFT_MASK:
-
- if gtk.gdk.keyval_name(event.keyval) == "C":
- if self._vte.get_has_selection():
- self._vte.copy_clipboard()
- return True
- elif gtk.gdk.keyval_name(event.keyval) == "V":
- self._vte.paste_clipboard()
- return True
+ def _fullscreen_cb(self, btn):
+ self.fullscreen()
+
+ def _key_press_cb(self, window, event):
+ if gtk.gdk.keyval_name(event.keyval) == 'Escape':
+ return True
return False
-class VTE(vte.Terminal):
- def __init__(self):
- vte.Terminal.__init__(self)
- self._configure_vte()
- self.drag_dest_set(gtk.DEST_DEFAULT_MOTION|
- gtk.DEST_DEFAULT_DROP,
- [('text/plain', 0, 0),
- ('STRING', 0, 1)],
- gtk.gdk.ACTION_DEFAULT|
- gtk.gdk.ACTION_COPY)
- self.connect('drag_data_received', self.data_cb)
-
- os.chdir(os.environ["HOME"])
- self.fork_command()
-
- def data_cb(self, widget, context, x, y, selection, target, time):
- self.feed_child(selection.data)
- context.finish(True, False, time)
- return True
-
- def _configure_vte(self):
- conf = ConfigParser.ConfigParser()
- conf_file = os.path.join(env.get_profile_path(), 'terminalrc')
-
- if os.path.isfile(conf_file):
- f = open(conf_file, 'r')
- conf.readfp(f)
- f.close()
- else:
- conf.add_section('terminal')
+ def read_file(self, file_path):
+ if self.metadata['mime_type'] != 'text/plain':
+ return
- if conf.has_option('terminal', 'font'):
- font = conf.get('terminal', 'font')
- else:
- font = 'Monospace 8'
- conf.set('terminal', 'font', font)
- self.set_font(pango.FontDescription(font))
+ fd = open(file_path, 'r')
+ text = fd.read()
+ data = simplejson.loads(text)
+ fd.close()
- if conf.has_option('terminal', 'fg_color'):
- fg_color = conf.get('terminal', 'fg_color')
- else:
- fg_color = '#000000'
- conf.set('terminal', 'fg_color', fg_color)
- if conf.has_option('terminal', 'bg_color'):
- bg_color = conf.get('terminal', 'bg_color')
- else:
- bg_color = '#FFFFFF'
- conf.set('terminal', 'bg_color', bg_color)
- self.set_colors(gtk.gdk.color_parse (fg_color),
- gtk.gdk.color_parse (bg_color),
- [])
-
- if conf.has_option('terminal', 'cursor_blink'):
- blink = conf.getboolean('terminal', 'cursor_blink')
- else:
- blink = False
- conf.set('terminal', 'cursor_blink', blink)
-
- self.set_cursor_blinks(blink)
+ data_file = file_path
- if conf.has_option('terminal', 'bell'):
- bell = conf.getboolean('terminal', 'bell')
- else:
- bell = False
- conf.set('terminal', 'bell', bell)
- self.set_audible_bell(bell)
-
- if conf.has_option('terminal', 'scrollback_lines'):
- scrollback_lines = conf.getint('terminal', 'scrollback_lines')
- else:
- scrollback_lines = 1000
- conf.set('terminal', 'scrollback_lines', scrollback_lines)
-
- self.set_scrollback_lines(scrollback_lines)
- self.set_allow_bold(True)
-
- if conf.has_option('terminal', 'scroll_on_keystroke'):
- scroll_key = conf.getboolean('terminal', 'scroll_on_keystroke')
- else:
- scroll_key = False
- conf.set('terminal', 'scroll_on_keystroke', scroll_key)
- self.set_scroll_on_keystroke(scroll_key)
+ while self.notebook.get_n_pages():
+ self.notebook.remove_page(0)
- if conf.has_option('terminal', 'scroll_on_output'):
- scroll_output = conf.getboolean('terminal', 'scroll_on_output')
- else:
- scroll_output = False
- conf.set('terminal', 'scroll_on_output', scroll_output)
- self.set_scroll_on_output(scroll_output)
-
- if conf.has_option('terminal', 'emulation'):
- emulation = conf.get('terminal', 'emulation')
- else:
- emulation = 'xterm'
- conf.set('terminal', 'emulation', emulation)
- self.set_emulation(emulation)
+ for tab in data['tabs']:
+ index = self._create_tab(tab)
- if conf.has_option('terminal', 'visible_bell'):
- visible_bell = conf.getboolean('terminal', 'visible_bell')
- else:
- visible_bell = False
- conf.set('terminal', 'visible_bell', visible_bell)
- self.set_visible_bell(visible_bell)
- conf.write(open(conf_file, 'w'))
+ self.notebook.props.page = data['current-tab']
- def on_gconf_notification(self, client, cnxn_id, entry, what):
- self.reconfigure_vte()
+ def write_file(self, file_path):
+ if not self.metadata['mime_type']:
+ self.metadata['mime_type'] = 'text/plain'
- def on_vte_button_press(self, term, event):
- if event.button == 3:
- self.do_popup(event)
- return True
+ data = {}
+ data['current-tab'] = self.notebook.get_current_page()
+ data['tabs'] = []
+
+ for i in range(self.notebook.get_n_pages()):
+ page = self.notebook.get_nth_page(i)
+
+ def selected_cb(terminal, c, row, cb_data):
+ return 1
+ (scrollback_text, attrs) = page.vt.get_text(selected_cb, 1)
+
+ scrollback_lines = scrollback_text.split('\n')
+
+ tab = { 'env': {}, 'cwd': '', 'scrollback': scrollback_lines }
+ data['tabs'].append(tab)
+
+ fd = open(file_path, 'w')
+ text = simplejson.dumps(data)
+ fd.write(text)
+ fd.close()
- def on_vte_popup_menu(self, term):
- pass