diff options
Diffstat (limited to 'terminal.py')
-rw-r--r-- | terminal.py | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/terminal.py b/terminal.py new file mode 100644 index 0000000..8690a16 --- /dev/null +++ b/terminal.py @@ -0,0 +1,373 @@ +# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>. +# Copyright (C) 2008, One Laptop Per Child +# Copyright (C) 2009, Simon Schampijer +# +# 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sys +#import simplejson +import pickle +import ConfigParser +import shutil +import gobject +from exceptions import * + +import logging +from gettext import gettext as _ + +import gtk +import vte +import pango + +from sugar.graphics.toolbutton import ToolButton +from sugar.activity import activity +from sugar import env +#from help.help import Help + +# Attempt to import the new toolbar classes. If the import fails, +# fall back to the old toolbar style. +try: + from sugar.graphics.toolbarbox import ToolbarBox + from sugar.graphics.toolbarbox import ToolbarButton + from sugar.activity.widgets import ActivityToolbarButton + from sugar.activity.widgets import StopButton + NEW_TOOLBARS = True +except ImportError: + from sugar.activity.activity import ActivityToolbox + NEW_TOOLBARS = False + + +MASKED_ENVIRONMENT = [ + 'DBUS_SESSION_BUS_ADDRESS', + 'PPID'] + +logging.basicConfig() +_logger = logging.getLogger('vimtutor') +_logger.setLevel(logging.DEBUG) + +#A place where a student can make world writeable, hence multi-Activity +ICTDIR = '/home/olpc/.ictcore' + +class Error(EnvironmentError): + pass + +class TerminalBase: + + def __init__(self, vimtutor, handle): + self._vimtutor = vimtutor + self._notebook = gtk.Notebook() + self._notebook.set_property("tab-pos", gtk.POS_TOP) + self._notebook.set_scrollable(True) + self._notebook.show() + + #self._create_tab(None) + + def tab_child_exited_cb(self, vt): + #override in parent to provide action + pass + + def tab_title_changed_cb(self, vt): + pass + + def drag_data_received_cb(self, widget, context, x, y, selection, + target, time): + pass + + def _create_tab(self, tab_state): + vt = vte.Terminal() + vt.connect("child-exited", self.tab_child_exited_cb) + vt.connect("window-title-changed", self.tab_title_changed_cb) + + # FIXME have to resend motion events to parent, see #1402 + vt.connect('motion-notify-event', self.__motion_notify_cb) + + vt.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) + vt.connect('drag_data_received', self.drag_data_received_cb) + + self._configure_vt(vt) + + vt.show() + + label = gtk.Label() + + scrollbar = gtk.VScrollbar(vt.get_adjustment()) + scrollbar.show() + + box = gtk.HBox() + box.pack_start(vt) + box.pack_start(scrollbar) + + box.vt = vt + box.label = label + + index = self._notebook.append_page(box, label) + self._notebook.show_all() + + # Uncomment this to only show the tab bar when there is at least + # one tab. I think it's useful to always see it, since it displays + # the 'window title'. + # self._notebook.props.show_tabs = self._notebook.get_n_pages() > 1 + + # Launch the default shell in the HOME directory. + os.chdir(os.environ["HOME"]) + + session = file_path = '' + if not tab_state: + box.pid = vt.fork_command() + self._notebook.props.page = index + vt.grab_focus() + os.chdir(os.path.join(self._vimtutor.home,'vimtutor')) + return index + else: + # Restore the environment. + # This is currently not enabled. + environment = tab_state['env'] + + filtered_env = [] + for e in environment: + var, sep, value = e.partition('=') + if var not in MASKED_ENVIRONMENT: + filtered_env.append(var + sep + value) + + # TODO: Make the shell restore these environment variables, + # then clear out TERMINAL_ENV. + # os.environ['TERMINAL_ENV'] = '\n'.join(filtered_env) + + # startup vim if we have session info + session = tab_state.get('vim_session','') + file_path = tab_state.get('session_path','') + server = tab_state.get('server_name','') + if session and session in self.session_cwd: + cwd = self._vimtutor.session_cwd[session] + elif 'cwd' in tab_state: + cwd = tab_state['cwd'] + + # Restore the working directory. + if os.path.exists(cwd): + try: + os.chdir(cwd) + except: + # ACLs may deny access + sys.stdout.write("Could not chdir to " + cwd) + + # Restore the scrollback buffer. + for l in tab_state['scrollback']: + vt.feed(l + '\r\n') + + box.pid = vt.fork_command() + + if session and file_path: + fd = open(file_path, 'w') + fd.write(session) + fd.close() + if server and server[0] == 'G': + exe = 'gvim' + else: + exe = 'vim' + start_vim = os.path.join(activity.get_bundle_path(), + 'bin',self._vimtutor.vim_version_selector, exe) + \ + ' -S ' + file_path + '\n\r' + vt.feed_child(start_vim) + + self._notebook.props.page = index + vt.grab_focus() + + return index + + def __motion_notify_cb(self, widget, event): + self.canvas.parent.emit('motion-notify-event', event) + + def __key_press_cb(self, window, event): + # Escape keypresses are routed directly to the vte and then dropped. + # This hack prevents Sugar from hijacking them and canceling + # fullscreen mode. + if gtk.gdk.keyval_name(event.keyval) == 'Escape': + current_page = self._notebook.get_current_page() + vt = self._notebook.get_nth_page(current_page).vt + vt.event(event) + return True + + return False + + def read_file(self, file_path): + if self.metadata['mime_type'] != 'text/plain': + return + + fd = open(file_path, 'r') + text = fd.read() + data = pickle.loads(text) + fd.close() + + if data.has_key('tutor_count'): + if data['tutor_count'] > 0: + data['tutor_count'] -= 1 + self._vimtutor.tutor_count = data['tutor_count'] + _logger.debug('in read_file. tutor_count:%s sessions:%r'%( + self._vimtutor.tutor_count,data.get('sessions',''))) + + # Let user choose not to autostart tutor next time + if self._vimtutor.tutor_count == 0: + self._vimtutor.util.confirmation_alert(_('Click OK to disable tutorial auto-start?'), + _('Are you finished with VimTutor tutorial?'),self.disable_autotutor) + + # Clean out any existing tabs. + while self._notebook.get_n_pages(): + self._notebook.remove_page(0) + + # create tabs from saved state. + for tab_state in data['tabs']: + self._create_tab(tab_state) + + if self._vimtutor.tutor_count > -1: + #self._create_tab(None) + self.feed_terminal(0, self.tutor) + + # Restore active tab. + self._notebook.props.page = data['current-tab'] + + if self._notebook.get_n_pages() > 1: + self._delete_tab_button.props.sensitive = True + self._previous_tab_button.props.sensitive = True + self._next_tab_button.props.sensitive = True + + def disable_autotutor(self, alert, response): + self._vimtutor.tutor_count = -1 + + def write_file(self, file_path): + _logger.debug('entered write file') + if not self.metadata['mime_type']: + self.metadata['mime_type'] = 'text/plain' + + # if keep button, ojbect_id will be None + if not self._jobject.object_id: + self._vimtutor.tutor_count = 1 + self.metadata['title'] = 'VimTutor' + + data = {} + data['current-tab'] = self._notebook.get_current_page() + data['tabs'] = [] + data['sessions'] = [] + data['tutor_count'] = self._vimtutor.tutor_count + + 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 = page.vt.get_text(selected_cb, False) + + scrollback_lines = scrollback_text.split('\n') + + # Note- this currently gets the child's initial environment + # rather than the current environment, making it not very useful. + environment = open('/proc/%d/environ' % + page.pid, 'r').read().split('\0') + + cwd = os.readlink('/proc/%d/cwd' % page.pid) + + #=======vim additions======== + text = session_file = server = '' + #reserve the first tab for vimtutor + j = i + if self._vimtutor.tutor_count > -1: + j -= 1 + if j > -1 and j < len(self._vimtutor.session_list): + + server = self._vimtutor.session_list[j] + if server: + session_file = os.path.join(self.home, '.vim', 'sessions', server + '.vim') + try: + fd = open(session_file, 'r') + text = fd.read() + fd.close() + #os.unlink(session_file) + except: + pass + data['sessions'].append(server) + #======= end vim additions======== + + tab_state = {'env': environment, 'cwd': cwd, + 'scrollback': scrollback_lines, + 'vim_session':text, + 'session_path': session_file, + 'server_name' : server, + } + + data['tabs'].append(tab_state) + fd = open(file_path, 'w') + text = pickle.dumps(data) + fd.write(text) + fd.close() + + def _get_conf(self, conf, var, default): + if conf.has_option('terminal', var): + if isinstance(default, bool): + return conf.getboolean('terminal', var) + elif isinstance(default, int): + return conf.getint('terminal', var) + else: + return conf.get('terminal', var) + else: + conf.set('terminal', var, default) + + return default + + def _configure_vt(self, vt): + 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') + + font = self._get_conf(conf, 'font', 'Monospace') + vt.set_font(pango.FontDescription(font)) + + fg_color = self._get_conf(conf, 'fg_color', '#000000') + bg_color = self._get_conf(conf, 'bg_color', '#FFFFFF') + vt.set_colors(gtk.gdk.color_parse(fg_color), + gtk.gdk.color_parse(bg_color), []) + + blink = self._get_conf(conf, 'cursor_blink', False) + vt.set_cursor_blinks(blink) + + bell = self._get_conf(conf, 'bell', False) + vt.set_audible_bell(bell) + + scrollback_lines = self._get_conf(conf, 'scrollback_lines', 1000) + vt.set_scrollback_lines(scrollback_lines) + + vt.set_allow_bold(True) + + scroll_key = self._get_conf(conf, 'scroll_on_keystroke', True) + vt.set_scroll_on_keystroke(scroll_key) + + scroll_output = self._get_conf(conf, 'scroll_on_output', False) + vt.set_scroll_on_output(scroll_output) + + emulation = self._get_conf(conf, 'emulation', 'xterm') + vt.set_emulation(emulation) + + visible_bell = self._get_conf(conf, 'visible_bell', False) + vt.set_visible_bell(visible_bell) + + conf.write(open(conf_file, 'w')) |