From 24112ed67b6e1654d161816b4eb63a21121e6734 Mon Sep 17 00:00:00 2001 From: Graeme Humphries Date: Thu, 22 Mar 2012 16:41:39 -0700 Subject: [PATCH] Updated to conform to PEP-8, based on suggestions from SublimeLinter. --- crussh.py | 529 +++++++++++++++++++++++++++--------------------------- 1 file changed, 266 insertions(+), 263 deletions(-) diff --git a/crussh.py b/crussh.py index e8c4d33..6da1f7b 100755 --- a/crussh.py +++ b/crussh.py @@ -12,315 +12,318 @@ import json import os.path try: - import gtk + import gtk except: - print >>sys.stderr, "Missing Python GTK2 bindings." - sys.exit(1) + print >>sys.stderr, "Missing Python GTK2 bindings." + sys.exit(1) try: - import vte + import vte except: - print >>sys.stderr, "Missing Python VTE bindings." - sys.exit(1) + print >>sys.stderr, "Missing Python VTE bindings." + sys.exit(1) + ### Config Dialog ### class CruSSHConf: - ### State Vars ### - Config = {} - MainWin = gtk.Window() + ### State Vars ### + Config = {} + MainWin = gtk.Window() - ### Signal Hooks ### - def save_hook(self, discard, save_func): - self.MainWin.destroy() - if save_func is not None: - save_func(self.Config) + ### Signal Hooks ### + def save_hook(self, discard, save_func): + self.MainWin.destroy() + if save_func is not None: + save_func(self.Config) - def font_hook(self, fontbutton): - self.Config["font"] = fontbutton.get_font_name() + def font_hook(self, fontbutton): + self.Config["font"] = fontbutton.get_font_name() - def opacity_hook(self, range): - self.Config["opacity"] = range.get_value() + def opacity_hook(self, range): + self.Config["opacity"] = range.get_value() - def width_hook(self, spinbutton): - self.Config["min-width"] = spinbutton.get_value_as_int() + def width_hook(self, spinbutton): + self.Config["min-width"] = spinbutton.get_value_as_int() - def height_hook(self, spinbutton): - self.Config["min-height"] = spinbutton.get_value_as_int() + def height_hook(self, spinbutton): + self.Config["min-height"] = spinbutton.get_value_as_int() - ### GUI Objects ### - def initGUI(self, save_func=None): - self.MainWin.set_modal(True) - self.MainWin.props.allow_grow = False + ### GUI Objects ### + def initGUI(self, save_func=None): + self.MainWin.set_modal(True) + self.MainWin.props.allow_grow = False - MainBox = gtk.VBox(spacing=5) - MainBox.props.border_width = 5 - self.MainWin.add(MainBox) - - TermConfFrame = gtk.Frame(label="Terminal Options") - TermConfTable = gtk.Table(3, 2) - TermConfTable.props.border_width = 5 - TermConfTable.props.row_spacing = 5 - TermConfTable.props.column_spacing = 5 - TermConfFrame.add(TermConfTable) - MainBox.pack_start(TermConfFrame) + MainBox = gtk.VBox(spacing=5) + MainBox.props.border_width = 5 + self.MainWin.add(MainBox) - TermConfTable.attach(gtk.Label("Font:"), 1, 2, 1, 2, gtk.EXPAND) - FontConf = gtk.FontButton(fontname=self.Config["font"]) - FontConf.connect("font-set", self.font_hook) - TermConfTable.attach(FontConf, 2, 3, 1, 2, gtk.EXPAND) + TermConfFrame = gtk.Frame(label="Terminal Options") + TermConfTable = gtk.Table(3, 2) + TermConfTable.props.border_width = 5 + TermConfTable.props.row_spacing = 5 + TermConfTable.props.column_spacing = 5 + TermConfFrame.add(TermConfTable) + MainBox.pack_start(TermConfFrame) - SizeBox = gtk.HBox() - SizeBox.props.spacing = 5 - TermConfTable.attach(SizeBox, 1, 3, 2, 3) - SizeBox.pack_start(gtk.Label("Min Width:"), fill=False, expand=False) - WidthEntry = gtk.SpinButton(gtk.Adjustment(value=self.Config["min-width"], lower=1, upper=9999, step_incr=1)) - WidthEntry.connect("value-changed", self.width_hook) - SizeBox.pack_start(WidthEntry, fill=False, expand=False) - SizeBox.pack_start(gtk.Label("Min Height:"), fill=False, expand=False) - HeightEntry = gtk.SpinButton(gtk.Adjustment(value=self.Config["min-height"], lower=1, upper=9999, step_incr=1)) - HeightEntry.connect("value-changed", self.height_hook) - SizeBox.pack_start(HeightEntry, fill=False, expand=False) + TermConfTable.attach(gtk.Label("Font:"), 1, 2, 1, 2, gtk.EXPAND) + FontConf = gtk.FontButton(fontname=self.Config["font"]) + FontConf.connect("font-set", self.font_hook) + TermConfTable.attach(FontConf, 2, 3, 1, 2, gtk.EXPAND) - TermConfTable.attach(gtk.Label("Opacity:"), 1, 2, 3, 4, gtk.EXPAND) - OpacityAdj = gtk.Adjustment(upper=65535, step_incr=1, value=self.Config["opacity"]) - OpacityScale = gtk.HScale(OpacityAdj) - OpacityScale.set_draw_value(False) - # disconnect this until we get it working. - # OpacityScale.connect("value-changed", self.opacity_hook) - TermConfTable.attach(OpacityScale, 2, 3, 3, 4) + SizeBox = gtk.HBox() + SizeBox.props.spacing = 5 + TermConfTable.attach(SizeBox, 1, 3, 2, 3) + SizeBox.pack_start(gtk.Label("Min Width:"), fill=False, expand=False) + WidthEntry = gtk.SpinButton(gtk.Adjustment(value=self.Config["min-width"], lower=1, upper=9999, step_incr=1)) + WidthEntry.connect("value-changed", self.width_hook) + SizeBox.pack_start(WidthEntry, fill=False, expand=False) + SizeBox.pack_start(gtk.Label("Min Height:"), fill=False, expand=False) + HeightEntry = gtk.SpinButton(gtk.Adjustment(value=self.Config["min-height"], lower=1, upper=9999, step_incr=1)) + HeightEntry.connect("value-changed", self.height_hook) + SizeBox.pack_start(HeightEntry, fill=False, expand=False) - ConfirmBox = gtk.HBox(spacing=5) - CancelButton = gtk.Button(stock=gtk.STOCK_CANCEL) - ConfirmBox.pack_start(CancelButton, fill=False) - SaveButton = gtk.Button(stock=gtk.STOCK_SAVE) - ConfirmBox.pack_start(SaveButton, fill=False) - MainBox.pack_start(ConfirmBox, fill=False, expand=False) - - # wire up behaviour - CancelButton.connect("clicked", lambda discard: self.MainWin.destroy()) - SaveButton.connect("clicked", self.save_hook, save_func) + TermConfTable.attach(gtk.Label("Opacity:"), 1, 2, 3, 4, gtk.EXPAND) + OpacityAdj = gtk.Adjustment(upper=65535, step_incr=1, value=self.Config["opacity"]) + OpacityScale = gtk.HScale(OpacityAdj) + OpacityScale.set_draw_value(False) + # disconnect this until we get it working. + # OpacityScale.connect("value-changed", self.opacity_hook) + TermConfTable.attach(OpacityScale, 2, 3, 3, 4) - self.MainWin.show_all() + ConfirmBox = gtk.HBox(spacing=5) + CancelButton = gtk.Button(stock=gtk.STOCK_CANCEL) + ConfirmBox.pack_start(CancelButton, fill=False) + SaveButton = gtk.Button(stock=gtk.STOCK_SAVE) + ConfirmBox.pack_start(SaveButton, fill=False) + MainBox.pack_start(ConfirmBox, fill=False, expand=False) - # we'll wire up a supplied save_func that takes the Config dict as an argument. - def __init__(self, config=None, save_func=None): - if config is not None: - self.Config = config + # wire up behaviour + CancelButton.connect("clicked", lambda discard: self.MainWin.destroy()) + SaveButton.connect("clicked", self.save_hook, save_func) + + self.MainWin.show_all() + + # we'll wire up a supplied save_func that takes the Config dict as an argument. + def __init__(self, config=None, save_func=None): + if config is not None: + self.Config = config + + self.initGUI(save_func) - self.initGUI(save_func) ### CruSSH! ### class CruSSH: - ### Config Vars ### - # config defaults - Config = { - "min-width": 80, - "min-height": 24, - "font": "Ubuntu Mono Bold 10", - "opacity": 65535 - } + ### Config Vars ### + # config defaults + Config = { + "min-width": 80, + "min-height": 24, + "font": "Ubuntu Mono Bold 10", + "opacity": 65535 + } - ### State Vars ### - Terminals = {} - TermMinWidth = 1 - TermMinHeight = 1 + ### State Vars ### + Terminals = {} + TermMinWidth = 1 + TermMinHeight = 1 - ### GUI Objects ### - MainWin = gtk.Window() - ScrollWin = gtk.ScrolledWindow() - LayoutTable = gtk.Table() - EntryBox = gtk.Entry() + ### GUI Objects ### + MainWin = gtk.Window() + ScrollWin = gtk.ScrolledWindow() + LayoutTable = gtk.Table() + EntryBox = gtk.Entry() - ### Methods ### - def reflowTable(self, cols=1, rows=1): - # empty table and re-size - hosts = sorted(self.Terminals.keys(), reverse=True) - for host in hosts: - if self.Terminals[host].parent == self.LayoutTable: - self.LayoutTable.remove(self.Terminals[host]) - self.LayoutTable.resize(rows, cols) - # layout terminals - for row in range(rows): - for col in range(cols): - if len(hosts) > 0: - host = hosts.pop() - self.Terminals[host].set_size(self.Config["min-width"], self.Config["min-height"]) - self.LayoutTable.attach(self.Terminals[host], col, col+1, row, row+1) + ### Methods ### + def reflowTable(self, cols=1, rows=1): + # empty table and re-size + hosts = sorted(self.Terminals.keys(), reverse=True) + for host in hosts: + if self.Terminals[host].parent == self.LayoutTable: + self.LayoutTable.remove(self.Terminals[host]) + self.LayoutTable.resize(rows, cols) + # layout terminals + for row in range(rows): + for col in range(cols): + if len(hosts) > 0: + host = hosts.pop() + self.Terminals[host].set_size(self.Config["min-width"], self.Config["min-height"]) + self.LayoutTable.attach(self.Terminals[host], col, col + 1, row, row + 1) - def reflow(self, force=False): - # reconfigure before updating rows and columns - self.configTerminals() + def reflow(self, force=False): + # reconfigure before updating rows and columns + self.configTerminals() - num_terms = len(self.Terminals.keys()) - if num_terms < 1: - gtk.main_quit() - # main_quit desn't happen immediately - return False - size = self.MainWin.allocation - cols = int(math.floor((size.width + self.LayoutTable.props.column_spacing) / float(self.TermMinWidth))) - if cols < 1 or num_terms == 1: - cols = 1 - elif cols > num_terms: - cols = num_terms - rows = int(math.ceil(num_terms/float(cols))) - if rows < 1: - rows = 1 - if (self.LayoutTable.props.n_columns != cols) or (self.LayoutTable.props.n_rows != rows) or force: - self.reflowTable(cols, rows) - self.MainWin.show_all() + num_terms = len(self.Terminals.keys()) + if num_terms < 1: + gtk.main_quit() + # main_quit desn't happen immediately + return False + size = self.MainWin.allocation + cols = int(math.floor((size.width + self.LayoutTable.props.column_spacing) / float(self.TermMinWidth))) + if cols < 1 or num_terms == 1: + cols = 1 + elif cols > num_terms: + cols = num_terms + rows = int(math.ceil(num_terms / float(cols))) + if rows < 1: + rows = 1 + if (self.LayoutTable.props.n_columns != cols) or (self.LayoutTable.props.n_rows != rows) or force: + self.reflowTable(cols, rows) + self.MainWin.show_all() - def configTerminals(self): - for host in self.Terminals: - terminal = self.Terminals[host] - # -1 == infinite scrollback - terminal.set_scrollback_lines(-1) - terminal.set_size(self.Config["min-width"], self.Config["min-height"]) - terminal.set_font_from_string(self.Config["font"]) - terminal.set_opacity(int(self.Config["opacity"])) - self.TermMinWidth = (terminal.get_char_width() * self.Config["min-width"]) + terminal.get_padding()[0] - self.TermMinHeight = (terminal.get_char_height() * self.Config["min-height"]) + terminal.get_padding()[1] + def configTerminals(self): + for host in self.Terminals: + terminal = self.Terminals[host] + # -1 == infinite scrollback + terminal.set_scrollback_lines(-1) + terminal.set_size(self.Config["min-width"], self.Config["min-height"]) + terminal.set_font_from_string(self.Config["font"]) + terminal.set_opacity(int(self.Config["opacity"])) + self.TermMinWidth = (terminal.get_char_width() * self.Config["min-width"]) + terminal.get_padding()[0] + self.TermMinHeight = (terminal.get_char_height() * self.Config["min-height"]) + terminal.get_padding()[1] - def removeTerminal(self, terminal): - # brute force search since we don't actually know the hostname from the - # terminal object. this is an infrequent operation, so it should be fine. - for host in self.Terminals.keys(): - if terminal == self.Terminals[host]: - self.LayoutTable.remove(self.Terminals[host]) - del self.Terminals[host] - self.reflow(force=True) + def removeTerminal(self, terminal): + # brute force search since we don't actually know the hostname from the + # terminal object. this is an infrequent operation, so it should be fine. + for host in self.Terminals.keys(): + if terminal == self.Terminals[host]: + self.LayoutTable.remove(self.Terminals[host]) + del self.Terminals[host] + self.reflow(force=True) - def initGUI(self): - theme = gtk.icon_theme_get_default() - icon_list = [] - if theme.has_icon("terminal"): - icon = theme.lookup_icon("terminal", 128, flags=gtk.ICON_LOOKUP_USE_BUILTIN) - if icon is not None: - gtk.window_set_default_icon(icon.load_icon()) - self.MainWin.set_title("crussh: " + ' '.join(self.Terminals.keys())) - self.MainWin.set_role(role="crussh_main_win") - self.MainWin.connect("delete-event", lambda window, event: gtk.main_quit()) - MainVBox = gtk.VBox() - self.MainWin.add(MainVBox) + def initGUI(self): + theme = gtk.icon_theme_get_default() + if theme.has_icon("terminal"): + icon = theme.lookup_icon("terminal", 128, flags=gtk.ICON_LOOKUP_USE_BUILTIN) + if icon is not None: + gtk.window_set_default_icon(icon.load_icon()) + self.MainWin.set_title("crussh: " + ' '.join(self.Terminals.keys())) + self.MainWin.set_role(role="crussh_main_win") + self.MainWin.connect("delete-event", lambda window, event: gtk.main_quit()) + MainVBox = gtk.VBox() + self.MainWin.add(MainVBox) - MainMenuBar = gtk.MenuBar() - MainVBox.pack_start(MainMenuBar, fill=True, expand=False) + MainMenuBar = gtk.MenuBar() + MainVBox.pack_start(MainMenuBar, fill=True, expand=False) - FileItem = gtk.MenuItem(label="File") - FileMenu = gtk.Menu() - FileItem.set_submenu(FileMenu) - QuitItem = gtk.MenuItem(label="Quit") - QuitItem.connect("activate", lambda discard: gtk.main_quit()) - FileMenu.append(QuitItem) - MainMenuBar.append(FileItem) + FileItem = gtk.MenuItem(label="File") + FileMenu = gtk.Menu() + FileItem.set_submenu(FileMenu) + QuitItem = gtk.MenuItem(label="Quit") + QuitItem.connect("activate", lambda discard: gtk.main_quit()) + FileMenu.append(QuitItem) + MainMenuBar.append(FileItem) - EditItem = gtk.MenuItem(label="Edit") - EditMenu = gtk.Menu() - EditItem.set_submenu(EditMenu) - PrefsItem = gtk.MenuItem(label="Preferences") - def save_func(new_config): - self.Config = new_config - self.reflow(force=True) - # save to file last, so it doesn't hold up other GUI actions - conf_json = json.dumps(self.Config, sort_keys=True, indent=4) - try: - conf_file = open(os.path.expanduser("~/.crusshrc"), 'w') - conf_file.write(conf_json) - conf_file.close() - except: - pass - PrefsItem.connect("activate", lambda discard: CruSSHConf(self.Config, save_func)) - EditMenu.append(PrefsItem) - MainMenuBar.append(EditItem) + EditItem = gtk.MenuItem(label="Edit") + EditMenu = gtk.Menu() + EditItem.set_submenu(EditMenu) + PrefsItem = gtk.MenuItem(label="Preferences") - self.ScrollWin.props.hscrollbar_policy = gtk.POLICY_NEVER - self.ScrollWin.props.vscrollbar_policy = gtk.POLICY_ALWAYS - self.ScrollWin.props.shadow_type = gtk.SHADOW_ETCHED_IN - MainVBox.pack_start(self.ScrollWin) + def save_func(new_config): + self.Config = new_config + self.reflow(force=True) + # save to file last, so it doesn't hold up other GUI actions + conf_json = json.dumps(self.Config, sort_keys=True, indent=4) + try: + conf_file = open(os.path.expanduser("~/.crusshrc"), 'w') + conf_file.write(conf_json) + conf_file.close() + except: + pass + PrefsItem.connect("activate", lambda discard: CruSSHConf(self.Config, save_func)) + EditMenu.append(PrefsItem) + MainMenuBar.append(EditItem) - self.LayoutTable.set_homogeneous(True) - 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) + self.ScrollWin.props.hscrollbar_policy = gtk.POLICY_NEVER + self.ScrollWin.props.vscrollbar_policy = gtk.POLICY_ALWAYS + self.ScrollWin.props.shadow_type = gtk.SHADOW_ETCHED_IN + MainVBox.pack_start(self.ScrollWin) - # don't display chars while typing. - self.EntryBox.set_visibility(False) - self.EntryBox.set_invisible_char(' ') - # forward key events to all terminals - def feed_input(widget, event): - if event.type in [gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE]: - # erase buffer on every entry, so that passwords aren't revealed - self.EntryBox.props.buffer.delete_text(0, -1) - # propagate to every terminal - for host in self.Terminals: - t_event = event.copy() - self.Terminals[host].event(t_event) - # this stops regular handler from firing, switching focus. - return True - self.EntryBox.connect("key_press_event", feed_input) - self.EntryBox.connect("key_release_event", feed_input) - MainVBox.pack_start(self.EntryBox, False, False) + self.LayoutTable.set_homogeneous(True) + 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) - # reflow layout on size change. - self.MainWin.connect("size-allocate", lambda widget, allocation: self.reflow()) + # don't display chars while typing. + self.EntryBox.set_visibility(False) + self.EntryBox.set_invisible_char(' ') - # give EntryBox default focus on init - self.EntryBox.props.has_focus = True + # forward key events to all terminals + def feed_input(widget, event): + if event.type in [gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE]: + # erase buffer on every entry, so that passwords aren't revealed + self.EntryBox.props.buffer.delete_text(0, -1) + # propagate to every terminal + for host in self.Terminals: + t_event = event.copy() + self.Terminals[host].event(t_event) + # this stops regular handler from firing, switching focus. + return True + self.EntryBox.connect("key_press_event", feed_input) + self.EntryBox.connect("key_release_event", feed_input) + MainVBox.pack_start(self.EntryBox, False, False) - def __init__(self, hosts, ssh_cmd="/usr/bin/ssh", ssh_args=None): - # load existing config file, if present - try: - # merge dicts to allow upgrade from old configs - new_config = json.load(open(os.path.expanduser('~/.crusshrc'))) - self.Config.update(new_config) - except: - pass + # reflow layout on size change. + self.MainWin.connect("size-allocate", lambda widget, allocation: self.reflow()) - # init all terminals - for host in hosts: - terminal = vte.Terminal() - # TODO: disable only this terminal widget on child exit - # v.connect("child-exited", lambda term: gtk.main_quit()) - cmd_str = ssh_cmd - if ssh_args is not None: - cmd_str += " " + ssh_args - cmd_str += " " + host - cmd = cmd_str.split(' ') - terminal.fork_command(command=cmd[0], argv=cmd) - self.Terminals[host] = terminal + # give EntryBox default focus on init + self.EntryBox.props.has_focus = True - # hook terminals so they reflow layout on exit - self.Terminals[host].connect("child-exited", self.removeTerminal) - # configure all terminals - self.configTerminals() - # reflow after reconfig for font size changes - self.initGUI() - self.reflow(force=True) + def __init__(self, hosts, ssh_cmd="/usr/bin/ssh", ssh_args=None): + # load existing config file, if present + try: + # merge dicts to allow upgrade from old configs + new_config = json.load(open(os.path.expanduser('~/.crusshrc'))) + self.Config.update(new_config) + except: + pass + + # init all terminals + for host in hosts: + terminal = vte.Terminal() + # TODO: disable only this terminal widget on child exit + # v.connect("child-exited", lambda term: gtk.main_quit()) + cmd_str = ssh_cmd + if ssh_args is not None: + cmd_str += " " + ssh_args + 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) + # configure all terminals + self.configTerminals() + # reflow after reconfig for font size changes + self.initGUI() + self.reflow(force=True) if __name__ == "__main__": - import argparse + import argparse - ### Parse CLI Args ### - parser = argparse.ArgumentParser( - description="Connect to multiple hosts in parallel.", - usage="%(prog)s [OPTIONS] [--] HOST [HOST ...]", - epilog="* NOTE: You can pass options to ssh if you add '--' before your list of hosts") - parser.add_argument("--ssh", dest='ssh', default="/usr/bin/ssh", - help="specify the SSH executable to use (default: %(default)s)") - (args, hosts) = parser.parse_known_args() + ### Parse CLI Args ### + parser = argparse.ArgumentParser( + description="Connect to multiple hosts in parallel.", + usage="%(prog)s [OPTIONS] [--] HOST [HOST ...]", + epilog="* NOTE: You can pass options to ssh if you add '--' before your list of hosts") + parser.add_argument("--ssh", dest='ssh', default="/usr/bin/ssh", + help="specify the SSH executable to use (default: %(default)s)") + (args, hosts) = parser.parse_known_args() - if len(hosts) == 0: - parser.print_usage() - parser.exit(2) + if len(hosts) == 0: + parser.print_usage() + parser.exit(2) - try: - offset = hosts.index("--") - except: - ssh_args = None - else: - ssh_args = " ".join(hosts[0:offset]) - hosts = hosts[offset + 1:] + try: + offset = hosts.index("--") + except: + ssh_args = None + else: + ssh_args = " ".join(hosts[0:offset]) + hosts = hosts[offset + 1:] - ### Start Execution ### - crussh = CruSSH(hosts, args.ssh, ssh_args) - gtk.main() + ### Start Execution ### + crussh = CruSSH(hosts, args.ssh, ssh_args) + gtk.main()