Ejemplo n.º 1
0
 def build_notebook(self):
     self._notebook = BrowserNotebook()
     self._notebook.connect("tab-added", self.__open_tab_cb)
     self._notebook.set_property("tab-pos", Gtk.PositionType.TOP)
     self._notebook.set_scrollable(True)
     self._notebook.show()
     self.set_canvas(self._notebook)
     self._create_tab(None)
    def __init__(self):
        BrowserNotebook.__init__(self)

        self.props.show_border = False
        self.props.scrollable = True

        self.connect('size-allocate', self.__size_allocate_cb)
        self.connect('page-added', self.__page_added_cb)
        self.connect('page-removed', self.__page_removed_cb)

        self.add_tab()
        self._update_closing_buttons()
        self._update_tab_sizes()
    def __init__(self):
        BrowserNotebook.__init__(self)

        self.props.show_border = False
        self.props.scrollable = True

        self.connect('size-allocate', self.__size_allocate_cb)
        self.connect('page-added', self.__page_added_cb)
        self.connect('page-removed', self.__page_removed_cb)

        self.add_tab()
        self._update_closing_buttons()
        self._update_tab_sizes()
    def __init__(self, activity):
        self._activity = activity
        BrowserNotebook.__init__(self)
        self.props.show_border = False
        self.props.scrollable = True

        # Used to connect and disconnect functions when 'switch-page'
        self._browser = None
        self._load_status_changed_hid = None

        self.connect('size-allocate', self.__size_allocate_cb)
        self.connect('page-added', self.__page_added_cb)
        self.connect('page-removed', self.__page_removed_cb)

        self.connect_after('switch-page', self.__switch_page_cb)

        self.add_tab()
        self._update_closing_buttons()
        self._update_tab_sizes()
Ejemplo n.º 5
0
    def __init__(self):
        BrowserNotebook.__init__(self)

        self.props.show_border = False
        self.props.scrollable = True

        # Used to connect and disconnect functions when 'switch-page'
        self._browser = None
        self._load_status_changed_hid = None

        self.connect('size-allocate', self.__size_allocate_cb)
        self.connect('page-added', self.__page_added_cb)
        self.connect('page-removed', self.__page_removed_cb)

        self.connect_after('switch-page', self.__switch_page_cb)

        self.add_tab()
        self._update_closing_buttons()
        self._update_tab_sizes()
Ejemplo n.º 6
0
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)

        # HACK to avoid Escape key disable fullscreen mode on Terminal Activity
        # This is related with http://bugs.sugarlabs.org/ticket/440
        self.disconnect_by_func(self._Window__key_press_cb)
        self.connect('key-press-event', self.__key_press_cb)

        self.max_participants = 1

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        edit_toolbar = self._create_edit_toolbar()
        edit_toolbar_button = ToolbarButton(
                page=edit_toolbar,
                icon_name='toolbar-edit')
        edit_toolbar.show()
        toolbar_box.toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        view_toolbar = self._create_view_toolbar()
        view_toolbar_button = ToolbarButton(
                page=view_toolbar,
                icon_name='toolbar-view')
        view_toolbar.show()
        toolbar_box.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

        self._delete_tab_toolbar = None
        self._previous_tab_toolbar = None
        self._next_tab_toolbar = None

        helpbutton = self._create_help_button()
        toolbar_box.toolbar.insert(helpbutton, -1)
        helpbutton.show_all()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl><Shift>Q'
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._notebook = BrowserNotebook()
        self._notebook.connect("tab-added", self.__open_tab_cb)
        self._notebook.set_property("tab-pos", Gtk.PositionType.TOP)
        self._notebook.set_scrollable(True)
        self._notebook.show()

        self.set_canvas(self._notebook)

        self._create_tab(None)
Ejemplo n.º 7
0
class TerminalActivity(activity.Activity):

    def __init__(self, handle):
        activity.Activity.__init__(self, handle)

        # HACK to avoid Escape key disable fullscreen mode on Terminal Activity
        # This is related with http://bugs.sugarlabs.org/ticket/440
        self.disconnect_by_func(self._Window__key_press_cb)
        self.connect('key-press-event', self.__key_press_cb)

        self.max_participants = 1

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        edit_toolbar = self._create_edit_toolbar()
        edit_toolbar_button = ToolbarButton(
                page=edit_toolbar,
                icon_name='toolbar-edit')
        edit_toolbar.show()
        toolbar_box.toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        view_toolbar = self._create_view_toolbar()
        view_toolbar_button = ToolbarButton(
                page=view_toolbar,
                icon_name='toolbar-view')
        view_toolbar.show()
        toolbar_box.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

        self._delete_tab_toolbar = None
        self._previous_tab_toolbar = None
        self._next_tab_toolbar = None

        helpbutton = self._create_help_button()
        toolbar_box.toolbar.insert(helpbutton, -1)
        helpbutton.show_all()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl><Shift>Q'
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._notebook = BrowserNotebook()
        self._notebook.connect("tab-added", self.__open_tab_cb)
        self._notebook.set_property("tab-pos", Gtk.PositionType.TOP)
        self._notebook.set_scrollable(True)
        self._notebook.show()

        self.set_canvas(self._notebook)

        self._create_tab(None)

    def _create_edit_toolbar(self):
        edit_toolbar = EditToolbar()
        edit_toolbar.undo.props.visible = False
        edit_toolbar.redo.props.visible = False
        edit_toolbar.separator.props.visible = False
        edit_toolbar.copy.connect('clicked', self.__copy_cb)
        edit_toolbar.copy.props.accelerator = '<Ctrl><Shift>C'
        edit_toolbar.paste.connect('clicked', self.__paste_cb)
        edit_toolbar.paste.props.accelerator = '<Ctrl><Shift>V'
        return edit_toolbar

    def __copy_cb(self, button):
        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):
        vt = self._notebook.get_nth_page(self._notebook.get_current_page()).vt
        vt.paste_clipboard()

    def _create_view_toolbar(self):  # Zoom toolbar
        view_toolbar = Gtk.Toolbar()

        zoom_out_button = ToolButton('zoom-out')
        zoom_out_button.set_tooltip(_('Zoom out'))
        zoom_out_button.props.accelerator = '<Ctrl>minus'
        zoom_out_button.connect('clicked', self.__zoom_out_cb)
        view_toolbar.insert(zoom_out_button, -1)
        zoom_out_button.show()

        zoom_in_button = ToolButton('zoom-in')
        zoom_in_button.set_tooltip(_('Zoom in'))
        zoom_in_button.props.accelerator = '<Ctrl>plus'
        zoom_in_button.connect('clicked', self.__zoom_in_cb)
        view_toolbar.insert(zoom_in_button, -1)
        zoom_in_button.show()

        fullscreen_button = ToolButton('view-fullscreen')
        fullscreen_button.set_tooltip(_("Fullscreen"))
        fullscreen_button.props.accelerator = '<Alt>Return'
        fullscreen_button.connect('clicked', self.__fullscreen_cb)
        view_toolbar.insert(fullscreen_button, -1)
        fullscreen_button.show()
        return view_toolbar

    def _zoom(self, step):
        current_page = self._notebook.get_current_page()
        vt = self._notebook.get_nth_page(current_page).vt
        font_desc = vt.get_font()
        font_desc.set_size(font_desc.get_size() + step)
        vt.set_font(font_desc)

    def __zoom_out_cb(self, button):
        self._zoom(ZOOM_STEP * -1)

    def __zoom_in_cb(self, button):
        self._zoom(ZOOM_STEP)

    def __fullscreen_cb(self, button):
        self.fullscreen()

    def _create_help_button(self):
        helpitem = HelpButton()

        helpitem.add_section(_('Useful commands'))
        helpitem.add_section(_('cd'))
        helpitem.add_paragraph(_('Change directory'))
        helpitem.add_paragraph(_('To use it, write: cd directory'))
        helpitem.add_paragraph(_(
    'If you call it without parameters, will change\nto the user directory'))
        helpitem.add_section(_('ls'))
        helpitem.add_paragraph(_('List the content of a directory.'))
        helpitem.add_paragraph(_('To use it, write: ls directory'))
        helpitem.add_paragraph(
    _('If you call it without parameters, will list the\nworking directory'))
        helpitem.add_section(_('cp'))
        helpitem.add_paragraph(_('Copy a file to a specific location'))
        helpitem.add_paragraph(_('Call it with the file and the new location'))
        helpitem.add_paragraph(_('Use: cp file directory/'))
        helpitem.add_section(_('rm'))
        helpitem.add_paragraph(_('Removes a file in any path'))
        helpitem.add_paragraph(_('Use: rm file'))
        helpitem.add_section(_('su'))
        helpitem.add_paragraph(_('Login as superuser (root)'))
        helpitem.add_paragraph(
            _('The root user is the administrator of the\nsystem'))
        helpitem.add_paragraph(
            _('You must be careful, because you can modify\nsystem files'))

        return helpitem

    def __open_tab_cb(self, btn):
        index = self._create_tab(None)
        self._notebook.page = index
        if self._notebook.get_n_pages() == 2:
            self._notebook.get_tab_label(self._notebook.get_nth_page(0
                                                    )).show_close_button()

    def __close_tab_cb(self, btn, child):
        index = self._notebook.page_num(child)
        self._close_tab(index)
        if self._notebook.get_n_pages() == 1:
            self._notebook.get_tab_label(self._notebook.get_nth_page(0
                                                    )).hide_close_button()

    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 _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 __drag_data_received_cb(self, widget, context, x, y, selection,
                                target, time):
        widget.feed_child(selection.get_text(), -1)
        context.finish(True, False, time)
        return True

    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.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
                         [Gtk.TargetEntry.new('text/plain', 0, 0),
                          Gtk.TargetEntry.new('STRING', 0, 1)],
                          Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
        vt.drag_dest_add_text_targets()
        vt.connect('drag_data_received', self.__drag_data_received_cb)

        self._configure_vt(vt)

        vt.show()

        scrollbar = Gtk.VScrollbar.new(vt.get_vadjustment())

        box = Gtk.HBox()
        box.pack_start(vt, True, True, 0)
        box.pack_start(scrollbar, False, True, 0)

        box.vt = vt
        box.show()

        tablabel = TabLabel(box)
        tablabel.connect('tab-close', self.__close_tab_cb)
        tablabel.update_size(200)
        box.label = tablabel

        index = self._notebook.append_page(box, tablabel)
        tablabel.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
        tablabel.hide_close_button() if self._notebook.get_n_pages() == 1\
                                     else None
        self._notebook.show_all()

        # Launch the default shell in the HOME directory.
        os.chdir(os.environ["HOME"])

        if tab_state:
            # 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)

            # Restore the working directory.
            if 'cwd' in tab_state and os.path.exists(tab_state['cwd']):
                try:
                    os.chdir(tab_state['cwd'])
                except:
                    # ACLs may deny access
                    sys.stdout.write("Could not chdir to " + tab_state['cwd'])

            # Restore the scrollback buffer.
            for l in tab_state['scrollback']:
                vt.feed(str(l) + '\r\n')

        sucess_, box.pid = vt.fork_command_full(Vte.PtyFlags.DEFAULT,
                                            os.environ["HOME"],
                                            ["/bin/bash"],
                                            [],
                                            GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                            None,
                                            None)
        self._notebook.props.page = index
        vt.grab_focus()

        return index

    def __motion_notify_cb(self, widget, event):
        self.emit('motion-notify-event', Gdk.Event(event))

    def __key_press_cb(self, window, event):
        """Route some keypresses directly to the vte and then drop them.

        This prevents Sugar from hijacking events that are useful in
        the vte.

        """

        def event_to_vt(event):
            current_page = self._notebook.get_current_page()
            vt = self._notebook.get_nth_page(current_page).vt
            vt.event(event)

        key_name = Gdk.keyval_name(event.keyval)

        # Escape is used in Sugar to cancel fullscreen mode.
        if key_name == 'Escape':
            event_to_vt(event)
            return True

        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if key_name in ['z', 'q']:
                event_to_vt(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 = json.loads(text)
        fd.close()

        # Clean out any existing tabs.
        while self._notebook.get_n_pages():
            self._notebook.remove_page(0)

        # Create new tabs from saved state.
        for tab_state in data['tabs']:
            self._create_tab(tab_state)

        # Restore active tab.
        self._notebook.props.page = data['current-tab']

        # Create a blank one if this state had no terminals.
        if self._notebook.get_n_pages() == 0:
            self._create_tab(None)

    def write_file(self, file_path):
        if not self.metadata['mime_type']:
            self.metadata['mime_type'] = 'text/plain'

        data = {}
        data['current-tab'] = self._notebook.get_current_page()
        data['tabs'] = []

        for i in range(self._notebook.get_n_pages()):

            def is_selected(vte, *args):
                return True

            page = self._notebook.get_nth_page(i)
            """
            try:
                # get_text is only available in latest vte #676999
                # and pygobject/gobject-introspection #690041
                text, attr_ = page.vt.get_text(is_selected, None)
            except AttributeError:
                text = ''
            """
            #TODO: vt.get_text continue crashing at random
            text = ''

            scrollback_lines = 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)

            tab_state = {'env': environment, 'cwd': cwd,
                         'scrollback': scrollback_lines}

            data['tabs'].append(tab_state)

        fd = open(file_path, 'w')
        text = json.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(Gdk.color_parse(fg_color),
                      Gdk.color_parse(bg_color), [])

        blink = self._get_conf(conf, 'cursor_blink', False)
        vt.set_cursor_blink_mode(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'))
Ejemplo n.º 8
0
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)

        # HACK to avoid Escape key disable fullscreen mode on Terminal Activity
        # This is related with http://bugs.sugarlabs.org/ticket/440
        self.disconnect_by_func(self._Window__key_press_cb)
        self.connect('key-press-event', self.__key_press_cb)

        self.max_participants = 1

        self._theme_state = "light"
        self._font_size = FONT_SIZE

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        edit_toolbar = self._create_edit_toolbar()
        edit_toolbar_button = ToolbarButton(page=edit_toolbar,
                                            icon_name='toolbar-edit')
        edit_toolbar.show()
        toolbar_box.toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        view_toolbar = self._create_view_toolbar()
        view_toolbar_button = ToolbarButton(page=view_toolbar,
                                            icon_name='toolbar-view')
        view_toolbar.show()
        toolbar_box.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

        self._delete_tab_toolbar = None
        self._previous_tab_toolbar = None
        self._next_tab_toolbar = None

        helpbutton = self._create_help_button()
        toolbar_box.toolbar.insert(helpbutton, -1)
        helpbutton.show_all()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl><Shift>Q'
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._notebook = BrowserNotebook()
        self._notebook.connect("tab-added", self.__open_tab_cb)
        self._notebook.set_property("tab-pos", Gtk.PositionType.TOP)
        self._notebook.set_scrollable(True)
        self._notebook.show()

        self.set_canvas(self._notebook)

        self._create_tab(None)
Ejemplo n.º 9
0
class TerminalActivity(activity.Activity):
    def __init__(self, handle):
        activity.Activity.__init__(self, handle)

        # HACK to avoid Escape key disable fullscreen mode on Terminal Activity
        # This is related with http://bugs.sugarlabs.org/ticket/440
        self.disconnect_by_func(self._Window__key_press_cb)
        self.connect('key-press-event', self.__key_press_cb)

        self.max_participants = 1

        self._theme_state = "light"
        self._font_size = FONT_SIZE

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        edit_toolbar = self._create_edit_toolbar()
        edit_toolbar_button = ToolbarButton(page=edit_toolbar,
                                            icon_name='toolbar-edit')
        edit_toolbar.show()
        toolbar_box.toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        view_toolbar = self._create_view_toolbar()
        view_toolbar_button = ToolbarButton(page=view_toolbar,
                                            icon_name='toolbar-view')
        view_toolbar.show()
        toolbar_box.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

        self._delete_tab_toolbar = None
        self._previous_tab_toolbar = None
        self._next_tab_toolbar = None

        helpbutton = self._create_help_button()
        toolbar_box.toolbar.insert(helpbutton, -1)
        helpbutton.show_all()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl><Shift>Q'
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._notebook = BrowserNotebook()
        self._notebook.connect("tab-added", self.__open_tab_cb)
        self._notebook.set_property("tab-pos", Gtk.PositionType.TOP)
        self._notebook.set_scrollable(True)
        self._notebook.show()

        self.set_canvas(self._notebook)

        self._create_tab(None)

    def fullscreen(self):
        self._notebook.set_show_tabs(False)
        activity.Activity.fullscreen(self)

    def unfullscreen(self):
        self._notebook.set_show_tabs(True)
        activity.Activity.unfullscreen(self)

    def _create_edit_toolbar(self):
        edit_toolbar = EditToolbar()
        edit_toolbar.undo.props.visible = False
        edit_toolbar.redo.props.visible = False
        edit_toolbar.separator.props.visible = False
        edit_toolbar.copy.connect('clicked', self.__copy_cb)
        edit_toolbar.copy.props.accelerator = '<Ctrl><Shift>C'
        edit_toolbar.paste.connect('clicked', self.__paste_cb)
        edit_toolbar.paste.props.accelerator = '<Ctrl><Shift>V'
        return edit_toolbar

    def __copy_cb(self, button):
        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):
        vt = self._notebook.get_nth_page(self._notebook.get_current_page()).vt
        vt.paste_clipboard()

    def _toggled_theme(self, button):
        if self._theme_state == "dark":
            self._theme_state = "light"
        elif self._theme_state == "light":
            self._theme_state = "dark"
        self._update_theme()

    def _update_theme(self):
        if self._theme_state == "light":
            self._theme_toggler.set_icon_name('dark-theme')
            self._theme_toggler.set_tooltip('Switch to Dark Theme')
        elif self._theme_state == "dark":
            self._theme_toggler.set_icon_name('light-theme')
            self._theme_toggler.set_tooltip('Switch to Light Theme')

        for i in range(self._notebook.get_n_pages()):
            vt = self._notebook.get_nth_page(i).vt
            self._configure_vt(vt)

    def _create_view_toolbar(self):  # Color changer and Zoom toolbar
        view_toolbar = Gtk.Toolbar()

        self._theme_toggler = ToolButton('dark-theme')
        self._theme_toggler.set_tooltip('Switch to Dark Theme')
        self._theme_toggler.props.accelerator = '<Ctrl><Shift>I'
        self._theme_toggler.connect('clicked', self._toggled_theme)
        view_toolbar.insert(self._theme_toggler, -1)
        self._theme_toggler.show()

        sep = Gtk.SeparatorToolItem()
        view_toolbar.insert(sep, -1)
        sep.show()

        zoom_out_button = ToolButton('zoom-out')
        zoom_out_button.set_tooltip(_('Zoom out'))
        zoom_out_button.props.accelerator = '<Ctrl>minus'
        zoom_out_button.connect('clicked', self.__zoom_out_cb)
        view_toolbar.insert(zoom_out_button, -1)
        zoom_out_button.show()

        zoom_in_button = ToolButton('zoom-in')
        zoom_in_button.set_tooltip(_('Zoom in'))
        zoom_in_button.props.accelerator = '<Ctrl>plus'
        zoom_in_button.connect('clicked', self.__zoom_in_cb)
        view_toolbar.insert(zoom_in_button, -1)
        zoom_in_button.show()

        fullscreen_button = ToolButton('view-fullscreen')
        fullscreen_button.set_tooltip(_("Fullscreen"))
        fullscreen_button.props.accelerator = '<Alt>Return'
        fullscreen_button.connect('clicked', self.__fullscreen_cb)
        view_toolbar.insert(fullscreen_button, -1)
        fullscreen_button.show()
        return view_toolbar

    def _zoom(self, step):
        current_page = self._notebook.get_current_page()
        vt = self._notebook.get_nth_page(current_page).vt
        font_desc = vt.get_font()
        font_desc.set_size(font_desc.get_size() + Pango.SCALE * step)
        vt.set_font(font_desc)

    def __zoom_out_cb(self, button):
        self._zoom(-1)

    def __zoom_in_cb(self, button):
        self._zoom(1)

    def __fullscreen_cb(self, button):
        self.fullscreen()

    def _create_help_button(self):
        helpitem = HelpButton()

        helpitem.add_section(_('Useful commands'))
        helpitem.add_section(_('cd'))
        helpitem.add_paragraph(_('Change directory'))
        helpitem.add_paragraph(_('To use it, write: cd directory'))
        helpitem.add_paragraph(
            _('If you call it without parameters, will change\n'
              'to the user directory'))
        helpitem.add_section(_('ls'))
        helpitem.add_paragraph(_('List the content of a directory.'))
        helpitem.add_paragraph(_('To use it, write: ls directory'))
        helpitem.add_paragraph(
            _('If you call it without parameters, will list the\n'
              'working directory'))
        helpitem.add_section(_('cp'))
        helpitem.add_paragraph(_('Copy a file to a specific location'))
        helpitem.add_paragraph(_('Call it with the file and the new location'))
        helpitem.add_paragraph(_('Use: cp file directory'))
        helpitem.add_section(_('rm'))
        helpitem.add_paragraph(_('Removes a file in any path'))
        helpitem.add_paragraph(_('Use: rm file'))
        helpitem.add_section(_('su'))
        helpitem.add_paragraph(_('Login as superuser (root)'))
        helpitem.add_paragraph(
            _('The root user is the administrator of the\nsystem'))
        helpitem.add_paragraph(
            _('You must be careful, because you can modify\nsystem files'))

        return helpitem

    def __open_tab_cb(self, btn):
        vt = self._notebook.get_nth_page(self._notebook.get_current_page()).vt
        font_desc = vt.get_font()
        self._font_size = font_desc.get_size() / Pango.SCALE

        index = self._create_tab(None)
        self._notebook.page = index

    def __close_tab_cb(self, btn, child):
        index = self._notebook.page_num(child)
        self._close_tab(index)

    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 _close_tab(self, index):
        self._notebook.remove_page(index)
        if self._notebook.get_n_pages() == 0:
            self.close()
        if self._notebook.get_n_pages() == 1:
            self._notebook.get_tab_label(
                self._notebook.get_nth_page(0)).hide_close_button()

    def __tab_child_exited_cb(self, vt, status=None):
        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 __drag_data_received_cb(self, widget, context, x, y, selection, target,
                                time):
        widget.feed_child(selection.get_text(), -1)
        context.finish(True, False, time)
        return True

    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)

        vt.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP, [
            Gtk.TargetEntry.new('text/plain', 0, 0),
            Gtk.TargetEntry.new('STRING', 0, 1)
        ], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
        vt.drag_dest_add_text_targets()
        vt.connect('drag_data_received', self.__drag_data_received_cb)

        self._configure_vt(vt)

        vt.show()

        scrollbar = Gtk.VScrollbar.new(vt.get_vadjustment())

        box = Gtk.HBox()
        box.pack_start(vt, True, True, 0)
        box.pack_start(scrollbar, False, True, 0)

        box.vt = vt
        box.show()

        tablabel = TabLabel(box)
        tablabel.connect('tab-close', self.__close_tab_cb)
        tablabel.update_size(200)
        box.label = tablabel

        index = self._notebook.append_page(box, tablabel)
        tablabel.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
        if self._notebook.get_n_pages() == 1:
            tablabel.hide_close_button()
        if self._notebook.get_n_pages() == 2:
            self._notebook.get_tab_label(
                self._notebook.get_nth_page(0)).show_close_button()
        self._notebook.show_all()

        # Launch the default shell in the HOME directory.
        os.chdir(os.environ["HOME"])

        if tab_state:
            # 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)

            # Restore the working directory.
            if 'cwd' in tab_state and os.path.exists(tab_state['cwd']):
                try:
                    os.chdir(tab_state['cwd'])
                except:
                    # ACLs may deny access
                    sys.stdout.write("Could not chdir to " + tab_state['cwd'])

            if 'font_size' in tab_state:
                font_desc = vt.get_font()
                font_desc.set_size(tab_state['font_size'])
                vt.set_font(font_desc)

            # Restore the scrollback buffer.
            for l in tab_state['scrollback']:
                vt.feed(str(l) + '\r\n')

        shell_cmd = os.environ.get('SHELL') or '/bin/bash'
        if hasattr(vt, 'fork_command_full'):
            sucess_, box.pid = vt.fork_command_full(
                Vte.PtyFlags.DEFAULT, os.environ["HOME"], [shell_cmd], [],
                GLib.SpawnFlags.DO_NOT_REAP_CHILD, None, None)
        else:
            sucess_, box.pid = vt.spawn_sync(Vte.PtyFlags.DEFAULT,
                                             os.environ["HOME"], [shell_cmd],
                                             [],
                                             GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                             None, None)

        self._notebook.props.page = index
        vt.grab_focus()

        return index

    def __key_press_cb(self, window, event):
        """Route some keypresses directly to the vte and then drop them.

        This prevents Sugar from hijacking events that are useful in
        the vte.

        """
        def event_to_vt(event):
            current_page = self._notebook.get_current_page()
            vt = self._notebook.get_nth_page(current_page).vt
            vt.event(event)

        key_name = Gdk.keyval_name(event.keyval)

        # Escape is used in Sugar to cancel fullscreen mode.
        if key_name == 'Escape':
            event_to_vt(event)
            return True

        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if key_name in ['z', 'q']:
                event_to_vt(event)
                return True
            elif key_name == 'Tab':
                current_index = self._notebook.get_current_page()
                if current_index == self._notebook.get_n_pages() - 1:
                    self._notebook.set_current_page(0)
                else:
                    self._notebook.set_current_page(current_index + 1)
                return True
            elif event.get_state() & Gdk.ModifierType.SHIFT_MASK:
                if key_name == 'ISO_Left_Tab':
                    current_index = self._notebook.get_current_page()
                    if current_index == 0:
                        self._notebook.set_current_page(
                            self._notebook.get_n_pages() - 1)
                    else:
                        self._notebook.set_current_page(current_index - 1)
                    return True
                elif key_name == 'T':
                    self._create_tab(None)
                    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 = json.loads(text)
        fd.close()

        # Clean out any existing tabs.
        while self._notebook.get_n_pages():
            self._notebook.remove_page(0)

        # Restore theme
        self._theme_state = data['theme']
        self._update_theme()

        # Create new tabs from saved state.
        for tab_state in data['tabs']:
            self._create_tab(tab_state)

        # Restore active tab.
        self._notebook.props.page = data['current-tab']

        # Create a blank one if this state had no terminals.
        if self._notebook.get_n_pages() == 0:
            self._create_tab(None)

    def write_file(self, file_path):
        if not self.metadata['mime_type']:
            self.metadata['mime_type'] = 'text/plain'

        data = {}
        data['current-tab'] = self._notebook.get_current_page()
        data['theme'] = self._theme_state
        data['tabs'] = []

        for i in range(self._notebook.get_n_pages()):

            def is_selected(vte, *args):
                return True

            page = self._notebook.get_nth_page(i)

            text = ''
            if VTE_VERSION >= 38:
                # in older versions of vte, get_text() makes crash
                # the activity at random - SL #4627
                try:
                    # get_text is only available in latest vte #676999
                    # and pygobject/gobject-introspection #690041
                    text, attr_ = page.vt.get_text(is_selected, None)
                except AttributeError:
                    pass

            scrollback_lines = text.split('\n')

            environ_file = '/proc/%d/environ' % page.pid
            if os.path.isfile(environ_file):
                # Note- this currently gets the child's initial environment
                # rather than the current environment,
                # making it not very useful.
                environment = open(environ_file, 'r').read().split('\0')

                cwd = os.readlink('/proc/%d/cwd' % page.pid)
            else:
                # terminal killed by the user
                environment = []
                cwd = '~'

            font_desc = page.vt.get_font()

            tab_state = {
                'env': environment,
                'cwd': cwd,
                'font_size': font_desc.get_size(),
                'scrollback': scrollback_lines
            }

            data['tabs'].append(tab_state)

        fd = open(file_path, 'w')
        text = json.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_desc = vt.get_font()
        if font_desc is None:
            font_size = self._font_size * Pango.SCALE
        else:
            font_size = font_desc.get_size()
        font = self._get_conf(conf, 'font', 'Monospace')
        font_desc = Pango.FontDescription(font)
        font_desc.set_size(font_size)
        vt.set_font(font_desc)

        self._theme_colors = {
            "light": {
                'fg_color': '#000000',
                'bg_color': '#FFFFFF'
            },
            "dark": {
                'fg_color': '#FFFFFF',
                'bg_color': '#000000'
            }
        }
        fg_color = self._theme_colors[self._theme_state]['fg_color']
        bg_color = self._theme_colors[self._theme_state]['bg_color']
        try:
            vt.set_colors(Gdk.color_parse(fg_color), Gdk.color_parse(bg_color),
                          [])
        except TypeError:
            # Vte 0.38 requires the colors set as a different type
            # in Fedora 21 we get a exception
            # TypeError: argument foreground: Expected Gdk.RGBA,
            # but got gi.overrides.Gdk.Color
            vt.set_colors(Gdk.RGBA(*Gdk.color_parse(fg_color).to_floats()),
                          Gdk.RGBA(*Gdk.color_parse(bg_color).to_floats()), [])

        blink = self._get_conf(conf, 'cursor_blink', False)
        vt.set_cursor_blink_mode(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)

        if hasattr(vt, 'set_emulation'):
            # set_emulation is not available after vte commit
            # 4e253be9282829f594c8a55ca08d1299e80e471d
            emulation = self._get_conf(conf, 'emulation', 'xterm')
            vt.set_emulation(emulation)

        if hasattr(vt, 'set_visible_bell'):
            visible_bell = self._get_conf(conf, 'visible_bell', False)
            vt.set_visible_bell(visible_bell)

        conf.write(open(conf_file, 'w'))
Ejemplo n.º 10
0
class TerminalActivity(activity.Activity):

    def __init__(self, handle):
        activity.Activity.__init__(self, handle)

        # HACK to avoid Escape key disable fullscreen mode on Terminal Activity
        # This is related with http://bugs.sugarlabs.org/ticket/440
        self.disconnect_by_func(self._Window__key_press_cb)
        self.connect('key-press-event', self.__key_press_cb)

        self.max_participants = 1

        self._theme_state = "light"
        self._font_size = FONT_SIZE

        toolbar_box = ToolbarBox()

        activity_button = ActivityToolbarButton(self)
        toolbar_box.toolbar.insert(activity_button, 0)
        activity_button.show()

        edit_toolbar = self._create_edit_toolbar()
        edit_toolbar_button = ToolbarButton(
            page=edit_toolbar,
            icon_name='toolbar-edit'
        )
        edit_toolbar.show()
        toolbar_box.toolbar.insert(edit_toolbar_button, -1)
        edit_toolbar_button.show()

        view_toolbar = self._create_view_toolbar()
        view_toolbar_button = ToolbarButton(
            page=view_toolbar,
            icon_name='toolbar-view')
        view_toolbar.show()
        toolbar_box.toolbar.insert(view_toolbar_button, -1)
        view_toolbar_button.show()

        self._delete_tab_toolbar = None
        self._previous_tab_toolbar = None
        self._next_tab_toolbar = None

        helpbutton = self._create_help_button()
        toolbar_box.toolbar.insert(helpbutton, -1)
        helpbutton.show_all()

        separator = Gtk.SeparatorToolItem()
        separator.props.draw = False
        separator.set_expand(True)
        toolbar_box.toolbar.insert(separator, -1)
        separator.show()

        stop_button = StopButton(self)
        stop_button.props.accelerator = '<Ctrl><Shift>Q'
        toolbar_box.toolbar.insert(stop_button, -1)
        stop_button.show()

        self.set_toolbar_box(toolbar_box)
        toolbar_box.show()

        self._notebook = BrowserNotebook()
        self._notebook.connect("tab-added", self.__open_tab_cb)
        self._notebook.set_property("tab-pos", Gtk.PositionType.TOP)
        self._notebook.set_scrollable(True)
        self._notebook.show()

        self.set_canvas(self._notebook)

        self._create_tab(None)

    def fullscreen(self):
        self._notebook.set_show_tabs(False)
        activity.Activity.fullscreen(self)

    def unfullscreen(self):
        self._notebook.set_show_tabs(True)
        activity.Activity.unfullscreen(self)

    def _create_edit_toolbar(self):
        edit_toolbar = EditToolbar()
        edit_toolbar.undo.props.visible = False
        edit_toolbar.redo.props.visible = False
        edit_toolbar.separator.props.visible = False
        edit_toolbar.copy.connect('clicked', self.__copy_cb)
        edit_toolbar.copy.props.accelerator = '<Ctrl><Shift>C'
        edit_toolbar.paste.connect('clicked', self.__paste_cb)
        edit_toolbar.paste.props.accelerator = '<Ctrl><Shift>V'

        clear = ToolButton('edit-clear')
        clear.set_tooltip(_('Clear scrollback'))
        clear.connect('clicked', self.__clear_cb)
        edit_toolbar.insert(clear, -1)
        clear.show()

        return edit_toolbar

    def __copy_cb(self, button):
        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):
        vt = self._notebook.get_nth_page(self._notebook.get_current_page()).vt
        vt.paste_clipboard()

    def _toggled_theme(self, button):
        if self._theme_state == "dark":
            self._theme_state = "light"
        elif self._theme_state == "light":
            self._theme_state = "dark"
        self._update_theme()

    def _update_theme(self):
        if self._theme_state == "light":
            self._theme_toggler.set_icon_name('dark-theme')
            self._theme_toggler.set_tooltip('Switch to Dark Theme')
        elif self._theme_state == "dark":
            self._theme_toggler.set_icon_name('light-theme')
            self._theme_toggler.set_tooltip('Switch to Light Theme')

        for i in range(self._notebook.get_n_pages()):
            vt = self._notebook.get_nth_page(i).vt
            self._configure_vt(vt)

    def _create_view_toolbar(self):  # Color changer and Zoom toolbar
        view_toolbar = Gtk.Toolbar()

        self._theme_toggler = ToolButton('dark-theme')
        self._theme_toggler.set_tooltip('Switch to Dark Theme')
        self._theme_toggler.props.accelerator = '<Ctrl><Shift>I'
        self._theme_toggler.connect('clicked', self._toggled_theme)
        view_toolbar.insert(self._theme_toggler, -1)
        self._theme_toggler.show()

        sep = Gtk.SeparatorToolItem()
        view_toolbar.insert(sep, -1)
        sep.show()

        zoom_out_button = ToolButton('zoom-out')
        zoom_out_button.set_tooltip(_('Zoom out'))
        zoom_out_button.props.accelerator = '<Ctrl>minus'
        zoom_out_button.connect('clicked', self.__zoom_out_cb)
        view_toolbar.insert(zoom_out_button, -1)
        zoom_out_button.show()

        zoom_in_button = ToolButton('zoom-in')
        zoom_in_button.set_tooltip(_('Zoom in'))
        zoom_in_button.props.accelerator = '<Ctrl>plus'
        zoom_in_button.connect('clicked', self.__zoom_in_cb)
        view_toolbar.insert(zoom_in_button, -1)
        zoom_in_button.show()

        fullscreen_button = ToolButton('view-fullscreen')
        fullscreen_button.set_tooltip(_("Fullscreen"))
        fullscreen_button.props.accelerator = '<Alt>Return'
        fullscreen_button.connect('clicked', self.__fullscreen_cb)
        view_toolbar.insert(fullscreen_button, -1)
        fullscreen_button.show()
        return view_toolbar

    def _zoom(self, step):
        current_page = self._notebook.get_current_page()
        vt = self._notebook.get_nth_page(current_page).vt
        font_desc = vt.get_font()
        font_desc.set_size(font_desc.get_size() + Pango.SCALE * step)
        vt.set_font(font_desc)

    def __zoom_out_cb(self, button):
        self._zoom(-1)

    def __zoom_in_cb(self, button):
        self._zoom(1)

    def __fullscreen_cb(self, button):
        self.fullscreen()

    def _create_help_button(self):
        helpitem = HelpButton()

        helpitem.add_section(_('Useful commands'))
        helpitem.add_section(_('cd'))
        helpitem.add_paragraph(_('Change directory'))
        helpitem.add_paragraph(_('To use it, write: cd directory'))
        helpitem.add_paragraph(
            _('If you call it without parameters, will change\n'
                'to the user directory'))
        helpitem.add_section(_('ls'))
        helpitem.add_paragraph(_('List the content of a directory.'))
        helpitem.add_paragraph(_('To use it, write: ls directory'))
        helpitem.add_paragraph(
            _('If you call it without parameters, will list the\n'
                'working directory'))
        helpitem.add_section(_('cp'))
        helpitem.add_paragraph(_('Copy a file to a specific location'))
        helpitem.add_paragraph(_('Call it with the file and the new location'))
        helpitem.add_paragraph(_('Use: cp file directory'))
        helpitem.add_section(_('rm'))
        helpitem.add_paragraph(_('Removes a file in any path'))
        helpitem.add_paragraph(_('Use: rm file'))
        helpitem.add_section(_('su'))
        helpitem.add_paragraph(_('Login as superuser (root)'))
        helpitem.add_paragraph(
            _('The root user is the administrator of the\nsystem'))
        helpitem.add_paragraph(
            _('You must be careful, because you can modify\nsystem files'))

        return helpitem

    def __open_tab_cb(self, btn):
        vt = self._notebook.get_nth_page(self._notebook.get_current_page()).vt
        font_desc = vt.get_font()
        self._font_size = font_desc.get_size() / Pango.SCALE

        index = self._create_tab(None)
        self._notebook.page = index

    def __close_tab_cb(self, btn, child):
        index = self._notebook.page_num(child)
        self._close_tab(index)

    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 _close_tab(self, index):
        self._notebook.remove_page(index)
        if self._notebook.get_n_pages() == 0:
            self.close()
        if self._notebook.get_n_pages() == 1:
            self._notebook.get_tab_label(
                self._notebook.get_nth_page(0)).hide_close_button()

    def __tab_child_exited_cb(self, vt, status=None):
        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 __drag_data_received_cb(self, widget, context, x, y, selection,
                                target, time):
        widget.feed_child(selection.get_text(), -1)
        context.finish(True, False, time)
        return True

    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)

        vt.drag_dest_set(Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
                         [Gtk.TargetEntry.new('text/plain', 0, 0),
                          Gtk.TargetEntry.new('STRING', 0, 1)],
                         Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
        vt.drag_dest_add_text_targets()
        vt.connect('drag_data_received', self.__drag_data_received_cb)

        self._configure_vt(vt)

        vt.show()

        scrollbar = Gtk.VScrollbar.new(vt.get_vadjustment())

        box = Gtk.HBox()
        box.pack_start(vt, True, True, 0)
        box.pack_start(scrollbar, False, True, 0)

        box.vt = vt
        box.show()

        tablabel = TabLabel(box)
        tablabel.connect('tab-close', self.__close_tab_cb)
        tablabel.update_size(200)
        box.label = tablabel

        index = self._notebook.append_page(box, tablabel)
        tablabel.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
        if self._notebook.get_n_pages() == 1:
            tablabel.hide_close_button()
        if self._notebook.get_n_pages() == 2:
            self._notebook.get_tab_label(
                self._notebook.get_nth_page(0)).show_close_button()
        self._notebook.show_all()

        # Launch the default shell in the HOME directory.
        os.chdir(os.environ["HOME"])

        if tab_state:
            # 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)

            # Restore the working directory.
            if 'cwd' in tab_state and os.path.exists(tab_state['cwd']):
                try:
                    os.chdir(tab_state['cwd'])
                except:
                    # ACLs may deny access
                    sys.stdout.write("Could not chdir to " + tab_state['cwd'])

            if 'font_size' in tab_state:
                font_desc = vt.get_font()
                font_desc.set_size(tab_state['font_size'])
                vt.set_font(font_desc)

            # Restore the scrollback buffer.
            for l in tab_state['scrollback']:
                vt.feed(str(l) + '\r\n')

        argv = [os.environ.get('SHELL') or '/bin/bash']
        envv = ['SUGAR_TERMINAL_VERSION=%s' %
                    os.environ['SUGAR_BUNDLE_VERSION']]

        saved = {}
        for name in ['SUGAR_BUNDLE_PATH', 'SUGAR_ACTIVITY_ROOT',
                         'SUGAR_BUNDLE_ID', 'SUGAR_BUNDLE_NAME',
                         'SUGAR_BUNDLE_VERSION']:
            if name in os.environ:
                saved[name] = os.environ[name]
                del os.environ[name]

        if hasattr(vt, 'fork_command_full'):
            _, box.pid = vt.fork_command_full(
                Vte.PtyFlags.DEFAULT, os.environ["HOME"],
                argv, envv, GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                None, None)
        else:
            _, box.pid = vt.spawn_sync(
                Vte.PtyFlags.DEFAULT, os.environ["HOME"],
                argv, envv, GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                None, None)

        for name in saved:
            os.environ[name] = saved[name]

        self._notebook.props.page = index
        vt.grab_focus()

        return index

    def __key_press_cb(self, window, event):
        """Route some keypresses directly to the vte and then drop them.

        This prevents Sugar from hijacking events that are useful in
        the vte.

        """

        def event_to_vt(event):
            current_page = self._notebook.get_current_page()
            vt = self._notebook.get_nth_page(current_page).vt
            vt.event(event)

        key_name = Gdk.keyval_name(event.keyval)

        # Escape is used in Sugar to cancel fullscreen mode.
        if key_name == 'Escape':
            event_to_vt(event)
            return True

        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
            if key_name in ['z', 'q']:
                event_to_vt(event)
                return True
            elif key_name == 'Tab':
                current_index = self._notebook.get_current_page()
                if current_index == self._notebook.get_n_pages() - 1:
                    self._notebook.set_current_page(0)
                else:
                    self._notebook.set_current_page(current_index + 1)
                return True
            elif event.get_state() & Gdk.ModifierType.SHIFT_MASK:
                if key_name == 'ISO_Left_Tab':
                    current_index = self._notebook.get_current_page()
                    if current_index == 0:
                        self._notebook.set_current_page(
                            self._notebook.get_n_pages() - 1)
                    else:
                        self._notebook.set_current_page(current_index - 1)
                    return True
                elif key_name == 'T':
                    self._create_tab(None)
                    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 = json.loads(text)
        fd.close()

        # Clean out any existing tabs.
        while self._notebook.get_n_pages():
            self._notebook.remove_page(0)

        # Restore theme
        self._theme_state = data['theme']
        self._update_theme()

        # Create new tabs from saved state.
        for tab_state in data['tabs']:
            self._create_tab(tab_state)

        # Restore active tab.
        self._notebook.props.page = data['current-tab']

        # Create a blank one if this state had no terminals.
        if self._notebook.get_n_pages() == 0:
            self._create_tab(None)

    def write_file(self, file_path):
        if not self.metadata['mime_type']:
            self.metadata['mime_type'] = 'text/plain'

        data = {}
        data['current-tab'] = self._notebook.get_current_page()
        data['theme'] = self._theme_state
        data['tabs'] = []

        for i in range(self._notebook.get_n_pages()):

            def is_selected(vte, *args):
                return True

            page = self._notebook.get_nth_page(i)

            text = ''
            if VTE_VERSION >= 38:
                # in older versions of vte, get_text() makes crash
                # the activity at random - SL #4627
                try:
                    # get_text is only available in latest vte #676999
                    # and pygobject/gobject-introspection #690041
                    text, attr_ = page.vt.get_text(is_selected, None)
                except AttributeError:
                    pass

            scrollback_lines = text.split('\n')

            environ_file = '/proc/%d/environ' % page.pid
            if os.path.isfile(environ_file):
                # Note- this currently gets the child's initial environment
                # rather than the current environment,
                # making it not very useful.
                environment = open(environ_file, 'r').read().split('\0')

                cwd = os.readlink('/proc/%d/cwd' % page.pid)
            else:
                # terminal killed by the user
                environment = []
                cwd = '~'

            font_desc = page.vt.get_font()

            tab_state = {'env': environment, 'cwd': cwd,
                         'font_size': font_desc.get_size(),
                         'scrollback': scrollback_lines}

            data['tabs'].append(tab_state)

        fd = open(file_path, 'w')
        text = json.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_desc = vt.get_font()
        if font_desc is None:
            font_size = self._font_size * Pango.SCALE
        else:
            font_size = font_desc.get_size()
        font = self._get_conf(conf, 'font', 'Monospace')
        font_desc = Pango.FontDescription(font)
        font_desc.set_size(font_size)
        vt.set_font(font_desc)

        self._theme_colors = {"light": {'fg_color': '#000000',
                                        'bg_color': '#FFFFFF'},
                              "dark": {'fg_color': '#FFFFFF',
                                       'bg_color': '#000000'}}
        fg_color = self._theme_colors[self._theme_state]['fg_color']
        bg_color = self._theme_colors[self._theme_state]['bg_color']
        try:
            vt.set_colors(Gdk.color_parse(fg_color),
                          Gdk.color_parse(bg_color), [])
        except TypeError:
            # Vte 0.38 requires the colors set as a different type
            # in Fedora 21 we get a exception
            # TypeError: argument foreground: Expected Gdk.RGBA,
            # but got gi.overrides.Gdk.Color
            vt.set_colors(Gdk.RGBA(*Gdk.color_parse(fg_color).to_floats()),
                          Gdk.RGBA(*Gdk.color_parse(bg_color).to_floats()), [])

        blink = self._get_conf(conf, 'cursor_blink', False)
        vt.set_cursor_blink_mode(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)

        if hasattr(vt, 'set_emulation'):
            # set_emulation is not available after vte commit
            # 4e253be9282829f594c8a55ca08d1299e80e471d
            emulation = self._get_conf(conf, 'emulation', 'xterm')
            vt.set_emulation(emulation)

        if hasattr(vt, 'set_visible_bell'):
            visible_bell = self._get_conf(conf, 'visible_bell', False)
            vt.set_visible_bell(visible_bell)

        conf.write(open(conf_file, 'w'))

    def __clear_cb(self, button):
        vt = self._notebook.get_nth_page(self._notebook.get_current_page()).vt
        n = vt.get_scrollback_lines()
        vt.set_scrollback_lines(0)
        vt.set_scrollback_lines(n)