silk /

nakamura b438c01 

nakamura 0856dff 
nakamura b438c01 

nakamura af3009f 
nakamura b438c01 

nakamura fc2c232 

nakamura b438c01 

nakamura 068b005 
nakamura b438c01 

nakamura 0856dff 

nakamura b438c01 

nakamura 25a0bcf 
nakamura b438c01 
nakamura 25a0bcf 

nakamura b438c01 

nakamura 25a0bcf 
nakamura 0c97531 

nakamura b438c01 
nakamura 0856dff 
nakamura af3009f 
nakamura b438c01 

nakamura 25a0bcf 

nakamura 0c97531 
nakamura 25a0bcf 
nakamura 0856dff 

nakamura af3009f 

nakamura 7ce0011 

nakamura b438c01 

nakamura f1301de 
nakamura b438c01 

nakamura 34aefe1 
nakamura b438c01 

nakamura 34aefe1 

nakamura f1301de 

nakamura b438c01 

nakamura 068b005 
nakamura b438c01 

nakamura 7ce0011 

nakamura b438c01 

nakamura fc2c232 

nakamura b438c01 
nakamura fc2c232 

nakamura b438c01 

nakamura 7ce0011 

nakamura b438c01 

nakamura 34aefe1 
nakamura b438c01 

silk - terminal emulator to control multiple commands at once

silk open multiple virtual terminals for each command
and send keyboard input for all/some/one terminal.

import gobject
import os

from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import Vte

class Terminal(Vte.Terminal):
    term_name = 'xterm'
    _size = (80, 25)

    def __init__(self, command, size=None, *args, **kwargs):
        super(Terminal, self).__init__(*args, **kwargs)

        if size is not None:
            self._size = size


        # TODO: right click context menu

    def width(self):
        # FIXME: how to get xpad?
        xpad = 5
        x, _ = self._size
        return self.get_char_width() * x + xpad

    def height(self):
        # FIXME: how to get ypad?
        ypad = 5
        _, y = self._size
        return self.get_char_height() * y + ypad

    def resize_terminal(self, size=None):
        if size is not None:
            self._size = size

        self.set_size_request(self.width, self.height)

    def child_exited(self):
        # if child exited, Terminal.get_pty_object() return None
        # XXX: realy?
        return self.get_pty_object() is None

    def run_command(self, command=None):
        if command is None:
            command = Vte.get_user_shell()

        # cwd
        working_directory = ''

        env = os.environ.copy()
        env['TERM'] = self.term_name
        envv = ['%s=%s' % kv for kv in env.iteritems()]

        if isinstance(command, (tuple, list)):
            argv = command
            argv = ['/bin/sh', '-c', command]

                               argv, envv, 0,
                               None, None)

class TerminalFrame(Gtk.VBox):
    def __init__(self, command, terminal_size=None):
        super(TerminalFrame, self).__init__()

        self._command = command

        self._hbox = Gtk.HBox()
        self.pack_start(self._hbox, False, False, 0)

        self._sync_check = Gtk.CheckButton()
        self._hbox.pack_start(self._sync_check, False, False, 0)

        self._eb = Gtk.EventBox()
        self._label = Gtk.Label(command)
        self._hbox.pack_start(self._eb, True, True, 0)

        self.terminal = Terminal(command, terminal_size)
        self.pack_end(self.terminal, True, True, 0)

        self._eb.connect('button-release-event', self.do_eb_button_release)
                                 lambda *_: self.terminal.grab_focus())

        self.terminal.connect('child-exited', self.do_terminal_child_exited)
        self.terminal.connect('key-press-event', self.do_terminal_key_press)

    def width(self):
        xpad = 2 # border_width * 2
        return self.terminal.width + self.get_border_width() + xpad

    def height(self):
        # FIXME: how to get ypad?
        ypad = 19
        return self.terminal.height + self.get_border_width() + ypad

    def is_synced(self):
        return self._sync_check.get_active()

    def do_eb_button_release(self, *args):
        self._sync_check.set_active(not self._sync_check.get_active())

    def do_terminal_child_exited(self, *args):
        msg = '\n[Press Enter to restart command]'
        self.terminal.feed(msg, len(msg))
        self.terminal.connect('commit', self.do_terminal_commit)

    def do_terminal_commit(self, term, text, length):
        if text in ('\n', '\r', '\r\n'):
            self.terminal.feed('\r\n', 2)

            # to avoid this input being feeded to this command,
            # call run_command after this callback
            gobject.timeout_add(0, self.terminal.run_command, self._command)

            return True

    def do_terminal_key_press(self, term, event):
        keyname = Gdk.keyval_name(event.keyval)

        if event.state == Gdk.ModifierType.CONTROL_MASK|Gdk.ModifierType.MOD1_MASK:
            # Ctrl + Alt + ...
            if keyname == 's':
                self._sync_check.set_active(not self._sync_check.get_active())
                return True

    def set_sync(self, is_synced):

class Config(object):
    def __init__(self, **kwargs):

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def get(self, *args, **kwargs):
        return self.__dict__.get(*args, **kwargs)

class MainWindow(Gtk.Window):
    config = Config(
        columns = 2,
        terminal_size = (80, 25),
        window_size = (800, 640),

    def __init__(self, config=None, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        if config is not None:


        self._scrolled = Gtk.ScrolledWindow()
        hadj = self._scrolled.get_hadjustment()
        vadj = self._scrolled.get_vadjustment()
        self._layout =, vadj)
        self._grid = Gtk.Grid()

        self._layout.put(self._grid, 0, 0)

        self.connect('destroy', lambda *_: Gtk.main_quit())

        # TODO: provide keyboard shortcut to move focus
        # TODO: highlights dffis of terminals
        # TODO: blur terminals that keyboard input will not send to
        # TODO: provide way to open new terminal

    def open(self, *targets):
        for t in targets:
            commands = self.config.get(t, t)
            if not isinstance(commands, (tuple, list)):
                commands = [commands]

            for c in commands:

    def terminals(self):
        return self._grid.get_children()

    def open_terminal(self, command=None):
        if command is None:
            command = Vte.get_user_shell()

        t = TerminalFrame(command, self.config.terminal_size)

    def add_terminal(self, term_frame):
        row, col = divmod(len(self.terminals), self.config.columns)
        self._grid.attach(term_frame, col, row, 1, 1)


                                           self.do_term_commit, term_frame)
                                           self.do_term_key_press, term_frame)


    def resize_layout(self):
        w = 0
        h = 0

        for row in (self.terminals[n:n+self.config.columns]
                    for n in xrange(0, len(self.terminals), self.config.columns)):
            w = max(w, sum(t.width for t in row))
            h += max(t.height for t in row)

        self._layout.set_size(w, h)
        self._grid.set_size_request(w, h)

    def do_term_commit(self, term_frame, text, length):
        if not term_frame.terminal.child_exited and \
           term_frame.terminal.is_focus() and term_frame.is_synced:
            for t in self.terminals:
                if t is not term_frame and \
                   not t.terminal.child_exited and t.is_synced:
                    t.terminal.feed_child(text, length)

    def do_term_key_press(self, term_frame, event):
        keyname = Gdk.keyval_name(event.keyval)

        # FIXME: wrap 80 chars
        if event.state == Gdk.ModifierType.CONTROL_MASK|Gdk.ModifierType.MOD1_MASK|Gdk.ModifierType.SHIFT_MASK:
            # Ctrl + Alt + Shift + ...
            if keyname == 'S':
                self.sync_all(not term_frame.is_synced)
                return True

    def sync_all(self, is_synced):
        for t in self.terminals:

# for develop/debug
def dump_props(widgets):
    print '------------------'
    print widgets
    for p in dir(widgets.props):
        if p.startswith('_'):
            print p, getattr(widgets.props, p)
    print '------------------'

if __name__ == '__main__':
    from optparse import OptionParser
    op = OptionParser(usage='USAGE: %prog [options] [command, command, ...]')

    op.add_option('-c', '--config', dest='config_file',
                  help='(default: ~/.silkrc)')

    opt, args = op.parse_args()

    user_config = {}
    if os.path.exists(opt.config_file):
        execfile(opt.config_file, user_config)

    w = MainWindow(config=user_config)

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.