Refactored as a class, fixed bugs where -l and -p behaviour would ignore ssh configs.

This commit is contained in:
Graeme Humphries 2012-03-15 11:21:45 -07:00
parent b7ca428dfb
commit 9a27fa34f3

151
crussh.py
View File

@ -26,107 +26,132 @@ except:
parser = argparse.ArgumentParser(description="Connect to multiple servers in parallel.") parser = argparse.ArgumentParser(description="Connect to multiple servers in parallel.")
parser.add_argument('hosts', metavar='HOST', nargs='+', parser.add_argument('hosts', metavar='HOST', nargs='+',
help="Host(s) to connect to.") help="Host(s) to connect to.")
parser.add_argument('-l', '--login', dest='login', default=getpass.getuser(), parser.add_argument('-l', '--login', dest='login', default=None,
help="Login name to use.") help="Login name to use.")
parser.add_argument('-p', '--port', dest='port', type=int, default=22, parser.add_argument('-p', '--port', dest='port', type=int, default=None,
help="Alternate SSH port to use.") help="Alternate SSH port to use.")
parser.add_argument('-s', '--fontsize', dest='fontsize', type=int, default=10, parser.add_argument('-s', '--fontsize', dest='fontsize', type=int, default=10,
help="Font size to use. (default=10)") help="Font size to use. (default=10)")
args = parser.parse_args() args = parser.parse_args()
### Global Vars ### ### CruSSH! ###
class CruSSH:
### State Vars ###
Terminals = {} Terminals = {}
TermMinWidth = 1 TermMinWidth = 1
TermMinHeight = 1 TermMinHeight = 1
### Configure Terminals ### ### GUI Objects ###
for host in args.hosts: MainWin = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
terminal = vte.Terminal() ScrollWin = gtk.ScrolledWindow()
terminal.set_size(80, 24) LayoutTable = gtk.Table()
terminal.set_font_from_string("Ubuntu Mono Bold " + str(args.fontsize)) EntryBox = gtk.Entry()
TermMinWidth = (terminal.get_char_width() * 80) + terminal.get_padding()[0]
TermMinHeight = (terminal.get_char_height() * 24) + terminal.get_padding()[1]
# TODO: disable only this terminal widget on child exit
# v.connect("child-exited", lambda term: gtk.main_quit())
cmd_str = "/usr/bin/ssh -l " + args.login + " -p " + str(args.port) + " " + host
cmd = cmd_str.split(' ')
terminal.fork_command(command=cmd[0], argv=cmd)
Terminals[host] = terminal
### Utility Functions and Vars ### ### Methods ###
def reflowTable(cols=1, rows=1): def reflowTable(self, cols=1, rows=1):
# empty table and re-size # empty table and re-size
for host in Terminals: hosts = sorted(self.Terminals.keys(), reverse=True)
Table.remove(Terminals[host]) for host in hosts:
Table.resize(rows, cols) self.LayoutTable.remove(self.Terminals[host])
self.LayoutTable.resize(rows, cols)
# layout terminals # layout terminals
hosts = sorted(args.hosts, reverse=True)
for row in range(rows): for row in range(rows):
for col in range(cols): for col in range(cols):
if len(hosts) > 0: if len(hosts) > 0:
host = hosts.pop() host = hosts.pop()
Table.attach(Terminals[host], col, col+1, row, row+1) self.LayoutTable.attach(self.Terminals[host], col, col+1, row, row+1)
Terminals[host].set_size(80, 24) self.Terminals[host].set_size(80, 24)
def reflow(): def reflow(self):
size = MainWin.allocation num_terms = len(self.Terminals)
cols = int(math.floor((size.width + Table.props.column_spacing) / TermMinWidth)) if num_terms < 1:
if cols < 1 or len(args.hosts) == 1: gtk.main_quit()
size = self.MainWin.allocation
cols = int(math.floor((size.width + self.LayoutTable.props.column_spacing) / self.TermMinWidth))
if cols < 1 or num_terms == 1:
cols = 1 cols = 1
rows = int(math.ceil(len(Terminals)/cols)) rows = int(math.ceil(num_terms/cols))
if rows < 1: if rows < 1:
rows = 1 rows = 1
if (Table.props.n_columns != cols) or (Table.props.n_rows != rows): if (self.LayoutTable.props.n_columns != cols) or (self.LayoutTable.props.n_rows != rows):
reflowTable(cols, rows) self.reflowTable(cols, rows)
MainWin.show_all() self.MainWin.show_all()
### Setup GTK Interface ### def removeTerminal(self, terminal):
MainWin = gtk.Window(type=gtk.WINDOW_TOPLEVEL) # TODO: make this work. ;)
MainWin.set_title("crussh: " + ' '.join(args.hosts)) # del self.Terminals[terminal.props.window_title]
MainWin.set_role(role="crussh_main_win") self.reflow()
MainWin.connect("delete-event", lambda window, event: gtk.main_quit())
ScrollWin = gtk.ScrolledWindow() def initGUI(self):
ScrollWin.props.hscrollbar_policy = gtk.POLICY_NEVER self.MainWin.set_title("crussh: " + ' '.join(self.Terminals.keys()))
ScrollWin.props.vscrollbar_policy = gtk.POLICY_ALWAYS self.MainWin.set_role(role="crussh_main_win")
ScrollWin.props.shadow_type = gtk.SHADOW_ETCHED_IN self.MainWin.connect("delete-event", lambda window, event: gtk.main_quit())
Table = gtk.Table() self.ScrollWin.props.hscrollbar_policy = gtk.POLICY_NEVER
Table.set_homogeneous(True) self.ScrollWin.props.vscrollbar_policy = gtk.POLICY_ALWAYS
Table.set_row_spacings(1) self.ScrollWin.props.shadow_type = gtk.SHADOW_ETCHED_IN
Table.set_col_spacings(1)
ScrollWin.add_with_viewport(Table) self.LayoutTable.set_homogeneous(True)
ScrollWin.set_size_request(TermMinWidth, TermMinHeight) self.LayoutTable.set_row_spacings(1)
self.LayoutTable.set_col_spacings(1)
self.ScrollWin.add_with_viewport(self.LayoutTable)
self.ScrollWin.set_size_request(self.TermMinWidth, self.TermMinHeight)
EntryBox = gtk.Entry()
# don't display chars while typing. # don't display chars while typing.
EntryBox.set_visibility(False) self.EntryBox.set_visibility(False)
EntryBox.set_invisible_char(' ') self.EntryBox.set_invisible_char(' ')
# forward key events to all terminals
def feed_input(widget, event): def feed_input(widget, event):
if event.type in [gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE]: if event.type in [gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE]:
# erase buffer on every entry, so that passwords aren't revealed # erase buffer on every entry, so that passwords aren't revealed
EntryBox.props.buffer.delete_text(0, -1) self.EntryBox.props.buffer.delete_text(0, -1)
# propagate to every terminal # propagate to every terminal
for host in Terminals: for host in self.Terminals:
t_event = event.copy() t_event = event.copy()
Terminals[host].event(t_event) self.Terminals[host].event(t_event)
# this stops regular handler from firing, switching focus. # this stops regular handler from firing, switching focus.
return True return True
EntryBox.connect("key_press_event", feed_input) self.EntryBox.connect("key_press_event", feed_input)
EntryBox.connect("key_release_event", feed_input) self.EntryBox.connect("key_release_event", feed_input)
VBox = gtk.VBox() VBox = gtk.VBox()
VBox.pack_start(ScrollWin) VBox.pack_start(self.ScrollWin)
VBox.pack_start(EntryBox, False, False) VBox.pack_start(self.EntryBox, False, False)
MainWin.add(VBox) self.MainWin.add(VBox)
# reflow layout on size change. # reflow layout on size change.
def resize_event_handler(widget, allocation): self.MainWin.connect("size-allocate", lambda widget, allocation: self.reflow())
reflow()
MainWin.connect("size-allocate", lambda widget, allocation: reflow()) # give EntryBox default focus on init
self.EntryBox.props.has_focus = True
def __init__(self, hosts):
# init all terminals
for host in hosts:
terminal = vte.Terminal()
terminal.set_size(80, 24)
terminal.set_font_from_string("Ubuntu Mono Bold " + str(args.fontsize))
self.TermMinWidth = (terminal.get_char_width() * 80) + terminal.get_padding()[0]
self.TermMinHeight = (terminal.get_char_height() * 24) + terminal.get_padding()[1]
# TODO: disable only this terminal widget on child exit
# v.connect("child-exited", lambda term: gtk.main_quit())
cmd_str = "/usr/bin/ssh"
if args.login != None:
cmd_str += " -l " + args.login
if args.port != None:
cmd_str += " -p " + str(args.port)
cmd_str += " " + host
cmd = cmd_str.split(' ')
terminal.fork_command(command=cmd[0], argv=cmd)
self.Terminals[host] = terminal
# hook terminals so they reflow layout on exit
self.Terminals[host].connect("child-exited", self.removeTerminal)
self.initGUI()
self.reflow()
### Start Execution ### ### Start Execution ###
reflowTable() crussh = CruSSH(args.hosts)
reflow()
gtk.main() gtk.main()