From 0ae2aba1672e82de46e6050c414fa41b926b4be9 Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Sat, 19 Nov 2022 20:06:24 +0530 Subject: [PATCH 1/8] 1) Mouse less/free opening / yanking feature 2) plugin_util file to assist this and other plugins with key-press Helper function 3) adding Key Binding Help Functions in above (2) to config and preferences->keybinding to have a consistent behavior --- terminatorlib/config.py | 15 +- terminatorlib/plugin_util.py | 93 +++++++++++ .../plugins/mousefree_url_handler.py | 158 ++++++++++++++++++ terminatorlib/prefseditor.py | 24 +++ 4 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 terminatorlib/plugin_util.py create mode 100644 terminatorlib/plugins/mousefree_url_handler.py diff --git a/terminatorlib/config.py b/terminatorlib/config.py index daaa1964..4c6307bd 100644 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -77,6 +77,7 @@ from configobj import ConfigObj, flatten_errors from validate import Validator from .borg import Borg from .util import dbg, err, DEBUG, get_system_config_dir, get_config_dir, dict_diff, update_config_to_cell_height +from terminatorlib.plugin_util import KeyBindUtil from gi.repository import Gio @@ -718,7 +719,19 @@ class ConfigBase(Borg): for section_name in ['global_config', 'keybindings']: dbg('Processing section: %s' % section_name) section = getattr(self, section_name) - parser[section_name] = dict_diff(DEFAULTS[section_name], section) + if section_name == 'keybindings': + # for plugin KeyBindUtil assist in plugin_util + keybindutil = KeyBindUtil(); + keyb_keys = keybindutil.get_act_to_keys() + # we only need keys as a reference so to match them + # against new values + keyb_keys = dict.fromkeys(keyb_keys, "") + + default_merged_section = {**keyb_keys, **DEFAULTS[section_name]} + merged_section = {**keyb_keys, **section} + parser[section_name] = dict_diff(default_merged_section, merged_section) + else: + parser[section_name] = dict_diff(DEFAULTS[section_name], section) from .configjson import JSON_PROFILE_NAME, JSON_LAYOUT_NAME diff --git a/terminatorlib/plugin_util.py b/terminatorlib/plugin_util.py new file mode 100644 index 00000000..e4a7c5c4 --- /dev/null +++ b/terminatorlib/plugin_util.py @@ -0,0 +1,93 @@ +""" +-Basic plugin util for key-press handling, has all mapping to be used +in layout keybindings + +Vishweshwar Saran Singh Deo vssdeo@gmail.com +""" + +from gi.repository import Gtk, Gdk + +from terminatorlib.util import get_config_dir, err, dbg, gerr +from terminatorlib.keybindings import Keybindings, KeymapError + +PLUGIN_UTIL_DESC = 0 +PLUGIN_UTIL_ACT = 1 +PLUGIN_UTIL_KEYS = 2 + +#FIXME to sync this with keybinding preferences +class KeyBindUtil: + + keybindings = Keybindings() + + map_key_to_act = {} + map_act_to_keys = {} + map_act_to_desc = {} + + + #Example + # bind + # first param is desc, second is action str + # self.keyb.bindkey([PluginUrlFindNext , PluginUrlActFindNext, "j"]) + # + # get action name + # act = self.keyb.keyaction(event) + + # if act == "url_find_next": + + + #check map key_val_mask -> action + def _check_keybind_change(self, key): + act = key[PLUGIN_UTIL_ACT] + for key_val_mask in self.map_key_to_act: + old_act = self.map_key_to_act[key_val_mask] + if act == old_act: + return key_val_mask + return None + + #check in config before binding + def bindkey_check_config(self, key, config): + actstr = key[PLUGIN_UTIL_ACT] + kbsect = config.base.get_item('keybindings') + keystr = kbsect[actstr] if actstr in kbsect else "" + dbg("bindkey_check_config:action (%s) key str:(%s)" % (actstr, keystr)) + if len(keystr): + key[PLUGIN_UTIL_KEYS] = keystr + dbg("found new Action->KeyVal in config: (%s, %s)" + % (actstr, keystr)); + self.bindkey(key) + + def bindkey(self, key): + (keyval, mask) = self.keybindings._parsebinding(key[PLUGIN_UTIL_KEYS]) + mask = Gdk.ModifierType(mask) + + ret = (keyval, mask) + dbg("bindkey: (%s) (%s)" % (key[PLUGIN_UTIL_KEYS], str(ret))) + + #remove if any old key_val_mask + old_key_val_mask = self._check_keybind_change(key) + if old_key_val_mask: + dbg("found old key binding, removing: (%s)" % str(old_key_val_mask)) + del self.map_key_to_act[old_key_val_mask] + + #map key-val-mask to action, used to ease key-press management + self.map_key_to_act[ret] = key[PLUGIN_UTIL_ACT] + + + #map action to key-combo-str, used in preferences->keybinding + self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_KEYS] + #map action to key-combo description, in used preferences->keybinding + self.map_act_to_desc[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_DESC] + + def keyaction(self, event): + #FIXME MOD2 mask comes in the event, remove + event.state &= ~Gdk.ModifierType.MOD2_MASK + + ret = (event.keyval, event.state) + dbg("keyaction: (%s)" % str(ret)) + return self.map_key_to_act.get(ret, None) + + def get_act_to_keys(self): + return self.map_act_to_keys + + def get_act_to_desc(self): + return self.map_act_to_desc diff --git a/terminatorlib/plugins/mousefree_url_handler.py b/terminatorlib/plugins/mousefree_url_handler.py new file mode 100644 index 00000000..37c80f90 --- /dev/null +++ b/terminatorlib/plugins/mousefree_url_handler.py @@ -0,0 +1,158 @@ +""" +- With min changes to main code base, implementing mouse free url handling. +- Using shortcuts to browse URL, next / previous, clear search. Selected URL +is copied to clipboard. + +- Vishweshwar Saran Singh Deo vssdeo@gmail.com +""" + +import gi +gi.require_version('Vte', '2.91') # vte-0.38 (gnome-3.14) +from gi.repository import Vte + +from terminatorlib.terminator import Terminator + +from terminatorlib.config import Config +import terminatorlib.plugin as plugin +from terminatorlib.plugin_util import KeyBindUtil + +from terminatorlib.util import get_config_dir, err, dbg, gerr +from terminatorlib.keybindings import Keybindings, KeymapError +from terminatorlib import regex + +import re + + +AVAILABLE = ['MouseFreeURLHandler'] + +PluginUrlActFindNext = "plugin_url_find_next" +PluginUrlActFindPrev = "plugin_url_find_prev" +PluginUrlActEsc = "plugin_url_esc" +PluginUrlActLaunch = "plugin_url_launch" + + +PluginUrlFindNext = "Plugin Url Find Next" +PluginUrlFindPrev = "Plugin Url Find Prev" +PluginUrlEsc = "Plugin Url Esc" +PluginUrlLaunch = "Plugin Url Launch" + +class MouseFreeURLHandler(plugin.Plugin): + + capabilities = ['url_handler'] + handler_name = 'MouseFreeURLHandler' + nameopen = None + namecopy = None + match = None + + flag_http_on = False + keyb = KeyBindUtil() + matches = [] + matches_ptr = -1 + #basic pattern + searchtext = "https?\:\/\/[^\s]+[\/\w]" + + def __init__(self): + self.connect_signals() + + config = Config() + self.keyb.bindkey_check_config([PluginUrlFindNext , PluginUrlActFindNext, "j"], config) + self.keyb.bindkey_check_config([PluginUrlFindPrev , PluginUrlActFindPrev, "k"], config) + self.keyb.bindkey_check_config([PluginUrlEsc , PluginUrlActEsc, "Escape"], config) + self.keyb.bindkey_check_config([PluginUrlLaunch, PluginUrlActLaunch, "Return"], config) + + def connect_signals(self): + self.windows = Terminator().get_windows() + for window in self.windows: + window.connect('key-press-event', self.on_keypress) + return + + def callback(self, url): + """Actually we don't need to do anything for this to work""" + return(url) + + def extract(self): + #can we do extract more efficiently + col, row = self.vte.get_cursor_position() + (txt, attr) = self.vte.get_text_range(0,0,row, col) + self.matches = re.findall(self.searchtext, txt) + self.matches_ptr = len(self.matches)-1 + + def get_term(self): + return Terminator().last_focused_term + + def get_selected_url(self): + if len(self.matches): + dbg("selected URL (%s %s)" % (self.matches_ptr, self.matches[self.matches_ptr])) + return self.matches[self.matches_ptr] + dbg("selected URL (%s %s)" % (self.matches_ptr, "not found")) + return None + + def on_keypress(self, widget, event): + act = self.keyb.keyaction(event) + dbg("keyaction: (%s) (%s)" % (str(act), event.keyval)) + + if act == PluginUrlActFindNext: + if not self.flag_http_on: + dbg("search URL on") + self.search() + self.extract() + #so when we start search last item be selected + self.vte.search_find_previous() + self.get_selected_url() # dbg url print + self.vte.copy_clipboard() + return True + else: + self.vte.search_find_next() + + if (self.matches_ptr < len(self.matches)-1): + self.matches_ptr += 1 + else: + self.matches_ptr = 0 + + self.vte.copy_clipboard() + self.get_selected_url() # dbg url print + return True + + if act == PluginUrlActFindPrev: + if not self.flag_http_on: + self.search() + self.extract() + self.get_selected_url() # dbg url print + self.vte.search_find_previous() + self.vte.copy_clipboard() + return True + + self.vte.search_find_previous() + + if self.matches_ptr > 0: + self.matches_ptr -= 1 + elif len(self.matches): + self.matches_ptr = len(self.matches)-1 + + self.vte.copy_clipboard() + self.get_selected_url() # dbg url print + return True + + if act == PluginUrlActEsc: + self.matches = [] + self.vte = self.get_term().get_vte() + self.vte.search_set_regex(None, 0) + self.flag_http_on = False + dbg("search URL off") + + if act == PluginUrlActLaunch: + url = self.get_selected_url() + if url: + self.get_term().open_url(url, prepare=False) + + def search(self): + self.flag_http_on = True + self.vte = self.get_term().get_vte() + + self.vte.search_set_wrap_around(True) + regex_flags_pcre2 = (regex.FLAGS_PCRE2 | regex.PCRE2_CASELESS) + searchre = Vte.Regex.new_for_search(self.searchtext, + len(self.searchtext), regex_flags_pcre2) + + self.vte.search_set_regex(searchre, 0) + diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 10339c30..2a394c62 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -18,6 +18,8 @@ from .terminator import Terminator from .plugin import PluginRegistry from .version import APP_NAME +from .plugin_util import KeyBindUtil + def get_color_string(widcol): return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8)) @@ -419,6 +421,15 @@ class PrefsEditor: liststore = widget.get_model() liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING) keybindings = self.config['keybindings'] + + keybindutil = KeyBindUtil() + plugin_keyb_act = keybindutil.get_act_to_keys() + plugin_keyb_desc = keybindutil.get_act_to_desc() + #merge give preference to main bindings over plugin + keybindings = {**plugin_keyb_act, **keybindings} + self.keybindingnames = {**plugin_keyb_desc, **self.keybindingnames} + #dbg("appended actions %s names %s" % (keybindings, self.keybindingnames)) + for keybinding in keybindings: keyval = 0 mask = 0 @@ -1797,6 +1808,19 @@ class PrefsEditor: binding = liststore.get_value(liststore.get_iter(path), 0) accel = Gtk.accelerator_name(key, mods) self.config['keybindings'][binding] = accel + + keybindutil = KeyBindUtil() + plugin_keyb_desc = keybindutil.get_act_to_desc() + + if binding in plugin_keyb_desc: + if binding in plugin_keyb_desc: + desc = plugin_keyb_desc[binding] + dbg("modifying plugin binding: %s, %s, %s" % (desc, binding, accel)) + keybindutil.bindkey([desc, binding, accel]) + else: + dbg("skipping: %s" % binding) + pass + self.config.save() def on_cellrenderer_accel_cleared(self, liststore, path): From 7bbb07c99348a9d6c57fed8f75e12d13e34ef3fd Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Mon, 21 Nov 2022 16:24:32 +0530 Subject: [PATCH 2/8] [bug 681] Plugin Submission + Generic Plugin Utility Functions & KeyBinding Feature: Mouseless / Mousefree / Keyboard URL opening or yanking #681 - there was a bug wherein the duplicate keybindings from plugins were not throwing the error "Duplicate Key Bindings Are Not Allowed", now while checking they are merged and then final list of keybindings are checked. --- terminatorlib/prefseditor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 9d3bd7e9..5cdfb336 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -1814,8 +1814,14 @@ class PrefsEditor: current_binding = liststore.get_value(liststore.get_iter(path), 0) parsed_accel = Gtk.accelerator_parse(accel) + keybindutil = KeyBindUtil() + keybindings = self.config["keybindings"] + #merge give preference to main bindings over plugin + plugin_keyb_act = keybindutil.get_act_to_keys() + keybindings = {**plugin_keyb_act, **keybindings} + duplicate_bindings = [] - for conf_binding, conf_accel in self.config["keybindings"].items(): + for conf_binding, conf_accel in keybindings.items(): if conf_accel is None: continue @@ -1859,7 +1865,6 @@ class PrefsEditor: accel = Gtk.accelerator_name(key, mods) self.config['keybindings'][binding] = accel - keybindutil = KeyBindUtil() plugin_keyb_desc = keybindutil.get_act_to_desc() if binding in plugin_keyb_desc: From 3e15ae40dfd23fde7d56be138cf7383a4de31fbe Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Mon, 21 Nov 2022 18:12:54 +0530 Subject: [PATCH 3/8] [bug 681] Plugin Submission + Generic Plugin Utility Functions & KeyBinding Feature: Mouseless / Mousefree / Keyboard URL opening or yanking #681 -convert control keys keyval to standard lowercase, else signals are missing --- terminatorlib/plugin_util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/terminatorlib/plugin_util.py b/terminatorlib/plugin_util.py index e4a7c5c4..e6547686 100644 --- a/terminatorlib/plugin_util.py +++ b/terminatorlib/plugin_util.py @@ -58,6 +58,7 @@ class KeyBindUtil: def bindkey(self, key): (keyval, mask) = self.keybindings._parsebinding(key[PLUGIN_UTIL_KEYS]) + keyval = Gdk.keyval_to_lower(keyval) mask = Gdk.ModifierType(mask) ret = (keyval, mask) @@ -82,7 +83,8 @@ class KeyBindUtil: #FIXME MOD2 mask comes in the event, remove event.state &= ~Gdk.ModifierType.MOD2_MASK - ret = (event.keyval, event.state) + keyval = Gdk.keyval_to_lower(event.keyval) + ret = (keyval, event.state) dbg("keyaction: (%s)" % str(ret)) return self.map_key_to_act.get(ret, None) From 8edebc6ca53cfa1c4074a9ad455c876a3a981be4 Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Tue, 22 Nov 2022 15:18:26 +0530 Subject: [PATCH 4/8] [bug 681] Plugin Submission + Generic Plugin Utility Functions & KeyBinding Feature: Mouseless / Mousefree / Keyboard URL opening or yanking #681 - removed plugin_util - added key unbind feature - updated plugin helper - updated plugin helper - to have config as part of constructor - updated MouseFreeURLHandler for plugin.py, unload - removed circular dependency between plugin.py and config.py due to KeyBindUtil due to merging of plugin_util.py --- terminatorlib/config.py | 2 +- terminatorlib/plugin.py | 121 ++++++++++++++++++ terminatorlib/plugin_util.py | 95 -------------- .../plugins/mousefree_url_handler.py | 37 ++++-- terminatorlib/prefseditor.py | 2 +- 5 files changed, 148 insertions(+), 109 deletions(-) delete mode 100644 terminatorlib/plugin_util.py diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 7bb23c7f..7129512c 100644 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -77,7 +77,6 @@ from configobj import ConfigObj, flatten_errors from validate import Validator from .borg import Borg from .util import dbg, err, DEBUG, get_system_config_dir, get_config_dir, dict_diff, update_config_to_cell_height -from terminatorlib.plugin_util import KeyBindUtil from gi.repository import Gio @@ -721,6 +720,7 @@ class ConfigBase(Borg): dbg('Processing section: %s' % section_name) section = getattr(self, section_name) if section_name == 'keybindings': + from terminatorlib.plugin import KeyBindUtil # for plugin KeyBindUtil assist in plugin_util keybindutil = KeyBindUtil(); keyb_keys = keybindutil.get_act_to_keys() diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 7a0487a4..db18835b 100644 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -187,3 +187,124 @@ class MenuItem(Plugin): def callback(self, menuitems, menu, terminal): """Callback to transform the enclosed URL""" raise NotImplementedError + + +""" +-Basic plugin util for key-press handling, has all mapping to be used +in layout keybindings + +Vishweshwar Saran Singh Deo vssdeo@gmail.com +""" + +from gi.repository import Gtk, Gdk +from terminatorlib.keybindings import Keybindings, KeymapError + +PLUGIN_UTIL_DESC = 0 +PLUGIN_UTIL_ACT = 1 +PLUGIN_UTIL_KEYS = 2 + +class KeyBindUtil: + + keybindings = Keybindings() + + map_key_to_act = {} + map_act_to_keys = {} + map_act_to_desc = {} + + def __init__(self, config=None): + self.config = config + + #Example + # bind + # first param is desc, second is action str + # self.keyb.bindkey([PluginUrlFindNext , PluginUrlActFindNext, "j"]) + # + # get action name + # act = self.keyb.keyaction(event) + + # if act == "url_find_next": + + + #check map key_val_mask -> action + def _check_keybind_change(self, key): + act = key[PLUGIN_UTIL_ACT] + for key_val_mask in self.map_key_to_act: + old_act = self.map_key_to_act[key_val_mask] + if act == old_act: + return key_val_mask + return None + + #check in config before binding + def bindkey_check_config(self, key): + if not self.config: + raise Warning("bindkey_check_config called without config init") + + actstr = key[PLUGIN_UTIL_ACT] + kbsect = self.config.base.get_item('keybindings') + keystr = kbsect[actstr] if actstr in kbsect else "" + dbg("bindkey_check_config:action (%s) key str:(%s)" % (actstr, keystr)) + if len(keystr): + key[PLUGIN_UTIL_KEYS] = keystr + dbg("found new Action->KeyVal in config: (%s, %s)" + % (actstr, keystr)); + self.bindkey(key) + + def bindkey(self, key): + (keyval, mask) = self.keybindings._parsebinding(key[PLUGIN_UTIL_KEYS]) + keyval = Gdk.keyval_to_lower(keyval) + mask = Gdk.ModifierType(mask) + + ret = (keyval, mask) + dbg("bindkey: (%s) (%s)" % (key[PLUGIN_UTIL_KEYS], str(ret))) + + #remove if any old key_val_mask + old_key_val_mask = self._check_keybind_change(key) + if old_key_val_mask: + dbg("found old key binding, removing: (%s)" % str(old_key_val_mask)) + del self.map_key_to_act[old_key_val_mask] + + #map key-val-mask to action, used to ease key-press management + self.map_key_to_act[ret] = key[PLUGIN_UTIL_ACT] + + + #map action to key-combo-str, used in preferences->keybinding + self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_KEYS] + #map action to key-combo description, in used preferences->keybinding + self.map_act_to_desc[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_DESC] + + def unbindkey(self, key): + + # Suppose user changed the key-combo and its diff from + # what the plugin had set by default, we need to get + # current key-combo + act = key[PLUGIN_UTIL_ACT] + act_keys = self.map_act_to_keys[act] + + (keyval, mask) = self.keybindings._parsebinding(act_keys) + keyval = Gdk.keyval_to_lower(keyval) + mask = Gdk.ModifierType(mask) + + ret = (keyval, mask) + dbg("unbindkey: (%s) (%s)" % (key[PLUGIN_UTIL_KEYS], str(ret))) + + # FIXME keys should always be there, can also use .pop(key, None) + # lets do it after testing + del self.map_key_to_act[ret] + del self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] + del self.map_act_to_desc[key[PLUGIN_UTIL_ACT]] + + + def keyaction(self, event): + #FIXME MOD2 mask comes in the event, remove + event.state &= ~Gdk.ModifierType.MOD2_MASK + + keyval = Gdk.keyval_to_lower(event.keyval) + ret = (keyval, event.state) + dbg("keyaction: (%s)" % str(ret)) + return self.map_key_to_act.get(ret, None) + + def get_act_to_keys(self): + return self.map_act_to_keys + + def get_act_to_desc(self): + return self.map_act_to_desc diff --git a/terminatorlib/plugin_util.py b/terminatorlib/plugin_util.py deleted file mode 100644 index e6547686..00000000 --- a/terminatorlib/plugin_util.py +++ /dev/null @@ -1,95 +0,0 @@ -""" --Basic plugin util for key-press handling, has all mapping to be used -in layout keybindings - -Vishweshwar Saran Singh Deo vssdeo@gmail.com -""" - -from gi.repository import Gtk, Gdk - -from terminatorlib.util import get_config_dir, err, dbg, gerr -from terminatorlib.keybindings import Keybindings, KeymapError - -PLUGIN_UTIL_DESC = 0 -PLUGIN_UTIL_ACT = 1 -PLUGIN_UTIL_KEYS = 2 - -#FIXME to sync this with keybinding preferences -class KeyBindUtil: - - keybindings = Keybindings() - - map_key_to_act = {} - map_act_to_keys = {} - map_act_to_desc = {} - - - #Example - # bind - # first param is desc, second is action str - # self.keyb.bindkey([PluginUrlFindNext , PluginUrlActFindNext, "j"]) - # - # get action name - # act = self.keyb.keyaction(event) - - # if act == "url_find_next": - - - #check map key_val_mask -> action - def _check_keybind_change(self, key): - act = key[PLUGIN_UTIL_ACT] - for key_val_mask in self.map_key_to_act: - old_act = self.map_key_to_act[key_val_mask] - if act == old_act: - return key_val_mask - return None - - #check in config before binding - def bindkey_check_config(self, key, config): - actstr = key[PLUGIN_UTIL_ACT] - kbsect = config.base.get_item('keybindings') - keystr = kbsect[actstr] if actstr in kbsect else "" - dbg("bindkey_check_config:action (%s) key str:(%s)" % (actstr, keystr)) - if len(keystr): - key[PLUGIN_UTIL_KEYS] = keystr - dbg("found new Action->KeyVal in config: (%s, %s)" - % (actstr, keystr)); - self.bindkey(key) - - def bindkey(self, key): - (keyval, mask) = self.keybindings._parsebinding(key[PLUGIN_UTIL_KEYS]) - keyval = Gdk.keyval_to_lower(keyval) - mask = Gdk.ModifierType(mask) - - ret = (keyval, mask) - dbg("bindkey: (%s) (%s)" % (key[PLUGIN_UTIL_KEYS], str(ret))) - - #remove if any old key_val_mask - old_key_val_mask = self._check_keybind_change(key) - if old_key_val_mask: - dbg("found old key binding, removing: (%s)" % str(old_key_val_mask)) - del self.map_key_to_act[old_key_val_mask] - - #map key-val-mask to action, used to ease key-press management - self.map_key_to_act[ret] = key[PLUGIN_UTIL_ACT] - - - #map action to key-combo-str, used in preferences->keybinding - self.map_act_to_keys[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_KEYS] - #map action to key-combo description, in used preferences->keybinding - self.map_act_to_desc[key[PLUGIN_UTIL_ACT]] = key[PLUGIN_UTIL_DESC] - - def keyaction(self, event): - #FIXME MOD2 mask comes in the event, remove - event.state &= ~Gdk.ModifierType.MOD2_MASK - - keyval = Gdk.keyval_to_lower(event.keyval) - ret = (keyval, event.state) - dbg("keyaction: (%s)" % str(ret)) - return self.map_key_to_act.get(ret, None) - - def get_act_to_keys(self): - return self.map_act_to_keys - - def get_act_to_desc(self): - return self.map_act_to_desc diff --git a/terminatorlib/plugins/mousefree_url_handler.py b/terminatorlib/plugins/mousefree_url_handler.py index 37c80f90..bb782ea3 100644 --- a/terminatorlib/plugins/mousefree_url_handler.py +++ b/terminatorlib/plugins/mousefree_url_handler.py @@ -14,10 +14,9 @@ from terminatorlib.terminator import Terminator from terminatorlib.config import Config import terminatorlib.plugin as plugin -from terminatorlib.plugin_util import KeyBindUtil +from terminatorlib.plugin import KeyBindUtil from terminatorlib.util import get_config_dir, err, dbg, gerr -from terminatorlib.keybindings import Keybindings, KeymapError from terminatorlib import regex import re @@ -45,7 +44,8 @@ class MouseFreeURLHandler(plugin.Plugin): match = None flag_http_on = False - keyb = KeyBindUtil() + config = Config() + keyb = KeyBindUtil(config) matches = [] matches_ptr = -1 #basic pattern @@ -54,21 +54,34 @@ class MouseFreeURLHandler(plugin.Plugin): def __init__(self): self.connect_signals() - config = Config() - self.keyb.bindkey_check_config([PluginUrlFindNext , PluginUrlActFindNext, "j"], config) - self.keyb.bindkey_check_config([PluginUrlFindPrev , PluginUrlActFindPrev, "k"], config) - self.keyb.bindkey_check_config([PluginUrlEsc , PluginUrlActEsc, "Escape"], config) - self.keyb.bindkey_check_config([PluginUrlLaunch, PluginUrlActLaunch, "Return"], config) + self.keyb.bindkey_check_config( + [PluginUrlFindNext , PluginUrlActFindNext, "j"]) + self.keyb.bindkey_check_config( + [PluginUrlFindPrev , PluginUrlActFindPrev, "k"]) + self.keyb.bindkey_check_config( + [PluginUrlEsc , PluginUrlActEsc, "Escape"]) + self.keyb.bindkey_check_config( + [PluginUrlLaunch, PluginUrlActLaunch, "Return"]) def connect_signals(self): self.windows = Terminator().get_windows() for window in self.windows: window.connect('key-press-event', self.on_keypress) - return - def callback(self, url): - """Actually we don't need to do anything for this to work""" - return(url) + + def unload(self): + # + for window in self.windows: + window.disconnect_by_func(self.on_keypress) + + self.keyb.unbindkey( + [PluginUrlFindNext , PluginUrlActFindNext, "j"]) + self.keyb.unbindkey( + [PluginUrlFindPrev , PluginUrlActFindPrev, "k"]) + self.keyb.unbindkey( + [PluginUrlEsc , PluginUrlActEsc, "Escape"]) + self.keyb.unbindkey( + [PluginUrlLaunch, PluginUrlActLaunch, "Return"]) def extract(self): #can we do extract more efficiently diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 5cdfb336..f34a0fac 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -18,7 +18,7 @@ from .terminator import Terminator from .plugin import PluginRegistry from .version import APP_NAME -from .plugin_util import KeyBindUtil +from .plugin import KeyBindUtil def get_color_string(widcol): return('#%02x%02x%02x' % (widcol.red>>8, widcol.green>>8, widcol.blue>>8)) From 8e5e6d1642c2959a2d57282552af4ca29b882fa2 Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Wed, 23 Nov 2022 15:34:22 +0530 Subject: [PATCH 5/8] [bug 681] Plugin Submission + Generic Plugin Utility Functions & KeyBinding Feature: Mouseless / Mousefree / Keyboard URL opening or yanking #681 - renamed api interfacted for better clarity - removed double checking of short-cut binding for plugins in keybindings --- terminatorlib/config.py | 2 +- terminatorlib/plugin.py | 18 ++++++++++++++++-- terminatorlib/prefseditor.py | 24 +++++++++++------------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 7129512c..04b08c8e 100644 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -723,7 +723,7 @@ class ConfigBase(Borg): from terminatorlib.plugin import KeyBindUtil # for plugin KeyBindUtil assist in plugin_util keybindutil = KeyBindUtil(); - keyb_keys = keybindutil.get_act_to_keys() + keyb_keys = keybindutil.get_all_act_to_keys() # we only need keys as a reference so to match them # against new values keyb_keys = dict.fromkeys(keyb_keys, "") diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index db18835b..8ab8ddf8 100644 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -303,8 +303,22 @@ class KeyBindUtil: dbg("keyaction: (%s)" % str(ret)) return self.map_key_to_act.get(ret, None) - def get_act_to_keys(self): + def get_act_to_keys(self, key): + return self.map_act_to_keys.get(key) + + def get_all_act_to_keys(self): return self.map_act_to_keys - def get_act_to_desc(self): + def get_all_act_to_desc(self): return self.map_act_to_desc + + def get_act_to_desc(self, act): + return self.map_act_to_desc.get(act) + + #get action to key binding from config + def get_act_to_keys_config(self, act): + if not self.config: + raise Warning("get_keyvalmask_for_act called without config init") + + keybindings = self.config["keybindings"] + return keybindings.get(act) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index f34a0fac..055b8b0a 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -464,8 +464,8 @@ class PrefsEditor: keybindings = self.config['keybindings'] keybindutil = KeyBindUtil() - plugin_keyb_act = keybindutil.get_act_to_keys() - plugin_keyb_desc = keybindutil.get_act_to_desc() + plugin_keyb_act = keybindutil.get_all_act_to_keys() + plugin_keyb_desc = keybindutil.get_all_act_to_desc() #merge give preference to main bindings over plugin keybindings = {**plugin_keyb_act, **keybindings} self.keybindingnames = {**plugin_keyb_desc, **self.keybindingnames} @@ -1817,7 +1817,7 @@ class PrefsEditor: keybindutil = KeyBindUtil() keybindings = self.config["keybindings"] #merge give preference to main bindings over plugin - plugin_keyb_act = keybindutil.get_act_to_keys() + plugin_keyb_act = keybindutil.get_all_act_to_keys() keybindings = {**plugin_keyb_act, **keybindings} duplicate_bindings = [] @@ -1865,16 +1865,14 @@ class PrefsEditor: accel = Gtk.accelerator_name(key, mods) self.config['keybindings'][binding] = accel - plugin_keyb_desc = keybindutil.get_act_to_desc() - - if binding in plugin_keyb_desc: - if binding in plugin_keyb_desc: - desc = plugin_keyb_desc[binding] - dbg("modifying plugin binding: %s, %s, %s" % (desc, binding, accel)) - keybindutil.bindkey([desc, binding, accel]) - else: - dbg("skipping: %s" % binding) - pass + plugin_keyb_desc = keybindutil.get_act_to_desc(binding) + if plugin_keyb_desc: + dbg("modifying plugin binding: %s, %s, %s" % + (plugin_keyb_desc, binding, accel)) + keybindutil.bindkey([plugin_keyb_desc, binding, accel]) + else: + dbg("skipping: %s" % binding) + pass self.config.save() From d91d017e20df848891d3d601637951253736e4be Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Sun, 6 Aug 2023 20:18:28 +0530 Subject: [PATCH 6/8] -remove the plugin from the plugin shortcut util / helper code --- .../plugins/mousefree_url_handler.py | 171 ------------------ 1 file changed, 171 deletions(-) delete mode 100644 terminatorlib/plugins/mousefree_url_handler.py diff --git a/terminatorlib/plugins/mousefree_url_handler.py b/terminatorlib/plugins/mousefree_url_handler.py deleted file mode 100644 index bb782ea3..00000000 --- a/terminatorlib/plugins/mousefree_url_handler.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -- With min changes to main code base, implementing mouse free url handling. -- Using shortcuts to browse URL, next / previous, clear search. Selected URL -is copied to clipboard. - -- Vishweshwar Saran Singh Deo vssdeo@gmail.com -""" - -import gi -gi.require_version('Vte', '2.91') # vte-0.38 (gnome-3.14) -from gi.repository import Vte - -from terminatorlib.terminator import Terminator - -from terminatorlib.config import Config -import terminatorlib.plugin as plugin -from terminatorlib.plugin import KeyBindUtil - -from terminatorlib.util import get_config_dir, err, dbg, gerr -from terminatorlib import regex - -import re - - -AVAILABLE = ['MouseFreeURLHandler'] - -PluginUrlActFindNext = "plugin_url_find_next" -PluginUrlActFindPrev = "plugin_url_find_prev" -PluginUrlActEsc = "plugin_url_esc" -PluginUrlActLaunch = "plugin_url_launch" - - -PluginUrlFindNext = "Plugin Url Find Next" -PluginUrlFindPrev = "Plugin Url Find Prev" -PluginUrlEsc = "Plugin Url Esc" -PluginUrlLaunch = "Plugin Url Launch" - -class MouseFreeURLHandler(plugin.Plugin): - - capabilities = ['url_handler'] - handler_name = 'MouseFreeURLHandler' - nameopen = None - namecopy = None - match = None - - flag_http_on = False - config = Config() - keyb = KeyBindUtil(config) - matches = [] - matches_ptr = -1 - #basic pattern - searchtext = "https?\:\/\/[^\s]+[\/\w]" - - def __init__(self): - self.connect_signals() - - self.keyb.bindkey_check_config( - [PluginUrlFindNext , PluginUrlActFindNext, "j"]) - self.keyb.bindkey_check_config( - [PluginUrlFindPrev , PluginUrlActFindPrev, "k"]) - self.keyb.bindkey_check_config( - [PluginUrlEsc , PluginUrlActEsc, "Escape"]) - self.keyb.bindkey_check_config( - [PluginUrlLaunch, PluginUrlActLaunch, "Return"]) - - def connect_signals(self): - self.windows = Terminator().get_windows() - for window in self.windows: - window.connect('key-press-event', self.on_keypress) - - - def unload(self): - # - for window in self.windows: - window.disconnect_by_func(self.on_keypress) - - self.keyb.unbindkey( - [PluginUrlFindNext , PluginUrlActFindNext, "j"]) - self.keyb.unbindkey( - [PluginUrlFindPrev , PluginUrlActFindPrev, "k"]) - self.keyb.unbindkey( - [PluginUrlEsc , PluginUrlActEsc, "Escape"]) - self.keyb.unbindkey( - [PluginUrlLaunch, PluginUrlActLaunch, "Return"]) - - def extract(self): - #can we do extract more efficiently - col, row = self.vte.get_cursor_position() - (txt, attr) = self.vte.get_text_range(0,0,row, col) - self.matches = re.findall(self.searchtext, txt) - self.matches_ptr = len(self.matches)-1 - - def get_term(self): - return Terminator().last_focused_term - - def get_selected_url(self): - if len(self.matches): - dbg("selected URL (%s %s)" % (self.matches_ptr, self.matches[self.matches_ptr])) - return self.matches[self.matches_ptr] - dbg("selected URL (%s %s)" % (self.matches_ptr, "not found")) - return None - - def on_keypress(self, widget, event): - act = self.keyb.keyaction(event) - dbg("keyaction: (%s) (%s)" % (str(act), event.keyval)) - - if act == PluginUrlActFindNext: - if not self.flag_http_on: - dbg("search URL on") - self.search() - self.extract() - #so when we start search last item be selected - self.vte.search_find_previous() - self.get_selected_url() # dbg url print - self.vte.copy_clipboard() - return True - else: - self.vte.search_find_next() - - if (self.matches_ptr < len(self.matches)-1): - self.matches_ptr += 1 - else: - self.matches_ptr = 0 - - self.vte.copy_clipboard() - self.get_selected_url() # dbg url print - return True - - if act == PluginUrlActFindPrev: - if not self.flag_http_on: - self.search() - self.extract() - self.get_selected_url() # dbg url print - self.vte.search_find_previous() - self.vte.copy_clipboard() - return True - - self.vte.search_find_previous() - - if self.matches_ptr > 0: - self.matches_ptr -= 1 - elif len(self.matches): - self.matches_ptr = len(self.matches)-1 - - self.vte.copy_clipboard() - self.get_selected_url() # dbg url print - return True - - if act == PluginUrlActEsc: - self.matches = [] - self.vte = self.get_term().get_vte() - self.vte.search_set_regex(None, 0) - self.flag_http_on = False - dbg("search URL off") - - if act == PluginUrlActLaunch: - url = self.get_selected_url() - if url: - self.get_term().open_url(url, prepare=False) - - def search(self): - self.flag_http_on = True - self.vte = self.get_term().get_vte() - - self.vte.search_set_wrap_around(True) - regex_flags_pcre2 = (regex.FLAGS_PCRE2 | regex.PCRE2_CASELESS) - searchre = Vte.Regex.new_for_search(self.searchtext, - len(self.searchtext), regex_flags_pcre2) - - self.vte.search_set_regex(searchre, 0) - From 3b4d32cda315c8c33b7ce585b07c4c7ee78b658e Mon Sep 17 00:00:00 2001 From: Vishweshwar Saran Singh Deo Date: Wed, 9 Aug 2023 16:31:41 +0530 Subject: [PATCH 7/8] [bug 808] Plugins-dont-receive-keboard-signals-on-newly-opened-windows - code separated for force loading of plugins on every terminal instance --- terminatorlib/plugin.py | 16 +++++++++++++--- terminatorlib/terminal.py | 11 +++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/terminatorlib/plugin.py b/terminatorlib/plugin.py index 7a0487a4..ac9d3aa9 100644 --- a/terminatorlib/plugin.py +++ b/terminatorlib/plugin.py @@ -69,12 +69,14 @@ class PluginRegistry(borg.Borg): if not self.available_plugins: self.available_plugins = {} - def load_plugins(self): + def load_plugins(self, force=False): """Load all plugins present in the plugins/ directory in our module""" - if self.done: + if self.done and (not force): dbg('Already loaded') return + dbg('loading plugins, force:(%s)' % force) + config = Config() for plugindir in self.path: @@ -93,8 +95,8 @@ class PluginRegistry(borg.Borg): try: module = __import__(plugin[:-3], None, None, ['']) for item in getattr(module, 'AVAILABLE'): + func = getattr(module, item) if item not in list(self.available_plugins.keys()): - func = getattr(module, item) self.available_plugins[item] = func if item not in config['enabled_plugins']: @@ -102,6 +104,14 @@ class PluginRegistry(borg.Borg): continue if item not in self.instances: self.instances[item] = func() + elif force: + #instead of multiple copies of loaded + #plugin objects, unload where plugins + #can clean up and then re-init so there + #is one plugin object + self.instances[item].unload() + self.instances.pop(item, None) + self.instances[item] = func() except Exception as ex: err('PluginRegistry::load_plugins: Importing plugin %s \ failed: %s' % (plugin, ex)) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 914d69d7..8680d20b 100644 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -156,6 +156,13 @@ class Terminal(Gtk.VBox): dbg('composite_support: %s' % self.composite_support) self.vte.show() + + #force to load for new window/terminal use case loading plugin + #and connecting signals, note the line update_url_matches also + #calls load_plugins, but it won't reload since already loaded + + self.load_plugins(force = True) + self.update_url_matches() self.terminalbox = self.create_terminalbox() @@ -285,6 +292,10 @@ class Terminal(Gtk.VBox): return(terminalbox) + def load_plugins(self, force = False): + registry = plugin.PluginRegistry() + registry.load_plugins(force) + def _add_regex(self, name, re): match = -1 if regex.FLAGS_PCRE2: From f7b6ea07bc8f48dd95ae5fc863d785c32e063b5a Mon Sep 17 00:00:00 2001 From: Matthew Rose Date: Sat, 26 Aug 2023 09:21:18 -0400 Subject: [PATCH 8/8] Fix Insert Term Name Plugin error The "Insert terminal name" Plugin causes the following traceback Traceback (most recent call last): File "/Users/mattrose/Code/terminator/terminatorlib/terminator.py", line 588, in do_insert_term_name term.feed(name) File "/Users/mattrose/Code/terminator/terminatorlib/terminal.py", line 1709, in feed self.vte.feed_child(text) This PR fixes it so the plugin inserts the terminal name, as intended. --- terminatorlib/terminator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/terminator.py b/terminatorlib/terminator.py index db3f465f..8a01b6ad 100644 --- a/terminatorlib/terminator.py +++ b/terminatorlib/terminator.py @@ -585,7 +585,7 @@ class Terminator(Borg): for term in self.get_target_terms(widget): name = term.titlebar.get_custom_string() or term.get_window_title() - term.feed(name) + term.feed(name.encode()) def get_sibling_terms(self, widget): termset = []