From 6220af803030b6bb135d795c754cd7bd2b9338f1 Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Thu, 3 Sep 2020 18:00:08 -0400 Subject: [PATCH 01/12] add plugin to notify when command is complete --- terminatorlib/plugins/command_notify.py | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 terminatorlib/plugins/command_notify.py diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py new file mode 100644 index 00000000..8d94c1bd --- /dev/null +++ b/terminatorlib/plugins/command_notify.py @@ -0,0 +1,58 @@ +""" +This plugin will launch a notification when a long running command finishes +and terminal is not active. + +It uses VTE's special sequence which is sent when shell prints the prompt. It +depends on https://github.com/GNOME/vte/blob/vte-0-58/src/vte.sh (which has to +be added to /etc/profile.d) and you need to ensure `__vte_prompt_command` is +executed on `PROMPT_COMMAND` in Bash or in `precmd_functions` in Zsh. + +Code is written in Hy and generated to Python3. +""" +import terminatorlib.plugin as plugin +from terminatorlib.terminator import Terminator +import gi +gi.require_version('Notify', '0.7') +from gi.repository import GObject, GLib, Notify +VERSION = '0.1.0' +AVAILABLE = ['LongCommandNotify'] + + +class LongCommandNotify(plugin.Plugin): + capabilities = ['command_watch'] + watched = set() + + def __init__(self): + self.update_watched() + Notify.init('Terminator') + return None + + def update_watched(self): + """Updates the list of watched terminals""" + new_watched = set() + for term in Terminator().terminals: + new_watched.add(term) + if not term in self.watched: + vte = term.get_vte() + term.connect('focus-out', self.update_watched_delayed, None) + vte.connect('focus-out-event', self. + update_watched_delayed, None) + notify = vte.connect( + 'notification-received', self.notification_received, term) + else: + notify = None + self.watched = new_watched + + def update_watched_delayed(self): + + def add_watch(self): + self.update_watched() + return False + GObject.idle_add(add_watch, self) + return True + + def notification_received(self, vte, summary, body, _terminator_term): + Notify.Notification.new(summary, body, 'terminator').show( + ) if not vte.has_focus() else None + return None + From 93c11691041c3b34e4fd3bacf898bf0d242294fc Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Thu, 3 Sep 2020 18:14:49 -0400 Subject: [PATCH 02/12] update description --- terminatorlib/plugins/command_notify.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py index 8d94c1bd..613ab338 100644 --- a/terminatorlib/plugins/command_notify.py +++ b/terminatorlib/plugins/command_notify.py @@ -7,7 +7,8 @@ depends on https://github.com/GNOME/vte/blob/vte-0-58/src/vte.sh (which has to be added to /etc/profile.d) and you need to ensure `__vte_prompt_command` is executed on `PROMPT_COMMAND` in Bash or in `precmd_functions` in Zsh. -Code is written in Hy and generated to Python3. +Code is adapted from https://github.com/x4lldux/terminator-long-cmd-notify +Thanks to @xll4dux on Github for the code and his permission to use it """ import terminatorlib.plugin as plugin from terminatorlib.terminator import Terminator From 752311b8fe3dd0b6818392464966c242cc5915fc Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Thu, 3 Sep 2020 18:18:08 -0400 Subject: [PATCH 03/12] Tweak Name so it does not interfere with @xll4dux plugin --- terminatorlib/plugins/command_notify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py index 613ab338..a32a4bfb 100644 --- a/terminatorlib/plugins/command_notify.py +++ b/terminatorlib/plugins/command_notify.py @@ -16,7 +16,7 @@ import gi gi.require_version('Notify', '0.7') from gi.repository import GObject, GLib, Notify VERSION = '0.1.0' -AVAILABLE = ['LongCommandNotify'] +AVAILABLE = ['CommandNotify'] class LongCommandNotify(plugin.Plugin): From bad60a03f2686b415096f78074ce2f572a82df82 Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Fri, 4 Sep 2020 10:29:46 -0400 Subject: [PATCH 04/12] tweak class name as well --- terminatorlib/plugins/command_notify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py index a32a4bfb..c7575b06 100644 --- a/terminatorlib/plugins/command_notify.py +++ b/terminatorlib/plugins/command_notify.py @@ -19,7 +19,7 @@ VERSION = '0.1.0' AVAILABLE = ['CommandNotify'] -class LongCommandNotify(plugin.Plugin): +class CommandNotify(plugin.Plugin): capabilities = ['command_watch'] watched = set() From 8cd329c5c5694ce0dca987e84a9efe74800d04db Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Fri, 4 Sep 2020 10:40:14 -0400 Subject: [PATCH 05/12] added some more documentation --- terminatorlib/plugins/command_notify.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py index c7575b06..65bc0926 100644 --- a/terminatorlib/plugins/command_notify.py +++ b/terminatorlib/plugins/command_notify.py @@ -7,8 +7,12 @@ depends on https://github.com/GNOME/vte/blob/vte-0-58/src/vte.sh (which has to be added to /etc/profile.d) and you need to ensure `__vte_prompt_command` is executed on `PROMPT_COMMAND` in Bash or in `precmd_functions` in Zsh. +This plugin also relies on the widely distributed vte patches to wire in a +`notification_received` signal to catch the OSC escape sequence in the prompt + Code is adapted from https://github.com/x4lldux/terminator-long-cmd-notify Thanks to @xll4dux on Github for the code and his permission to use it + """ import terminatorlib.plugin as plugin from terminatorlib.terminator import Terminator From 98d11928c60853c97f078765b7248e68e8cbee38 Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Fri, 4 Sep 2020 12:15:25 -0400 Subject: [PATCH 06/12] add proper arguments --- terminatorlib/plugins/command_notify.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py index 65bc0926..64b3b48b 100644 --- a/terminatorlib/plugins/command_notify.py +++ b/terminatorlib/plugins/command_notify.py @@ -48,8 +48,8 @@ class CommandNotify(plugin.Plugin): notify = None self.watched = new_watched - def update_watched_delayed(self): - + def update_watched_delayed(self, term, event, arg1 = None): + print('foo: %s / bar: %s / baz: %s' % (str(term),str(event),arg1)) def add_watch(self): self.update_watched() return False From 793ac673aabb3518e5a9d50c3577e77a21031b4f Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Wed, 9 Sep 2020 14:05:08 -0400 Subject: [PATCH 07/12] update Terminal.spawn_child() to use spawn_async, not spawn_sync --- terminatorlib/terminal.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index e8876518..5d13dfeb 100644 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -1488,14 +1488,18 @@ class Terminal(Gtk.VBox): dbg('Forking shell: "%s" with args: %s' % (shell, args)) args.insert(0, shell) - result, self.pid = self.vte.spawn_sync(Vte.PtyFlags.DEFAULT, - self.cwd, - args, - envv, - GLib.SpawnFlags.FILE_AND_ARGV_ZERO | GLib.SpawnFlags.DO_NOT_REAP_CHILD, - None, - None, - None) + self.pid = self.vte.spawn_async( + Vte.PtyFlags.DEFAULT, + self.cwd, + args, + envv, + GLib.SpawnFlags.FILE_AND_ARGV_ZERO | GLib.SpawnFlags.DO_NOT_REAP_CHILD, + None, + None, + -1, + None, + None, + None) self.command = shell self.titlebar.update() From 6ec295cdbc918722313040ea623b1432ee90e935 Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Fri, 11 Sep 2020 20:47:57 -0400 Subject: [PATCH 08/12] add preferences keybindings --- terminatorlib/config.py | 1 + terminatorlib/prefseditor.py | 1 + terminatorlib/terminal.py | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 4abc564b..34e7f8d3 100644 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -195,6 +195,7 @@ DEFAULTS = { 'layout_launcher' : 'l', 'next_profile' : '', 'previous_profile' : '', + 'preferences' : '', 'help' : 'F1' }, 'profiles': { diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index c77ab7c9..97c8ed2b 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -174,6 +174,7 @@ class PrefsEditor: 'layout_launcher' : _('Open layout launcher window'), 'next_profile' : _('Switch to next profile'), 'previous_profile' : _('Switch to previous profile'), + 'preferences' : _('Open the Preferences window'), 'help' : _('Open the manual') } diff --git a/terminatorlib/terminal.py b/terminatorlib/terminal.py index 30639ba1..6acf4552 100644 --- a/terminatorlib/terminal.py +++ b/terminatorlib/terminal.py @@ -23,6 +23,7 @@ from .factory import Factory from .terminator import Terminator from .titlebar import Titlebar from .terminal_popup_menu import TerminalPopupMenu +from .prefseditor import PrefsEditor from .searchbar import Searchbar from .translation import _ from .signalman import Signalman @@ -1988,6 +1989,9 @@ class Terminal(Gtk.VBox): def key_line_down(self): self.scroll_by_line(1) + def key_preferences(self): + PrefsEditor(self) + def key_help(self): manual_index_page = manual_lookup() if manual_index_page: From 6c404d035577e185fa4911080325f6ef9554fb7e Mon Sep 17 00:00:00 2001 From: dkmvs <67212386+dkmvs@users.noreply.github.com> Date: Mon, 14 Sep 2020 04:26:13 +0300 Subject: [PATCH 09/12] Allow `Shift+Tab` Key Binding Accelerator This commit allows to assign the `Shift+Tab` key binding to an action in `Preferences>Keybindings`. In GTK the Tab key can be modified by the Shift key. Such a key combination has a special key value - `Gdk.KEY_ISO_Left_Tab`. To allow it, `key = key_with_shift.keyval` was added to the code. However, `Gdk.KEY_ISO_Left_Tab` key value is displayed as `Left Tab` in `Preferences>Keybindings`, which is confusing as it is not obvious that it corresponds to the `Shift+Tab` key combination. To make sure that `Shift+Tab` is displayed as `Shift+Tab`, the `Shift+Tab` case is treated as if no Shift was pressed at all. --- terminatorlib/prefseditor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index c77ab7c9..96290904 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -1673,7 +1673,9 @@ class PrefsEditor: def on_cellrenderer_accel_edited(self, liststore, path, key, mods, _code): """Handle an edited keybinding""" - if mods & Gdk.ModifierType.SHIFT_MASK: + # 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`). + if mods & Gdk.ModifierType.SHIFT_MASK and key != Gdk.KEY_Tab: key_with_shift = Gdk.Keymap.translate_keyboard_state( self.keybindings.keymap, hardware_keycode=_code, @@ -1687,6 +1689,7 @@ class PrefsEditor: # Shift key. if key_with_shift.level != 0 and keyval_lower == keyval_upper: mods = Gdk.ModifierType(mods & ~Gdk.ModifierType.SHIFT_MASK) + key = key_with_shift.keyval accel = Gtk.accelerator_name(key, mods) current_binding = liststore.get_value(liststore.get_iter(path), 0) From 5e417ee09d6fc72220d96e336329eb26259bec9a Mon Sep 17 00:00:00 2001 From: dkmvs <67212386+dkmvs@users.noreply.github.com> Date: Fri, 18 Sep 2020 03:45:31 +0300 Subject: [PATCH 10/12] Add Tests for `Keybinding>Preferences` The test in this commit checks that editing a key binding using a predefined key combination produces the expected accelerator. --- tests/test_prefseditor_keybindings.py | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/test_prefseditor_keybindings.py b/tests/test_prefseditor_keybindings.py index 2fd2314d..4fa9db70 100644 --- a/tests/test_prefseditor_keybindings.py +++ b/tests/test_prefseditor_keybindings.py @@ -178,3 +178,76 @@ def test_duplicate_accels_not_possible_to_set(accel_params): assert default_accelerator == new_accelerator reset_config_keybindings() + + +@pytest.mark.parametrize( + "input_key_params,expected_accel", + [ + # 1) `Ctrl+Shift+1` should become `Ctrl+!` + ( + (Gdk.KEY_1, CONTROL_SHIFT_MOD, 10), + (Gdk.KEY_exclam, Gdk.ModifierType.CONTROL_MASK), + ), + # 2) `Ctrl+a` shouldn't change + ( + (Gdk.KEY_a, Gdk.ModifierType.CONTROL_MASK, 38), + (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),), + # 4) `Ctrl+Shift+Alt+F1` shouldn't change + ( + (Gdk.KEY_F1, CONTROL_ALT_SHIFT_MOD, 67), + (Gdk.KEY_F1, CONTROL_ALT_SHIFT_MOD), + ), + # 5) `Shift+Up` shouldn't change + ( + (Gdk.KEY_Up, Gdk.ModifierType.SHIFT_MASK, 111), + (Gdk.KEY_Up, Gdk.ModifierType.SHIFT_MASK), + ), + # 6) `Ctrl+Shift+[` should become `Ctrl+{` + ( + (Gdk.KEY_bracketleft, CONTROL_SHIFT_MOD, 34), + (Gdk.KEY_braceleft, Gdk.ModifierType.CONTROL_MASK), + ), + # 7) `Shift+Tab` shouldn't change + ( + (Gdk.KEY_Tab, Gdk.ModifierType.SHIFT_MASK, 23), + (Gdk.KEY_Tab, Gdk.ModifierType.SHIFT_MASK), + ), + ], +) +def test_keybinding_edit_produce_expected_accels( + input_key_params, expected_accel +): + """ + Tests that editing a key binding using a predefined key combination + `input_key_params` produces the expected accelerator. + """ + from terminatorlib import terminal + from terminatorlib import prefseditor + + term = terminal.Terminal() + prefs_editor = prefseditor.PrefsEditor(term=term) + + widget = prefs_editor.builder.get_object("keybindingtreeview") + liststore = widget.get_model() + + path = 0 # Edit the first listed key binding in `Preferences>Keybindings` + key, mods, hardware_keycode = input_key_params + + prefs_editor.on_cellrenderer_accel_edited( + liststore=liststore, + path=str(path), + key=key, + mods=mods, + _code=hardware_keycode, + ) + + liststore_iter = liststore.get_iter(path) + keyval_after_edit = liststore.get_value(liststore_iter, 2) + mods_after_edit = Gdk.ModifierType(liststore.get_value(liststore_iter, 3)) + + accel_after_edit = (keyval_after_edit, mods_after_edit) + + assert accel_after_edit == expected_accel From 0ff6a7b4981be3ac11e4f42de78a9e8037565f83 Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Tue, 22 Sep 2020 16:59:26 -0400 Subject: [PATCH 11/12] fix spacing --- terminatorlib/config.py | 2 +- terminatorlib/prefseditor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 34e7f8d3..43d9a0b0 100644 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -195,7 +195,7 @@ DEFAULTS = { 'layout_launcher' : 'l', 'next_profile' : '', 'previous_profile' : '', - 'preferences' : '', + 'preferences' : '', 'help' : 'F1' }, 'profiles': { diff --git a/terminatorlib/prefseditor.py b/terminatorlib/prefseditor.py index 97c8ed2b..dedc55aa 100755 --- a/terminatorlib/prefseditor.py +++ b/terminatorlib/prefseditor.py @@ -174,7 +174,7 @@ class PrefsEditor: 'layout_launcher' : _('Open layout launcher window'), 'next_profile' : _('Switch to next profile'), 'previous_profile' : _('Switch to previous profile'), - 'preferences' : _('Open the Preferences window'), + 'preferences' : _('Open the Preferences window'), 'help' : _('Open the manual') } From dad40bb1b23a1235cf6cd3c285a534142e20ee48 Mon Sep 17 00:00:00 2001 From: Matt Rose Date: Tue, 22 Sep 2020 17:48:37 -0400 Subject: [PATCH 12/12] do not advertise as AVAILABLE if the signal is not present in the Vte library --- terminatorlib/plugins/command_notify.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/terminatorlib/plugins/command_notify.py b/terminatorlib/plugins/command_notify.py index 64b3b48b..83849332 100644 --- a/terminatorlib/plugins/command_notify.py +++ b/terminatorlib/plugins/command_notify.py @@ -18,10 +18,15 @@ import terminatorlib.plugin as plugin from terminatorlib.terminator import Terminator import gi gi.require_version('Notify', '0.7') -from gi.repository import GObject, GLib, Notify +from gi.repository import GObject, GLib, Notify, Vte VERSION = '0.1.0' -AVAILABLE = ['CommandNotify'] +### Test for proper signal +try: + Vte.Terminal().connect('notification-received',lambda *args: None,None) + AVAILABLE = ['CommandNotify'] +except TypeError as e: + pass class CommandNotify(plugin.Plugin): capabilities = ['command_watch'] @@ -49,7 +54,6 @@ class CommandNotify(plugin.Plugin): self.watched = new_watched def update_watched_delayed(self, term, event, arg1 = None): - print('foo: %s / bar: %s / baz: %s' % (str(term),str(event),arg1)) def add_watch(self): self.update_watched() return False