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
1=== modified file 'terminatorlib/ipc.py'
2--- terminatorlib/ipc.py 2017-02-13 14:36:55 +0000
3+++ terminatorlib/ipc.py 2018-10-23 02:50:13 +0000
4@@ -183,6 +183,27 @@
5 if terminal in terms:
6 return root_widget.get_tab_label(tab_child).get_label()
7
8+ @dbus.service.method(dbus_interface=BUS_NAME)
9+ def push_environment(self, data):
10+ environment = {}
11+ for line in iter(data.splitlines()):
12+ if '=' not in line:
13+ environment[var[0]] + line
14+ else:
15+ var = line.split('=')
16+ environment[var[0]] = var[1]
17+ dbg('received environment!')
18+ dbg(environment)
19+ if 'TERMINATOR_UUID' in environment.keys():
20+ terminal = self.terminator.find_terminal_by_uuid(environment['TERMINATOR_UUID'])
21+ terminal.environment = environment
22+
23+ @dbus.service.method(dbus_interface=BUS_NAME)
24+ def save_layout(self):
25+ dbg('received save layout!')
26+ self.terminator.save_layout()
27+
28+
29 def with_proxy(func):
30 """Decorator function to connect to the session dbus bus"""
31 dbg('dbus client call: %s' % func.func_name)
32
33=== modified file 'terminatorlib/preferences.glade'
34--- terminatorlib/preferences.glade 2016-12-13 21:08:02 +0000
35+++ terminatorlib/preferences.glade 2018-10-23 02:50:13 +0000
36@@ -3424,6 +3424,18 @@
37 </packing>
38 </child>
39 <child>
40+ <object class="GtkLabel" id="label44">
41+ <property name="visible">True</property>
42+ <property name="can_focus">False</property>
43+ <property name="label" translatable="yes">Virtual Environment:</property>
44+ <property name="xalign">1</property>
45+ </object>
46+ <packing>
47+ <property name="left_attach">0</property>
48+ <property name="top_attach">3</property>
49+ </packing>
50+ </child>
51+ <child>
52 <object class="GtkComboBoxText" id="layout_profile_chooser">
53 <property name="visible">True</property>
54 <property name="can_focus">False</property>
55@@ -3469,6 +3481,21 @@
56 <property name="top_attach">2</property>
57 </packing>
58 </child>
59+ <child>
60+ <object class="GtkEntry" id="layout_profile_virtualenv">
61+ <property name="visible">True</property>
62+ <property name="can_focus">True</property>
63+ <property name="hexpand">True</property>
64+ <property name="invisible_char">•</property>
65+ <property name="primary_icon_activatable">False</property>
66+ <property name="secondary_icon_activatable">False</property>
67+ <signal name="changed" handler="on_layout_profile_virtualenv_changed" swapped="no"/>
68+ </object>
69+ <packing>
70+ <property name="left_attach">1</property>
71+ <property name="top_attach">3</property>
72+ </packing>
73+ </child>
74 </object>
75 <packing>
76 <property name="resize">True</property>
77
78=== modified file 'terminatorlib/prefseditor.py'
79--- terminatorlib/prefseditor.py 2017-06-24 02:02:38 +0000
80+++ terminatorlib/prefseditor.py 2018-10-23 02:50:13 +0000
81@@ -1423,6 +1423,10 @@
82 """A different working directory has been entered for this item"""
83 self.layouteditor.on_layout_profile_workingdir_activate(widget)
84
85+ def on_layout_profile_virtualenv_changed(self, widget):
86+ """A different virtualenvironment has been entered for this item"""
87+ self.layouteditor.on_layout_profile_virtualenv_activate(widget)
88+
89 def on_layout_name_edited(self, cell, path, newtext):
90 """Update a layout name"""
91 oldname = cell.get_property('text')
92@@ -1620,9 +1624,11 @@
93 command = self.builder.get_object('layout_profile_command')
94 chooser = self.builder.get_object('layout_profile_chooser')
95 workdir = self.builder.get_object('layout_profile_workingdir')
96+ virtualenv = self.builder.get_object('layout_profile_virtualenv')
97 command.set_sensitive(False)
98 chooser.set_sensitive(False)
99 workdir.set_sensitive(False)
100+ virtualenv.set_sensitive(False)
101
102 def on_layout_item_selection_changed(self, selection):
103 """A different item in the layout was selected"""
104@@ -1640,16 +1646,20 @@
105 command = self.builder.get_object('layout_profile_command')
106 chooser = self.builder.get_object('layout_profile_chooser')
107 workdir = self.builder.get_object('layout_profile_workingdir')
108+ virtualenv = self.builder.get_object('layout_profile_virtualenv')
109
110 if layout_item['type'] != 'Terminal':
111 command.set_sensitive(False)
112 chooser.set_sensitive(False)
113 workdir.set_sensitive(False)
114+ virtualenv.set_sensitive(False)
115 return
116
117 command.set_sensitive(True)
118 chooser.set_sensitive(True)
119 workdir.set_sensitive(True)
120+ virtualenv.set_sensitive(True)
121+
122 if layout_item.has_key('command') and layout_item['command'] != '':
123 command.set_text(layout_item['command'])
124 else:
125@@ -1665,6 +1675,11 @@
126 else:
127 workdir.set_text('')
128
129+ if layout_item.has_key('virtualenv') and layout_item['virtualenv'] != '':
130+ virtualenv.set_text(layout_item['virtualenv'])
131+ else:
132+ virtualenv.set_text('')
133+
134 def on_layout_profile_chooser_changed(self, widget):
135 """A new profile has been selected for this item"""
136 if not self.layout_item:
137@@ -1688,6 +1703,14 @@
138 layout[self.layout_item]['directory'] = workdir
139 self.config.save()
140
141+ def on_layout_profile_virtualenv_activate(self, widget):
142+ """A new virtualenv has been entered for this item"""
143+ virtualenv = widget.get_text()
144+ layout = self.config.layout_get_config(self.layout_name)
145+ layout[self.layout_item]['virtualenv'] = virtualenv
146+ self.config.save()
147+
148+
149 if __name__ == '__main__':
150 import util
151 util.DEBUG = True
152
153=== modified file 'terminatorlib/terminal.py'
154--- terminatorlib/terminal.py 2017-06-24 02:02:38 +0000
155+++ terminatorlib/terminal.py 2018-10-23 02:50:13 +0000
156@@ -12,6 +12,7 @@
157 from gi.repository import Vte
158 import subprocess
159 import urllib
160+import psutil
161
162 from util import dbg, err, spawn_new_terminator, make_uuid, manual_lookup, display_manager
163 import util
164@@ -108,6 +109,8 @@
165 cnxids = None
166 targets_for_new_group = None
167
168+ environment = None
169+
170 def __init__(self):
171 """Class initialiser"""
172 GObject.GObject.__init__(self)
173@@ -176,6 +179,9 @@
174 self.reconfigure()
175 self.vte.set_size(80, 24)
176
177+ self.virtualenv = None
178+ self.uuid = None
179+
180 def get_vte(self):
181 """This simply returns the vte widget we are using"""
182 return(self.vte)
183@@ -1402,8 +1408,10 @@
184 else:
185 args.insert(0, shell)
186
187- if command is not None:
188- args += ['-c', command]
189+ # This is commented out in order to keep the terminal running after command ends.
190+ # Downside is that I figured out no way to run the command without using vte.feed_child
191+ #if command is not None:
192+ # args += ['-c', command]
193
194 if shell is None:
195 self.vte.feed(_('Unable to find a shell'))
196@@ -1442,6 +1450,18 @@
197 self.vte.feed(_('Unable to start shell:') + shell)
198 return(-1)
199
200+ if self.virtualenv is not None:
201+ virtualenv = 'workon {}'.format(self.virtualenv.split('/')[-1])
202+
203+ if command is None:
204+ command = virtualenv
205+ else:
206+ command = virtualenv + '\n' + command
207+
208+ if command is not None:
209+ command += '\n'
210+ self.vte.feed_child(command, len(command))
211+
212 def prepare_url(self, urlmatch):
213 """Prepare a URL from a VTE match"""
214 url = urlmatch[0]
215@@ -1621,6 +1641,27 @@
216 if title:
217 layout['title'] = title
218 layout['uuid'] = self.uuid
219+
220+ if self.environment:
221+ if 'VIRTUAL_ENV' in self.environment:
222+ layout['virtualenv'] = self.environment['VIRTUAL_ENV']
223+ if 'PWD' in self.environment:
224+ layout['directory'] = self.environment['PWD']
225+ else:
226+ layout['virtualenv'] = ''
227+ layout['directory'] = self.get_cwd()
228+
229+ try:
230+ parent = psutil.Process(self.pid)
231+ children = parent.children(recursive=True)
232+ if len(children) > 0:
233+ for process in children:
234+ if "+" in subprocess.check_output(["ps", "-o", "stat=", "-p", str(process.pid)]):
235+ cmdline = process.cmdline()
236+ layout['command'] = " ".join(cmdline)
237+ except psutil.NoSuchProcess:
238+ pass
239+
240 name = 'terminal%d' % count
241 count = count + 1
242 global_layout[name] = layout
243@@ -1642,6 +1683,8 @@
244 self.titlebar.set_custom_string(layout['title'])
245 if layout.has_key('directory') and layout['directory'] != '':
246 self.directory = layout['directory']
247+ if layout.has_key('virtualenv') and layout['virtualenv'] != '':
248+ self.virtualenv = layout['virtualenv']
249 if layout.has_key('uuid') and layout['uuid'] != '':
250 self.uuid = make_uuid(layout['uuid'])
251
252
253=== modified file 'terminatorlib/terminal_popup_menu.py'
254--- terminatorlib/terminal_popup_menu.py 2017-02-19 15:57:36 +0000
255+++ terminatorlib/terminal_popup_menu.py 2018-10-23 02:50:13 +0000
256@@ -191,6 +191,10 @@
257 item.connect('activate', lambda x: PrefsEditor(self.terminal))
258 menu.append(item)
259
260+ item = Gtk.ImageMenuItem.new_with_mnemonic(_('_Save Layout'))
261+ item.connect('activate', lambda x: self.terminator.save_layout())
262+ menu.append(item)
263+
264 profilelist = sorted(self.config.list_profiles(), key=string.lower)
265
266 if len(profilelist) > 1:
267
268=== modified file 'terminatorlib/terminator.py'
269--- terminatorlib/terminator.py 2017-02-28 19:48:11 +0000
270+++ terminatorlib/terminator.py 2018-10-23 02:50:13 +0000
271@@ -132,6 +132,12 @@
272 self.gnome_client = False
273 dbg('GNOME session support not available')
274
275+ def save_layout(self):
276+ """Save Current Layout"""
277+ dbg('saving current layout')
278+ self.config.layout_set_config(self.layoutname, self.describe_layout())
279+ self.config.save()
280+
281 def save_yourself(self, *args):
282 """Save as much state as possible for the session manager"""
283 dbg('preparing session manager state')

Subscribers

People subscribed via source and target branches

to status/vote changes: