Merge lp:~sprataa/terminator/layoutsonsteroids into lp:terminator/gtk3

Proposed by Sérgio Prata Almeida
Status: Needs review
Proposed branch: lp:~sprataa/terminator/layoutsonsteroids
Merge into: lp:terminator/gtk3
Diff against target: 283 lines (+126/-2)
6 files modified
terminatorlib/ipc.py (+21/-0)
terminatorlib/preferences.glade (+27/-0)
terminatorlib/prefseditor.py (+23/-0)
terminatorlib/terminal.py (+45/-2)
terminatorlib/terminal_popup_menu.py (+4/-0)
terminatorlib/terminator.py (+6/-0)
To merge this branch: bzr merge lp:~sprataa/terminator/layoutsonsteroids
Reviewer Review Type Date Requested Status
Terminator Pending
Review via email: mp+357667@code.launchpad.net

Commit message

This commit is a PoC on how to improve Layout Saving mechanism further and allow automation;

Changelog:

Added virtualenvwrapper Virtual Environment setting to Terminal Layout;
Added Save Layout shortcut to Popup/Context Menu;
Saving a Layout now detects shell child processes and saves it in "Custom Command" field;
Changed "Custom Command" mechanism in order to be ran in child shell via input instead of using -c "command";
Improved PWD/CWD detection;
Added save_layout and push_environment to IPC/DBus Methods;

Description of the change

Example DBUS shell functions and usage:

function push_environment {
 if [ TERMINATOR_UUID ]
 then
  env=`env | egrep "TERMINATOR_UUID|VIRTUAL_ENV|PWD"`
  dbus-send --session --type=method_call --dest=$TERMINATOR_DBUS_NAME /net/tenshu/Terminator2 $TERMINATOR_DBUS_NAME.push_environment string:"$env" &>/dev/null
 fi
}

function save_layout {
 if [ TERMINATOR_UUID ]
 then
  push_environment
  dbus-send --session --type=method_call --dest=$TERMINATOR_DBUS_NAME /net/tenshu/Terminator2 $TERMINATOR_DBUS_NAME.save_layout &>/dev/null
 fi
}

push_environment needs to be called from each terminal's child shell in order to inform terminator of the current environment.
The only place where you can access the current shell's environment. TERMINATOR_UUID needs to be sent in order to determine where it came from.

I currently have push_environment ran every time my shell's prompt is redrawn but this is optional.
You can run push_environment on each terminal you wish to update Virtual Environment and Working Directory.
Be advised that you still need to call save_layout function, "Save Layout" via the context menu or press "Save" in Layout Preferences window.

To post a comment you must log in.
Revision history for this message
Sérgio Prata Almeida (sprataa) wrote :

Greetings! First of all, thank you all for allowing Terminator to thrive!

These changes have been thoroughly tested and are being used daily for a week or so. I confess, mostly with the 'default' profile. Other than the 'default' profile have also been tested but now so much.

Totally changed mine and a couple of co-worker's lives for the better. Saving/Restoring SSH Sessions without having to dial it in manually every time has become priceless - especially because Preferences-Layouts-Save clears the Working Directory and Custom Command fields - not cool.

With these small changes, we turned our static profiles into dynamic, ever-evolving and self updating profiles. Auto-saving should also be present (there is a guy that wants it because he forgets to save) but I didn't want to break Terminator's architecture further before discussing the best way to do it with Terminator's team. Ideally we should add an option to auto-save and define the interval. Well, save_layout can also be called on every prompt but it makes every prompt significantly slower this we are all currently saving it manually either via the context menu or by running the save_layout function in a shell. push_environment on every prompt is unnoticeable for all of us. My suggested shell push_environment function is not optimal but it does the job.

Yes, every user has to add the functions and the every prompt behaviour manually to it's bashrc/zshrc. Examples:

[ ${ZSH_VERSION} ] && precmd() { push_environment; }
[ ${BASH_VERSION} ] && PROMPT_COMMAND=push_environment

We can also create a profile.d/XX-terminator.sh to be included that adds these hooks automatically when TERMINATOR_UUID is present. This behavior could also be controlled via Terminator' Preferences and propagated to child shells by using an extra environment variable such as TERMINATOR_AUTO_REFRESH that the profile.d/XX-terminator.sh would interpret and act accordingly.

Please let me know what you think about my approach and let us work together to make this possible.

Cheers!

Unmerged revisions

1808. By Sérgio Prata Almeida

This commit is a PoC on how to improve Layout Saving mechanism further and allow automation;

Changelog:

Added virtualenvwrapper Virtual Environment setting to Terminal Layout;
Added Save Layout shortcut to Popup/Context Menu;
Saving a Layout now detects shell child processes and saves it in "Custom Command" field;
Changed "Custom Command" mechanism in order to be ran in child shell via input instead of using -c "command";
Improved PWD/CWD detection;
Added save_layout and push_environment to IPC/DBus Methods;

-----------------------------------------------

Example DBUS shell functions and usage:

function push_environment {
    if [ TERMINATOR_UUID ]
    then
        env=`env | egrep "TERMINATOR_UUID|VIRTUAL_ENV|PWD"`
        dbus-send --session --type=method_call --dest=$TERMINATOR_DBUS_NAME /net/tenshu/Terminator2 $TERMINATOR_DBUS_NAME.push_environment string:"$env" &>/dev/null
    fi
}

function save_layout {
    if [ TERMINATOR_UUID ]
    then
        push_environment
        dbus-send --session --type=method_call --dest=$TERMINATOR_DBUS_NAME /net/tenshu/Terminator2 $TERMINATOR_DBUS_NAME.save_layout &>/dev/null
    fi
}

push_environment needs to be called from each terminal's child shell in order to inform terminator of the current environment.
The only place where you can access the current shell's environment. TERMINATOR_UUID needs to be sent in order to determine where it came from.

I currently have push_environment ran every time my shell's prompt is redrawn but this is optional.
You can run push_environment on each terminal you wish to update Virtual Environment and Working Directory.
Be advised that you still need to call save_layout function, "Save Layout" via the context menu or press "Save" in Layout Preferences window.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'terminatorlib/ipc.py'
--- terminatorlib/ipc.py 2017-02-13 14:36:55 +0000
+++ terminatorlib/ipc.py 2018-10-23 02:50:13 +0000
@@ -183,6 +183,27 @@
183 if terminal in terms:183 if terminal in terms:
184 return root_widget.get_tab_label(tab_child).get_label()184 return root_widget.get_tab_label(tab_child).get_label()
185185
186 @dbus.service.method(dbus_interface=BUS_NAME)
187 def push_environment(self, data):
188 environment = {}
189 for line in iter(data.splitlines()):
190 if '=' not in line:
191 environment[var[0]] + line
192 else:
193 var = line.split('=')
194 environment[var[0]] = var[1]
195 dbg('received environment!')
196 dbg(environment)
197 if 'TERMINATOR_UUID' in environment.keys():
198 terminal = self.terminator.find_terminal_by_uuid(environment['TERMINATOR_UUID'])
199 terminal.environment = environment
200
201 @dbus.service.method(dbus_interface=BUS_NAME)
202 def save_layout(self):
203 dbg('received save layout!')
204 self.terminator.save_layout()
205
206
186def with_proxy(func):207def with_proxy(func):
187 """Decorator function to connect to the session dbus bus"""208 """Decorator function to connect to the session dbus bus"""
188 dbg('dbus client call: %s' % func.func_name)209 dbg('dbus client call: %s' % func.func_name)
189210
=== modified file 'terminatorlib/preferences.glade'
--- terminatorlib/preferences.glade 2016-12-13 21:08:02 +0000
+++ terminatorlib/preferences.glade 2018-10-23 02:50:13 +0000
@@ -3424,6 +3424,18 @@
3424 </packing>3424 </packing>
3425 </child>3425 </child>
3426 <child>3426 <child>
3427 <object class="GtkLabel" id="label44">
3428 <property name="visible">True</property>
3429 <property name="can_focus">False</property>
3430 <property name="label" translatable="yes">Virtual Environment:</property>
3431 <property name="xalign">1</property>
3432 </object>
3433 <packing>
3434 <property name="left_attach">0</property>
3435 <property name="top_attach">3</property>
3436 </packing>
3437 </child>
3438 <child>
3427 <object class="GtkComboBoxText" id="layout_profile_chooser">3439 <object class="GtkComboBoxText" id="layout_profile_chooser">
3428 <property name="visible">True</property>3440 <property name="visible">True</property>
3429 <property name="can_focus">False</property>3441 <property name="can_focus">False</property>
@@ -3469,6 +3481,21 @@
3469 <property name="top_attach">2</property>3481 <property name="top_attach">2</property>
3470 </packing>3482 </packing>
3471 </child>3483 </child>
3484 <child>
3485 <object class="GtkEntry" id="layout_profile_virtualenv">
3486 <property name="visible">True</property>
3487 <property name="can_focus">True</property>
3488 <property name="hexpand">True</property>
3489 <property name="invisible_char">•</property>
3490 <property name="primary_icon_activatable">False</property>
3491 <property name="secondary_icon_activatable">False</property>
3492 <signal name="changed" handler="on_layout_profile_virtualenv_changed" swapped="no"/>
3493 </object>
3494 <packing>
3495 <property name="left_attach">1</property>
3496 <property name="top_attach">3</property>
3497 </packing>
3498 </child>
3472 </object>3499 </object>
3473 <packing>3500 <packing>
3474 <property name="resize">True</property>3501 <property name="resize">True</property>
34753502
=== modified file 'terminatorlib/prefseditor.py'
--- terminatorlib/prefseditor.py 2017-06-24 02:02:38 +0000
+++ terminatorlib/prefseditor.py 2018-10-23 02:50:13 +0000
@@ -1423,6 +1423,10 @@
1423 """A different working directory has been entered for this item"""1423 """A different working directory has been entered for this item"""
1424 self.layouteditor.on_layout_profile_workingdir_activate(widget)1424 self.layouteditor.on_layout_profile_workingdir_activate(widget)
14251425
1426 def on_layout_profile_virtualenv_changed(self, widget):
1427 """A different virtualenvironment has been entered for this item"""
1428 self.layouteditor.on_layout_profile_virtualenv_activate(widget)
1429
1426 def on_layout_name_edited(self, cell, path, newtext):1430 def on_layout_name_edited(self, cell, path, newtext):
1427 """Update a layout name"""1431 """Update a layout name"""
1428 oldname = cell.get_property('text')1432 oldname = cell.get_property('text')
@@ -1620,9 +1624,11 @@
1620 command = self.builder.get_object('layout_profile_command')1624 command = self.builder.get_object('layout_profile_command')
1621 chooser = self.builder.get_object('layout_profile_chooser')1625 chooser = self.builder.get_object('layout_profile_chooser')
1622 workdir = self.builder.get_object('layout_profile_workingdir')1626 workdir = self.builder.get_object('layout_profile_workingdir')
1627 virtualenv = self.builder.get_object('layout_profile_virtualenv')
1623 command.set_sensitive(False)1628 command.set_sensitive(False)
1624 chooser.set_sensitive(False)1629 chooser.set_sensitive(False)
1625 workdir.set_sensitive(False)1630 workdir.set_sensitive(False)
1631 virtualenv.set_sensitive(False)
16261632
1627 def on_layout_item_selection_changed(self, selection):1633 def on_layout_item_selection_changed(self, selection):
1628 """A different item in the layout was selected"""1634 """A different item in the layout was selected"""
@@ -1640,16 +1646,20 @@
1640 command = self.builder.get_object('layout_profile_command')1646 command = self.builder.get_object('layout_profile_command')
1641 chooser = self.builder.get_object('layout_profile_chooser')1647 chooser = self.builder.get_object('layout_profile_chooser')
1642 workdir = self.builder.get_object('layout_profile_workingdir')1648 workdir = self.builder.get_object('layout_profile_workingdir')
1649 virtualenv = self.builder.get_object('layout_profile_virtualenv')
16431650
1644 if layout_item['type'] != 'Terminal':1651 if layout_item['type'] != 'Terminal':
1645 command.set_sensitive(False)1652 command.set_sensitive(False)
1646 chooser.set_sensitive(False)1653 chooser.set_sensitive(False)
1647 workdir.set_sensitive(False)1654 workdir.set_sensitive(False)
1655 virtualenv.set_sensitive(False)
1648 return1656 return
16491657
1650 command.set_sensitive(True)1658 command.set_sensitive(True)
1651 chooser.set_sensitive(True)1659 chooser.set_sensitive(True)
1652 workdir.set_sensitive(True)1660 workdir.set_sensitive(True)
1661 virtualenv.set_sensitive(True)
1662
1653 if layout_item.has_key('command') and layout_item['command'] != '':1663 if layout_item.has_key('command') and layout_item['command'] != '':
1654 command.set_text(layout_item['command'])1664 command.set_text(layout_item['command'])
1655 else:1665 else:
@@ -1665,6 +1675,11 @@
1665 else:1675 else:
1666 workdir.set_text('')1676 workdir.set_text('')
16671677
1678 if layout_item.has_key('virtualenv') and layout_item['virtualenv'] != '':
1679 virtualenv.set_text(layout_item['virtualenv'])
1680 else:
1681 virtualenv.set_text('')
1682
1668 def on_layout_profile_chooser_changed(self, widget):1683 def on_layout_profile_chooser_changed(self, widget):
1669 """A new profile has been selected for this item"""1684 """A new profile has been selected for this item"""
1670 if not self.layout_item:1685 if not self.layout_item:
@@ -1688,6 +1703,14 @@
1688 layout[self.layout_item]['directory'] = workdir1703 layout[self.layout_item]['directory'] = workdir
1689 self.config.save()1704 self.config.save()
16901705
1706 def on_layout_profile_virtualenv_activate(self, widget):
1707 """A new virtualenv has been entered for this item"""
1708 virtualenv = widget.get_text()
1709 layout = self.config.layout_get_config(self.layout_name)
1710 layout[self.layout_item]['virtualenv'] = virtualenv
1711 self.config.save()
1712
1713
1691if __name__ == '__main__':1714if __name__ == '__main__':
1692 import util1715 import util
1693 util.DEBUG = True1716 util.DEBUG = True
16941717
=== modified file 'terminatorlib/terminal.py'
--- terminatorlib/terminal.py 2017-06-24 02:02:38 +0000
+++ terminatorlib/terminal.py 2018-10-23 02:50:13 +0000
@@ -12,6 +12,7 @@
12from gi.repository import Vte12from gi.repository import Vte
13import subprocess13import subprocess
14import urllib14import urllib
15import psutil
1516
16from util import dbg, err, spawn_new_terminator, make_uuid, manual_lookup, display_manager17from util import dbg, err, spawn_new_terminator, make_uuid, manual_lookup, display_manager
17import util18import util
@@ -108,6 +109,8 @@
108 cnxids = None109 cnxids = None
109 targets_for_new_group = None110 targets_for_new_group = None
110111
112 environment = None
113
111 def __init__(self):114 def __init__(self):
112 """Class initialiser"""115 """Class initialiser"""
113 GObject.GObject.__init__(self)116 GObject.GObject.__init__(self)
@@ -176,6 +179,9 @@
176 self.reconfigure()179 self.reconfigure()
177 self.vte.set_size(80, 24)180 self.vte.set_size(80, 24)
178181
182 self.virtualenv = None
183 self.uuid = None
184
179 def get_vte(self):185 def get_vte(self):
180 """This simply returns the vte widget we are using"""186 """This simply returns the vte widget we are using"""
181 return(self.vte)187 return(self.vte)
@@ -1402,8 +1408,10 @@
1402 else:1408 else:
1403 args.insert(0, shell)1409 args.insert(0, shell)
14041410
1405 if command is not None:1411 # This is commented out in order to keep the terminal running after command ends.
1406 args += ['-c', command]1412 # Downside is that I figured out no way to run the command without using vte.feed_child
1413 #if command is not None:
1414 # args += ['-c', command]
14071415
1408 if shell is None:1416 if shell is None:
1409 self.vte.feed(_('Unable to find a shell'))1417 self.vte.feed(_('Unable to find a shell'))
@@ -1442,6 +1450,18 @@
1442 self.vte.feed(_('Unable to start shell:') + shell)1450 self.vte.feed(_('Unable to start shell:') + shell)
1443 return(-1)1451 return(-1)
14441452
1453 if self.virtualenv is not None:
1454 virtualenv = 'workon {}'.format(self.virtualenv.split('/')[-1])
1455
1456 if command is None:
1457 command = virtualenv
1458 else:
1459 command = virtualenv + '\n' + command
1460
1461 if command is not None:
1462 command += '\n'
1463 self.vte.feed_child(command, len(command))
1464
1445 def prepare_url(self, urlmatch):1465 def prepare_url(self, urlmatch):
1446 """Prepare a URL from a VTE match"""1466 """Prepare a URL from a VTE match"""
1447 url = urlmatch[0]1467 url = urlmatch[0]
@@ -1621,6 +1641,27 @@
1621 if title:1641 if title:
1622 layout['title'] = title1642 layout['title'] = title
1623 layout['uuid'] = self.uuid1643 layout['uuid'] = self.uuid
1644
1645 if self.environment:
1646 if 'VIRTUAL_ENV' in self.environment:
1647 layout['virtualenv'] = self.environment['VIRTUAL_ENV']
1648 if 'PWD' in self.environment:
1649 layout['directory'] = self.environment['PWD']
1650 else:
1651 layout['virtualenv'] = ''
1652 layout['directory'] = self.get_cwd()
1653
1654 try:
1655 parent = psutil.Process(self.pid)
1656 children = parent.children(recursive=True)
1657 if len(children) > 0:
1658 for process in children:
1659 if "+" in subprocess.check_output(["ps", "-o", "stat=", "-p", str(process.pid)]):
1660 cmdline = process.cmdline()
1661 layout['command'] = " ".join(cmdline)
1662 except psutil.NoSuchProcess:
1663 pass
1664
1624 name = 'terminal%d' % count1665 name = 'terminal%d' % count
1625 count = count + 11666 count = count + 1
1626 global_layout[name] = layout1667 global_layout[name] = layout
@@ -1642,6 +1683,8 @@
1642 self.titlebar.set_custom_string(layout['title'])1683 self.titlebar.set_custom_string(layout['title'])
1643 if layout.has_key('directory') and layout['directory'] != '':1684 if layout.has_key('directory') and layout['directory'] != '':
1644 self.directory = layout['directory']1685 self.directory = layout['directory']
1686 if layout.has_key('virtualenv') and layout['virtualenv'] != '':
1687 self.virtualenv = layout['virtualenv']
1645 if layout.has_key('uuid') and layout['uuid'] != '':1688 if layout.has_key('uuid') and layout['uuid'] != '':
1646 self.uuid = make_uuid(layout['uuid'])1689 self.uuid = make_uuid(layout['uuid'])
16471690
16481691
=== modified file 'terminatorlib/terminal_popup_menu.py'
--- terminatorlib/terminal_popup_menu.py 2017-02-19 15:57:36 +0000
+++ terminatorlib/terminal_popup_menu.py 2018-10-23 02:50:13 +0000
@@ -191,6 +191,10 @@
191 item.connect('activate', lambda x: PrefsEditor(self.terminal))191 item.connect('activate', lambda x: PrefsEditor(self.terminal))
192 menu.append(item)192 menu.append(item)
193193
194 item = Gtk.ImageMenuItem.new_with_mnemonic(_('_Save Layout'))
195 item.connect('activate', lambda x: self.terminator.save_layout())
196 menu.append(item)
197
194 profilelist = sorted(self.config.list_profiles(), key=string.lower)198 profilelist = sorted(self.config.list_profiles(), key=string.lower)
195199
196 if len(profilelist) > 1:200 if len(profilelist) > 1:
197201
=== modified file 'terminatorlib/terminator.py'
--- terminatorlib/terminator.py 2017-02-28 19:48:11 +0000
+++ terminatorlib/terminator.py 2018-10-23 02:50:13 +0000
@@ -132,6 +132,12 @@
132 self.gnome_client = False132 self.gnome_client = False
133 dbg('GNOME session support not available')133 dbg('GNOME session support not available')
134134
135 def save_layout(self):
136 """Save Current Layout"""
137 dbg('saving current layout')
138 self.config.layout_set_config(self.layoutname, self.describe_layout())
139 self.config.save()
140
135 def save_yourself(self, *args):141 def save_yourself(self, *args):
136 """Save as much state as possible for the session manager"""142 """Save as much state as possible for the session manager"""
137 dbg('preparing session manager state')143 dbg('preparing session manager state')

Subscribers

People subscribed via source and target branches

to status/vote changes: