diff --git a/terminatorlib/plugins/custom_commands.py b/terminatorlib/plugins/custom_commands.py index e624358e..99d45ac5 100644 --- a/terminatorlib/plugins/custom_commands.py +++ b/terminatorlib/plugins/custom_commands.py @@ -1,8 +1,15 @@ # Terminator by Chris Jones # GPL v2 only +# +# -added keybinding, bookmark functionality +# -made name parsing to menu, optional +# - Vishweshwar Saran Singh Deo vssdeo@gmail.com +# TODO: tags + """custom_commands.py - Terminator Plugin to add custom command menu entries""" import sys import os +import time # Fix imports when testing this file directly if __name__ == '__main__': @@ -14,8 +21,17 @@ import terminatorlib.plugin as plugin from terminatorlib.config import Config from terminatorlib.translation import _ from terminatorlib.util import get_config_dir, err, dbg, gerr +from terminatorlib.terminator import Terminator -(CC_COL_ENABLED, CC_COL_NAME, CC_COL_COMMAND) = list(range(0,3)) +from terminatorlib.plugin import KeyBindUtil + +(CC_COL_ENABLED, CC_COL_NAME, CC_COL_NAME_PARSE, CC_COL_COMMAND) = list(range(0,4)) + +PluginActAdd = "plugin_add" +PluginActBmk = "plugin_bmk" + +PluginAddDesc = "Plugin Add Bookmark" +PluginBmkDesc = "Plugin Open Bookmark Preferences" # Every plugin you want Terminator to load *must* be listed in 'AVAILABLE' AVAILABLE = ['CustomCommandsMenu'] @@ -25,10 +41,27 @@ class CustomCommandsMenu(plugin.MenuItem): capabilities = ['terminal_menu'] cmd_list = {} conf_file = os.path.join(get_config_dir(),"custom_commands") + keyb = None def __init__( self): + + # In prev code dbox is needed if _create_command_dialog func is called + # after configure func where dbox is init. In case we call + # _create_command_dialog without calling configure func, like in quick + # bookmark add then we need to check. + self.dbox = None + config = Config() sections = config.plugin_get_config(self.__class__.__name__) + + self.connect_signals() + self.keyb = KeyBindUtil(config) + self.keyb.bindkey_check_config( + [PluginAddDesc , PluginActAdd, "b"]) + + self.keyb.bindkey_check_config( + [PluginBmkDesc , PluginActBmk, "b"]) + if not isinstance(sections, dict): return noord_cmds = [] @@ -38,24 +71,110 @@ class CustomCommandsMenu(plugin.MenuItem): print("CustomCommandsMenu: Ignoring section %s" % s) continue name = s["name"] + name_parse = s.get("name_parse", "True") command = s["command"] enabled = s["enabled"] and s["enabled"] or False if "position" in s: self.cmd_list[int(s["position"])] = {'enabled' : enabled, 'name' : name, + 'name_parse' : name_parse, 'command' : command } else: noord_cmds.append( {'enabled' : enabled, 'name' : name, + 'name_parse' : name_parse, 'command' : command } ) for cmd in noord_cmds: self.cmd_list[len(self.cmd_list)] = cmd + + def unload(self): + dbg("unloading") + for window in self.windows: + try: + window.disconnect_by_func(self.on_keypress) + except: + dbg("no connected signals") + + self.keyb.unbindkey( + [PluginAddDesc , PluginActAdd, "b"]) + self.keyb.unbindkey( + [PluginBmkDesc , PluginActBmk, "b"]) + + def connect_signals(self): + self.windows = Terminator().get_windows() + for window in self.windows: + window.connect('key-press-event', self.on_keypress) + + def get_last_exe_cmd(self): + cur_win = Terminator().last_focused_term.get_toplevel() + + #TODO: there has to be a better way to get the last command executed + focus_term = cur_win.get_focussed_terminal() + tmp_file = os.path.join(os.sep, 'tmp', 'term_cmd') + command = 'fc -n -l -1 -1 > ' + tmp_file + '; #bookmark last cmd\n' + focus_term.vte.feed_child(str(command).encode("utf-8")) + + fsz = 0 + count = 0 + while not (count == 2 or fsz): + time.sleep(0.1) + if os.path.exists(tmp_file): + fsz = os.path.getsize(tmp_file) + count += 1 + + last_cmd = None + try: + with open(tmp_file, 'r') as file: + last_cmd = file.read() + file.close() + except Exception as ex: + err('Unable to open ā€˜%sā€™ ex: (%s)' % (tmp_file, ex)) + + if os.path.exists(tmp_file): + os.remove(tmp_file) + + if last_cmd: + last_cmd = last_cmd.rstrip() + dbg('last exec cmd: (%s)' % last_cmd) + return last_cmd + + def get_last_exe_cmd_dialog_vars(self): + last_exe_cmd = self.get_last_exe_cmd() + + dialog_vars = { 'enabled' : True, + 'name' : last_exe_cmd, + 'name_parse': False, + 'command' : last_exe_cmd } + return dialog_vars + + + def on_keypress(self, widget, event): + act = self.keyb.keyaction(event) + dbg("keyaction: (%s) (%s)" % (str(act), event.keyval)) + + if act == PluginActAdd: + dbg("add bookmark") + + self.setup_store() + dialog_vars = self.get_last_exe_cmd_dialog_vars() + self.on_new(None, {'dialog_vars' : dialog_vars }) + self.update_cmd_list(self.store) + self._save_config() + return True + + if act == PluginActBmk: + dbg("open custom command preferences") + self.configure(None) + return True + + def callback(self, menuitems, menu, terminal): + """Add our menu items to the menu""" submenus = {} item = Gtk.MenuItem.new_with_mnemonic(_('_Custom Commands')) @@ -81,16 +200,20 @@ class CustomCommandsMenu(plugin.MenuItem): branch_names = command['name'].split('/')[:-1] target_submenu = submenu parent_submenu = submenu - for idx in range(len(branch_names)): - lookup_name = '/'.join(branch_names[0:idx+1]) - target_submenu = submenus.get(lookup_name, None) - if not target_submenu: - item = Gtk.MenuItem(_(branch_names[idx])) - parent_submenu.append(item) - target_submenu = Gtk.Menu() - item.set_submenu(target_submenu) - submenus[lookup_name] = target_submenu - parent_submenu = target_submenu + if not command['name_parse']: + leaf_name = command['name'] + branch_names = '' + else: + for idx in range(len(branch_names)): + lookup_name = '/'.join(branch_names[0:idx+1]) + target_submenu = submenus.get(lookup_name, None) + if not target_submenu: + item = Gtk.MenuItem(_(branch_names[idx])) + parent_submenu.append(item) + target_submenu = Gtk.Menu() + item.set_submenu(target_submenu) + submenus[lookup_name] = target_submenu + parent_submenu = target_submenu if iconinfo: image = Gtk.Image() image.set_from_icon_name(exe, Gtk.IconSize.MENU) @@ -109,11 +232,13 @@ class CustomCommandsMenu(plugin.MenuItem): for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ] : enabled = command['enabled'] name = command['name'] + name_parse = command['name_parse'] command = command['command'] item = {} item['enabled'] = enabled item['name'] = name + item['name_parse'] = name_parse item['command'] = command item['position'] = i @@ -128,6 +253,13 @@ class CustomCommandsMenu(plugin.MenuItem): for terminal in data['terminals']: terminal.vte.feed_child(command.encode()) + def setup_store(self): + self.store = Gtk.ListStore(bool, str, bool, str) + for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ]: + self.store.append([command['enabled'], command['name'], + command['name_parse'], command['command']]) + return self.store + def configure(self, widget, data = None): ui = {} dbox = Gtk.Dialog( @@ -139,7 +271,8 @@ class CustomCommandsMenu(plugin.MenuItem): _("_OK"), Gtk.ResponseType.ACCEPT ) ) - dbox.set_transient_for(widget.get_toplevel()) + if widget: + dbox.set_transient_for(widget.get_toplevel()) icon_theme = Gtk.IconTheme.get_default() if icon_theme.lookup_icon('terminator-custom-commands', 48, 0): @@ -149,11 +282,8 @@ class CustomCommandsMenu(plugin.MenuItem): icon = dbox.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON) dbox.set_icon(icon) - store = Gtk.ListStore(bool, str, str) + store = self.setup_store() - for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ]: - store.append([command['enabled'], command['name'], command['command']]) - treeview = Gtk.TreeView(store) #treeview.connect("cursor-changed", self.on_cursor_changed, ui) selection = treeview.get_selection() @@ -226,7 +356,10 @@ class CustomCommandsMenu(plugin.MenuItem): button.set_sensitive(False) ui['button_delete'] = button - + button = Gtk.Button(_("Bookmark Last Cmd")) + button_box.pack_start(button, False, True, 0) + button.connect("clicked", self.on_last_exe_cmd, ui) + ui['button_save_last_cmd'] = button hbox.pack_start(button_box, False, True, 0) self.dbox = dbox @@ -237,6 +370,7 @@ class CustomCommandsMenu(plugin.MenuItem): self._save_config() del(self.dbox) dbox.destroy() + self.dbox = None return @@ -245,12 +379,14 @@ class CustomCommandsMenu(plugin.MenuItem): self.cmd_list = {} i=0 while iter: - (enabled, name, command) = store.get(iter, + (enabled, name, name_parse, command) = store.get(iter, CC_COL_ENABLED, CC_COL_NAME, + CC_COL_NAME_PARSE, CC_COL_COMMAND) self.cmd_list[i] = {'enabled' : enabled, 'name': name, + 'name_parse' : name_parse, 'command' : command} iter = store.iter_next(iter) i = i + 1 @@ -278,7 +414,8 @@ class CustomCommandsMenu(plugin.MenuItem): data['button_edit'].set_sensitive(iter is not None) data['button_delete'].set_sensitive(iter is not None) - def _create_command_dialog(self, enabled_var = False, name_var = "", command_var = ""): + def _create_command_dialog(self, enabled_var = False, name_var = "", + name_parse_var = "", command_var = ""): dialog = Gtk.Dialog( _("New Command"), None, @@ -288,38 +425,85 @@ class CustomCommandsMenu(plugin.MenuItem): _("_OK"), Gtk.ResponseType.ACCEPT ) ) - dialog.set_transient_for(self.dbox) - table = Gtk.Table(3, 2) + + #since we call this via shortcut keybinding + #lets focus on OK button + buttonbox = dialog.get_action_area() + buttons = buttonbox.get_children() + dialog.set_focus(buttons[1]) + + # dbox is init in configure function, in case we want to + # create dialog directly + + if self.dbox: + dialog.set_transient_for(self.dbox) + + table = Gtk.Table(4, 2) + table.set_row_spacings(5) + table.set_col_spacings(5) label = Gtk.Label(label=_("Enabled:")) + label.set_alignment(0, 0) table.attach(label, 0, 1, 0, 1) enabled = Gtk.CheckButton() enabled.set_active(enabled_var) table.attach(enabled, 1, 2, 0, 1) - label = Gtk.Label(label=_("Name:")) + label = Gtk.Label(label=_("Parse Name into SubMenu's:")) + label.set_alignment(0, 0) table.attach(label, 0, 1, 1, 2) + name_parse = Gtk.CheckButton() + name_parse.set_active(name_parse_var) + table.attach(name_parse, 1, 2, 1, 2) + + label = Gtk.Label(label=_("Name:")) + table.attach(label, 0, 1, 2, 3) name = Gtk.Entry() name.set_text(name_var) - table.attach(name, 1, 2, 1, 2) + table.attach(name, 1, 2, 2, 3) label = Gtk.Label(label=_("Command:")) - table.attach(label, 0, 1, 2, 3) + table.attach(label, 0, 1, 3, 4) command = Gtk.TextView() command.get_buffer().set_text(command_var) - table.attach(command, 1, 2, 2, 3) + table.attach(command, 1, 2, 3, 4) - dialog.vbox.pack_start(table, True, True, 0) + dialog.vbox.pack_start(table, True, True, 10) dialog.show_all() - return (dialog,enabled,name,command) + return (dialog,enabled,name,name_parse,command) + + def on_last_exe_cmd(self, button, data): + new_data = data.copy() + new_data['dialog_vars'] = self.get_last_exe_cmd_dialog_vars() + self.on_new(button, new_data) def on_new(self, button, data): - (dialog,enabled,name,command) = self._create_command_dialog() + + #default values can be passed to dialogue window if required + enabled_var = '' + name_var = '' + name_parse_var= '' + command_var = '' + + if data and 'dialog_vars' in data: + dialog_vars = data.get('dialog_vars', {}) + enabled_var = dialog_vars.get('enabled', True) + name_var = dialog_vars.get('name', '') + name_parse_var= dialog_vars.get('name_parse', False) + command_var = dialog_vars.get('command', '') + + (dialog,enabled,name,name_parse,command) = self._create_command_dialog( + enabled_var = enabled_var, + name_var = name_var, + name_parse_var = name_parse_var, + command_var = command_var) + res = dialog.run() item = {} if res == Gtk.ResponseType.ACCEPT: item['enabled'] = enabled.get_active() item['name'] = name.get_text() + item['name_parse'] = name_parse.get_active() item['command'] = command.get_buffer().get_text(command.get_buffer().get_start_iter(), command.get_buffer().get_end_iter(), True) if item['name'] == '' or item['command'] == '': err = Gtk.MessageDialog(dialog, @@ -332,7 +516,9 @@ class CustomCommandsMenu(plugin.MenuItem): err.destroy() else: # we have a new command - store = data['treeview'].get_model() + store = data['treeview'].get_model() if 'treeview' in data else None + if not store: + store = self.setup_store() iter = store.get_iter_first() name_exist = False while iter != None: @@ -341,7 +527,8 @@ class CustomCommandsMenu(plugin.MenuItem): break iter = store.iter_next(iter) if not name_exist: - store.append((item['enabled'], item['name'], item['command'])) + store.append((item['enabled'], item['name'], + item['name_parse'], item['command'])) else: gerr(_("Name *%s* already exist") % item['name']) dialog.destroy() @@ -420,16 +607,17 @@ class CustomCommandsMenu(plugin.MenuItem): if not iter: return - (dialog,enabled,name,command) = self._create_command_dialog( - enabled_var = store.get_value(iter, CC_COL_ENABLED), - name_var = store.get_value(iter, CC_COL_NAME), - command_var = store.get_value(iter, CC_COL_COMMAND) - ) + (dialog,enabled,name,name_parse,command) = self._create_command_dialog( + enabled_var = store.get_value(iter, CC_COL_ENABLED), + name_var = store.get_value(iter, CC_COL_NAME), + name_parse_var = store.get_value(iter, CC_COL_NAME_PARSE), + command_var = store.get_value(iter, CC_COL_COMMAND)) res = dialog.run() item = {} if res == Gtk.ResponseType.ACCEPT: item['enabled'] = enabled.get_active() item['name'] = name.get_text() + item['name_parse'] = name_parse.get_active() item['command'] = command.get_buffer().get_text(command.get_buffer().get_start_iter(), command.get_buffer().get_end_iter(), True) if item['name'] == '' or item['command'] == '': err = Gtk.MessageDialog(dialog, @@ -452,14 +640,15 @@ class CustomCommandsMenu(plugin.MenuItem): store.set(iter, CC_COL_ENABLED,item['enabled'], CC_COL_NAME, item['name'], + CC_COL_NAME_PARSE, item['name_parse'], CC_COL_COMMAND, item['command'] ) else: gerr(_("Name *%s* already exist") % item['name']) dialog.destroy() - - + + if __name__ == '__main__': c = CustomCommandsMenu() c.configure(None, None)