terminator/terminatorlib/plugins/custom_commands.py
Vishweshwar Saran Singh Deo a5ddcbe255 [bug 706] 706-Favorites-Bookmarks-Plugin #706
- the dialog window taking command for bookmark via shortcut keybinding will have focus on OK Button for faster interaction
- fixed a transient window issue for loading pref window via shortcut and context menu
- filed new command text area  was getting filled with last command
- fixed list store init
2023-10-17 01:11:21 +05:30

657 lines
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Terminator by Chris Jones <cmsj@tenshu.net>
# 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__':
sys.path.append( os.path.join(os.path.dirname(__file__), "../.."))
from gi.repository import Gtk
from gi.repository import GObject
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
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']
class CustomCommandsMenu(plugin.MenuItem):
"""Add custom commands to the terminal menu"""
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, "<Alt>b"])
self.keyb.bindkey_check_config(
[PluginBmkDesc , PluginActBmk, "<Shift><Alt>b"])
if not isinstance(sections, dict):
return
noord_cmds = []
for part in sections:
s = sections[part]
if not ("name" in s and "command" in s):
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, "<Alt>b"])
self.keyb.unbindkey(
[PluginBmkDesc , PluginActBmk, "<Shift><Alt>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'))
menuitems.append(item)
submenu = Gtk.Menu()
item.set_submenu(submenu)
menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Preferences'))
menuitem.connect("activate", self.configure)
submenu.append(menuitem)
menuitem = Gtk.SeparatorMenuItem()
submenu.append(menuitem)
theme = Gtk.IconTheme.get_default()
for command in [ self.cmd_list[key] for key in sorted(self.cmd_list.keys()) ] :
if not command['enabled']:
continue
exe = command['command'].split(' ')[0]
iconinfo = theme.choose_icon([exe], Gtk.IconSize.MENU, Gtk.IconLookupFlags.USE_BUILTIN)
leaf_name = command['name'].split('/')[-1]
branch_names = command['name'].split('/')[:-1]
target_submenu = submenu
parent_submenu = 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)
menuitem = Gtk.ImageMenuItem(leaf_name)
menuitem.set_image(image)
else:
menuitem = Gtk.MenuItem(leaf_name)
terminals = terminal.terminator.get_target_terms(terminal)
menuitem.connect("activate", self._execute, {'terminals' : terminals, 'command' : command['command'] })
target_submenu.append(menuitem)
def _save_config(self):
config = Config()
config.plugin_del_config(self.__class__.__name__)
i = 0
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
config.plugin_set(self.__class__.__name__, name, item)
i = i + 1
config.save()
def _execute(self, widget, data):
command = data['command']
if command[-1] != '\n':
command = command + '\n'
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(
_("Custom Commands Configuration"),
None,
Gtk.DialogFlags.MODAL,
(
_("_Cancel"), Gtk.ResponseType.REJECT,
_("_OK"), Gtk.ResponseType.ACCEPT
)
)
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):
dbox.set_icon_name('terminator-custom-commands')
else:
dbg('Unable to load Terminator custom command icon')
icon = dbox.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON)
dbox.set_icon(icon)
store = self.setup_store()
treeview = Gtk.TreeView(store)
#treeview.connect("cursor-changed", self.on_cursor_changed, ui)
selection = treeview.get_selection()
selection.set_mode(Gtk.SelectionMode.SINGLE)
selection.connect("changed", self.on_selection_changed, ui)
ui['treeview'] = treeview
renderer = Gtk.CellRendererToggle()
renderer.connect('toggled', self.on_toggled, ui)
column = Gtk.TreeViewColumn(_("Enabled"), renderer, active=CC_COL_ENABLED)
treeview.append_column(column)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(_("Name"), renderer, text=CC_COL_NAME)
treeview.append_column(column)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(_("Command"), renderer, text=CC_COL_COMMAND)
treeview.append_column(column)
scroll_window = Gtk.ScrolledWindow()
scroll_window.set_size_request(500, 250)
scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll_window.add_with_viewport(treeview)
hbox = Gtk.HBox()
hbox.pack_start(scroll_window, True, True, 0)
dbox.vbox.pack_start(hbox, True, True, 0)
button_box = Gtk.VBox()
button = Gtk.Button(_("Top"))
button_box.pack_start(button, False, True, 0)
button.connect("clicked", self.on_goto_top, ui)
button.set_sensitive(False)
ui['button_top'] = button
button = Gtk.Button(_("Up"))
button_box.pack_start(button, False, True, 0)
button.connect("clicked", self.on_go_up, ui)
button.set_sensitive(False)
ui['button_up'] = button
button = Gtk.Button(_("Down"))
button_box.pack_start(button, False, True, 0)
button.connect("clicked", self.on_go_down, ui)
button.set_sensitive(False)
ui['button_down'] = button
button = Gtk.Button(_("Last"))
button_box.pack_start(button, False, True, 0)
button.connect("clicked", self.on_goto_last, ui)
button.set_sensitive(False)
ui['button_last'] = button
button = Gtk.Button(_("New"))
button_box.pack_start(button, False, True, 0)
button.connect("clicked", self.on_new, ui)
ui['button_new'] = button
button = Gtk.Button(_("Edit"))
button_box.pack_start(button, False, True, 0)
button.set_sensitive(False)
button.connect("clicked", self.on_edit, ui)
ui['button_edit'] = button
button = Gtk.Button(_("Delete"))
button_box.pack_start(button, False, True, 0)
button.connect("clicked", self.on_delete, ui)
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
dbox.show_all()
res = dbox.run()
if res == Gtk.ResponseType.ACCEPT:
self.update_cmd_list(store)
self._save_config()
del(self.dbox)
dbox.destroy()
self.dbox = None
return
def update_cmd_list(self, store):
iter = store.get_iter_first()
self.cmd_list = {}
i=0
while 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
def on_toggled(self, widget, path, data):
treeview = data['treeview']
store = treeview.get_model()
iter = store.get_iter(path)
(enabled, name, command) = store.get(iter,
CC_COL_ENABLED,
CC_COL_NAME,
CC_COL_COMMAND
)
store.set_value(iter, CC_COL_ENABLED, not enabled)
def on_selection_changed(self,selection, data=None):
treeview = selection.get_tree_view()
(model, iter) = selection.get_selected()
data['button_top'].set_sensitive(iter is not None)
data['button_up'].set_sensitive(iter is not None)
data['button_down'].set_sensitive(iter is not None)
data['button_last'].set_sensitive(iter is not None)
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 = "",
name_parse_var = "", command_var = ""):
dialog = Gtk.Dialog(
_("New Command"),
None,
Gtk.DialogFlags.MODAL,
(
_("_Cancel"), Gtk.ResponseType.REJECT,
_("_OK"), Gtk.ResponseType.ACCEPT
)
)
#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=_("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, 2, 3)
label = Gtk.Label(label=_("Command:"))
table.attach(label, 0, 1, 3, 4)
command = Gtk.TextView()
command.get_buffer().set_text(command_var)
table.attach(command, 1, 2, 3, 4)
dialog.vbox.pack_start(table, True, True, 10)
dialog.show_all()
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):
#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,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.CLOSE,
_("You need to define a name and command")
)
err.run()
err.destroy()
else:
# we have a new command
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:
if store.get_value(iter,CC_COL_NAME) == item['name']:
name_exist = True
break
iter = store.iter_next(iter)
if not name_exist:
store.append((item['enabled'], item['name'],
item['name_parse'], item['command']))
else:
gerr(_("Name *%s* already exist") % item['name'])
dialog.destroy()
def on_goto_top(self, button, data):
treeview = data['treeview']
selection = treeview.get_selection()
(store, iter) = selection.get_selected()
if not iter:
return
firstiter = store.get_iter_first()
store.move_before(iter, firstiter)
def on_go_up(self, button, data):
treeview = data['treeview']
selection = treeview.get_selection()
(store, iter) = selection.get_selected()
if not iter:
return
tmpiter = store.get_iter_first()
if(store.get_path(tmpiter) == store.get_path(iter)):
return
while tmpiter:
next = store.iter_next(tmpiter)
if(store.get_path(next) == store.get_path(iter)):
store.swap(iter, tmpiter)
break
tmpiter = next
def on_go_down(self, button, data):
treeview = data['treeview']
selection = treeview.get_selection()
(store, iter) = selection.get_selected()
if not iter:
return
next = store.iter_next(iter)
if next:
store.swap(iter, next)
def on_goto_last(self, button, data):
treeview = data['treeview']
selection = treeview.get_selection()
(store, iter) = selection.get_selected()
if not iter:
return
lastiter = iter
tmpiter = store.get_iter_first()
while tmpiter:
lastiter = tmpiter
tmpiter = store.iter_next(tmpiter)
store.move_after(iter, lastiter)
def on_delete(self, button, data):
treeview = data['treeview']
selection = treeview.get_selection()
(store, iter) = selection.get_selected()
if iter:
store.remove(iter)
return
def on_edit(self, button, data):
treeview = data['treeview']
selection = treeview.get_selection()
(store, iter) = selection.get_selected()
if not iter:
return
(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,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.CLOSE,
_("You need to define a name and command")
)
err.run()
err.destroy()
else:
tmpiter = store.get_iter_first()
name_exist = False
while tmpiter != None:
if store.get_path(tmpiter) != store.get_path(iter) and store.get_value(tmpiter,CC_COL_NAME) == item['name']:
name_exist = True
break
tmpiter = store.iter_next(tmpiter)
if not name_exist:
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)
Gtk.main()