1506 lines
51 KiB
Python
Executable File
1506 lines
51 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Terminator - multiple gnome terminals in one window
|
|
# Copyright (C) 2006-2008 cmsj@tenshu.net
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, version 2 only.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
"""Terminator by Chris Jones <cmsj@tenshu.net>"""
|
|
|
|
# Global defines
|
|
APP_NAME = 'terminator'
|
|
APP_VERSION = '0.9'
|
|
|
|
# import standard python libs
|
|
import os, platform, sys, string, time, math
|
|
from optparse import OptionParser
|
|
|
|
try:
|
|
import gettext
|
|
gettext.install (APP_NAME)
|
|
except:
|
|
def _ (text):
|
|
return text
|
|
|
|
# import unix-lib
|
|
import pwd
|
|
|
|
TARGET_TYPE_VTE = 8
|
|
|
|
# import our configuration loader
|
|
from terminatorlib import config
|
|
from terminatorlib.config import dbg
|
|
|
|
#import encoding list
|
|
from terminatorlib.encoding import TerminatorEncoding
|
|
|
|
# Sort out cwd detection code, if available
|
|
pid_get_cwd = lambda pid: None
|
|
if platform.system() == 'FreeBSD':
|
|
try:
|
|
from terminatorlib import freebsd
|
|
pid_get_cwd = lambda pid: freebsd.get_process_cwd(pid)
|
|
dbg ('Using FreeBSD pid_get_cwd')
|
|
except:
|
|
dbg ('FreeBSD version too old for pid_get_cwd')
|
|
pass
|
|
elif platform.system() == 'Linux':
|
|
dbg ('Using Linux pid_get_cwd')
|
|
pid_get_cwd = lambda pid: os.path.realpath ('/proc/%s/cwd' % pid)
|
|
else:
|
|
dbg ('Unable to set a pid_get_cwd, unknown system: %s'%platform.system)
|
|
|
|
# import gtk libs
|
|
# check just in case anyone runs it on a non-gnome system.
|
|
try:
|
|
import gobject, gtk, pango
|
|
except:
|
|
print >> sys.stderr, _("You need to install the python bindings for " \
|
|
"gobject, gtk and pango to run Terminator.")
|
|
sys.exit(1)
|
|
|
|
# import a library for viewing URLs
|
|
try:
|
|
# gnome.url_show() is really useful
|
|
import gnome
|
|
url_show = gnome.url_show
|
|
except:
|
|
# webbrowser.open() is not really useful, but will do as a fallback
|
|
import webbrowser
|
|
url_show = webbrowser.open
|
|
|
|
# import vte-bindings
|
|
try:
|
|
import vte
|
|
except:
|
|
error = gtk.MessageDialog (None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
|
|
_('You need to install python bindings for libvte ("python-vte" in debian/ubuntu)'))
|
|
error.run()
|
|
sys.exit (1)
|
|
|
|
def openurl (url):
|
|
try:
|
|
url_show (url)
|
|
except:
|
|
pass
|
|
|
|
class TerminatorTerm (gtk.VBox):
|
|
|
|
matches = {}
|
|
|
|
def __init__ (self, terminator, profile = None, command = None, cwd = None):
|
|
gtk.VBox.__init__ (self)
|
|
self.terminator = terminator
|
|
self.conf = terminator.conf
|
|
self.command = command
|
|
|
|
self.cwd = cwd or os.getcwd();
|
|
if not os.path.exists(self.cwd) or not os.path.isdir(self.cwd):
|
|
self.cwd = pwd.getpwuid(os.getuid ())[5]
|
|
|
|
self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD)
|
|
self.scrollbar_position = self.conf.scrollbar_position
|
|
|
|
self._vte = vte.Terminal ()
|
|
self._vte.set_size (80, 24)
|
|
self.reconfigure_vte ()
|
|
self._vte.show ()
|
|
|
|
self._termbox = gtk.HBox ()
|
|
self._termbox.show()
|
|
self._title = gtk.Label()
|
|
self._title.show()
|
|
self._titlebox = gtk.EventBox()
|
|
self._titlebox.add(self._title)
|
|
self.show()
|
|
self.pack_start(self._titlebox, False)
|
|
self.pack_start(self._termbox)
|
|
|
|
if self.conf.titlebars and len(self.terminator.term_list)>0:
|
|
self._titlebox.show()
|
|
else:
|
|
self._titlebox.hide()
|
|
|
|
self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ())
|
|
if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled":
|
|
self._scrollbar.show ()
|
|
|
|
if self.scrollbar_position == 'right':
|
|
packfunc = self._termbox.pack_start
|
|
else:
|
|
packfunc = self._termbox.pack_end
|
|
|
|
packfunc (self._vte)
|
|
packfunc (self._scrollbar, False)
|
|
|
|
self._vte.connect ("key-press-event", self.on_vte_key_press)
|
|
self._vte.connect ("button-press-event", self.on_vte_button_press)
|
|
self._vte.connect ("popup-menu", self.on_vte_popup_menu)
|
|
|
|
"""drag and drop"""
|
|
srcvtetargets = [ ( "vte", gtk.TARGET_SAME_APP, TARGET_TYPE_VTE ) ]
|
|
dsttargets = [ ( "vte", gtk.TARGET_SAME_APP, TARGET_TYPE_VTE ), ('text/plain', 0, 0) , ("STRING", 0, 0), ("COMPOUND_TEXT", 0, 0)]
|
|
self._vte.drag_source_set( gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE)
|
|
self._titlebox.drag_source_set( gtk.gdk.BUTTON1_MASK, srcvtetargets, gtk.gdk.ACTION_MOVE)
|
|
#self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE)
|
|
self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,dsttargets, gtk.gdk.ACTION_MOVE)
|
|
self._vte.connect("drag-begin", self.on_drag_begin, self)
|
|
self._titlebox.connect("drag-begin", self.on_drag_begin, self)
|
|
self._vte.connect("drag-data-get", self.on_drag_data_get, self)
|
|
self._titlebox.connect("drag-data-get", self.on_drag_data_get, self)
|
|
#for testing purpose: drag-motion
|
|
self._vte.connect("drag-motion", self.on_drag_motion, self)
|
|
self._vte.connect("drag-data-received", self.on_drag_data_received, self)
|
|
|
|
self._vte.connect ("composited-changed", self.on_composited_changed)
|
|
self._vte.connect ("window-title-changed", self.on_vte_title_change)
|
|
self._vte.connect ("grab-focus", self.on_vte_focus)
|
|
self._vte.connect ("focus-out-event", self.on_vte_focus_out)
|
|
self._vte.connect ("focus-in-event", self.on_vte_focus_in)
|
|
|
|
exit_action = self.conf.exit_action
|
|
if exit_action == "restart":
|
|
self._vte.connect ("child-exited", self.spawn_child)
|
|
# We need to support "left" because some buggy versions of gnome-terminal
|
|
# set it in some situations
|
|
elif exit_action in ("close", "left"):
|
|
self._vte.connect ("child-exited", lambda close_term: self.terminator.closeterm (self))
|
|
|
|
self._vte.add_events (gtk.gdk.ENTER_NOTIFY_MASK)
|
|
self._vte.connect ("enter_notify_event", self.on_vte_notify_enter)
|
|
|
|
self.add_matches()
|
|
|
|
env_proxy = os.getenv ('http_proxy')
|
|
if not env_proxy and self.conf.http_proxy:
|
|
os.putenv ('http_proxy', self.conf.http_proxy)
|
|
|
|
os.putenv ('COLORTERM', 'gnome-terminal')
|
|
|
|
def on_drag_begin(self, widget, drag_context, data):
|
|
dbg ('Drag begins')
|
|
if os.path.exists("/usr/share/icons/hicolor/48x48/apps/terminator.png"):
|
|
widget.drag_source_set_icon_pixbuf( gtk.gdk.pixbuf_new_from_file("/usr/share/icons/hicolor/48x48/apps/terminator.png"))
|
|
|
|
def on_drag_data_get(self,widget, drag_context, selection_data, info, time, data):
|
|
dbg ("Drag data get")
|
|
selection_data.set("vte",info, str(data.terminator.term_list.index (self)))
|
|
|
|
|
|
def on_drag_motion(self, widget, drag_context, x, y, time, data):
|
|
dbg ("Drag Motion on ")
|
|
"""
|
|
x-special/gnome-icon-list
|
|
text/uri-list
|
|
UTF8_STRING
|
|
COMPOUND_TEXT
|
|
TEXT
|
|
STRING
|
|
text/plain;charset=utf-8
|
|
text/plain;charset=UTF-8
|
|
text/plain
|
|
"""
|
|
|
|
if 'text/plain' in drag_context.targets:
|
|
#copy text from another widget
|
|
return
|
|
srcwidget = drag_context.get_source_widget()
|
|
if (isinstance(srcwidget, gtk.EventBox) and srcwidget == self._titlebox) or widget == srcwidget:
|
|
#on self
|
|
return
|
|
|
|
|
|
alloc = widget.allocation
|
|
rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height)
|
|
widget.window.invalidate_rect(rect, True)
|
|
widget.window.process_updates(True)
|
|
|
|
context = widget.window.cairo_create()
|
|
if self.conf.use_theme_colors:
|
|
color = self._vte.get_style ().text[gtk.STATE_NORMAL]
|
|
else:
|
|
color = gtk.gdk.color_parse (self.conf.foreground_color)
|
|
|
|
|
|
context.set_source_rgba(color.red, color.green, color.blue, 0.5)
|
|
|
|
pos = self.get_location(widget, x, y)
|
|
topleft = (0,0)
|
|
topright = (alloc.width,0)
|
|
topmiddle = (alloc.width/2,0)
|
|
bottomleft = (0, alloc.height)
|
|
bottomright = (alloc.width,alloc.height)
|
|
bottommiddle = (alloc.width/2, alloc.height)
|
|
middle = (alloc.width/2, alloc.height/2)
|
|
middleleft = (0, alloc.height/2)
|
|
middleright = (alloc.width, alloc.height/2)
|
|
#print "%f %f %d %d" %(coef1, coef2, b1,b2)
|
|
coord = ()
|
|
if pos == "right":
|
|
coord = (topright, topmiddle, bottommiddle, bottomright)
|
|
if pos == "top":
|
|
coord = (topleft, topright, middleright , middleleft)
|
|
if pos == "left":
|
|
coord = (topleft, topmiddle, bottommiddle, bottomleft)
|
|
if pos == "bottom":
|
|
coord = (bottomleft, bottomright, middleright , middleleft)
|
|
|
|
if len(coord) > 0 :
|
|
context.move_to(coord[len(coord)-1][0],coord[len(coord)-1][1])
|
|
for i in coord:
|
|
context.line_to(i[0],i[1])
|
|
|
|
context.fill()
|
|
|
|
|
|
def on_drag_drop(self, widget, drag_context, x, y, time):
|
|
parent = widget.get_parent()
|
|
dbg ('Drag drop on %s'%parent)
|
|
|
|
def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data):
|
|
dbg ("Drag Data Received")
|
|
if selection_data.type == 'text/plain':
|
|
#copy text to destination
|
|
#print "%s %s" % (selection_data.type, selection_data.target)
|
|
txt = selection_data.data.strip()
|
|
if txt[0:7] == "file://":
|
|
txt = "'%s'" % txt[7:]
|
|
self._vte.feed_child(txt)
|
|
return
|
|
|
|
widgetsrc = data.terminator.term_list[int(selection_data.data)]
|
|
srcvte = drag_context.get_source_widget()
|
|
#check if computation requireds
|
|
if (isinstance(srcvte, gtk.EventBox) and srcvte == self._titlebox) or srcvte == widget:
|
|
dbg (" on itself")
|
|
return
|
|
|
|
srchbox = widgetsrc
|
|
dsthbox = widget.get_parent().get_parent()
|
|
|
|
dstpaned = dsthbox.get_parent()
|
|
srcpaned = srchbox.get_parent()
|
|
if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window):
|
|
dbg (" Only one terminal")
|
|
return
|
|
pos = self.get_location(widget, x, y)
|
|
|
|
data.terminator.remove(widgetsrc)
|
|
data.terminator.add(self, widgetsrc,pos)
|
|
return
|
|
|
|
def get_location(self, vte, x, y):
|
|
pos = ""
|
|
#get the diagonales function for the receiving widget
|
|
coef1 = float(vte.allocation.height)/float(vte.allocation.width)
|
|
coef2 = -float(vte.allocation.height)/float(vte.allocation.width)
|
|
b1 = 0
|
|
b2 = vte.allocation.height
|
|
#determine position in rectangle
|
|
"""
|
|
--------
|
|
|\ /|
|
|
| \ / |
|
|
| \/ |
|
|
| /\ |
|
|
| / \ |
|
|
|/ \|
|
|
--------
|
|
"""
|
|
if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ):
|
|
pos = "right"
|
|
if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ):
|
|
pos = "top"
|
|
if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ):
|
|
pos = "left"
|
|
if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ):
|
|
pos = "bottom"
|
|
return pos
|
|
|
|
|
|
def add_matches (self, lboundry="[[:<:]]", rboundry="[[:>:]]"):
|
|
userchars = "-A-Za-z0-9"
|
|
passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
|
|
hostchars = "-A-Za-z0-9"
|
|
pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
|
|
schemes = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
|
|
user = "[" + userchars + "]+(:[" + passchars + "]+)?"
|
|
urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]"
|
|
|
|
self.matches['full_uri'] = self._vte.match_add(lboundry + schemes + "//(" + user + "@)?[" + hostchars +".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
|
|
|
|
# FreeBSD works with [[:<:]], Linux works with \<
|
|
if self.matches['full_uri'] == -1:
|
|
if lboundry != "\\<":
|
|
self.add_matches(lboundry = "\\<", rboundry = "\\>")
|
|
else:
|
|
self.matches['addr_only'] = self._vte.match_add (lboundry + "(www|ftp)[" + hostchars + "]*\.[" + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
|
|
self.matches['email'] = self._vte.match_add (lboundry + "(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)+" + rboundry)
|
|
self.matches['nntp'] = self._vte.match_add (lboundry + '''news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?''' + rboundry)
|
|
|
|
def spawn_child (self, event=None):
|
|
update_records = self.conf.update_records
|
|
login = self.conf.login_shell
|
|
args = []
|
|
shell = ''
|
|
|
|
if self.command:
|
|
args = self.command
|
|
shell = self.command[0]
|
|
elif self.conf.use_custom_command:
|
|
args = self.conf.custom_command.split ()
|
|
shell = args[0]
|
|
|
|
if not os.path.exists (shell):
|
|
shell = os.getenv ('SHELL') or ''
|
|
if not os.path.exists (shell):
|
|
shell = pwd.getpwuid (os.getuid ())[6] or ''
|
|
if not os.path.exists (shell):
|
|
for i in ['bash','zsh','tcsh','ksh','csh','sh']:
|
|
shell = '/usr/bin/%s'%i
|
|
if not os.path.exists (shell):
|
|
shell = '/bin/%s'%i
|
|
if not os.path.exists (shell):
|
|
continue
|
|
else:
|
|
break
|
|
else:
|
|
break;
|
|
|
|
if not os.path.exists (shell):
|
|
# Give up, we're completely stuck
|
|
print >> sys.stderr, _('Unable to find a shell')
|
|
gobject.timeout_add (100, self.terminator.closeterm, self)
|
|
return (-1)
|
|
|
|
if not args:
|
|
args.append (shell)
|
|
|
|
os.putenv ('WINDOWID', '%s'%self._vte.get_parent_window().xid)
|
|
|
|
self._pid = self._vte.fork_command (command = shell, argv = args, envv = [], directory=self.cwd, loglastlog = login, logwtmp = update_records, logutmp = update_records)
|
|
|
|
if self._pid == -1:
|
|
print >>sys.stderr, _('Unable to start shell: ') + shell
|
|
return (-1)
|
|
|
|
def get_cwd (self):
|
|
""" Return the current working directory of the subprocess.
|
|
This function requires OS specific behaviours
|
|
"""
|
|
cwd = pid_get_cwd (self._pid)
|
|
dbg ('get_cwd found: %s'%cwd)
|
|
return (cwd)
|
|
|
|
def reconfigure_vte (self):
|
|
# Set our emulation
|
|
self._vte.set_emulation (self.conf.emulation)
|
|
|
|
# Set our wordchars
|
|
self._vte.set_word_chars (self.conf.word_chars)
|
|
|
|
# Set our mouselation
|
|
self._vte.set_mouse_autohide (self.conf.mouse_autohide)
|
|
|
|
# Set our compatibility
|
|
backspace = self.conf.backspace_binding
|
|
delete = self.conf.delete_binding
|
|
|
|
# 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 :/
|
|
if backspace == "ascii-del":
|
|
# backbind = vte.ERASE_ASCII_BACKSPACE
|
|
backbind = 2
|
|
else:
|
|
# backbind = vte.ERASE_AUTO_BACKSPACE
|
|
backbind = 1
|
|
|
|
if delete == "escape-sequence":
|
|
# delbind = vte.ERASE_DELETE_SEQUENCE
|
|
delbind = 3
|
|
else:
|
|
# delbind = vte.ERASE_AUTO
|
|
delbind = 0
|
|
|
|
self._vte.set_backspace_binding (backbind)
|
|
self._vte.set_delete_binding (delbind)
|
|
|
|
# Set our font
|
|
try:
|
|
self._vte.set_font (pango.FontDescription (self.conf.font))
|
|
except:
|
|
pass
|
|
|
|
# Set our boldness
|
|
self._vte.set_allow_bold (self.conf.allow_bold)
|
|
|
|
# Set our color scheme
|
|
palette = self.conf.palette
|
|
if self.conf.use_theme_colors:
|
|
fg_color = self._vte.get_style ().text[gtk.STATE_NORMAL]
|
|
bg_color = self._vte.get_style ().base[gtk.STATE_NORMAL]
|
|
else:
|
|
fg_color = gtk.gdk.color_parse (self.conf.foreground_color)
|
|
bg_color = gtk.gdk.color_parse (self.conf.background_color)
|
|
|
|
colors = palette.split (':')
|
|
palette = []
|
|
for color in colors:
|
|
if color:
|
|
palette.append (gtk.gdk.color_parse (color))
|
|
self._vte.set_colors (fg_color, bg_color, palette)
|
|
|
|
# Set our background image, transparency and type
|
|
# Many thanks to the authors of gnome-terminal, on which this code is based.
|
|
background_type = self.conf.background_type
|
|
|
|
# set background image settings
|
|
if background_type == "image":
|
|
self._vte.set_background_image_file (self.conf.background_image)
|
|
self._vte.set_scroll_background (self.conf.scroll_background)
|
|
else:
|
|
self._vte.set_background_image_file('')
|
|
self._vte.set_scroll_background(False)
|
|
|
|
# set transparency for the background (image)
|
|
if background_type in ("image", "transparent"):
|
|
self._vte.set_background_tint_color (bg_color)
|
|
self._vte.set_background_saturation(1 - (self.conf.background_darkness))
|
|
self._vte.set_opacity(int(self.conf.background_darkness * 65535))
|
|
else:
|
|
self._vte.set_background_saturation(1)
|
|
self._vte.set_opacity(65535)
|
|
|
|
if not self._vte.is_composited():
|
|
self._vte.set_background_transparent (background_type == "transparent")
|
|
else:
|
|
self._vte.set_background_transparent (False)
|
|
|
|
# Set our cursor blinkiness
|
|
self._vte.set_cursor_blinks = (self.conf.cursor_blink)
|
|
|
|
# Set our audible belliness
|
|
silent_bell = self.conf.silent_bell
|
|
self._vte.set_audible_bell (not silent_bell)
|
|
self._vte.set_visible_bell (silent_bell)
|
|
|
|
# Set our scrolliness
|
|
self._vte.set_scrollback_lines (self.conf.scrollback_lines)
|
|
self._vte.set_scroll_on_keystroke (self.conf.scroll_on_keystroke)
|
|
self._vte.set_scroll_on_output (self.conf.scroll_on_output)
|
|
|
|
if self.scrollbar_position != self.conf.scrollbar_position:
|
|
self.scrollbar_position = self.conf.scrollbar_position
|
|
|
|
if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled':
|
|
self._scrollbar.hide ()
|
|
else:
|
|
self._scrollbar.show ()
|
|
if self.scrollbar_position == 'right':
|
|
self._termbox.reorder_child (self._vte, 0)
|
|
elif self.scrollbar_position == 'left':
|
|
self._termbox.reorder_child (self._scrollbar, 0)
|
|
|
|
# Set our sloppiness
|
|
self.focus = self.conf.focus
|
|
|
|
def on_composited_changed (self, widget):
|
|
self.reconfigure_vte ()
|
|
|
|
def on_vte_button_press (self, term, event):
|
|
# Left mouse button + Ctrl while over a link should open it
|
|
mask = gtk.gdk.CONTROL_MASK
|
|
if (event.state & mask) == mask:
|
|
if event.button == 1:
|
|
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
|
|
if url:
|
|
if (url[0][0:7] != "mailto:") & (url[1] == self.matches['email']):
|
|
address = "mailto:" + url[0]
|
|
else:
|
|
address = url[0]
|
|
openurl ( address )
|
|
return False
|
|
|
|
# Left mouse button should transfer focus to this vte widget
|
|
if event.button == 1:
|
|
self._vte.grab_focus ()
|
|
return False
|
|
|
|
# Right mouse button should display a context menu if ctrl not pressed
|
|
if event.button == 3 and event.state & gtk.gdk.CONTROL_MASK == 0:
|
|
self.do_popup (event)
|
|
return True
|
|
|
|
def on_vte_notify_enter (self, term, event):
|
|
if (self.focus == "sloppy" or self.focus == "mouse"):
|
|
term.grab_focus ()
|
|
return False
|
|
|
|
def do_scrollbar_toggle (self):
|
|
self.toggle_widget_visibility (self._scrollbar)
|
|
|
|
def do_title_toggle (self):
|
|
self.toggle_widget_visibility (self._titlebox)
|
|
|
|
def toggle_widget_visibility (self, widget):
|
|
if not isinstance (widget, gtk.Widget):
|
|
raise TypeError
|
|
|
|
if widget.get_property ('visible'):
|
|
widget.hide ()
|
|
else:
|
|
widget.show ()
|
|
|
|
#keybindings for the individual splited terminals (affects only the
|
|
#the selected terminal)
|
|
def on_vte_key_press (self, term, event):
|
|
keyname = gtk.gdk.keyval_name (event.keyval)
|
|
|
|
mask = gtk.gdk.CONTROL_MASK
|
|
if (event.state & mask) == mask:
|
|
if keyname == 'plus':
|
|
self.zoom (True)
|
|
return (True)
|
|
elif keyname == 'minus':
|
|
self.zoom (False)
|
|
return (True)
|
|
|
|
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK | gtk.gdk.MOD1_MASK
|
|
if (event.state & mask) == mask:
|
|
#Top level tab
|
|
if keyname == 'T':
|
|
self.terminator.newtab (self, True)
|
|
return (True)
|
|
# bindings that should be moved to Terminator as they all just call
|
|
# a function of Terminator. It would be cleaner is TerminatorTerm
|
|
# has absolutely no reference to Terminator.
|
|
# N (next) - P (previous) - O (horizontal) - E (vertical) - W (close)
|
|
|
|
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
|
|
if (event.state & mask) == mask:
|
|
if keyname == 'N':
|
|
self.terminator.go_next (self)
|
|
return (True)
|
|
elif keyname == "P":
|
|
self.terminator.go_prev (self)
|
|
return (True)
|
|
elif keyname == 'O':
|
|
self.terminator.splitaxis (self, False)
|
|
return (True)
|
|
elif keyname == 'E':
|
|
self.terminator.splitaxis (self, True)
|
|
return (True)
|
|
elif keyname == 'W':
|
|
self.terminator.closeterm (self)
|
|
return (True)
|
|
elif keyname == 'C':
|
|
self._vte.copy_clipboard ()
|
|
return (True)
|
|
elif keyname == 'V':
|
|
self._vte.paste_clipboard ()
|
|
return (True)
|
|
elif keyname == 'S':
|
|
self.do_scrollbar_toggle ()
|
|
return (True)
|
|
elif keyname == 'T':
|
|
self.terminator.newtab(self)
|
|
return (True)
|
|
elif keyname in ('Up', 'Down', 'Left', 'Right'):
|
|
self.terminator.resizeterm (self, keyname)
|
|
return (True)
|
|
elif keyname == 'Page_Down':
|
|
self.terminator.move_tab(self, 'right')
|
|
return (True)
|
|
elif keyname == 'Page_Up':
|
|
self.terminator.move_tab(self, 'left')
|
|
return (True)
|
|
|
|
mask = gtk.gdk.CONTROL_MASK
|
|
if (event.state & mask) == mask:
|
|
if keyname == 'Page_Down':
|
|
self.terminator.next_tab(self)
|
|
return (True)
|
|
elif keyname == 'Page_Up':
|
|
self.terminator.previous_tab(self)
|
|
return (True)
|
|
|
|
if keyname and (keyname == 'Tab' or keyname.endswith('_Tab')):
|
|
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
|
|
if (event.state & mask) == mask:
|
|
self.terminator.go_prev (self)
|
|
return (True)
|
|
mask = gtk.gdk.CONTROL_MASK
|
|
if (event.state & mask) == mask:
|
|
self.terminator.go_next (self)
|
|
return (True)
|
|
# Warning, mask value is either gtk.gdk.CONTROL_MASK or gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
|
|
# if you intend to use it, reinit it
|
|
return (False)
|
|
|
|
def zoom (self, zoom_in):
|
|
pangodesc = self._vte.get_font ()
|
|
fontsize = pangodesc.get_size ()
|
|
|
|
if fontsize > pango.SCALE and not zoom_in:
|
|
fontsize -= pango.SCALE
|
|
elif zoom_in:
|
|
fontsize += pango.SCALE
|
|
|
|
pangodesc.set_size (fontsize)
|
|
self._vte.set_font (pangodesc)
|
|
|
|
def on_vte_popup_menu (self, term, event):
|
|
self.do_popup (event)
|
|
|
|
def do_popup (self, event = None):
|
|
menu = self.create_popup_menu (event)
|
|
menu.popup (None, None, None, event.button, event.time)
|
|
|
|
def create_popup_menu (self, event):
|
|
menu = gtk.Menu ()
|
|
url = None
|
|
|
|
if event:
|
|
url = self._vte.match_check (int (event.x / self._vte.get_char_width ()), int (event.y / self._vte.get_char_height ()))
|
|
|
|
if url:
|
|
if url[1] != self.matches['email']:
|
|
address = url[0]
|
|
nameopen = _("_Open Link")
|
|
namecopy = _("_Copy Link Address")
|
|
else:
|
|
if url[0][0:7] != "mailto:":
|
|
address = "mailto:" + url[0]
|
|
else:
|
|
address = url[0]
|
|
nameopen = _("_Send Mail To...")
|
|
namecopy = _("_Copy Email Address")
|
|
|
|
item = gtk.MenuItem (nameopen)
|
|
item.connect ("activate", lambda menu_item: openurl (address))
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem (namecopy)
|
|
item.connect ("activate", lambda menu_item: self.clipboard.set_text (url[0]))
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem ()
|
|
menu.append (item)
|
|
|
|
item = gtk.ImageMenuItem (gtk.STOCK_COPY)
|
|
item.connect ("activate", lambda menu_item: self._vte.copy_clipboard ())
|
|
item.set_sensitive (self._vte.get_has_selection ())
|
|
menu.append (item)
|
|
|
|
item = gtk.ImageMenuItem (gtk.STOCK_PASTE)
|
|
item.connect ("activate", lambda menu_item: self._vte.paste_clipboard ())
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem ()
|
|
menu.append (item)
|
|
|
|
item = gtk.CheckMenuItem (_("Show _scrollbar"))
|
|
item.set_active (self._scrollbar.get_property ('visible'))
|
|
item.connect ("toggled", lambda menu_item: self.do_scrollbar_toggle ())
|
|
menu.append (item)
|
|
|
|
item = gtk.CheckMenuItem (_("Show _titlebar"))
|
|
item.set_active (self._titlebox.get_property ('visible'))
|
|
item.connect ("toggled", lambda menu_item: self.do_title_toggle ())
|
|
menu.append (item)
|
|
|
|
self._do_encoding_items (menu)
|
|
|
|
item = gtk.MenuItem ()
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem (_("Split H_orizontally"))
|
|
item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, False))
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem (_("Split V_ertically"))
|
|
item.connect ("activate", lambda menu_item: self.terminator.splitaxis (self, True))
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem (_("Open _Tab"))
|
|
item.connect ("activate", lambda menu_item: self.terminator.newtab (self))
|
|
menu.append (item)
|
|
|
|
if self.conf.extreme_tabs:
|
|
item = gtk.MenuItem (_("Open Top Level Tab"))
|
|
item.connect ("activate", lambda menu_item: self.terminator.newtab (self, True))
|
|
menu.append (item)
|
|
|
|
item = gtk.MenuItem ()
|
|
menu.append (item)
|
|
|
|
item = gtk.ImageMenuItem (gtk.STOCK_CLOSE)
|
|
item.connect ("activate", lambda menu_item: self.terminator.closeterm (self))
|
|
menu.append (item)
|
|
|
|
menu.show_all ()
|
|
return menu
|
|
|
|
def on_encoding_change (self, widget, encoding):
|
|
current = self._vte.get_encoding ()
|
|
if current != encoding:
|
|
dbg ('Setting Encoding to: %s'%encoding)
|
|
self._vte.set_encoding (encoding)
|
|
|
|
def _do_encoding_items (self, menu):
|
|
active_encodings = self.conf.active_encodings
|
|
item = gtk.MenuItem (_("Encodings"))
|
|
menu.append (item)
|
|
submenu = gtk.Menu ()
|
|
item.set_submenu (submenu)
|
|
|
|
current_encoding = self._vte.get_encoding ()
|
|
group = None
|
|
for encoding in active_encodings:
|
|
radioitem = gtk.RadioMenuItem (group, _(encoding))
|
|
if group is None:
|
|
group = radioitem
|
|
|
|
if encoding == current_encoding:
|
|
radioitem.set_active (True)
|
|
|
|
radioitem.connect ('activate', self.on_encoding_change, encoding)
|
|
submenu.append (radioitem)
|
|
|
|
item = gtk.MenuItem (_("Other Encodings"))
|
|
submenu.append (item)
|
|
#second level
|
|
|
|
submenu = gtk.Menu ()
|
|
item.set_submenu (submenu)
|
|
encodings = TerminatorEncoding ().get_list ()
|
|
encodings.sort (lambda x, y: cmp (x[2].lower (), y[2].lower ()))
|
|
group = None
|
|
|
|
for encoding in encodings:
|
|
if encoding[1] in active_encodings:
|
|
continue
|
|
|
|
if encoding[1] is None:
|
|
label = "%s %s"%(encoding[2], self._vte.get_encoding ())
|
|
else:
|
|
label = "%s %s"%(encoding[2], encoding[1])
|
|
|
|
radioitem = gtk.RadioMenuItem (group, label)
|
|
if group is None:
|
|
group = radioitem
|
|
|
|
if encoding[1] == current_encoding:
|
|
radioitem.set_active (True)
|
|
|
|
radioitem.connect ('activate', self.on_encoding_change, encoding[1])
|
|
submenu.append (radioitem)
|
|
|
|
def on_vte_title_change(self, vte):
|
|
if self.conf.titletips:
|
|
vte.set_property ("has-tooltip", True)
|
|
vte.set_property ("tooltip-text", vte.get_window_title ())
|
|
#set the title anyhow, titlebars setting only show/hide the label
|
|
self._title.set_text(vte.get_window_title ())
|
|
self.terminator.set_window_title("%s: %s" %(APP_NAME.capitalize(), vte.get_window_title ()))
|
|
notebookpage = self.terminator.get_first_notebook_page(vte)
|
|
while notebookpage != None:
|
|
notebookpage[0].set_tab_label_text(notebookpage[1], vte.get_window_title ())
|
|
notebookpage = self.terminator.get_first_notebook_page(notebookpage[0])
|
|
|
|
def on_vte_focus_in(self, vte, event):
|
|
self._titlebox.modify_bg(gtk.STATE_NORMAL,self.terminator.window.get_style().bg[gtk.STATE_SELECTED])
|
|
self._title.modify_fg(gtk.STATE_NORMAL, self.terminator.window.get_style().fg[gtk.STATE_SELECTED])
|
|
return
|
|
|
|
def on_vte_focus_out(self, vte, event):
|
|
self._titlebox.modify_bg(gtk.STATE_NORMAL, self.terminator.window.get_style().bg[gtk.STATE_NORMAL])
|
|
self._title.modify_fg(gtk.STATE_NORMAL, self.terminator.window.get_style().fg[gtk.STATE_NORMAL])
|
|
return
|
|
|
|
def on_vte_focus(self, vte):
|
|
if vte.get_window_title ():
|
|
self.terminator.set_window_title("%s: %s" %(APP_NAME.capitalize(), vte.get_window_title ()))
|
|
notebookpage = self.terminator.get_first_notebook_page(vte)
|
|
while notebookpage != None:
|
|
notebookpage[0].set_tab_label_text(notebookpage[1], vte.get_window_title ())
|
|
notebookpage = self.terminator.get_first_notebook_page(notebookpage[0])
|
|
|
|
def destroy(self):
|
|
self._vte.destroy()
|
|
|
|
class Terminator:
|
|
def __init__ (self, profile, command = None, fullscreen = False, maximise = False, borderless = False):
|
|
self.profile = profile
|
|
self.command = command
|
|
|
|
self._fullscreen = False
|
|
self.term_list = []
|
|
stores = []
|
|
stores.append (config.TerminatorConfValuestoreRC ())
|
|
|
|
try:
|
|
import gconf
|
|
store = config.TerminatorConfValuestoreGConf ()
|
|
store.set_reconfigure_callback (self.reconfigure_vtes)
|
|
stores.append (store)
|
|
except:
|
|
pass
|
|
|
|
self.conf = config.TerminatorConfig (stores)
|
|
self.window = gtk.Window ()
|
|
self.window.set_title (APP_NAME.capitalize())
|
|
|
|
# FIXME: This really shouldn't be a hardcoded path
|
|
try:
|
|
self.window.set_icon_from_file ("/usr/share/icons/hicolor/48x48/apps/" + APP_NAME + ".png")
|
|
except:
|
|
self.icon = self.window.render_icon (gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_BUTTON)
|
|
self.window.set_icon (self.icon)
|
|
|
|
self.window.connect ("key-press-event", self.on_key_press)
|
|
self.window.connect ("delete_event", self.on_delete_event)
|
|
self.window.connect ("destroy", self.on_destroy_event)
|
|
self.window.connect ("window-state-event", self.on_window_state_changed)
|
|
|
|
self.window.set_property ('allow-shrink', True)
|
|
|
|
if fullscreen:
|
|
self.fullscreen_toggle ()
|
|
|
|
if maximise:
|
|
self.maximize ()
|
|
|
|
if borderless:
|
|
self.window.set_decorated (False)
|
|
|
|
# Set RGBA colormap if possible so VTE can use real alpha
|
|
# channels for transparency.
|
|
screen = self.window.get_screen()
|
|
colormap = screen.get_rgba_colormap()
|
|
if colormap:
|
|
self.window.set_colormap(colormap)
|
|
|
|
# Start out with just one terminal
|
|
# FIXME: This should be really be decided from some kind of profile
|
|
term = (TerminatorTerm (self, self.profile, self.command))
|
|
self.term_list = [term]
|
|
|
|
self.window.add (term)
|
|
self.window.show ()
|
|
term.spawn_child ()
|
|
|
|
def maximize (self):
|
|
""" Maximize the Terminator window."""
|
|
self.window.maximize ()
|
|
|
|
def fullscreen_toggle (self):
|
|
""" Toggle the fullscreen state of the window. If it is in
|
|
fullscreen state, it will be unfullscreened. If it is not, it
|
|
will be set to fullscreen state.
|
|
"""
|
|
if self._fullscreen:
|
|
self.window.unfullscreen ()
|
|
else:
|
|
self.window.fullscreen ()
|
|
|
|
def on_window_state_changed (self, window, event):
|
|
state = event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN
|
|
self._fullscreen = bool (state)
|
|
|
|
return (False)
|
|
|
|
def on_delete_event (self, window, event, data=None):
|
|
if len (self.term_list) == 1:
|
|
return False
|
|
|
|
# show dialog
|
|
dialog = gtk.Dialog (_("Close?"), window, gtk.DIALOG_MODAL,
|
|
(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT))
|
|
dialog.set_has_separator (False)
|
|
dialog.set_resizable (False)
|
|
|
|
primairy = gtk.Label (_('<big><b>Close all terminals?</b></big>'))
|
|
primairy.set_use_markup (True)
|
|
primairy.set_alignment (0, 0.5)
|
|
secundairy = gtk.Label (_("This window has %s terminals open. Closing the window will also close all terminals.") % len(self.term_list))
|
|
secundairy.set_line_wrap(True)
|
|
primairy.set_alignment (0, 0.5)
|
|
|
|
labels = gtk.VBox ()
|
|
labels.pack_start (primairy, False, False, 6)
|
|
labels.pack_start (secundairy, False, False, 6)
|
|
|
|
image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_DIALOG)
|
|
image.set_alignment (0.5, 0)
|
|
|
|
box = gtk.HBox()
|
|
box.pack_start (image, False, False, 6)
|
|
box.pack_start (labels, False, False, 6)
|
|
dialog.vbox.pack_start (box, False, False, 12)
|
|
|
|
dialog.show_all ()
|
|
result = dialog.run ()
|
|
dialog.destroy ()
|
|
return not (result == gtk.RESPONSE_ACCEPT)
|
|
|
|
def on_destroy_event (self, widget, data=None):
|
|
gtk.main_quit ()
|
|
|
|
# keybindings for the whole terminal window (affects the main
|
|
# windows containing the splited terminals)
|
|
def on_key_press (self, window, event):
|
|
""" Callback for the window to determine what to do with special
|
|
keys. Currently handled key-combo's:
|
|
* F11: toggle fullscreen state of the window.
|
|
* CTRL - SHIFT - Q: close all terminals
|
|
"""
|
|
keyname = gtk.gdk.keyval_name (event.keyval)
|
|
mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
|
|
|
|
if (keyname == 'F11'):
|
|
self.fullscreen_toggle ()
|
|
return (True)
|
|
|
|
if (event.state & mask) == mask:
|
|
if keyname == 'Q':
|
|
if not self.on_delete_event (window, gtk.gdk.Event (gtk.gdk.DELETE)):
|
|
self.on_destroy_event (window, gtk.gdk.Event (gtk.gdk.DESTROY))
|
|
|
|
def set_window_title(self, title):
|
|
"""
|
|
Modifies Terminator window title
|
|
"""
|
|
self.window.set_title(title)
|
|
|
|
def add(self, widget, terminal, pos = "bottom"):
|
|
"""
|
|
Add a term to another at position pos
|
|
"""
|
|
vertical = pos in ("top", "bottom")
|
|
pane = (vertical) and gtk.VPaned () or gtk.HPaned ()
|
|
|
|
# get the parent of the provided terminal
|
|
parent = widget.get_parent ()
|
|
if isinstance (parent, gtk.Window):
|
|
# We have just one term
|
|
widget.reparent (pane)
|
|
if pos in ("top", "left"):
|
|
pane.remove(widget)
|
|
pane.pack1 (terminal, True, True)
|
|
pane.pack2 (widget, True, True)
|
|
else:
|
|
pane.pack1 (widget, True, True)
|
|
pane.pack2 (terminal, True, True)
|
|
parent.add (pane)
|
|
|
|
position = (vertical) and parent.allocation.height \
|
|
or parent.allocation.width
|
|
|
|
if isinstance (parent, gtk.Notebook):
|
|
page = -1
|
|
#not the only term in the notebook anymore, need to reshow the title
|
|
if widget.conf.titlebars:
|
|
widget._titlebox.show()
|
|
for i in range(0, parent.get_n_pages()):
|
|
if parent.get_nth_page(i) == widget:
|
|
page = i
|
|
break
|
|
widget.reparent (pane)
|
|
if pos in ("top", "left"):
|
|
pane.remove(widget)
|
|
pane.pack1 (terminal, True, True)
|
|
pane.pack2 (widget, True, True)
|
|
else:
|
|
pane.pack1 (widget, True, True)
|
|
pane.pack2 (terminal, True, True)
|
|
#parent.remove_page(page)
|
|
pane.show()
|
|
parent.insert_page(pane, None, page)
|
|
parent.set_tab_label_text(pane, widget._vte.get_window_title())
|
|
parent.set_tab_label_packing(pane, True, True, gtk.PACK_START)
|
|
parent.set_tab_reorderable(pane, True)
|
|
parent.set_current_page(page)
|
|
|
|
|
|
position = (vertical) and parent.allocation.height \
|
|
or parent.allocation.width
|
|
if isinstance (parent, gtk.Paned):
|
|
# We are inside a split term
|
|
position = (vertical) and widget.allocation.height \
|
|
or widget.allocation.width
|
|
|
|
if (widget == parent.get_child1 ()):
|
|
widget.reparent (pane)
|
|
parent.pack1 (pane, True, True)
|
|
else:
|
|
widget.reparent (pane)
|
|
parent.pack2 (pane, True, True)
|
|
|
|
if pos in ("top", "left"):
|
|
pane.remove(widget)
|
|
pane.pack1 (terminal, True, True)
|
|
pane.pack2 (widget, True, True)
|
|
else:
|
|
pane.pack1 (widget, True, True)
|
|
pane.pack2 (terminal, True, True)
|
|
|
|
pane.pack1 (widget, True, True)
|
|
pane.pack2 (terminal, True, True)
|
|
|
|
# show all, set position of the divider
|
|
pane.show ()
|
|
pane.set_position (position / 2)
|
|
terminal.show ()
|
|
|
|
# insert the term reference into the list
|
|
index = self.term_list.index (widget)
|
|
if pos in ('bottom', 'right'):
|
|
index = index + 1
|
|
self.term_list.insert (index, terminal)
|
|
|
|
# make the new terminal grab the focus
|
|
terminal._vte.grab_focus ()
|
|
|
|
return (terminal)
|
|
|
|
def on_page_reordered(self, notebook, child, page_num):
|
|
#page has been reordered, we need to get the
|
|
# first term and last term
|
|
dbg ("Reordered: %d"%page_num)
|
|
nbpages = notebook.get_n_pages()
|
|
if nbpages == 1:
|
|
dbg("[ERROR] only one page in on_page_reordered")
|
|
|
|
first = self._notebook_first_term(notebook.get_nth_page(page_num))
|
|
last = self._notebook_last_term(notebook.get_nth_page(page_num))
|
|
firstidx = self.term_list.index(first)
|
|
lastidx = self.term_list.index(last)
|
|
termslice = self.term_list[firstidx:lastidx+1]
|
|
#remove them from the list
|
|
for term in termslice:
|
|
self.term_list.remove(term)
|
|
|
|
if page_num == 0:
|
|
#first page, we insert before the first term of next page
|
|
nexttab = notebook.get_nth_page(1)
|
|
sibling = self._notebook_first_term(nexttab)
|
|
siblingindex = self.term_list.index(sibling)
|
|
for term in termslice:
|
|
self.term_list.insert(siblingindex, term)
|
|
siblingindex += 1
|
|
else:
|
|
#other pages, we insert after the last term of previous page
|
|
previoustab = notebook.get_nth_page(page_num - 1)
|
|
sibling = self._notebook_last_term(previoustab)
|
|
siblingindex = self.term_list.index(sibling)
|
|
for term in termslice:
|
|
siblingindex += 1
|
|
self.term_list.insert(siblingindex, term)
|
|
|
|
#for page reorder, we need to get the first term of a notebook
|
|
def notebook_first_term(self, notebook):
|
|
return self._notebook_first_term(notebook.get_nth_page(0))
|
|
|
|
def _notebook_first_term(self, child):
|
|
if isinstance(child, TerminatorTerm):
|
|
return child
|
|
elif isinstance(child, gtk.Paned):
|
|
return self._notebook_first_term(child.get_child1())
|
|
elif isinstance(child, gtk.Notebook):
|
|
return self._notebook_first_term(child.get_nth_page(0))
|
|
|
|
dbg("[ERROR] unsupported class %s in _notebook_first_term" % child.__class__.__name__)
|
|
return None
|
|
|
|
#for page reorder, we need to get the last term of a notebook
|
|
def notebook_last_term(self, notebook):
|
|
return self._notebook_last_term(notebook.get_nth_page(notebook.get_n_pages()-1))
|
|
|
|
def _notebook_last_term(self, child):
|
|
if isinstance(child, TerminatorTerm):
|
|
return child
|
|
elif isinstance(child, gtk.Paned):
|
|
return self._notebook_last_term(child.get_child2())
|
|
elif isinstance(child, gtk.Notebook):
|
|
return self._notebook_last_term(child.get_nth_page(child.get_n_pages()-1))
|
|
|
|
dbg("[ERROR] unsupported class %s in _notebook_last_term" % child.__class__.__name__)
|
|
return None
|
|
|
|
def newtab(self,widget, toplevel = False):
|
|
terminal = TerminatorTerm (self, self.profile, None, widget.get_cwd())
|
|
#only one term, we don't show the title
|
|
terminal._titlebox.hide()
|
|
if self.conf.extreme_tabs and not toplevel:
|
|
parent = widget.get_parent ()
|
|
child = widget
|
|
else:
|
|
child = self.window.get_children()[0]
|
|
parent = child.get_parent()
|
|
|
|
if isinstance(parent, gtk.Paned) or (isinstance(parent, gtk.Window)
|
|
and
|
|
((self.conf.extreme_tabs and not toplevel) or not isinstance(child, gtk.Notebook))):
|
|
#no notebook yet.
|
|
notebook = gtk.Notebook()
|
|
notebook.set_tab_pos(gtk.POS_TOP)
|
|
notebook.connect('page-reordered',self.on_page_reordered)
|
|
notebook.set_property('homogeneous', True)
|
|
notebook.set_tab_reorderable(widget, True)
|
|
|
|
if isinstance(parent, gtk.Paned):
|
|
if parent.get_child1() == child:
|
|
child.reparent(notebook)
|
|
parent.pack1(notebook)
|
|
else:
|
|
child.reparent(notebook)
|
|
parent.pack2(notebook)
|
|
elif isinstance(parent, gtk.Window):
|
|
child.reparent(notebook)
|
|
parent.add(notebook)
|
|
notebook.set_tab_reorderable(child,True)
|
|
notebooklabel = ""
|
|
if widget._vte.get_window_title() is not None:
|
|
notebooklabel = widget._vte.get_window_title()
|
|
notebook.set_tab_label_text(child, notebooklabel)
|
|
notebook. set_tab_label_packing(child, True, True, gtk.PACK_START)
|
|
notebook.show()
|
|
elif isinstance(parent, gtk.Notebook):
|
|
notebook = parent
|
|
elif isinstance(parent, gtk.Window) and isinstance(child, gtk.Notebook):
|
|
notebook = child
|
|
else:
|
|
return (False)
|
|
|
|
## NOTE
|
|
## Here we need to append to the notebook before we can
|
|
## spawn the terminal (WINDOW_ID needs to be set)
|
|
|
|
notebook.append_page(terminal,None)
|
|
terminal.show ()
|
|
terminal.spawn_child ()
|
|
## Some gtk/vte weirdness
|
|
## If we don't use this silly test,
|
|
## terminal._vte.get_window_title() might return
|
|
## bogus values
|
|
notebooklabel = ""
|
|
if terminal._vte.get_window_title() is not None:
|
|
notebooklabel = terminal._vte.get_window_title()
|
|
notebook.set_tab_label_text(terminal, notebooklabel)
|
|
notebook.set_tab_label_packing(terminal, True, True, gtk.PACK_START)
|
|
notebook.set_tab_reorderable(terminal,True)
|
|
## Now, we set focus on the new term
|
|
notebook.set_current_page(-1)
|
|
terminal._vte.grab_focus ()
|
|
|
|
#adding a new tab, thus we need to get the
|
|
# last term of the previous tab and add
|
|
# the new term just after
|
|
sibling = self._notebook_last_term(notebook.get_nth_page(notebook.page_num(terminal)-1))
|
|
index = self.term_list.index(sibling)
|
|
self.term_list.insert (index + 1, terminal)
|
|
return (True)
|
|
|
|
|
|
return terminal
|
|
|
|
def splitaxis (self, widget, vertical=True):
|
|
""" Split the provided widget on the horizontal or vertical axis. """
|
|
# create a new terminal and parent pane.
|
|
terminal = TerminatorTerm (self, self.profile, None, widget.get_cwd())
|
|
pos = vertical and "bottom" or "right"
|
|
self.add(widget, terminal, pos)
|
|
terminal.show ()
|
|
terminal.spawn_child ()
|
|
return terminal
|
|
|
|
def remove(self, widget):
|
|
"""Remove a TerminatorTerm from the Terminator view and terms list
|
|
Returns True on success, False on failure"""
|
|
parent = widget.get_parent ()
|
|
sibling = None
|
|
|
|
if isinstance (parent, gtk.Window):
|
|
# We are the only term
|
|
if not self.on_delete_event (parent, gtk.gdk.Event (gtk.gdk.DELETE)):
|
|
self.on_destroy_event (parent, gtk.gdk.Event (gtk.gdk.DESTROY))
|
|
return
|
|
|
|
if isinstance (parent, gtk.Paned):
|
|
index = self.term_list.index (widget)
|
|
grandparent = parent.get_parent ()
|
|
|
|
# Discover sibling while all objects exist
|
|
if widget == parent.get_child1 ():
|
|
sibling = parent.get_child2 ()
|
|
if widget == parent.get_child2 ():
|
|
sibling = parent.get_child1 ()
|
|
|
|
if not sibling:
|
|
# something is wrong, give up
|
|
print >> sys.stderr, "Error: %s is not a child of %s"%(widget, parent)
|
|
return False
|
|
|
|
parent.remove(widget)
|
|
if isinstance(grandparent, gtk.Notebook):
|
|
page = -1
|
|
for i in range(0, grandparent.get_n_pages()):
|
|
if grandparent.get_nth_page(i) == parent:
|
|
page = i
|
|
break
|
|
parent.remove(sibling)
|
|
grandparent.remove_page(page)
|
|
grandparent.insert_page(sibling, None,page)
|
|
grandparent.set_tab_label_packing(sibling, True, True, gtk.PACK_START)
|
|
grandparent.set_tab_reorderable(sibling, True)
|
|
grandparent.set_current_page(page)
|
|
|
|
|
|
else:
|
|
grandparent.remove (parent)
|
|
sibling.reparent (grandparent)
|
|
grandparent.resize_children()
|
|
parent.destroy ()
|
|
if isinstance(sibling, TerminatorTerm) and isinstance(sibling.get_parent(), gtk.Notebook):
|
|
sibling._titlebox.hide()
|
|
|
|
self.term_list.remove (widget)
|
|
|
|
if not isinstance (sibling, gtk.Paned):
|
|
for term in self.term_list:
|
|
if term == sibling:
|
|
term._vte.grab_focus ()
|
|
break
|
|
else:
|
|
if index == 0: index = 1
|
|
self.term_list[index - 1]._vte.grab_focus ()
|
|
elif isinstance (parent, gtk.Notebook):
|
|
parent.remove(widget)
|
|
nbpages = parent.get_n_pages()
|
|
index = self.term_list.index (widget)
|
|
self.term_list.remove (widget)
|
|
if nbpages == 1:
|
|
sibling = parent.get_nth_page(0)
|
|
parent.remove(sibling)
|
|
gdparent = parent.get_parent()
|
|
if isinstance(gdparent, gtk.Window):
|
|
gdparent.remove(parent)
|
|
gdparent.add(sibling)
|
|
elif isinstance(gdparent, gtk.Paned):
|
|
if gdparent.get_child1() == parent:
|
|
gdparent.remove(parent)
|
|
gdparent.pack1(sibling)
|
|
else:
|
|
gdparent.remove(parent)
|
|
gdparent.pack2(sibling)
|
|
parent.destroy()
|
|
if index == 0: index = 1
|
|
self.term_list[index - 1]._vte.grab_focus ()
|
|
self._set_current_notebook_page_recursive(self.term_list[index - 1])
|
|
if len(self.term_list) == 1:
|
|
self.term_list[0]._titlebox.hide()
|
|
|
|
return True
|
|
|
|
def closeterm (self, widget):
|
|
if self.remove(widget):
|
|
widget.destroy ()
|
|
return True
|
|
return False
|
|
|
|
def go_next (self, term):
|
|
current = self.term_list.index (term)
|
|
next = current
|
|
|
|
if current == len (self.term_list) - 1:
|
|
next = 0
|
|
else:
|
|
next += 1
|
|
|
|
|
|
nextterm = self.term_list[next]
|
|
##we need to set the current page of each notebook
|
|
self._set_current_notebook_page_recursive(nextterm)
|
|
|
|
nextterm._vte.grab_focus ()
|
|
|
|
|
|
def go_prev (self, term):
|
|
current = self.term_list.index (term)
|
|
previous = current
|
|
|
|
if current == 0:
|
|
previous = len (self.term_list) - 1
|
|
else:
|
|
previous -= 1
|
|
|
|
#self.window.set_title(self.term_list[previous]._vte.get_window_title())
|
|
previousterm = self.term_list[previous]
|
|
##we need to set the current page of each notebook
|
|
self._set_current_notebook_page_recursive(previousterm)
|
|
previousterm._vte.grab_focus ()
|
|
|
|
|
|
def _set_current_notebook_page_recursive(self, widget):
|
|
page = self.get_first_notebook_page(widget)
|
|
while page:
|
|
child = None
|
|
page_num = page[0].page_num(page[1])
|
|
page[0].set_current_page(page_num)
|
|
page = self.get_first_notebook_page(page[0])
|
|
|
|
|
|
def resizeterm (self, widget, keyname):
|
|
vertical = False
|
|
if keyname in ('Up', 'Down'):
|
|
vertical = True
|
|
elif keyname in ('Left', 'Right'):
|
|
vertical = False
|
|
else:
|
|
return
|
|
parent = self.get_first_parent_paned(widget,vertical)
|
|
if parent == None:
|
|
return
|
|
|
|
#We have a corresponding parent pane
|
|
#
|
|
#allocation = parent.get_allocation()
|
|
|
|
if keyname in ('Up', 'Down'):
|
|
maxi = parent.get_child1().get_allocation().height + parent.get_child2().get_allocation().height - 1
|
|
|
|
else:
|
|
maxi = parent.get_child1().get_allocation().width + parent.get_child2().get_allocation().width - 1
|
|
move = 10
|
|
if keyname in ('Up', 'Left'):
|
|
move = -10
|
|
|
|
move = max(2, parent.get_position() + move)
|
|
move = min(maxi, move)
|
|
|
|
parent.set_position(move)
|
|
|
|
def previous_tab(self, term):
|
|
notebook = self.get_first_parent_notebook(term)
|
|
notebook.prev_page()
|
|
return
|
|
|
|
def next_tab(self, term):
|
|
notebook = self.get_first_parent_notebook(term)
|
|
notebook.next_page()
|
|
return
|
|
|
|
def move_tab(self, term, direction):
|
|
dbg("moving to direction %s" % direction)
|
|
(notebook, page) = self.get_first_notebook_page(term)
|
|
page_num = notebook.page_num(page)
|
|
nbpages = notebook.get_n_pages()
|
|
#dbg ("%s %s %s %s" % (page_num, nbpages,notebook, page))
|
|
if page_num == 0 and direction == 'left':
|
|
new_page_num = nbpages
|
|
elif page_num == nbpages - 1 and direction == 'right':
|
|
new_page_num = 0
|
|
elif direction == 'left':
|
|
new_page_num = page_num - 1
|
|
elif direction == 'right':
|
|
new_page_num = page_num + 1
|
|
else:
|
|
dbg("[ERROR] unhandled combination in move_tab: direction = %s page_num = %d" % (direction, page_num))
|
|
return False
|
|
notebook.reorder_child(page, new_page_num)
|
|
return True
|
|
|
|
def get_first_parent_notebook(self, widget):
|
|
if isinstance (widget, gtk.Window):
|
|
return None
|
|
parent = widget.get_parent()
|
|
if isinstance (parent, gtk.Notebook):
|
|
return parent
|
|
return self.get_first_parent_notebook(parent)
|
|
|
|
def get_first_parent_paned (self, widget, vertical = None):
|
|
"""This method returns the first parent pane of a widget.
|
|
if vertical is True returns the first VPaned
|
|
if vertical is False return the first Hpaned
|
|
if is None return the First Paned"""
|
|
if isinstance (widget, gtk.Window):
|
|
return None
|
|
parent = widget.get_parent()
|
|
if isinstance (parent, gtk.Paned) and vertical is None:
|
|
return parent
|
|
if isinstance (parent, gtk.VPaned) and vertical:
|
|
return parent
|
|
elif isinstance (parent, gtk.HPaned) and not vertical:
|
|
return parent
|
|
return self.get_first_parent_paned(parent, vertical)
|
|
|
|
def get_first_notebook_page(self, widget):
|
|
if isinstance (widget, gtk.Window):
|
|
return None
|
|
parent = widget.get_parent()
|
|
if isinstance (parent, gtk.Notebook):
|
|
page = -1
|
|
for i in range(0, parent.get_n_pages()):
|
|
if parent.get_nth_page(i) == widget:
|
|
return (parent, widget)
|
|
return self.get_first_notebook_page(parent)
|
|
|
|
def reconfigure_vtes (self):
|
|
for term in self.term_list:
|
|
term.reconfigure_vte ()
|
|
|
|
if __name__ == '__main__':
|
|
def execute_cb (option, opt, value, parser):
|
|
assert value is None
|
|
value = []
|
|
while parser.rargs:
|
|
arg = parser.rargs[0]
|
|
value.append (arg)
|
|
del (parser.rargs[0])
|
|
setattr(parser.values, option.dest, value)
|
|
|
|
usage = "usage: %prog [options]"
|
|
parser = OptionParser (usage)
|
|
parser.add_option ("-v", "--version", action="store_true", dest="version", help="Display program version")
|
|
parser.add_option ("-d", "--debug", action="store_true", dest="debug", help="Enable debugging information")
|
|
parser.add_option ("-m", "--maximise", action="store_true", dest="maximise", help="Open the %s window maximised"%APP_NAME.capitalize())
|
|
parser.add_option ("-f", "--fullscreen", action="store_true", dest="fullscreen", help="Set the window into fullscreen mode")
|
|
parser.add_option ("-b", "--borderless", action="store_true", dest="borderless", help="Turn off the window's borders")
|
|
parser.add_option ("-p", "--profile", dest="profile", help="Specify a GNOME Terminal profile to emulate")
|
|
parser.add_option ("-e", "--command", dest="command", help="Execute the argument to this option inside the terminal")
|
|
parser.add_option ("-x", "--execute", dest="execute", action="callback", callback=execute_cb, help="Execute the remainder of the command line inside the terminal")
|
|
|
|
(options, args) = parser.parse_args ()
|
|
if len (args) != 0:
|
|
parser.error("Expecting zero additional arguments, found: %d"%len (args))
|
|
|
|
if options.version:
|
|
print "%s %s"%(APP_NAME, APP_VERSION)
|
|
sys.exit (0)
|
|
|
|
command = []
|
|
if (options.command):
|
|
command.append (options.command)
|
|
if (options.execute):
|
|
command = options.execute
|
|
|
|
if gtk.gdk.display_get_default() == None:
|
|
print >> sys.stderr, _("You need to run terminator in an X environment. " \
|
|
"Make sure DISPLAY is properly set")
|
|
sys.exit(1)
|
|
|
|
term = Terminator (options.profile, command, options.fullscreen, options.maximise, options.borderless)
|
|
|
|
gtk.main ()
|
|
|