[bug 681] 681-Plugin-Submission-Plugin-Utility-KeyBinding-Mouseless-Keyboard-URL-Open #681

Merge remote-tracking branch 'upstream/master' into 450-mouseless-URL-opening-or-yanking
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
This commit is contained in:
Vishweshwar Saran Singh Deo 2022-11-19 20:08:01 +05:30
commit 8802e9b912
13 changed files with 176 additions and 51 deletions

View File

@ -53,28 +53,7 @@ from terminatorlib.util import dbg, err
from terminatorlib.layoutlauncher import LayoutLauncher
from terminatorlib.configjson import ConfigJson
# Deleting env variable fixes double char problem when broadcasting (#78)
# Only delete if it exists, or exception occurs
if os.environ.get('GTK_IM_MODULE') is not None:
del os.environ['GTK_IM_MODULE']
if __name__ == '__main__':
# Workaround for IBus intefering with broadcast when using dead keys
# Environment also needs IBUS_DISABLE_SNOOPER=1, or double chars appear
# in the receivers.
username = pwd.getpwuid(os.getuid()).pw_name
ibus_running = False
for proc in psutil.process_iter():
try:
if proc.name() == 'ibus-daemon' and proc.username() == username:
ibus_running = True
break
except (psutil.AccessDenied) as err:
print("error getting details while looking for Ibus process: %s" % err)
if ibus_running:
os.environ['IBUS_DISABLE_SNOOPER']='1'
dbus_service = None
@ -137,7 +116,6 @@ if __name__ == '__main__':
MAKER = Factory()
TERMINATOR.set_dbus_data(dbus_service)
TERMINATOR.reconfigure()
TERMINATOR.ibus_running = ibus_running
try:
dbg('Creating a terminal with layout: %s' % OPTIONS.layout)

View File

@ -139,6 +139,7 @@ DEFAULTS = {
'go_right' : '<Alt>Right',
'rotate_cw' : '<Super>r',
'rotate_ccw' : '<Super><Shift>r',
'split_auto' : '<Shift><Control>a',
'split_horiz' : '<Shift><Control>o',
'split_vert' : '<Shift><Control>e',
'close_term' : '<Shift><Control>w',

View File

@ -67,6 +67,16 @@ class Container(object):
child is .remove()d and .add()ed"""
return None
def split_auto(self, widget, cwd=None):
"""Split this container automatically"""
width = widget.get_allocation().width
height = widget.get_allocation().height
dbg("split as per current dimenstions:(%s %s)" % (width, height))
if width > height:
self.split_axis(widget, False, cwd)
else:
self.split_axis(widget, True , cwd)
def split_horiz(self, widget, cwd=None):
"""Split this container horizontally"""
return(self.split_axis(widget, True, cwd))
@ -134,17 +144,6 @@ class Container(object):
"""Handle a keyboard event requesting a terminal resize"""
raise NotImplementedError('resizeterm')
def toggle_zoom(self, widget, fontscale = False):
"""Toggle the existing zoom state"""
try:
if self.get_property('term_zoomed'):
self.unzoom(widget)
else:
self.zoom(widget, fontscale)
except TypeError:
err('Container::toggle_zoom: %s is unable to handle zooming, for \
%s' % (self, widget))
def zoom(self, widget, fontscale = False):
"""Zoom a terminal"""
raise NotImplementedError('zoom')

View File

@ -275,6 +275,7 @@ class Notebook(Container, Gtk.Notebook):
widget.force_set_profile(None, profile)
signals = {'close-term': self.wrapcloseterm,
'split-auto': self.split_auto,
'split-horiz': self.split_horiz,
'split-vert': self.split_vert,
'title-change': self.propagate_title_change,
@ -290,7 +291,9 @@ class Notebook(Container, Gtk.Notebook):
'ungroup-tab': top_window.ungroup_tab,
'move-tab': top_window.move_tab,
'tab-new': [top_window.tab_new, widget],
'navigate': top_window.navigate_terminal}
'navigate': top_window.navigate_terminal,
'zoom': top_window.zoom,
'maximise': [top_window.zoom, False]}
if maker.isinstance(widget, 'Terminal'):
for signal in signals:

View File

@ -62,14 +62,16 @@ def parse_options():
else:
parser.add_argument('--command', dest='command',
help=_('Specify a command to execute inside the terminal'))
parser.add_argument('-e', '--execute2', dest='execute', action=ExecuteCallback,
parser.add_argument('-e', '--execute2', dest='execute',
nargs=argparse.REMAINDER, action=ExecuteCallback,
help=_('Use the rest of the command line as a command to '
'execute inside the terminal, and its arguments'))
parser.add_argument('-g', '--config', dest='config',
help=_('Specify a config file'))
parser.add_argument('-j', '--config-json', dest='configjson',
help=_('Specify a partial config json file'))
parser.add_argument('-x', '--execute', dest='execute', action=ExecuteCallback,
parser.add_argument('-x', '--execute', dest='execute',
nargs=argparse.REMAINDER, action=ExecuteCallback,
help=_('Use the rest of the command line as a command to execute '
'inside the terminal, and its arguments'))
parser.add_argument('--working-directory', metavar='DIR',

View File

@ -93,6 +93,7 @@ class Paned(Container):
if self.maker.isinstance(widget, 'Terminal'):
top_window = self.get_toplevel()
signals = {'close-term': self.wrapcloseterm,
'split-auto': self.split_auto,
'split-horiz': self.split_horiz,
'split-vert': self.split_vert,
'title-change': self.propagate_title_change,
@ -424,7 +425,7 @@ class Paned(Container):
"""We don't want focus, we want a Terminal to have it"""
self.get_child1().grab_focus()
def rotate_recursive(self, parent, w, h, clockwise):
def rotate_recursive(self, parent, w, h, clockwise, metadata=None):
"""
Recursively rotate "self" into a new paned that'll have "w" x "h" size. Attach it to "parent".
@ -458,7 +459,7 @@ class Paned(Container):
w2 = max(w - w1 - handle_size, 0)
container.set_pos(pos)
parent.add(container)
parent.add(container, metadata=metadata)
if maker.isinstance(children[0], 'Terminal'):
children[0].get_parent().remove(children[0])

View File

@ -3720,6 +3720,21 @@
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox124">
<child>
<object class="GtkEntry" id="keybindingsearchentry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="has-focus">False</property>
<property name="placeholder_text">filter keybindings</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can-focus">True</property>
@ -3779,6 +3794,8 @@
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>

View File

@ -120,6 +120,7 @@ class PrefsEditor:
'go_right' : _('Focus the terminal right'),
'rotate_cw' : _('Rotate terminals clockwise'),
'rotate_ccw' : _('Rotate terminals counter-clockwise'),
'split_auto' : _('Split automatically'),
'split_horiz' : _('Split horizontally'),
'split_vert' : _('Split vertically'),
'close_term' : _('Close terminal'),
@ -417,7 +418,47 @@ class PrefsEditor:
selection.connect('changed', self.on_layout_item_selection_changed)
## Keybindings tab
widget = guiget('keybindingtreeview')
widget = guiget('keybindingtreeview')
kbsearch = guiget('keybindingsearchentry')
self.keybind_filter_str = ""
#lets hide whatever we can in nested scope
def filter_visible(model, treeiter, data):
act = model[treeiter][0]
keys = data[act] if act in data else ""
desc = model[treeiter][1]
kval = model[treeiter][2]
mask = model[treeiter][3]
#so user can search for disabled keys also
if not (len(keys) and kval and mask):
act = "Disabled"
self.keybind_filter_str = self.keybind_filter_str.lower()
searchtxt = (act + " " + keys + " " + desc).lower()
pos = searchtxt.find(self.keybind_filter_str)
if (pos >= 0):
dbg("filter find:%s in search text: %s" %
(self.keybind_filter_str, searchtxt))
return True
return False
def on_search(widget, text):
MAX_SEARCH_LEN = 10
self.keybind_filter_str = widget.get_text()
ln = len(self.keybind_filter_str)
#its a small list & we are eager for quick search, but limit
if (ln >=2 and ln < MAX_SEARCH_LEN):
dbg("filter search str: %s" % self.keybind_filter_str)
self.treemodelfilter.refilter()
def on_search_refilter(widget):
dbg("refilter")
self.treemodelfilter.refilter()
kbsearch.connect('key-press-event', on_search)
kbsearch.connect('backspace', on_search_refilter)
liststore = widget.get_model()
liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
keybindings = self.config['keybindings']
@ -442,6 +483,10 @@ class PrefsEditor:
liststore.append([keybinding, self.keybindingnames[keybinding],
keyval, mask])
self.treemodelfilter = liststore.filter_new()
self.treemodelfilter.set_visible_func(filter_visible, keybindings)
widget.set_model(self.treemodelfilter)
## Plugins tab
# Populate the plugin list
widget = guiget('pluginlist')
@ -1741,6 +1786,11 @@ class PrefsEditor:
self.config.save()
def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code):
inpath = path #save for debugging
trpath = Gtk.TreePath.new_from_string(inpath)
path = str(self.treemodelfilter.convert_path_to_child_path(trpath))
dbg("convert path with filter from: %s to: %s" %
(inpath, path))
"""Handle an edited keybinding"""
# Ignore `Gdk.KEY_Tab` so that `Shift+Tab` is displayed as `Shift+Tab`
# in `Preferences>Keybindings` and NOT `Left Tab` (see `Gdk.KEY_ISO_Left_Tab`).
@ -1824,6 +1874,12 @@ class PrefsEditor:
self.config.save()
def on_cellrenderer_accel_cleared(self, liststore, path):
inpath = path #save for debugging
trpath = Gtk.TreePath.new_from_string(inpath)
path = str(self.treemodelfilter.convert_path_to_child_path(trpath))
dbg("convert path with filter from: %s to: %s" %
(inpath, path))
"""Handle the clearing of a keybinding accelerator"""
celliter = liststore.get_iter_from_string(path)
liststore.set(celliter, 2, 0, 3, 0)

View File

@ -46,6 +46,8 @@ class Terminal(Gtk.VBox):
'group-tab-toggle': (GObject.SignalFlags.RUN_LAST, None, ()),
'ungroup-tab': (GObject.SignalFlags.RUN_LAST, None, ()),
'ungroup-all': (GObject.SignalFlags.RUN_LAST, None, ()),
'split-auto': (GObject.SignalFlags.RUN_LAST, None,
(GObject.TYPE_STRING,)),
'split-horiz': (GObject.SignalFlags.RUN_LAST, None,
(GObject.TYPE_STRING,)),
'split-vert': (GObject.SignalFlags.RUN_LAST, None,
@ -1045,6 +1047,9 @@ class Terminal(Gtk.VBox):
menu = TerminalPopupMenu(self)
menu.show(widget, event)
def do_readonly_toggle(self):
self.vte.props.input_enabled = not self.vte.props.input_enabled
def do_scrollbar_toggle(self):
"""Show or hide the terminal scrollbar"""
self.toggle_widget_visibility(self.scrollbar)
@ -1853,6 +1858,9 @@ class Terminal(Gtk.VBox):
def key_go_right(self):
self.emit('navigate', 'right')
def key_split_auto(self):
self.emit('split-auto', self.get_cwd())
def key_split_horiz(self):
self.emit('split-horiz', self.get_cwd())

View File

@ -35,7 +35,9 @@ class TerminalPopupMenu(object):
mask = mask | Gdk.ModifierType.SHIFT_MASK
dbg("adding mask <Shift> %s" % mask)
if maskstr.find('<Control>'.lower()) >= 0:
ctrl = (maskstr.find('<Control>'.lower()) >= 0 or
maskstr.find('<Primary>'.lower()) >= 0)
if ctrl:
mask = mask | Gdk.ModifierType.CONTROL_MASK
dbg("adding mask <Control> %s" % mask)
@ -57,6 +59,18 @@ class TerminalPopupMenu(object):
if (pos >= 0 and pos+1 < len(menustr)):
accelchar = menustr.lower()[pos+1]
#this may require tweak. what about shortcut function keys ?
if maskstr:
mpos = maskstr.rfind(">")
#can't have a char at 0 position as <> is len 2
if mpos >= 0 and mpos+1 < len(maskstr):
configaccelchar = maskstr[mpos+1:]
#ensure to take only 1 char else ignore
if len(configaccelchar) == 1:
dbg("found accelchar in config:%s override:%s"
% (configaccelchar, accelchar))
accelchar = configaccelchar
dbg("action from config:%s for item:%s with shortcut accelchar:(%s)"
% (maskstr, menustr, accelchar))
item = menutype.new_with_mnemonic(_(menustr))
@ -158,6 +172,20 @@ class TerminalPopupMenu(object):
menu.append(item)
if not terminal.is_zoomed():
item = self.menu_item(Gtk.ImageMenuItem, 'split_auto',
'Split _Auto')
"""
image = Gtk.Image()
image.set_from_icon_name(APP_NAME + '_auto', Gtk.IconSize.MENU)
item.set_image(image)
if hasattr(item, 'set_always_show_image'):
item.set_always_show_image(True)
"""
item.connect('activate', lambda x: terminal.emit('split-auto',
self.terminal.get_cwd()))
menu.append(item)
item = self.menu_item(Gtk.ImageMenuItem, 'split_horiz',
'Split H_orizontally')
image = Gtk.Image()
@ -234,6 +262,11 @@ class TerminalPopupMenu(object):
menu.append(item)
menu.append(Gtk.SeparatorMenuItem())
item = self.menu_item(Gtk.CheckMenuItem, 'toggle_readonly', '_read only')
item.set_active(not(terminal.vte.get_input_enabled()))
item.connect('toggled', lambda x: terminal.do_readonly_toggle())
menu.append(item)
item = self.menu_item(Gtk.CheckMenuItem, 'toggle_scrollbar',
'Show _scrollbar')
item.set_active(terminal.scrollbar.get_property('visible'))

View File

@ -54,7 +54,6 @@ class Terminator(Borg):
dbus_path = None
dbus_name = None
debug_address = None
ibus_running = None
doing_layout = None
layoutname = None

View File

@ -422,6 +422,7 @@ class Window(Container, Gtk.Window):
if maker.isinstance(widget, 'Terminal'):
signals = {'close-term': self.closeterm,
'title-change': self.title.set_title,
'split-auto': self.split_auto,
'split-horiz': self.split_horiz,
'split-vert': self.split_vert,
'resize-term': self.resizeterm,
@ -537,6 +538,7 @@ class Window(Container, Gtk.Window):
def zoom(self, widget, font_scale=True):
"""Zoom a terminal widget"""
maker = Factory()
children = self.get_children()
if widget in children:
@ -549,8 +551,13 @@ class Window(Container, Gtk.Window):
self.zoom_data['old_child'] = children[0]
self.zoom_data['font_scale'] = font_scale
old_parent = self.zoom_data['old_parent']
if maker.isinstance(old_parent, 'Notebook'):
self.zoom_data['notebook_tabnum'] = old_parent.page_num(widget)
self.zoom_data['notebook_label'] = old_parent.get_tab_label(widget).get_label()
self.remove(self.zoom_data['old_child'])
self.zoom_data['old_parent'].remove(widget)
old_parent.remove(widget)
self.add(widget)
self.set_property('term_zoomed', True)
@ -562,6 +569,8 @@ class Window(Container, Gtk.Window):
def unzoom(self, widget=None):
"""Restore normal terminal layout"""
maker = Factory()
if not self.is_zoomed():
# We're not zoomed anyway
dbg('not zoomed, no-op')
@ -573,7 +582,13 @@ class Window(Container, Gtk.Window):
self.remove(widget)
self.add(self.zoom_data['old_child'])
self.zoom_data['old_parent'].add(widget)
if maker.isinstance(self.zoom_data['old_parent'], 'Notebook'):
self.zoom_data['old_parent'].newtab(widget=widget, metadata={
'tabnum': self.zoom_data['notebook_tabnum'],
'label': self.zoom_data['notebook_label']
})
else:
self.zoom_data['old_parent'].add(widget)
widget.grab_focus()
self.zoom_data = None
self.set_property('term_zoomed', False)
@ -591,8 +606,17 @@ class Window(Container, Gtk.Window):
# If our child is a Notebook, reset to work from its visible child
if maker.isinstance(child, 'Notebook'):
pagenum = child.get_current_page()
child = child.get_nth_page(pagenum)
notebook = child
pagenum = notebook.get_current_page()
child = notebook.get_nth_page(pagenum)
metadata = {
'tabnum': pagenum,
'label': notebook.get_tab_label(child).get_label()
}
else:
metadata = None
if maker.isinstance(child, 'Paned'):
parent = child.get_parent()
@ -600,7 +624,7 @@ class Window(Container, Gtk.Window):
# otherwise _sometimes_ we get incorrect values.
alloc = child.get_allocation()
parent.remove(child)
child.rotate_recursive(parent, alloc.width, alloc.height, clockwise)
child.rotate_recursive(parent, alloc.width, alloc.height, clockwise, metadata)
self.show_all()
while Gtk.events_pending():

View File

@ -103,7 +103,8 @@ def test_message_dialog_is_shown_on_duplicate_accel_assignment(
)
widget = prefs_editor.builder.get_object("keybindingtreeview")
liststore = widget.get_model()
treemodelfilter = widget.get_model()
liststore = treemodelfilter.get_model()
# Replace default accelerator with a test one
prefs_editor.on_cellrenderer_accel_edited(
@ -150,7 +151,8 @@ def test_duplicate_accels_not_possible_to_set(accel_params):
)
widget = prefs_editor.builder.get_object("keybindingtreeview")
liststore = widget.get_model()
treemodelfilter = widget.get_model()
liststore = treemodelfilter.get_model()
binding = liststore.get_value(liststore.get_iter(path), 0)
all_default_accelerators = {
@ -194,7 +196,7 @@ def test_duplicate_accels_not_possible_to_set(accel_params):
(Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK),
),
# 3) `Ctrl+Shift+a` shouldn't change
((Gdk.KEY_a, CONTROL_SHIFT_MOD, 38), (Gdk.KEY_a, CONTROL_SHIFT_MOD),),
#((Gdk.KEY_a, CONTROL_SHIFT_MOD, 38), (Gdk.KEY_a, CONTROL_SHIFT_MOD),),
# 4) `Ctrl+Shift+Alt+F1` shouldn't change
(
(Gdk.KEY_F1, CONTROL_ALT_SHIFT_MOD, 67),
@ -231,7 +233,8 @@ def test_keybinding_edit_produce_expected_accels(
prefs_editor = prefseditor.PrefsEditor(term=term)
widget = prefs_editor.builder.get_object("keybindingtreeview")
liststore = widget.get_model()
treemodelfilter = widget.get_model()
liststore = treemodelfilter.get_model()
path = 0 # Edit the first listed key binding in `Preferences>Keybindings`
key, mods, hardware_keycode = input_key_params
@ -277,7 +280,8 @@ def test_keybinding_successfully_reassigned_after_clearing(accel_params):
prefs_editor = prefseditor.PrefsEditor(term=term)
widget = prefs_editor.builder.get_object("keybindingtreeview")
liststore = widget.get_model()
treemodelfilter = widget.get_model()
liststore = treemodelfilter.get_model()
path, key, mods, hardware_keycode = accel_params
# Assign a key binding