2
# Terminator - multiple gnome terminals in one window
3
# Copyright (C) 2006-2008 cmsj@tenshu.net
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, version 2 only.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
21
import gobject, gtk, pango
22
import os, signal, sys, subprocess, pwd, re, urllib2
24
#import version details
25
from terminatorlib.version import *
27
# import our configuration loader
28
from terminatorlib import config
29
from terminatorlib.config import dbg, err, debug
32
from terminatorlib.encoding import TerminatorEncoding
33
from terminatorlib.terminatoreditablelabel import TerminatorEditableLabel
34
# import translation support
35
from terminatorlib import translation
41
error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
42
_('You need to install python bindings for libvte ("python-vte" in debian/ubuntu)'))
46
class TerminatorTermTitle (gtk.EventBox):
58
_unzoomed_title = None
62
def __init__ (self, terminal, terminator, configwanted = False):
63
gtk.EventBox.__init__ (self)
65
self._title = TerminatorEditableLabel()
66
self._group = gtk.Label ()
67
self._separator = gtk.VSeparator ()
68
self._ebox = gtk.EventBox ()
69
self._grouphbox = gtk.HBox ()
70
self._icon = gtk.Image ()
71
self._hbox = gtk.HBox ()
72
self._terminal = terminal
74
self.terminator = terminator
75
if self.terminator.groupsend == 2:
76
self.set_from_icon_name (APP_NAME + \
77
'_active_broadcast_all', gtk.ICON_SIZE_MENU)
78
elif self.terminator.groupsend == 1:
79
self.set_from_icon_name (APP_NAME + \
80
'_active_broadcast_group', gtk.ICON_SIZE_MENU)
82
self.set_from_icon_name (APP_NAME + \
83
'_active_broadcast_off', gtk.ICON_SIZE_MENU)
85
self._grouphbox.pack_start (self._icon, False, True, 2)
86
self._grouphbox.pack_start (self._group, False, True, 2)
87
self._ebox.add (self._grouphbox)
88
self._ebox.show_all ()
90
self._hbox.pack_start (self._ebox, False, True, 0)
91
self._hbox.pack_start (self._separator, False, True, 0)
92
self._hbox.pack_start (self._title, True, True)
95
self._title.show_all ()
98
self.wanted = configwanted
100
self.connect ("button-press-event", self.on_clicked)
102
def connect_icon (self, func):
103
self._ebox.connect ("button-release-event", func)
105
def on_clicked (self, widget, event):
106
if self._parent is not None:
107
self._parent._vte.grab_focus ()
109
def set_group_label (self, name):
110
"""If 'name' is None, hide the group name object, otherwise set it as the group label"""
112
self._group.set_text (name)
116
self._separator.show ()
118
def set_terminal_title (self, name):
119
"""Set the title text shown in the titlebar"""
120
self._termtext = name
123
def set_terminal_size (self, width, height):
124
"""Set the terminal size shown in the titlebar"""
125
self._sizetext = "%sx%s" % (width, height)
128
def update_label (self):
129
"""Update the gtk label with values previously set"""
130
self._title.set_text ("%s %s" % (self._termtext, self._sizetext))
132
def get_terminal_title (self):
133
"""Return the text showin in the titlebar"""
134
return (self._termtext)
136
def set_from_icon_name (self, name, size = gtk.ICON_SIZE_MENU):
137
"""Set an icon for the group label"""
142
self._icon.set_from_icon_name (APP_NAME + name, size)
145
def update_colors(self, source):
146
"""Update terminals titlebar colours based on grouping"""
147
term = self._terminal
148
if term != source and term._group != None and term._group == source._group:
149
# Not active, group is not none, and in active's group
150
if self.terminator.groupsend == 0:
151
title_fg = term.conf.title_ia_txt_color
152
title_bg = term.conf.title_ia_bg_color
153
icon = '_receive_off'
155
title_fg = term.conf.title_rx_txt_color
156
title_bg = term.conf.title_rx_bg_color
158
group_fg = term.conf.title_rx_txt_color
159
group_bg = term.conf.title_rx_bg_color
160
elif term != source and term._group == None or term._group != source._group:
161
# Not active, group is not none, not in active's group
162
if self.terminator.groupsend == 2:
163
title_fg = term.conf.title_rx_txt_color
164
title_bg = term.conf.title_rx_bg_color
167
title_fg = term.conf.title_ia_txt_color
168
title_bg = term.conf.title_ia_bg_color
169
icon = '_receive_off'
170
group_fg = term.conf.title_ia_txt_color
171
group_bg = term.conf.title_ia_bg_color
173
title_fg = term.conf.title_tx_txt_color
174
title_bg = term.conf.title_tx_bg_color
175
if self.terminator.groupsend == 2:
176
icon = '_active_broadcast_all'
177
elif self.terminator.groupsend == 1:
178
icon = '_active_broadcast_group'
180
icon = '_active_broadcast_off'
181
group_fg = term.conf.title_tx_txt_color
182
group_bg = term.conf.title_tx_bg_color
184
self._title.modify_fg (gtk.STATE_NORMAL, gtk.gdk.color_parse (title_fg))
185
self._group.modify_fg (gtk.STATE_NORMAL, gtk.gdk.color_parse (group_fg))
186
self.modify_bg (gtk.STATE_NORMAL, gtk.gdk.color_parse (title_bg))
187
self._ebox.modify_bg (gtk.STATE_NORMAL, gtk.gdk.color_parse (group_bg))
188
self.set_from_icon_name(icon, gtk.ICON_SIZE_MENU)
192
"""Update our state"""
194
self._parent = self.get_parent ()
196
if self._parent.terminator._zoomed and len (self._parent.terminator.term_list):
197
if not self._unzoomed_title:
198
self._unzoomed_title = self.get_terminal_title ()
199
if self._parent.conf.zoomedtitlebar:
200
self.set_terminal_title ("Zoomed/Maximised terminal, %d hidden" % (len (self._parent.terminator.term_list) - 1))
206
if self._unzoomed_title:
207
self.set_terminal_title (self._unzoomed_title)
208
self._unzoomed_title = None
210
if isinstance (self._parent.get_parent (), gtk.Window):
214
if (self._parent.conf.titlebars and self.wanted) or self._parent._group:
219
if self._parent._group:
220
self.set_group_label (self._parent._group)
222
self.set_group_label (None)
224
class TerminatorTerm (gtk.VBox):
228
_custom_font_size = None
229
_custom_encoding = None
230
_default_encoding = None
233
_urgent_bell_cnid = None
235
def __init__ (self, terminator, profile = None, command = None, cwd = None):
236
gtk.VBox.__init__ (self)
237
self.terminator = terminator
238
self.conf = terminator.conf
239
self.command = command
243
self.cwd = cwd or os.getcwd();
244
if not os.path.exists(self.cwd) or not os.path.isdir(self.cwd):
245
self.cwd = pwd.getpwuid(os.getuid ())[5]
247
self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD)
248
self.scrollbar_position = self.conf.scrollbar_position
250
self._composited_support = True
251
self._vte = vte.Terminal ()
252
self._default_encoding = self._vte.get_encoding()
253
if not hasattr(self._vte, "set_opacity") or not hasattr(self._vte, "is_composited"):
254
self._composited_support = False
255
dbg ('H9TRANS: composited_support: %s' % self._composited_support)
256
#self._vte.set_double_buffered(True)
257
self._vte.set_size (80, 24)
258
self._vte._expose_data = None
261
self._termbox = gtk.HBox ()
264
self._titlebox = TerminatorTermTitle (self, self.terminator, self.conf.titlebars)
266
self._search_string = None
267
self._searchbox = gtk.HBox()
268
self._searchinput = gtk.Entry()
269
self._searchinput.set_activates_default(True)
270
self._searchinput.show()
272
self._searchinput.connect('activate', self.do_search)
273
self._searchinput.connect('key-press-event', self.search_keypress)
276
slabel.set_text(_("Search:"))
279
sclose = gtk.Button()
280
sclose.set_relief(gtk.RELIEF_NONE)
281
sclose.set_focus_on_click(False)
282
sclose.set_relief(gtk.RELIEF_NONE)
283
sclose_icon = gtk.Image()
284
sclose_icon.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
285
sclose.add(sclose_icon)
286
sclose.set_name("terminator-search-close-button")
287
if hasattr(sclose, "set_tooltip_text"):
288
sclose.set_tooltip_text("Close Search Bar")
289
sclose.connect('clicked', self.end_search)
292
# Button for the next result. Explicitly not show()n by default.
293
self._search_next = gtk.Button(_("Next"))
294
self._search_next.connect('clicked', self.next_search)
296
self._searchbox.pack_start(slabel, False)
297
self._search_result_label = gtk.Label()
298
self._search_result_label.set_text("")
299
self._search_result_label.show()
300
self._searchbox.pack_start(self._searchinput)
301
self._searchbox.pack_start(self._search_result_label, False)
302
self._searchbox.pack_start(self._search_next, False, False)
303
self._searchbox.pack_end(sclose, False, False)
306
self.pack_start(self._titlebox, False)
307
self.pack_start(self._termbox)
308
self.pack_end(self._searchbox)
310
self._titlebox.update ()
312
self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ())
313
if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled":
314
self._scrollbar.show ()
316
if self.scrollbar_position == 'left':
317
packfunc = self._termbox.pack_end
319
packfunc = self._termbox.pack_start
322
packfunc (self._scrollbar, False)
324
self._vte.connect ("key-press-event", self.on_vte_key_press)
325
self._vte.connect ("button-press-event", self.on_vte_button_press)
326
self._vte.connect ("popup-menu", self.create_popup_menu)
328
srcvtetargets = [ ( "vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE ) ]
329
dsttargets = [ ( "vte", gtk.TARGET_SAME_APP, self.TARGET_TYPE_VTE ), ('text/plain', 0, 0) , ("STRING", 0, 0), ("COMPOUND_TEXT", 0, 0)]
330
self._vte.drag_source_set( gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE)
331
self._titlebox.drag_source_set( gtk.gdk.BUTTON1_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE)
332
#self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE)
333
self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE)
334
self._vte.connect("drag-begin", self.on_drag_begin, self)
335
self._titlebox.connect("drag-begin", self.on_drag_begin, self)
336
self._vte.connect("drag-data-get", self.on_drag_data_get, self)
337
self._titlebox.connect("drag-data-get", self.on_drag_data_get, self)
338
#for testing purpose: drag-motion
339
self._vte.connect("drag-motion", self.on_drag_motion, self)
340
self._vte.connect("drag-data-received", self.on_drag_data_received, self)
342
if self.conf.copy_on_selection:
343
self._vte.connect ("selection-changed", lambda widget: self._vte.copy_clipboard ())
344
if self._composited_support :
345
self._vte.connect ("composited-changed", self.on_composited_changed)
346
self._vte.connect ("window-title-changed", self.on_vte_title_change)
347
self._vte.connect ("grab-focus", self.on_vte_focus)
348
self._vte.connect ("focus-out-event", self.on_vte_focus_out)
349
self._vte.connect ("focus-in-event", self.on_vte_focus_in)
350
self._vte.connect ("resize-window", self.on_resize_window)
351
self._vte.connect ("size-allocate", self.on_vte_size_allocate)
353
self._titlebox.connect_icon (self.on_group_button_press)
355
exit_action = self.conf.exit_action
356
if exit_action == "restart":
357
self._vte.connect ("child-exited", self.spawn_child)
358
# We need to support "left" because some buggy versions of gnome-terminal
359
# set it in some situations
360
elif exit_action in ("close", "left"):
361
self._vte.connect ("child-exited", lambda close_term: self.terminator.closeterm (self))
363
self._vte.add_events (gtk.gdk.ENTER_NOTIFY_MASK)
364
self._vte.connect ("enter_notify_event", self.on_vte_notify_enter)
366
self._vte.connect_after ("realize", self.reconfigure_vte)
368
self.add_matches(posix = self.conf.try_posix_regexp)
370
env_proxy = os.getenv ('http_proxy')
371
if not env_proxy and self.conf.http_proxy and self.conf.http_proxy != '':
372
os.putenv ('http_proxy', self.conf.http_proxy)
374
os.putenv ('COLORTERM', 'gnome-terminal')
376
def prepareurl (self, url, match):
377
dbg ("prepareurl: Checking '%s' with a match of '%s'" % (url, match))
378
if match == self.matches['email'] and url[0:7] != 'mailto:':
379
url = 'mailto:' + url
380
elif match == self.matches['addr_only'] and url[0:3] == 'ftp':
382
elif match == self.matches['addr_only']:
383
url = 'http://' + url
384
elif match == self.matches['launchpad']:
385
for item in re.findall(r'[0-9]+',url):
386
url = 'https://bugs.launchpad.net/bugs/%s' % item
391
def openurl (self, url):
392
dbg ('openurl: viewing %s'%url)
394
dbg ('openurl: calling xdg-open')
395
subprocess.Popen(["xdg-open", url])
397
dbg ('openurl: xdg-open failed')
399
dbg ('openurl: calling url_show')
400
self.terminator.url_show (url)
402
dbg ('openurl: url_show failed. No URL for you')
405
def on_resize_window(self, widget, width, height):
406
dbg ('Resize window triggered on %s: %dx%d' % (widget, width, height))
408
def on_vte_size_allocate(self, widget, allocation):
409
dbg ('Terminal resized to %dx%d' % (self._vte.get_column_count (), self._vte.get_row_count ()))
410
self._titlebox.set_terminal_size (self._vte.get_column_count (), self._vte.get_row_count ())
411
if self._vte.window != None and (self.conf.geometry_hinting):
412
self.terminator.on_term_resized ()
414
def get_pixbuf(self, maxsize= None):
415
pixmap = self.get_snapshot()
416
(width, height) = pixmap.get_size()
417
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height)
418
pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, width, height)
420
longest = max(width, height)
422
if maxsize is not None:
423
factor = float(maxsize) / float(longest)
425
if not maxsize or (width * factor) > width or (height * factor) > height:
428
scaledpixbuf = pixbuf.scale_simple (int(width * factor), int(height * factor), gtk.gdk.INTERP_BILINEAR)
432
def on_drag_begin(self, widget, drag_context, data):
434
widget.drag_source_set_icon_pixbuf(self.get_pixbuf (512))
436
def on_drag_data_get(self,widget, drag_context, selection_data, info, time, data):
437
dbg ("Drag data get")
438
selection_data.set("vte",info, str(data.terminator.term_list.index (self)))
440
def on_expose_event(self, widget, event):
441
if widget._expose_data is None:
444
color = widget._expose_data['color']
445
coord = widget._expose_data['coord']
447
context = widget.window.cairo_create()
448
#leaving those xxx_group as they could be usefull
449
##http://macslow.thepimp.net/?p=153
450
#context.push_group()
451
context.set_source_rgba(color.red, color.green, color.blue, 0.5)
453
context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1])
455
context.line_to(i[0],i[1])
458
#context.pop_group_to_source()
462
def on_drag_motion(self, widget, drag_context, x, y, time, data):
463
dbg ("Drag Motion on ")
465
x-special/gnome-icon-list
471
text/plain;charset=utf-8
472
text/plain;charset=UTF-8
476
if 'text/plain' in drag_context.targets:
477
#copy text from another widget
479
srcwidget = drag_context.get_source_widget()
480
if (isinstance(srcwidget, gtk.EventBox) and srcwidget == self._titlebox) or widget == srcwidget:
484
alloc = widget.allocation
485
rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height)
487
if self.conf.use_theme_colors:
488
color = self._vte.get_style ().text[gtk.STATE_NORMAL]
490
color = gtk.gdk.color_parse (self.conf.foreground_color)
492
pos = self.get_location(widget, x, y)
494
topright = (alloc.width,0)
495
topmiddle = (alloc.width/2,0)
496
bottomleft = (0, alloc.height)
497
bottomright = (alloc.width,alloc.height)
498
bottommiddle = (alloc.width/2, alloc.height)
499
middle = (alloc.width/2, alloc.height/2)
500
middleleft = (0, alloc.height/2)
501
middleright = (alloc.width, alloc.height/2)
502
#print "%f %f %d %d" %(coef1, coef2, b1,b2)
505
coord = (topright, topmiddle, bottommiddle, bottomright)
507
coord = (topleft, topright, middleright , middleleft)
509
coord = (topleft, topmiddle, bottommiddle, bottomleft)
511
coord = (bottomleft, bottomright, middleright , middleleft)
514
#here, we define some widget internal values
515
widget._expose_data = { 'color': color, 'coord' : coord }
516
#redraw by forcing an event
517
connec = widget.connect_after('expose-event', self.on_expose_event)
518
widget.window.invalidate_rect(rect, True)
519
widget.window.process_updates(True)
520
#finaly reset the values
521
widget.disconnect(connec)
522
widget._expose_data = None
524
def on_drag_drop(self, widget, drag_context, x, y, time):
525
parent = widget.get_parent()
526
dbg ('Drag drop on %s'%parent)
528
def get_target_terms(self):
529
if self.terminator.groupsend == 2:
530
return self.terminator.term_list
531
elif self.terminator.groupsend == 1:
533
for term in self.terminator.term_list:
534
if term == self or (term._group != None and term._group == self._group):
535
term_subset.append(term)
540
def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data):
541
dbg ("Drag Data Received")
542
if selection_data.type == 'text/plain':
543
#copy text to destination
544
#print "%s %s" % (selection_data.type, selection_data.target)
545
txt = selection_data.data.strip()
546
if txt[0:7] == "file://":
547
txt = "'%s'" % urllib2.unquote(txt[7:])
548
for term in self.get_target_terms():
549
term._vte.feed_child(txt)
552
widgetsrc = data.terminator.term_list[int(selection_data.data)]
553
srcvte = drag_context.get_source_widget()
554
#check if computation requireds
555
if (isinstance(srcvte, gtk.EventBox) and srcvte == self._titlebox) or srcvte == widget:
560
dsthbox = widget.get_parent().get_parent()
562
dstpaned = dsthbox.get_parent()
563
srcpaned = srchbox.get_parent()
564
if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window):
565
dbg (" Only one terminal")
567
pos = self.get_location(widget, x, y)
569
data.terminator.remove(widgetsrc, True)
570
data.terminator.add(self, widgetsrc,pos)
573
def get_location(self, vte, x, y):
575
#get the diagonales function for the receiving widget
576
coef1 = float(vte.allocation.height)/float(vte.allocation.width)
577
coef2 = -float(vte.allocation.height)/float(vte.allocation.width)
579
b2 = vte.allocation.height
580
#determine position in rectangle
591
if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ):
593
if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ):
595
if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ):
597
if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ):
601
def add_matches (self, posix = True):
602
userchars = "-A-Za-z0-9"
603
passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
604
hostchars = "-A-Za-z0-9"
605
pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'\""
606
schemes = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
607
user = "[" + userchars + "]+(:[" + passchars + "]+)?"
608
urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]"
611
dbg ('add_matches: Trying POSIX URL regexps. Set try_posix_regexp = False in config to only try GNU if you get (harmless) VTE warnings.')
615
dbg ('add_matches: Trying GNU URL regexps. Set try_posix_regexp = True in config if URLs are not detected.')
619
self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
621
if self.matches['full_uri'] == -1:
623
err ('add_matches: POSIX match failed, trying GNU')
624
self.add_matches(posix = False)
626
err ('add_matches: Failed adding URL match patterns')
628
self.matches['voip'] = self._vte.match_add(lboundry + '(callto:|h323:|sip:)' + "[" + userchars + "+][" + userchars + ".]*(:[0-9]+)?@?[" + pathchars + "]+" + rboundry)
629
self.matches['addr_only'] = self._vte.match_add (lboundry + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
630
self.matches['email'] = self._vte.match_add (lboundry + "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*" + rboundry)
631
self.matches['nntp'] = self._vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry)
632
# if the url looks like a Launchpad changelog closure entry LP: #92953 - make it a url to http://bugs.launchpad.net
633
# the regular expression is similar to the perl one specified in the Ubuntu Policy Manual - /lp:\s+\#\d+(?:,\s*\#\d+)*/i
634
self.matches['launchpad'] = self._vte.match_add ('\\b(lp|LP):?\s?#?[0-9]+(,\s*#?[0-9]+)*\\b')
636
def _path_lookup(self, command):
637
if os.path.isabs (command):
638
if os.path.isfile (command):
642
elif command[:2] == './' and os.path.isfile(command):
643
dbg('path_lookup: Relative filename "%s" found in cwd' % command)
647
paths = os.environ['PATH'].split(':')
648
if len(paths[0]) == 0: raise (ValueError)
649
except (ValueError, NameError):
650
dbg('path_lookup: PATH not set in environment, using fallbacks')
651
paths = ['/usr/local/bin', '/usr/bin', '/bin']
653
dbg('path_lookup: Using %d paths: %s' % (len(paths), paths))
656
target = os.path.join (path, command)
657
if os.path.isfile (target):
658
dbg('path_lookup: found "%s"' % target)
661
dbg('path_lookup: Unable to locate "%s"' % command)
663
def _shell_lookup(self):
664
shells = [os.getenv('SHELL'), pwd.getpwuid(os.getuid())[6],
665
'bash', 'zsh', 'tcsh', 'ksh', 'csh', 'sh']
668
if shell is None: continue
669
elif os.path.isfile (shell):
672
rshell = self._path_lookup(shell)
673
if rshell is not None:
674
dbg('shell_lookup: Found "%s" at "%s"' % (shell, rshell))
677
dbg('shell_lookup: Unable to locate a shell')
679
def spawn_child (self, event=None):
680
update_records = self.conf.update_records
681
login = self.conf.login_shell
687
dbg ('spawn_child: using self.command: %s' % self.command)
688
command = self.command
689
elif self.conf.use_custom_command:
690
dbg ('spawn_child: using custom command: %s' % self.conf.custom_command)
691
command = self.conf.custom_command
693
if type(command) is list:
694
# List of arguments from -x
695
dbg('spawn_child: Bypassing shell and trying to run "%s" directly' % command[0])
696
shell = self._path_lookup(command[0])
699
shell = self._shell_lookup()
701
if self.conf.login_shell:
702
args.insert(0, "-%s" % shell)
704
args.insert(0, shell)
706
if command is not None:
707
args += ['-c', command]
710
# Give up, we're completely stuck
711
err (_('Unable to find a shell'))
712
gobject.timeout_add (100, self.terminator.closeterm, self)
715
os.putenv ('WINDOWID', '%s' % self._vte.get_parent_window().xid)
717
self._pid = self._vte.fork_command (command = shell, argv = args,
718
envv = [], loglastlog = login, logwtmp = update_records,
719
logutmp = update_records, directory=self.cwd)
721
self.on_vte_title_change(self._vte) # Force an initial update of our titles
722
self._titlebox.update ()
725
err (_('Unable to start shell: ') + shell)
729
""" Return the current working directory of the subprocess.
730
This function requires OS specific behaviours
733
cwd = self.terminator.pid_get_cwd (self._pid)
735
err ('get_cwd: unable to get cwd of %d' % self._pid)
738
dbg ('get_cwd found: %s'%cwd)
741
def reconfigure_vte (self, widget = None):
743
self._vte.set_emulation (self.conf.emulation)
746
if self._custom_encoding == False or self._custom_encoding == None:
747
self._vte.set_encoding (self.conf.encoding)
750
self._vte.set_word_chars (self.conf.word_chars)
752
# Set our mouselation
753
self._vte.set_mouse_autohide (self.conf.mouse_autohide)
755
# Set our compatibility
756
backspace = self.conf.backspace_binding
757
delete = self.conf.delete_binding
759
# Note, each of the 4 following comments should replace the line beneath it, but the python-vte bindings don't appear to support this constant, so the magic values are being assumed from the C enum :/
760
if backspace == "ascii-del":
761
# backbind = vte.ERASE_ASCII_BACKSPACE
764
# backbind = vte.ERASE_AUTO_BACKSPACE
767
if delete == "escape-sequence":
768
# delbind = vte.ERASE_DELETE_SEQUENCE
771
# delbind = vte.ERASE_AUTO
774
self._vte.set_backspace_binding (backbind)
775
self._vte.set_delete_binding (delbind)
778
if not self._custom_font_size:
780
self._vte.set_font (pango.FontDescription (self.conf.font))
785
self._vte.set_allow_bold (self.conf.allow_bold)
787
# Set our color scheme
788
palette = self.conf.palette
789
if self.conf.use_theme_colors:
790
fg_color = self._vte.get_style ().text[gtk.STATE_NORMAL]
791
bg_color = self._vte.get_style ().base[gtk.STATE_NORMAL]
793
fg_color = gtk.gdk.color_parse (self.conf.foreground_color)
794
bg_color = gtk.gdk.color_parse (self.conf.background_color)
796
colors = palette.split (':')
800
palette.append (gtk.gdk.color_parse (color))
801
self._vte.set_colors (fg_color, bg_color, palette)
803
cursor_color = self.conf.cursor_color
804
if cursor_color != '':
805
self._vte.set_color_cursor (gtk.gdk.color_parse (cursor_color))
808
if hasattr (self._vte, "set_cursor_shape"):
809
self._vte.set_cursor_shape (getattr (vte, "CURSOR_SHAPE_" + self.conf.cursor_shape.upper ()))
811
# Set our background image, transparency and type
812
# Many thanks to the authors of gnome-terminal, on which this code is based.
813
background_type = self.conf.background_type
814
dbg ('H9TRANS: Configuring background type as: %s' % background_type)
816
# set background image settings
817
if background_type == "image" and self.conf.background_image is not None and self.conf.background_image != '':
818
dbg ('H9TRANS: Setting background image to: %s' % self.conf.background_image)
819
self._vte.set_background_image_file (self.conf.background_image)
820
dbg ('H9TRANS: Setting background image scroll to: %s' % self.conf.scroll_background)
821
self._vte.set_scroll_background (self.conf.scroll_background)
823
dbg ('H9TRANS: Unsetting background image')
824
self._vte.set_background_image_file('')
825
dbg ('H9TRANS: Unsetting background image scrolling')
826
self._vte.set_scroll_background(False)
828
# set transparency for the background (image)
830
if background_type in ("image", "transparent"):
831
self._vte.set_background_tint_color (gtk.gdk.color_parse (self.conf.background_color))
832
self._vte.set_background_saturation(1 - (self.conf.background_darkness))
833
opacity = int(self.conf.background_darkness * 65535)
834
dbg ('H9TRANS: Set background tint color to: %s' % self.conf.background_color)
835
dbg ('H9TRANS: Set background saturation to: %s' % (1 - (self.conf.background_darkness)))
837
dbg ('H9TRANS: Set background saturation to: 1')
838
self._vte.set_background_saturation(1)
840
if self._composited_support:
841
dbg ('H9TRANS: Set opacity to: %s' % opacity)
842
self._vte.set_opacity(opacity)
844
if background_type == "transparent":
845
if not self.conf.enable_real_transparency:
846
self._vte.set_background_transparent (True)
848
self._vte.set_background_transparent (False)
850
# Set our cursor blinkiness
851
self._vte.set_cursor_blinks (self.conf.cursor_blink)
853
if self.conf.force_no_bell:
854
self._vte.set_audible_bell (False)
855
self._vte.set_visible_bell (False)
856
if self._urgent_bell_cnid:
857
self._vte.disconnect (self._urgent_bell_cnid)
858
self._urgent_bell_cnid = None
860
# Set our audible belliness
861
self._vte.set_audible_bell (self.conf.audible_bell)
863
# Set our visual flashiness
864
self._vte.set_visible_bell (self.conf.visible_bell)
866
# Set our urgent belliness
867
if self.conf.urgent_bell:
869
self._urgent_bell_cnid = self._vte.connect ("beep", self.terminator.on_beep)
871
err ("beep signal not supported by your VTE, urgent handler not available")
872
elif self._urgent_bell_cnid:
873
self._vte.disconnect (self._urgent_bell_cnid)
874
self._urgent_bell_cnid = None
876
# Set our scrolliness
877
self._vte.set_scrollback_lines (self.conf.scrollback_lines)
878
self._vte.set_scroll_on_keystroke (self.conf.scroll_on_keystroke)
879
self._vte.set_scroll_on_output (self.conf.scroll_on_output)
881
if self.scrollbar_position != self.conf.scrollbar_position:
882
self.scrollbar_position = self.conf.scrollbar_position
884
if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled':
885
self._scrollbar.hide ()
887
self._scrollbar.show ()
888
if self.scrollbar_position == 'right':
889
self._termbox.reorder_child (self._vte, 0)
890
elif self.scrollbar_position == 'left':
891
self._termbox.reorder_child (self._scrollbar, 0)
893
if hasattr (self._vte, "set_alternate_screen_scroll"):
894
self._vte.set_alternate_screen_scroll (self.conf.alternate_screen_scroll)
897
self.focus = self.conf.focus
899
# Sync our titlebar state
900
self._titlebox.update ()
901
self._vte.queue_draw ()
903
def get_size_details(self):
904
font_width = self._vte.get_char_width ()
905
font_height = self._vte.get_char_height ()
906
columns = self._vte.get_column_count ()
907
rows = self._vte.get_row_count ()
909
return (font_width, font_height, columns, rows)
911
def on_composited_changed (self, widget):
912
self.reconfigure_vte ()
914
def on_vte_button_press (self, term, event):
915
# Left mouse button + Ctrl while over a link should open it
916
mask = gtk.gdk.CONTROL_MASK
917
if (event.state & mask) == mask:
918
if event.button == 1:
919
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
921
self.openurl (self.prepareurl (url[0], url[1]))
924
# Left mouse button should transfer focus to this vte widget
925
# we also need to give focus on the widget where the paste occured
926
if event.button in (1 ,2):
927
if event.button == 2:
928
self.paste_clipboard (True)
930
self._vte.grab_focus ()
933
# Right mouse button should display a context menu if ctrl not pressed
934
if event.button == 3 and event.state & gtk.gdk.CONTROL_MASK == 0:
935
self.create_popup_menu (self._vte, event)
938
def on_vte_notify_enter (self, term, event):
939
if (self.focus == "sloppy" or self.focus == "mouse"):
943
def do_autocleangroups_toggle (self):
944
self.terminator.autocleangroups = not self.terminator.autocleangroups
945
if self.terminator.autocleangroups:
946
self.terminator.group_hoover()
948
def do_scrollbar_toggle (self):
949
self.toggle_widget_visibility (self._scrollbar)
951
def do_splittogroup_toggle (self):
952
self.terminator.splittogroup = not self.terminator.splittogroup
954
def do_title_toggle (self):
955
self._titlebox.wanted = not self._titlebox.get_property ('visible')
956
self.toggle_widget_visibility (self._titlebox)
958
def toggle_widget_visibility (self, widget):
959
if not isinstance (widget, gtk.Widget):
962
if widget.get_property ('visible'):
967
def paste_clipboard(self, primary = False):
968
for term in self.get_target_terms():
970
term._vte.paste_primary ()
972
term._vte.paste_clipboard ()
973
self._vte.grab_focus()
975
def do_enumerate(self, pad=False):
977
numstr='%0'+str(len(str(len(self.terminator.term_list))))+'d'
980
for term in self.get_target_terms():
981
idx=self.terminator.term_list.index(term)
982
term._vte.feed_child(numstr % (idx+1))
984
#keybindings for the individual splited terminals (affects only the
985
#the selected terminal)
986
UnhandledKeybindings = ('close_window', 'full_screen')
987
def on_vte_key_press (self, term, event):
989
dbg ('on_vte_key_press: Called on %s with no event' % term)
991
mapping = self.terminator.keybindings.lookup(event)
993
if mapping == "hide_window":
996
if mapping and mapping not in self.UnhandledKeybindings:
997
dbg("on_vte_key_press: lookup found %r" % mapping)
998
# handle the case where user has re-bound copy to ctrl+<key>
999
# we only copy if there is a selection otherwise let it fall through to ^<key>
1000
if (mapping == "copy" and event.state & gtk.gdk.CONTROL_MASK):
1001
if self._vte.get_has_selection ():
1002
getattr(self, "key_" + mapping)()
1005
getattr(self, "key_" + mapping)()
1008
if self.terminator.groupsend != 0 and self._vte.is_focus ():
1009
if self._group and self.terminator.groupsend == 1:
1010
self.terminator.group_emit (self, self._group, 'key-press-event', event)
1011
if self.terminator.groupsend == 2:
1012
self.terminator.all_emit (self, 'key-press-event', event)
1016
def key_zoom_in(self):
1019
def key_zoom_out(self):
1023
self._vte.copy_clipboard ()
1025
def key_paste(self):
1026
self.paste_clipboard ()
1028
def key_toggle_scrollbar(self):
1029
self.do_scrollbar_toggle ()
1031
def key_zoom_normal(self):
1034
def key_search(self):
1037
# bindings that should be moved to Terminator as they all just call
1038
# a function of Terminator. It would be cleaner if TerminatorTerm
1039
# has absolutely no reference to Terminator.
1040
# N (next) - P (previous) - O (horizontal) - E (vertical) - W (close)
1041
def key_new_root_tab(self):
1042
self.terminator.newtab (self, True)
1044
def key_go_next(self):
1045
self.terminator.go_next (self)
1047
def key_go_prev(self):
1048
self.terminator.go_prev (self)
1050
def key_go_up(self):
1051
self.terminator.go_up (self)
1053
def key_go_down(self):
1054
self.terminator.go_down (self)
1056
def key_go_left(self):
1057
self.terminator.go_left (self)
1059
def key_go_right(self):
1060
self.terminator.go_right (self)
1062
def key_split_horiz(self):
1063
self.terminator.splitaxis (self, False)
1065
def key_split_vert(self):
1066
self.terminator.splitaxis (self, True)
1068
def key_close_term(self):
1069
self.terminator.closeterm (self)
1071
def key_new_tab(self):
1072
self.terminator.newtab(self)
1074
def key_resize_up(self):
1075
self.terminator.resizeterm (self, 'Up')
1077
def key_resize_down(self):
1078
self.terminator.resizeterm (self, 'Down')
1080
def key_resize_left(self):
1081
self.terminator.resizeterm (self, 'Left')
1083
def key_resize_right(self):
1084
self.terminator.resizeterm (self, 'Right')
1086
def key_move_tab_right(self):
1087
self.terminator.move_tab (self, 'right')
1089
def key_move_tab_left(self):
1090
self.terminator.move_tab (self, 'left')
1092
def key_toggle_zoom(self):
1093
self.terminator.toggle_zoom (self)
1095
def key_scaled_zoom(self):
1096
self.terminator.toggle_zoom (self, True)
1098
def key_next_tab(self):
1099
self.terminator.next_tab (self)
1101
def key_prev_tab(self):
1102
self.terminator.previous_tab (self)
1104
def key_switch_to_tab_1(self):
1105
self.terminator.switch_to_tab (self, 0)
1107
def key_switch_to_tab_2(self):
1108
self.terminator.switch_to_tab (self, 1)
1110
def key_switch_to_tab_3(self):
1111
self.terminator.switch_to_tab (self, 2)
1113
def key_switch_to_tab_4(self):
1114
self.terminator.switch_to_tab (self, 3)
1116
def key_switch_to_tab_5(self):
1117
self.terminator.switch_to_tab (self, 4)
1119
def key_switch_to_tab_6(self):
1120
self.terminator.switch_to_tab (self, 5)
1122
def key_switch_to_tab_7(self):
1123
self.terminator.switch_to_tab (self, 6)
1125
def key_switch_to_tab_8(self):
1126
self.terminator.switch_to_tab (self, 7)
1128
def key_switch_to_tab_9(self):
1129
self.terminator.switch_to_tab (self, 8)
1131
def key_switch_to_tab_10(self):
1132
self.terminator.switch_to_tab (self, 9)
1134
def key_reset(self):
1135
self._vte.reset (True, False)
1137
def key_reset_clear(self):
1138
self._vte.reset (True, True)
1140
def key_group_all(self):
1141
self.group_all(self)
1143
def key_ungroup_all(self):
1144
self.ungroup_all(self)
1146
def key_group_tab(self):
1147
self.group_tab(self)
1149
def key_ungroup_tab(self):
1150
self.ungroup_tab(self)
1152
def key_new_window(self):
1155
if not os.path.isabs(cmd):
1156
# Command is not an absolute path. Figure out where we are
1157
cmd = os.path.join (self.terminator.origcwd, sys.argv[0])
1158
if not os.path.isfile(cmd):
1159
# we weren't started as ./terminator in a path. Give up
1160
err('Unable to locate Terminator')
1163
dbg("Spawning: %s" % cmd)
1164
subprocess.Popen([cmd,])
1167
def zoom_orig (self):
1168
self._custom_font_size = None
1169
self._vte.set_font (pango.FontDescription (self.conf.font))
1171
def zoom (self, zoom_in):
1172
pangodesc = self._vte.get_font ()
1173
fontsize = pangodesc.get_size ()
1175
if fontsize > pango.SCALE and not zoom_in:
1176
fontsize -= pango.SCALE
1178
fontsize += pango.SCALE
1180
pangodesc.set_size (fontsize)
1181
self._custom_font_size = fontsize
1182
self._vte.set_font (pangodesc)
1184
def start_search(self):
1185
self._searchbox.show()
1186
self._searchinput.grab_focus()
1188
def search_keypress(self, widget, event):
1189
key = gtk.gdk.keyval_name(event.keyval)
1193
def end_search(self, widget = None):
1194
self._search_row = 0
1195
self._search_string = None
1196
self._search_result_label.set_text("")
1197
self._searchbox.hide()
1198
self._scrollbar.set_value(self._vte.get_cursor_position()[1])
1199
self._vte.grab_focus()
1201
def do_search(self, widget):
1202
string = widget.get_text()
1203
dbg("do_search: Looking for %r" % string)
1207
if string != self._search_string:
1208
self._search_row = self._get_vte_buffer_range()[0]
1209
self._search_string = string
1211
self._search_result_label.set_text("Searching scrollback")
1214
# Called by get_text_range, once per character. Argh.
1215
def _search_character(self, widget, col, row, junk):
1218
def next_search(self, widget=None):
1219
startrow,endrow = self._get_vte_buffer_range()
1221
if self._search_row == endrow:
1222
self._search_row = startrow
1223
self._search_result_label.set_text("Finished Search")
1224
self._search_next.hide()
1226
buffer = self._vte.get_text_range(self._search_row, 0, self._search_row, -1, self._search_character)
1228
# dbg("Row %d buffer: %r" % (self._search_row, buffer))
1229
index = buffer.find(self._search_string)
1231
self._search_result_label.set_text("Found at row %d" % self._search_row)
1232
self._scrollbar.set_value(self._search_row)
1233
self._search_row += 1
1234
self._search_next.show()
1236
self._search_row += 1
1238
def _get_vte_buffer_range(self):
1239
column, endrow = self._vte.get_cursor_position()
1240
startrow = max(0, endrow - self.conf.scrollback_lines)
1241
return(startrow, endrow)
1243
def get_geometry (self):
1244
'''Returns Gdk.Window.get_position(), pixel-based cursor position,
1245
and Gdk.Window.get_geometry()'''
1247
if not self._vte.window:
1249
x, y = self._vte.window.get_origin ()
1250
reply.setdefault('origin_x',x)
1251
reply.setdefault('origin_y',y)
1253
column, row = self._vte.get_cursor_position ()
1254
cursor_x = column * self._vte.get_char_width ()
1255
cursor_y = row * self._vte.get_char_height ()
1256
reply.setdefault('cursor_x', cursor_x)
1257
reply.setdefault('cursor_y', cursor_y)
1259
geometry = self._vte.window.get_geometry()
1260
reply.setdefault('offset_x', geometry[0])
1261
reply.setdefault('offset_y', geometry[1])
1262
reply.setdefault('span_x', geometry[2])
1263
reply.setdefault('span_y', geometry[3])
1264
reply.setdefault('depth', geometry[4])
1268
def create_popup_menu (self, widget, event = None):
1274
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
1275
button = event.button
1282
address = self.prepareurl (url[0], url[1])
1284
if url[1] == self.matches['email']:
1285
nameopen = _("_Send Mail To...")
1286
namecopy = _("_Copy Email Address")
1287
item = gtk.MenuItem (nameopen)
1288
elif url[1] == self.matches['voip']:
1289
nameopen = _("Ca_ll To...")
1290
namecopy = _("_Copy Call Address")
1291
item = gtk.MenuItem (nameopen)
1293
nameopen = _("_Open Link")
1294
namecopy = _("_Copy Link Address")
1295
iconopen = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
1297
item = gtk.ImageMenuItem (nameopen)
1298
item.set_property('image', iconopen)
1300
item.connect ("activate", lambda menu_item: self.openurl (address))
1303
item = gtk.MenuItem (namecopy)
1304
item.connect ("activate", lambda menu_item: self.clipboard.set_text (url[0]))
1307
item = gtk.MenuItem ()
1310
item = gtk.ImageMenuItem (gtk.STOCK_COPY)
1311
item.connect ("activate", lambda menu_item: self._vte.copy_clipboard ())
1312
item.set_sensitive (self._vte.get_has_selection ())
1315
item = gtk.ImageMenuItem (gtk.STOCK_PASTE)
1316
item.connect ("activate", lambda menu_item: self.paste_clipboard ())
1319
item = gtk.MenuItem ()
1322
if not self.terminator._zoomed:
1323
str_horiz = _("Split H_orizontally")
1324
str_vert = _("Split V_ertically")
1326
item = gtk.ImageMenuItem (str_horiz)
1327
item_image = gtk.Image ()
1328
item_image.set_from_icon_name (APP_NAME + '_horiz', gtk.ICON_SIZE_MENU)
1329
item.set_image (item_image)
1330
if hasattr(item, "set_always_show_image"):
1331
item.set_always_show_image (True)
1333
item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, False))
1335
item = gtk.ImageMenuItem (str_vert)
1336
item_image = gtk.Image ()
1337
item_image.set_from_icon_name (APP_NAME + '_vert', gtk.ICON_SIZE_MENU)
1338
item.set_image (item_image)
1339
if hasattr(item, "set_always_show_image"):
1340
item.set_always_show_image (True)
1342
item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, True))
1345
item = gtk.MenuItem (_("Open _Tab"))
1346
item.connect ("activate", lambda menu_item: self.terminator.newtab (self))
1349
if self.terminator.debugaddress:
1350
item = gtk.MenuItem (_("Open _Debug Tab"))
1351
item.connect ("activate", lambda menu_item: self.terminator.newtab (self, command = "telnet %s" % ' '.join([str(x) for x in self.terminator.debugaddress])))
1355
if self.conf.extreme_tabs:
1356
item = gtk.MenuItem (_("Open Top Level Tab"))
1357
item.connect ("activate", lambda menu_item: self.terminator.newtab (self, True))
1360
item = gtk.MenuItem ()
1363
item = gtk.ImageMenuItem (gtk.STOCK_CLOSE)
1364
item.connect ("activate", lambda menu_item: self.terminator.closeterm (self))
1367
item = gtk.MenuItem ()
1370
if len (self.terminator.term_list) > 1:
1371
if not self.terminator._zoomed:
1372
item = gtk.MenuItem (_("_Zoom terminal"))
1373
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True))
1376
item = gtk.MenuItem (_("Ma_ximise terminal"))
1377
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self))
1380
if self.terminator._zoomed and not self.terminator._maximised:
1381
item = gtk.MenuItem (_("_Unzoom terminal"))
1382
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self, True))
1385
if self.terminator._zoomed and self.terminator._maximised:
1386
item = gtk.MenuItem (_("Unma_ximise terminal"))
1387
item.connect ("activate", lambda menu_item: self.terminator.toggle_zoom (self))
1390
item = gtk.MenuItem ()
1393
item = gtk.CheckMenuItem (_("Show _scrollbar"))
1394
item.set_active (self._scrollbar.get_property ('visible'))
1395
item.connect ("toggled", lambda menu_item: self.do_scrollbar_toggle ())
1398
item = gtk.CheckMenuItem (_("Show _titlebar"))
1399
item.set_active (self._titlebox.get_property ('visible'))
1400
item.connect ("toggled", lambda menu_item: self.do_title_toggle ())
1402
item.set_sensitive (False)
1405
item = gtk.MenuItem (_("Ed_it profile"))
1406
item.connect ("activate", lambda menu_item: self.terminator.edit_profile (self))
1409
self._do_encoding_items (menu)
1412
menu.popup (None, None, None, button, time)
1416
def create_popup_group_menu (self, widget, event = None):
1421
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
1422
button = event.button
1428
self.populate_grouping_menu (menu)
1431
if gtk.gtk_version > (2, 14, 0):
1432
menu.popup (None, None, self.position_popup_group_menu, button, time, widget)
1434
menu.popup (None, None, None, button, time, widget)
1438
def populate_grouping_menu (self, widget):
1441
item = gtk.MenuItem (_("Assign to group..."))
1442
item.connect ("activate", self.create_group)
1443
widget.append (item)
1445
if len (self.terminator.groupings) > 0:
1446
groupitem = gtk.RadioMenuItem (groupitem, _("None"))
1447
groupitem.set_active (self._group == None)
1448
groupitem.connect ("activate", self.set_group, None)
1449
widget.append (groupitem)
1451
for group in self.terminator.groupings:
1452
item = gtk.RadioMenuItem (groupitem, group, False)
1453
item.set_active (self._group == group)
1454
item.connect ("toggled", self.set_group, group)
1455
widget.append (item)
1458
if self._group != None or len (self.terminator.groupings) > 0:
1459
item = gtk.MenuItem ()
1460
widget.append (item)
1462
if self._group != None:
1463
item = gtk.MenuItem (_("Remove group %s") % (self._group))
1464
item.connect ("activate", self.ungroup, self._group)
1465
widget.append (item)
1467
if self.terminator.get_first_parent_widget (self, gtk.Notebook) is not None and \
1468
not isinstance (self.get_parent(), gtk.Notebook):
1469
item = gtk.MenuItem (_("G_roup all in tab"))
1470
item.connect ("activate", self.group_tab)
1471
widget.append (item)
1473
if self.terminator.get_first_parent_widget(self, gtk.Notebook) is not None and \
1474
not isinstance(self.get_parent(), gtk.Notebook) and \
1475
len(self.terminator.groupings) > 0:
1476
item = gtk.MenuItem(_("Ungr_oup all in tab"))
1477
item.connect("activate", self.ungroup_tab)
1480
if len (self.terminator.groupings) > 0:
1481
item = gtk.MenuItem (_("Remove all groups"))
1482
item.connect ("activate", self.ungroup_all)
1483
widget.append (item)
1485
if self._group != None:
1486
item = gtk.MenuItem ()
1487
widget.append (item)
1489
item = gtk.ImageMenuItem (_("Close group %s") % (self._group))
1490
grp_close_img = gtk.Image()
1491
grp_close_img.set_from_stock(gtk.STOCK_CLOSE, 1)
1492
item.set_image (grp_close_img)
1493
item.connect ("activate", lambda menu_item: self.terminator.closegroupedterms (self))
1494
widget.append (item)
1496
item = gtk.MenuItem ()
1497
widget.append (item)
1501
groupitem = gtk.RadioMenuItem (groupitem, _("Broadcast off"))
1502
groupitem.set_active (self.terminator.groupsend == 0)
1503
groupitem.connect ("activate", self.set_groupsend, 0)
1504
widget.append (groupitem)
1506
groupitem = gtk.RadioMenuItem (groupitem, _("Broadcast to group"))
1507
groupitem.set_active (self.terminator.groupsend == 1)
1508
groupitem.connect ("activate", self.set_groupsend, 1)
1509
widget.append (groupitem)
1511
groupitem = gtk.RadioMenuItem (groupitem, _("Broadcast to all"))
1512
groupitem.set_active (self.terminator.groupsend == 2)
1513
groupitem.connect ("activate", self.set_groupsend, 2)
1514
widget.append (groupitem)
1516
item = gtk.MenuItem ()
1517
widget.append (item)
1519
item = gtk.CheckMenuItem (_("Split to this group"))
1520
item.set_active (self.terminator.splittogroup)
1521
item.connect ("toggled", lambda menu_item: self.do_splittogroup_toggle ())
1522
if self._group == None:
1523
item.set_sensitive(False)
1524
widget.append (item)
1526
item = gtk.CheckMenuItem (_("Autoclean groups"))
1527
item.set_active (self.terminator.autocleangroups)
1528
item.connect ("toggled", lambda menu_item: self.do_autocleangroups_toggle ())
1529
widget.append (item)
1531
item = gtk.MenuItem ()
1532
widget.append (item)
1534
item = gtk.MenuItem (_("Insert terminal number"))
1535
item.connect ("activate", lambda menu_item: self.do_enumerate ())
1536
widget.append (item)
1538
item = gtk.MenuItem (_("Insert padded terminal number"))
1539
item.connect ("activate", lambda menu_item: self.do_enumerate (pad=True))
1540
widget.append (item)
1542
def position_popup_group_menu(self, menu, widget):
1543
screen_w = gtk.gdk.screen_width()
1544
screen_h = gtk.gdk.screen_height()
1546
widget_win = widget.get_window()
1547
widget_x, widget_y = widget_win.get_origin()
1548
widget_w, widget_h = widget_win.get_size()
1550
menu_w, menu_h = menu.size_request()
1552
if widget_y + widget_h + menu_h > screen_h:
1553
menu_y = max(widget_y - menu_h, 0)
1555
menu_y = widget_y + widget_h
1557
return (widget_x, menu_y, 1)
1559
def create_group (self, item):
1560
self.groupingscope = 0
1561
grplist=self.terminator.groupings[:]
1565
vbox = gtk.VBox (False, 6)
1566
vbox.set_border_width(5)
1569
# Populate the "Assign..." Section
1570
contentvbox = gtk.VBox (False, 6)
1571
selframe = gtk.Frame()
1572
selframe_label = gtk.Label()
1573
selframe_label.set_markup(_("<b>Assign...</b>"))
1574
selframe.set_shadow_type(gtk.SHADOW_NONE)
1575
selframe.set_label_widget(selframe_label)
1576
selframe_align = gtk.Alignment(0, 0, 1, 1)
1577
selframe_align.set_padding(0, 0, 12, 0)
1578
selframevbox = gtk.VBox ()
1579
selframehbox = gtk.HBox ()
1581
# Populate the Combo with existing group names (None at the top)
1582
sel_combo = gtk.combo_box_new_text()
1583
sel_combo.append_text(_("Terminals with no group"))
1585
sel_combo.append_text(grp)
1586
sel_combo.set_sensitive(False)
1588
# Here are the radio buttons
1591
groupitem = gtk.RadioButton (groupitem, _("Terminal"))
1592
groupitem.set_active (True)
1593
groupitem.connect ("toggled", self.set_groupingscope, 0, sel_combo)
1594
selframehbox.pack_start (groupitem, False)
1596
groupitem = gtk.RadioButton (groupitem, _("Group"))
1597
groupitem.connect ("toggled", self.set_groupingscope, 1, sel_combo)
1598
selframehbox.pack_start (groupitem, False)
1600
groupitem = gtk.RadioButton (groupitem, _("All"))
1601
groupitem.connect ("toggled", self.set_groupingscope, 2, sel_combo)
1602
selframehbox.pack_start (groupitem, False)
1604
selframevbox.pack_start(selframehbox, True, True)
1605
selframevbox.pack_start(sel_combo, True, True)
1606
selframe_align.add(selframevbox)
1607
selframe.add(selframe_align)
1608
contentvbox.pack_start(selframe)
1610
# Populate the "To..." Section
1611
tgtframe = gtk.Frame()
1612
tgtframe_label = gtk.Label()
1613
tgtframe_label.set_markup(_("<b>To...</b>"))
1614
tgtframe.set_shadow_type(gtk.SHADOW_NONE)
1615
tgtframe.set_label_widget(tgtframe_label)
1616
tgtframe_align = gtk.Alignment(0, 0, 1, 1)
1617
tgtframe_align.set_padding(0, 0, 12, 0)
1618
tgtframevbox = gtk.VBox ()
1620
# Populate the Combo with existing group names (None not needed)
1621
tgt_comboentry = gtk.combo_box_entry_new_text()
1623
tgt_comboentry.append_text(grp)
1625
tgtframevbox.pack_start(tgt_comboentry, True, True)
1627
tgtframe_align.add(tgtframevbox)
1628
tgtframe.add(tgtframe_align)
1629
contentvbox.pack_start(tgtframe)
1631
okbut = gtk.Button (stock=gtk.STOCK_OK)
1632
canbut = gtk.Button (stock=gtk.STOCK_CANCEL)
1633
hbuttonbox = gtk.HButtonBox()
1634
hbuttonbox.set_layout(gtk.BUTTONBOX_END)
1635
hbuttonbox.pack_start (canbut, True, True)
1636
hbuttonbox.pack_start (okbut, True, True)
1638
vbox.pack_start (contentvbox, False, True)
1639
vbox.pack_end (hbuttonbox, False, True)
1641
canbut.connect ("clicked", lambda kill: win.destroy())
1642
okbut.connect ("clicked", self.do_create_group, win, sel_combo, tgt_comboentry)
1643
tgt_comboentry.child.connect ("activate", self.do_create_group, win, sel_combo, tgt_comboentry)
1645
tgt_comboentry.grab_focus()
1647
# Center it over the current terminal (not perfect?!?)
1648
# This could be replaced by a less bothersome dialog, but then that would
1649
# center over the window, not the terminal
1650
screen_w = gtk.gdk.screen_width()
1651
screen_h = gtk.gdk.screen_height()
1652
local_x, local_y = self.allocation.x, self.allocation.y
1653
local_w, local_h = self.allocation.width, self.allocation.height
1654
window_x, window_y = self.get_window().get_origin()
1655
x = window_x + local_x
1656
y = window_y + local_y
1658
new_x = min(max(0, x+(local_w/2)-(win.allocation.width/2)), screen_w-win.allocation.width)
1659
new_y = min(max(0, y+(local_h/2)-(win.allocation.height/2)), screen_h-win.allocation.height)
1660
win.move(new_x, new_y)
1664
def set_groupingscope(self, widget, scope=None, sel_combo=None):
1665
if widget.get_active():
1666
self.groupingscope = scope
1667
if self.groupingscope == 1:
1668
sel_combo.set_sensitive(True)
1670
sel_combo.set_sensitive(False)
1672
def do_create_group (self, widget, window, src, tgt):
1673
tgt_name = tgt.child.get_text()
1675
src_name = src.get_active_text()
1676
src_id = src.get_active()
1680
if tgt_name == "" or (self.groupingscope == 1 and src_name == None):
1683
if tgt_name not in self.terminator.groupings:
1684
self.terminator.groupings.append (tgt_name)
1686
if self.groupingscope == 2:
1687
for term in self.terminator.term_list:
1688
term.set_group (None, tgt_name)
1689
elif self.groupingscope == 1:
1690
for term in self.terminator.term_list:
1691
if term._group == src_name or (src_id == 0 and term._group == None):
1692
term.set_group (None, tgt_name)
1694
self.set_group (None, tgt_name)
1698
def add_group (self, groupname):
1699
if not groupname in self.terminator.groupings:
1700
self.terminator.groupings.append(groupname)
1702
def set_group (self, item, data):
1703
if self._group == data:
1709
self._titlebox.set_group_label (data)
1710
self._titlebox.update ()
1713
# We were not previously in a group
1714
self._titlebox.show ()
1717
# We were previously in a group
1720
# We have been removed from a group
1721
if not self.conf.titlebars and not self._want_titlebar:
1722
self._titlebox.hide ()
1723
self.terminator.group_hoover ()
1725
def set_groupsend (self, item, data):
1726
self.terminator.groupsend = data
1728
def ungroup (self, widget, data):
1729
for term in self.terminator.term_list:
1730
if term._group == data:
1731
term.set_group (None, None)
1732
self.terminator.group_hoover ()
1734
def group_all (self, widget):
1736
self.add_group(allname)
1737
for term in self.terminator.term_list:
1738
term.set_group (None, allname)
1739
self.on_vte_focus_in(self._vte, None)
1740
self.terminator.group_hoover ()
1742
def ungroup_all (self, widget):
1743
for term in self.terminator.term_list:
1744
term.set_group (None, None)
1745
self.on_vte_focus_in(self._vte, None)
1746
self.terminator.group_hoover ()
1748
def find_all_terms_in_tab (self, notebook, pagenum=-1):
1750
pagenum = notebook.get_current_page()
1751
notebookchild = notebook.get_nth_page(pagenum)
1755
for term in self.terminator.term_list:
1756
termparent = term.get_parent()
1757
while not isinstance(termparent, gtk.Window):
1758
if termparent == notebookchild:
1760
termparent = termparent.get_parent()
1764
def group_tab (self, widget):
1766
notebook = self.terminator.get_first_parent_widget(self, gtk.Notebook)
1767
pagenum = notebook.get_current_page()
1768
notebookchild = notebook.get_nth_page(pagenum)
1769
terms = self.find_all_terms_in_tab(notebook)
1771
notebooktablabel = notebook.get_tab_label(notebookchild)
1772
if notebooktablabel._label._custom is True:
1773
groupname = notebooktablabel.get_title()
1776
tmppagenum = pagenum
1778
groupname = "Tab %d" % (tmppagenum + 1)
1779
if groupname not in self.terminator.groupings:
1783
self.add_group(groupname)
1785
term.set_group(None, groupname)
1786
self.on_vte_focus_in(self._vte, None)
1787
self.terminator.group_hoover()
1789
def ungroup_tab (self, widget):
1790
notebook = self.terminator.get_first_parent_widget(self, gtk.Notebook)
1791
terms = self.find_all_terms_in_tab (notebook)
1794
term.set_group (None, None)
1795
self.on_vte_focus_in(self._vte, None)
1796
self.terminator.group_hoover()
1798
def on_encoding_change (self, widget, encoding):
1799
current = self._vte.get_encoding ()
1800
if current != encoding:
1801
dbg ('Setting Encoding to: %s' % encoding)
1802
if encoding == self.conf.encoding:
1803
self._custom_encoding = False
1805
self._custom_encoding = True
1806
self._vte.set_encoding (encoding)
1808
def _do_encoding_items (self, menu):
1809
active_encodings = self.conf.active_encodings
1810
item = gtk.MenuItem (_("Encodings"))
1812
submenu = gtk.Menu ()
1813
item.set_submenu (submenu)
1814
encodings = TerminatorEncoding ().get_list ()
1815
encodings.sort (lambda x, y: cmp (x[2].lower (), y[2].lower ()))
1817
current_encoding = self._vte.get_encoding ()
1820
if current_encoding not in active_encodings:
1821
active_encodings.insert (0, _(current_encoding))
1823
for encoding in active_encodings:
1824
if encoding == self._default_encoding:
1825
extratext = " (%s)" % _("Default")
1826
elif encoding == current_encoding and self._custom_encoding == True:
1827
extratext = " (%s)" % _("User defined")
1831
radioitem = gtk.RadioMenuItem (group, _(encoding) + extratext)
1833
if encoding == current_encoding:
1834
radioitem.set_active (True)
1839
radioitem.connect ('activate', self.on_encoding_change, encoding)
1840
submenu.append (radioitem)
1842
item = gtk.MenuItem (_("Other Encodings"))
1843
submenu.append (item)
1846
submenu = gtk.Menu ()
1847
item.set_submenu (submenu)
1850
for encoding in encodings:
1851
if encoding[1] in active_encodings:
1854
if encoding[1] is None:
1855
label = "%s %s"%(encoding[2], self._vte.get_encoding ())
1857
label = "%s %s"%(encoding[2], encoding[1])
1859
radioitem = gtk.RadioMenuItem (group, label)
1863
if encoding[1] == current_encoding:
1864
radioitem.set_active (True)
1866
radioitem.connect ('activate', self.on_encoding_change, encoding[1])
1867
submenu.append (radioitem)
1869
def get_window_title(self, vte = None):
1872
title = vte.get_window_title ()
1874
title = str(self.command)
1877
def on_vte_title_change(self, vte):
1878
title = self.get_window_title(vte)
1879
if title == self._oldtitle:
1880
# Title hasn't changed, don't bother doing anything
1882
self._oldtitle = title
1884
if self.conf.titletips:
1885
vte.set_property ("has-tooltip", True)
1886
vte.set_property ("tooltip-text", title)
1887
#set the title anyhow, titlebars setting only show/hide the label
1888
self._titlebox.set_terminal_title (title)
1889
self.terminator.set_window_title (title)
1890
notebookpage = self.terminator.get_first_notebook_page(vte)
1891
while notebookpage != None:
1892
if notebookpage[0].get_tab_label(notebookpage[1]):
1893
label = notebookpage[0].get_tab_label(notebookpage[1])
1894
label.set_title(title)
1895
# FIXME: Is this necessary? The above line should update the label. LP #369370 might be related
1896
notebookpage[0].set_tab_label(notebookpage[1], label)
1897
notebookpage = self.terminator.get_first_notebook_page(notebookpage[0])
1899
def on_vte_focus_in(self, vte, event):
1900
for term in self.terminator.term_list:
1901
term._titlebox.update_colors(self)
1904
def on_vte_focus_out(self, vte, event):
1907
def on_vte_focus(self, vte):
1908
title = self.get_window_title(vte)
1909
self.terminator.set_window_title(title)
1910
notebookpage = self.terminator.get_first_notebook_page(vte)
1911
while notebookpage != None:
1912
if notebookpage[0].get_tab_label(notebookpage[1]):
1913
label = notebookpage[0].get_tab_label(notebookpage[1])
1914
label.set_title(title)
1915
notebookpage[0].set_tab_label(notebookpage[1], label)
1916
notebookpage = self.terminator.get_first_notebook_page(notebookpage[0])
1918
def is_scrollbar_present(self):
1919
return self._scrollbar.get_property('visible')
1921
def on_group_button_press(self, term, event):
1922
if event.button == 1:
1923
self.create_popup_group_menu(term, event)