Class SSHMenu::App
In: lib/sshmenu.rb  (Git)
Parent: Object

The SSHMenu::App class implements the framework of the application - a simple menu. Each item on the menu represents an SSH connection to a host, to be opened in a new terminal window.

This class is responsible for rendering the menu and taking appropriate action when the user makes a selection from the menu.

The application class uses the ClassMapper to delegate chunks of functionality to different classes as follows:

‘app.model’ => SSHMenu::Config
manage the data model - reading and writing the config file and maintaining an in-memory representation
‘app.history’ => SSHMenu::History
manage quick-connect command history
‘app.dialog.prefs’ => SSHMenu::PrefsDialog
manage the preferences dialog
‘app.dialog.host’ => SSHMenu::HostDialog
manage the dialog for editing a host
‘app.dialog.menu’ => SSHMenu::MenuDialog
manage the dialog for editing a sub-menu
‘app.geograbber’ => SSHMenu::GeoGrabber
manage ‘grabbing’ the geometry of a running window

Methods

Constants

AskpassPaths = [ '/usr/bin/ssh-askpass', '/etc/alternatives/ssh-askpass', '/usr/lib/ssh/gnome-ssh-askpass', '/usr/lib/ssh/x11-ssh-askpass', ]

Attributes

config  [R]  The ‘app.model’ object
display  [R]  The X11 DISPLAY object

Public Class methods

Uses a pop-up dialog to display a message and optional further detail.

[Source]

# File lib/sshmenu.rb, line 826
    def App.alert(message, extra_msg = nil)
      dialog = Gtk::Dialog.new(
        nil,
        nil,
        Gtk::Dialog::DESTROY_WITH_PARENT,
        [ Gtk::Stock::CLOSE, Gtk::Dialog::RESPONSE_NONE ]
      )
      dialog.has_separator = false
      stock_id = nil
      if message =~ /error/i
        dialog.title = 'Error'
        stock_id     = Gtk::Stock::DIALOG_ERROR
      else
        dialog.title = 'Warning'
        stock_id     = Gtk::Stock::DIALOG_WARNING
      end
      label = Gtk::Label.new(message)
      label.selectable = true
      icon  = Gtk::Image.new(stock_id, Gtk::IconSize::DIALOG)
      box = Gtk::HBox.new(false, 10)
      box.add(icon)
      box.add(label)
      box.border_width = 10
      dialog.vbox.add(box)
      if extra_msg
        expander = Gtk::Expander.new('Detail')
        expander.border_width = 10
        extra_label = Gtk::Label.new(extra_msg)
        extra_label.selectable = true
        expander.add(extra_label)
        dialog.vbox.add(expander)
      end
      dialog.screen = @@display if @@display
      dialog.show_all
      dialog.run
      dialog.destroy
    end

Takes an exception object and displays the error message and the backtrace using SSHMenu::App#alert.

[Source]

# File lib/sshmenu.rb, line 820
    def App.fatal_error(exception)
      alert('Fatal error: ' + exception.message, exception.backtrace.join("\n"))
    end

Called by SSHMenu::Factory#make_app

[Source]

# File lib/sshmenu.rb, line 272
    def initialize(config, options, launch_proc)
      @config      = config
      @options     = options
      @launch_proc = launch_proc
      @have_bcvi   = false
      @socket_window_id = nil
      @debug_level = 0
      @context_menu_active = false
      @deferred_actions    = []

      getopts(@options[:args])
      @app_win     = @options[:window] || default_container
      @entry_box   = nil

      inject_defaults
      get_initial_config
      check_for_bcvi
      @history   = mapper.get_class('app.history').new(config)

      @have_key  = false
      @is_applet = @app_win.respond_to?('popup_component')

      if not @deferred_actions.empty?
        @deferred_actions.each { |a| a.call() }
      end

      build_ui() unless @app_win == :none
    end

Public Instance methods

Invoked if the user selects the ‘Add SSH key to agent’ option from the main menu. Attempts to set up the environment to allow an askpass dialog window can be displayed and then runs the ssh-add command.

[Source]

# File lib/sshmenu.rb, line 1006
    def add_key
      return if @have_key

      if !ENV['SSH_AUTH_SOCK']
        alert("$SSH_AUTH_SOCK is not set.\nIs the ssh-agent running?")
        return
      end

      if !File.exists?(ENV['SSH_AUTH_SOCK'])
        alert(
          "$SSH_AUTH_SOCK points to #{ENV['SSH_AUTH_SOCK']},\n" +
          "but it does not exist!"
        )
        return
      end

      keylist = `ssh-add -l`
      if $? == 0
        @have_key = true
        return
      end

      setup_askpass_env or return
      shell_command("ssh-add </dev/null >/dev/null 2>&1")
    end

Adds the menu selections at the bottom of the main menu:

  • preferences dialog
  • add SSH key to agent
  • remove SSH keys from agent

[Source]

# File lib/sshmenu.rb, line 962
    def add_tools_menu_items(mif)
      mif.create_item("/tools-separator", '<Separator>')

      if not context_menu_active?
        mif.create_item(
          "/Preferences", "<StockItem>", nil, Gtk::Stock::PROPERTIES
        ){ edit_preferences }
      end

      mif.create_item("/Add SSH key to Agent",       "<Item>") { add_key     }
      mif.create_item("/Remove SSH keys from Agent", "<Item>") { remove_keys }
    end

Proxy for SSHMenu::App#alert class method

[Source]

# File lib/sshmenu.rb, line 866
    def alert(message, detail = nil)
      self.class.alert(message, detail)
    end

Callback invoked when the ‘About’ option on the context menu is selected.

[Source]

# File lib/sshmenu.rb, line 523
    def applet_menu_about
      dialog = Gtk::Dialog.new(
        nil,
        nil,
        Gtk::Dialog::DESTROY_WITH_PARENT,
        [ Gtk::Stock::CLOSE, Gtk::Dialog::RESPONSE_NONE ]
      )
      dialog.has_separator = false
      dialog.title = 'About SSHMenu'
      about_pane = make_about_pane
      dialog.vbox.add(about_pane)
      dialog.screen = @@display if @@display
      dialog.show_all
      dialog.run
      dialog.destroy
    end

Helper routine called from open_homepage. Attempts to find a browser program by looking for known browser executable names in each directory in the search path. Returns the name of the first program found.

[Source]

# File lib/sshmenu.rb, line 1150
    def browser_program
      progs = %w{ gnome-open sensible-browser firefox konqueror opera galeon }
      ENV['PATH'].split(':').each do |dir|
        progs.each do |p|
          path = "#{dir}/#{p}"
          return path if FileTest.executable?(path)
        end
      end
      alert(
        'Unable to locate a web browser program',
        "Tried:\n#{progs.join(', ')}"
      )
      return
    end

Build a text entry box with resize handle for display next to the main button (if required)

[Source]

# File lib/sshmenu.rb, line 379
    def build_text_entry
      hbox       = Gtk::HBox.new(false, 0)
      entry      = Gtk::Entry.new
      completion = Gtk::EntryCompletion.new
      store      = Gtk::ListStore.new(String)

      @completion_actions = []

      entry.width_chars           = 1
      entry.width_request         = @config.get('entry_width', 70)
      completion.model            = store
      completion.popup_completion = true
      completion.text_column      = 0
      completion.popup_set_width  = false

      completion.set_match_func { true }

      entry.completion = completion

      entry.signal_connect('activate') do
        @history.add_line(entry.text)
        open_win(@config.host_from_text(entry.text))
        entry.text = ''
      end

      completion.signal_connect('action-activated') do |c,i|
        target = @completion_actions[i]
        if target.is_a?(String)
          prefs = mapper.get_class('app.dialog.prefs').new(self, @config)
          prefs.append_host(@config.host_from_text(entry.text))
        else
          open_win(@completion_actions[i])
        end
        entry.text = ''
      end

      entry.signal_connect('button-press-event') do |w,e|
        if @app_win.respond_to?('request_focus')
          @app_win.request_focus(e.time)
        end
        false
      end

      entry.signal_connect('focus-in-event') do
        @history.freshen
        false
      end

      entry.signal_connect('changed') do
        update_entry_completions(store, entry.text)
        update_entry_actions(completion, entry.text)
      end

      hbox.pack_start(entry, true, true)      # Expand and fill

      handle = entry_resize_handle(entry)
      hbox.pack_start(handle, false, true)    # No expand but fill (y?)

      return hbox
    end

Called from the constructor to handle building the main user interface (a button).

[Source]

# File lib/sshmenu.rb, line 342
    def build_ui()
      hbox = Gtk::HBox.new(false, 0)
      @app_win.add(hbox)

      evbox = Gtk::EventBox.new
      evbox.signal_connect('button-press-event') { |w,e| on_click(w,e) }
      hbox.pack_start(evbox, false, false)

      @frame = Gtk::Frame.new
      set_button_border;
      evbox.add(@frame)

      label = Gtk::Label.new("SSH")
      label.set_padding(2, 2)
      @frame.add(label)

      tooltips = Gtk::Tooltips.new
      tooltips.set_tip(evbox, @config.tooltip_text, nil);

      @app_win.show_all

      @entry_box = build_text_entry
      hbox.pack_start(@entry_box, true, true) unless @entry_box.nil?
      show_hide_text_entry

      set_up_applet_menu

      # For multi-DISPLAY setups
      @@display = evbox.screen
      ENV['DISPLAY'] = @@display.display_name

      appease_popcon
    end

Takes a SSHMenu::HostItem object, builds a command line for invoking SSH in an xterm window, to connect to the specified host.

[Source]

# File lib/sshmenu.rb, line 1095
    def build_window_command(host)
      command = "#{host.env_settings}xterm -T " + shell_quote(host.title)
      if host.geometry and host.geometry.length > 0
        command += " -geometry #{host.geometry}"
      end
      ssh_cmnd = ssh_command(host)
      command += ' -e sh -c ' +
                 shell_quote("#{ssh_cmnd} #{host.sshparams_noenv}") + ' &'
      return command
    end

This method returns a boolean value which controls whether or not the preferences dialog includes an option to display a text entry box. If the application is running in a panel applet, version 0.19 of the Ruby bindings is required to display a text entry. For non-applet contexts, the return value is always true.

[Source]

# File lib/sshmenu.rb, line 314
    def can_show_entry?
      if @is_applet
        return GLib.check_binding_version?(0, 19, 0)
      end
      return true
    end

Called from the constructor to check if the ‘bcvi’ program is installed anywhere in the search path. If bcvi is found then a checkbox will be displayed in the host edit dialog.

[Source]

# File lib/sshmenu.rb, line 658
    def check_for_bcvi
      path = ENV['PATH'] || ''
      path.split(':').each do |dir|
        file = Pathname.new(dir) + 'bcvi'
        if FileTest.executable?(file)
          @have_bcvi = true
          break
        end
      end
    end

Returns a boolean flag indicating whether the program is running in an applet with context menu support.

[Source]

# File lib/sshmenu.rb, line 672
    def context_menu_active?
      return @context_menu_active
    end

Output diagnostic information to STDOUT if debugging is enabled

[Source]

# File lib/sshmenu.rb, line 1175
    def debug(level, message)
      return if @debug_level < level
      puts message
    end

Called if no container window was supplied to the constructor. Most commonly, the method would create and return a new top-level window object. However if the —socket-window-id option was supplied, a Gtk::Plug object will be created instead. This allows another application to embed the SSHMenu user interface.

[Source]

# File lib/sshmenu.rb, line 327
    def default_container()
      window = nil
      if @socket_window_id
        window       = Gtk::Plug.new(@socket_window_id)
      else
        window       = Gtk::Window.new( Gtk::Window::TOPLEVEL )
        window.title = 'SSH Menu'
      end
      window.signal_connect('destroy') { Gtk.main_quit }
      return window
    end

Takes a code block and schedules it to be called when option processing is complete

[Source]

# File lib/sshmenu.rb, line 813
    def defer_action(&action_proc)
      @deferred_actions.push action_proc
    end

Called when the ‘Preferences’ option is selected from the main menu. Instantiates an ‘app.dialog.prefs’ object and calls its invoke method (SSHMenu::PrefsDialog#invoke by default).

[Source]

# File lib/sshmenu.rb, line 1131
    def edit_preferences
      dialog_class = mapper.get_class('app.dialog.prefs')
      dialog_class.new(self, @config).invoke
      show_hide_text_entry
      set_button_border
    end

Add a resize handle to the right of the entry box

[Source]

# File lib/sshmenu.rb, line 442
    def entry_resize_handle(entry)
      handle = Gtk::DrawingArea.new
      handle.set_size_request(3,10)
      handle.events = Gdk::Event::BUTTON_PRESS_MASK |
                      Gdk::Event::BUTTON_RELEASE_MASK |
                      Gdk::Event::POINTER_MOTION_MASK

      handle.signal_connect('realize') do
        handle.window.cursor = Gdk::Cursor.new(Gdk::Cursor::RIGHT_SIDE)
      end

      dragging = false
      x_start  = 0
      w_start  = 0
      handle.signal_connect('button-press-event') do |w,e|
        if e.button = 1
          dragging = true
          (x,y,w,h) = entry.window.geometry
          w_start = w
          x_start = e.x_root
        end
      end

      handle.signal_connect('button-release-event') do |w,e|
        if e.button = 1
          dragging = false
          @config.set('entry_width', entry.width_request)
          @config.save
        end
      end

      handle.signal_connect('motion-notify-event') do |w,e|
        if dragging
          w = w_start + e.x_root - x_start
          if w >= 20
            entry.width_request = w
            @app_win.resize(1,1) if @app_win.respond_to?('resize')
          end
        end
      end

      return handle
    end

Reads the config file. If no config file exists at all, calls SSHMenu::Config#autoconfigure to invoke the configuration wizard.

[Source]

# File lib/sshmenu.rb, line 698
    def get_initial_config
      if @config.not_configured?
        @config.autoconfigure
      end

      get_latest_config
    end

Called before the menu is displayed. Ensures the latest config data has been loaded. This allows manual edits of the config file to be reflected without having to restart the app.

[Source]

# File lib/sshmenu.rb, line 946
    def get_latest_config
      begin
        @config.load
      rescue Exception => detail
        alert(
            "Error reading config file: #{@config.filename}",
            detail.message + "\n" + detail.backtrace.join("\n")
        )
      end
    end

Returns a list of command-line option definitions for use by GetoptLong.

[Source]

# File lib/sshmenu.rb, line 731
    def getopt_defs
      return(
        [
          [ "--version",           "-V",    GetoptLong::NO_ARGUMENT       ],
          [ "--debug",             "-d",    GetoptLong::OPTIONAL_ARGUMENT ],
          [ "--config-file",       "-c",    GetoptLong::REQUIRED_ARGUMENT ],
          [ "--socket-window-id",  "-s",    GetoptLong::REQUIRED_ARGUMENT ],
          [ "--list-completions",  "-l",    GetoptLong::NO_ARGUMENT       ]
        ]
      )
    end

Called from the wrapper script to handle the parsing of command-line options. Calls getopt_defs to determine which options are recognised and then calls the set_* method for each option as it is encountered (eg: set_config_file).

[Source]

# File lib/sshmenu.rb, line 711
    def getopts(argv)
      begin
        argv = argv.flatten      # Copy argument (which might already be ARGV)
        ARGV.clear               # Then copy contents into ARGV
        argv.each { |a| ARGV.push(a) }
        opts = GetoptLong.new( *getopt_defs )
        opts.quiet = true
        opts.each do |opt, arg|
          method = opt.gsub(/^-*/, 'set_').gsub(/\W/, '_')
          self.send(method, arg)
        end
        @options[:window] = :none if not ARGV.empty?
      rescue Exception => detail
        $stderr.puts detail.message
        exit 1
      end
    end

Accessor for the @have_bcvi attribute.

[Source]

# File lib/sshmenu.rb, line 678
    def have_bcvi?
      return @have_bcvi
    end

Called from the constructor to set up default class mappings for application components.

[Source]

# File lib/sshmenu.rb, line 685
    def inject_defaults
      mapper.inject(
        'app.history'      => SSHMenu::History,
        'app.dialog.prefs' => SSHMenu::PrefsDialog,
        'app.dialog.host'  => SSHMenu::HostDialog,
        'app.dialog.menu'  => SSHMenu::MenuDialog,
        'app.geograbber'   => SSHMenu::GeoGrabber
      )
    end

Returns true if the application window is a panel applet or false if it‘s a normal application window

[Source]

# File lib/sshmenu.rb, line 304
    def is_applet?
      return @is_applet
    end

Helper method for calculating menu item paths

[Source]

# File lib/sshmenu.rb, line 935
    def item_path(parents, item)
      path = [parents, item].flatten.map do |i|
        i.title.gsub(/\//, '\/').gsub(/_/, '__')
      end
      return '/' + path.join('/')
    end

Expects a single argument in ARGV after options have been processed. Returns a list of possible expansions to host title definitions and hostnames from the history file

[Source]

# File lib/sshmenu.rb, line 779
    def list_completions()
      if prefix = ARGV.shift
        prefix = prefix.gsub(/\\(.)/, "\\1")
        chars = prefix.size
        seen  = { }
        @config.each_item() do |parents, item|
          if item.host? and item.title.slice(0,chars) == prefix
            if not seen[item.title]
              puts item.title
              seen[item.title] = true
            end
          end
        end
        @history.each_match(prefix, true) do |name|
          if not seen[name]
            puts name
            seen[name] = true
          end
        end
      end
      exit
    end

Constructs the contents of the ‘About’ box, including: program name and version, copyright information and a link to the project home page.

[Source]

# File lib/sshmenu.rb, line 543
    def make_about_pane
      pane = Gtk::VBox.new(false, 12)
      panel = Gtk::VBox.new(false, 12)

      title = Gtk::Label.new
      title.set_markup("<span font_desc='sans bold 36'>SSHMenu</span>");
      title.selectable = true
      panel.pack_start(title, false, false, 0)

      version = Gtk::Label.new
      version.set_markup("<span font_desc='sans 24'>Version: #{SSHMenu.version}</span>");
      version.selectable = true
      panel.pack_start(version, false, false, 0)

      author = Gtk::Label.new
      detail = '(c) 2005-2009 Grant McLean &lt;grant@mclean.net.nz&gt;'
      author.set_markup("  <span font_desc='sans 10'>#{detail}</span>  ");
      author.selectable = true
      panel.pack_start(author, false, false, 10)

      evbox = Gtk::EventBox.new
      evbox.signal_connect('button-press-event') { |w,e| open_homepage() }
      evbox.signal_connect('realize') { |w| w.window.cursor = Gdk::Cursor.new(Gdk::Cursor::HAND2) }
      site_link = Gtk::Label.new
      site_link.set_markup("<span font_desc='sans 10' foreground='#0000FF' " +
                        "underline='single'>#{SSHMenu.homepage_url}</span>");
      evbox.add(site_link)
      panel.pack_start(evbox, false, false, 0)

      pane.pack_start(panel, true, false, 10)
      return pane
    end

Accessor for the SSHMenu::ClassMapper singleton object

[Source]

# File lib/sshmenu.rb, line 633
    def mapper
      ClassMapper.instance
    end

Called from show_hosts_menu to add a host to the menu

[Source]

# File lib/sshmenu.rb, line 909
    def menu_add_host(mif, parents, item)
      mif.create_item(item_path(parents, item), "<Item>") { open_win(item) }
    end

Called from show_hosts_menu to add the optional parts at the top of a sub-menu:

  • a ‘tear off’ strip
  • an ‘Open all windows’ option

[Source]

# File lib/sshmenu.rb, line 918
    def menu_add_menu_options(mif, parents, item)
      return unless item.has_children?
      need_sep = false
      if @config.menus_tearoff?
        mif.create_item(item_path(parents, item) + '/<tearoff>', '<Tearoff>')
      end
      if @config.menus_open_all?
        mif.create_item(
          item_path(parents, item) + '/Open all windows', "<Item>"
        ) { open_all(item) }
        need_sep = true
      end
      return need_sep
    end

Called from show_hosts_menu to add a separator to the menu

[Source]

# File lib/sshmenu.rb, line 903
    def menu_add_separator(mif, parents, item)
      mif.create_item(item_path(parents, item), '<Separator>')
    end

Helper method to calculate where to place the main menu

[Source]

# File lib/sshmenu.rb, line 977
    def menu_position(menu, event)
      (w, h) = event.window.size
      x = event.x_root - event.x - 1
      y = event.y_root - event.y + h + 1

      # Correct if window is near bottom

      (mw, mh) = menu.size_request
      sh       = menu.screen.height
      sw       = menu.screen.width
      if y > 200 and y + mh > sh
        y = event.y_root - event.y - mh - 1
        y = 0 if y < 0
      end

      # Correct if window is near right

      if x > 200 and x + mw > sw
        x = sw - mw
        x = 0 if x < 0
      end

      return [x, y]
    end

Called when the main application button is clicked. Responds by displaying the main menu.

[Source]

# File lib/sshmenu.rb, line 805
    def on_click(widget, event)
       return show_hosts_menu(event)  if event.button == 1
       return false
    end

Invoked if the user selects ‘Open all windows’ from a sub-menu. Does the same as open_win but for each host on the menu.

[Source]

# File lib/sshmenu.rb, line 1078
    def open_all(menu)
      add_key
      menu.items.each do |item|
        if item.host?
          if @launch_proc
            @launch_proc.call(item)
          else
            shell_command(build_window_command(item))
          end
          sleep 0.1  # to avoid .xauth lock conflicts with parallel connects
        end
      end
    end

Called from the SSHMenu::PrefsDialog if the user clicks on the home page URL in the ‘About’ box. Attempts to open the URL in a browser window.

[Source]

# File lib/sshmenu.rb, line 1141
    def open_homepage
      prog = browser_program or return
      shell_command("#{prog} #{SSHMenu.homepage_url}")
    end

Invoked if the user selects a host from the menu. Yields the SSHMenu::HostItem object to the wrapper script block if a block was supplied, otherwise builds a command line with build_window_command and executes it.

[Source]

# File lib/sshmenu.rb, line 1066
    def open_win(host)
      add_key
      if @launch_proc
        @launch_proc.call(host)
      else
        shell_command(build_window_command(host))
      end
    end

Invoked if the ‘Remove SSH keys from agent’ option is selected. Runs ssh-add -D.

[Source]

# File lib/sshmenu.rb, line 1056
    def remove_keys
      shell_command("ssh-add -D </dev/null >/dev/null 2>&1")
      @have_key = false
    end

Thin wrapper around the Gtk.main loop

[Source]

# File lib/sshmenu.rb, line 639
    def run
      return shell_run if @app_win == :none
      Gtk.main
    end

Show/hide the border around the main UI ‘button‘

[Source]

# File lib/sshmenu.rb, line 627
    def set_button_border
      @frame.shadow_type = @config.hide_border? ? Gtk::SHADOW_NONE : Gtk::SHADOW_OUT;
    end

Called by GetoptLong if the ’—config-file’ option was supplied.

[Source]

# File lib/sshmenu.rb, line 758
    def set_config_file(file)
      @config.set_config_file(file)
    end

Called by GetoptLong if the ’—debug’ option was supplied.

[Source]

# File lib/sshmenu.rb, line 751
    def set_debug(level)
      level = 1 if level.to_s == ''
      @debug_level = level.to_i
    end

Called by GetoptLong if the ’—list-completions’ option was supplied.

[Source]

# File lib/sshmenu.rb, line 770
    def set_list_completions(arg)
      defer_action { list_completions() }
      @options[:window] = :none
    end

Called by GetoptLong if the ’—socket-window-id’ option was supplied.

[Source]

# File lib/sshmenu.rb, line 764
    def set_socket_window_id(window_id)
      @socket_window_id = window_id.to_i
    end

Adds the ‘Properties’ and ‘About’ options to the applet context (right-click) menu - if the container is an applet.

[Source]

# File lib/sshmenu.rb, line 504
    def set_up_applet_menu
      return unless @is_applet and @app_win.respond_to?('set_menu')
      xml = %Q{<popup name="button3">
                 <menuitem name="prefs" verb="prefs" _label="Preferences"
                   pixtype="stock" pixname="gtk-properties" />
                 <menuitem name="about" verb="about" _label="About"
                   pixtype="stock" pixname="gtk-about" />
               </popup>}

      verbs = [['prefs', Proc.new{edit_preferences}],
               ['about', Proc.new{applet_menu_about}]]

      @app_win.set_menu xml, verbs
      @context_menu_active = true
    end

Called by GetoptLong if the ’—version’ option was supplied.

[Source]

# File lib/sshmenu.rb, line 745
    def set_version(arg)
      raise ShowVersionException
    end

Helper method called from add_key. Sets up the environment for an askpass helper window.

[Source]

# File lib/sshmenu.rb, line 1035
    def setup_askpass_env
      if(ENV['SSH_ASKPASS'] and File.executable?(ENV['SSH_ASKPASS']))
        return true
      end

      AskpassPaths.each do |path|
        if File.executable?(path)
          ENV['SSH_ASKPASS'] = path
          return true
        end
      end

      alert(
        "Can't find ssh-askpass.\nPerhaps you need to install a package."
      )
      return false
    end

Run a shell command via ‘system’. Optionally print command to STDOUT if debugging is enabled.

[Source]

# File lib/sshmenu.rb, line 1168
    def shell_command(command)
      debug(1, "shell_command(#{command})");
      system(command);
    end

Helper routine used by build_window_command to transform a string into a double-quoted string in which special characters have been escaped with backslashes as per standard Bourne shell quoting rules.

[Source]

# File lib/sshmenu.rb, line 1123
    def shell_quote(string)
      return '"' + string.gsub(/([\\"$`])/, '\\\\\1') + '"'
    end

Run method for non-GUI actions. Attempts to initiate a connection to each host listed in ARGV.

[Source]

# File lib/sshmenu.rb, line 647
    def shell_run
      return if ARGV.empty?
      ARGV.each do |name|
        open_win(@config.host_by_name(name))
      end
    end

Toggles the visibility of the text entry widget based on the value of the ‘show text entry’ option (requires at least version 0.19 of the Ruby Gtk bindings)

[Source]

# File lib/sshmenu.rb, line 490
    def show_hide_text_entry
      return unless @entry_box
      if @config.show_entry?
        return unless can_show_entry?
        @entry_box.show_all if not @entry_box.visible?
      elsif @entry_box.visible?
        @entry_box.hide
        @app_win.resize(1,1) if @app_win.respond_to?('resize')
      end
    end

Called by the main application button click handler. Makes sure the latest config data has been loaded; constructs a menu from that config and displays the menu.

[Source]

# File lib/sshmenu.rb, line 874
    def show_hosts_menu(event)
      get_latest_config

      mif = Gtk::ItemFactory.new(Gtk::ItemFactory::TYPE_MENU, "<main>", nil)

      @config.each_item() do |parents, item|
        if item.host?
          menu_add_host(mif, parents, item)
        elsif item.separator?
          menu_add_separator(mif, parents, item)
        elsif item.menu?
          if menu_add_menu_options(mif, parents, item)
            sep_path = item_path(parents, item) + '/<opt_sep>'
            mif.create_item(sep_path, '<Separator>')
          end
        end
      end

      add_tools_menu_items(mif)

      menu = mif.get_widget('<main>')
      menu.screen = @@display
      menu.popup(nil, nil, event.button, event.time){ menu_position(menu, event) }

      return false  # allow button press handling to continue
    end

Called from build_window_command to determine the name of the ssh command to use to connect to the specified host. Normally returns ‘ssh’ but if the supplied SSHMenu::HostItem object has its enable_bcvi property set to true then ‘bcvi —wrap-ssh —’ will be returned instead.

[Source]

# File lib/sshmenu.rb, line 1111
    def ssh_command(host)
      if host.enable_bcvi
        return 'bcvi --wrap-ssh --'
      else
        return 'ssh'
      end
    end

Signal handler called to update the list of matching host connection actions when text is typed into the entry box

[Source]

# File lib/sshmenu.rb, line 594
    def update_entry_actions(completion, text)
      # Clear out current actions
      while not @completion_actions.empty? do
        completion.delete_action(0)
        @completion_actions.shift
      end
      return unless text.length > 0

      # Build a new list of actions
      match_start = []
      match_other = []
      pattern     = Regexp.quote(text)
      @config.each_item do |parents, item|
        next unless item.host?
        if item.title =~ /^#{pattern}/i
          match_start.push(item)
        elsif item.title =~ /#{pattern}/i
          match_other.push(item)
        end
      end

      @completion_actions = [match_start, match_other].flatten[0..9]
      @completion_actions.each_with_index do |item,i|
        completion.insert_action_markup(i, "<b>Host:</b> #{item.title}")
      end

      i = @completion_actions.length
      @completion_actions.push(text)
      completion.insert_action_markup(i, "<b>Add menu item:</b> #{text}")
    end

Signal handler called to update the list of matching auto-completions when text is typed into the entry box

[Source]

# File lib/sshmenu.rb, line 579
    def update_entry_completions(store, text)
      store.clear
      return unless text.length > 0

      i = 0
      @history.each_match(text) do |line|
        store.set_value(store.append, 0, line)
        i += 1
        break if i > 10
      end
    end

[Validate]