terminator/terminator

989 lines
33 KiB
Plaintext
Raw Normal View History

#!/usr/bin/python
2007-07-28 01:33:48 +00:00
# Terminator - multiple gnome terminals in one window
2008-02-08 10:20:48 +00:00
# Copyright (C) 2006-2008 cmsj@tenshu.net
2007-07-28 01:33:48 +00:00
#
# 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>"""
# import standard python libs
import os, sys, string, time, math
from optparse import OptionParser
import gettext
gettext.install ('terminator')
# import unix-lib
2007-08-26 23:24:32 +00:00
import pwd
# import gtk libs
# check just in case anyone runs it on a non-gnome system.
try:
import gobject, gtk, gconf, pango
except:
print >> sys.stderr, _("You need to install the python bindings for " \
"gobject, gtk, gconf 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:
# Our settings
# FIXME: Add commandline and/or gconf options to change these
defaults = {
'gt_dir' : '/apps/gnome-terminal',
2008-01-29 14:00:02 +00:00
'_profile_dir' : '%s/profiles',
'allow_bold' : True,
'silent_bell' : True,
'background_color' : '#000000',
'background_darkness' : 0.5,
'background_type' : 'solid',
'backspace_binding' : 'ascii-del',
'delete_binding' : 'delete-sequence',
'cursor_blink' : False,
'emulation' : 'xterm',
'font' : 'Serif 10',
'foreground_color' : '#AAAAAA',
'scrollbar_position' : "right",
'scroll_background' : True,
'scroll_on_keystroke' : False,
'scroll_on_output' : False,
'scrollback_lines' : 100,
'focus' : 'sloppy',
'child_restart' : False,
'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF',
'word_chars' : '-A-Za-z0-9,./?%&#:_',
'mouse_autohide' : True,
}
2008-02-20 00:50:10 +00:00
if os.path.exists(pwd.getpwuid(os.getuid())[5] + "/.terminatorrc"):
f = open(pwd.getpwuid(os.getuid())[5] + "/.terminatorrc")
config = f.readlines()
f.close()
for line in config:
line = line.strip()
if line:
(key,value) = line.split("=")
defaults[key.strip()]=value.strip()
matches = {}
2008-02-20 00:50:10 +00:00
def __init__ (self, terminator, profile = None, command = None):
self.defaults['profile_dir'] = self.defaults['_profile_dir']%(self.defaults['gt_dir'])
self.terminator = terminator
self.gconf_client = gconf.client_get_default ()
self.command = command
if profile == None:
profile = self.gconf_client.get_string (self.defaults['gt_dir'] + '/global/default_profile')
self.profile = ""
profiles = self.gconf_client.get_list (self.defaults['gt_dir'] + '/global/profile_list', 'string')
if profile in profiles:
self.profile = '%s/%s'%(self.defaults['profile_dir'], profile)
else:
if profile != "Default" and "Default" in profiles:
self.profile = '%s/Default'%(self.defaults['profile_dir'])
if not self.profile:
print >> sys.stderr, _("Warning: unable to find profile %s. Continue with default values...") % profile
if self.profile:
self.gconf_client.add_dir (self.profile, gconf.CLIENT_PRELOAD_RECURSIVE)
self.gconf_client.notify_add (self.profile, self.on_gconf_notification)
self.gconf_client.add_dir ('/apps/metacity/general', gconf.CLIENT_PRELOAD_RECURSIVE)
self.gconf_client.notify_add ('/apps/metacity/general/focus_mode', self.on_gconf_notification)
self.clipboard = gtk.clipboard_get (gtk.gdk.SELECTION_CLIPBOARD)
self.scrollbar_position = self.reconf ('scrollbar_position')
self._vte = vte.Terminal ()
self._vte.set_size (80, 24)
self.reconfigure_vte ()
self._vte.show ()
self._box = gtk.HBox ()
self._box.show ()
self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ())
if self.scrollbar_position != "hidden":
self._scrollbar.show ()
if self.scrollbar_position == 'right':
packfunc = self._box.pack_start
else:
packfunc = self._box.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)
2008-02-24 20:42:08 +00:00
"""drag and drop"""
target = [ ( "vte", 0, 81 ) ]
self._vte.drag_source_set( gtk.gdk.CONTROL_MASK | gtk.gdk.BUTTON3_MASK, target, gtk.gdk.ACTION_MOVE)
self._vte.drag_dest_set(gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT |gtk.DEST_DEFAULT_DROP ,target, gtk.gdk.ACTION_MOVE)
self._vte.connect("drag-begin", self.on_drag_begin)
self._vte.connect("drag-data-get", self.on_drag_data_get, self)
self._vte.connect("drag-end", self.on_drag_end)
self._vte.connect("drag-data-delete", self.on_drag_data_delete)
self._vte.connect("drag-motion", self.on_drag_motion)
self._vte.connect("drag-drop", self.on_drag_drop)
self._vte.connect("drag-data-received", self.on_drag_data_received, self)
# self._vte.connect ("window-title-changed", self.on_vte_title_change)
2008-02-24 20:42:08 +00:00
2007-11-09 05:58:47 +00:00
exit_action = self.gconf_client.get_string (self.profile + "/exit_action")
if not exit_action:
if self.defaults['child_restart']:
exit_action = "restart"
else:
exit_action = "close"
2007-11-09 05:58:47 +00:00
if exit_action == "restart":
self._vte.connect ("child-exited", self.spawn_child)
2008-02-24 20:42:08 +00:00
#gnome-terminal Bug: http://bugzilla.gnome.org/show_bug.cgi?id=518184
#/apps/gnome-terminal/profiles/<profile name>/exit_action might be
#set to "left" instead of close
# LP#194771
if 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.matches['full_uri'] = self._vte.match_add ('''\<(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)//([-A-Za-z0-9]+(:[-A-Za-z0-9,?;.:/!%$^*&~"#']+)?@)?[-A-Za-z0-9.]+(:[0-9]+)?(/[-A-Za-z0-9_$.+!*(),;:@&=?/~#%]*[^]'.}>) \t\r\n,\"])?\>/?''')
self.matches['addr_only'] = self._vte.match_add ('''\<(www|ftp)[-A-Za-z0-9]*\.[-A-Za-z0-9.]+(:[0-9]+)?(/[-A-Za-z0-9_$.+!*(),;:@&=?/~#%]*[^]'.}>) \t\r\n,\"])?\>/?''')
self.matches['email'] = self._vte.match_add ('''\<(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)+\>''')
self.matches['nntp'] = self._vte.match_add ('''\<news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@[-A-Za-z0-9.]+(:[0-9]+)?\>''')
self.spawn_child ()
2008-02-24 20:42:08 +00:00
def on_drag_begin(self,widget, drag_context):
#parent is hbox
print "Drag begin on ",
parent = widget.get_parent()
print parent
def on_drag_data_get(self,widget, drag_context, selection_data, info, time, data):
print "Drag data get"
selection_data.set("vte",81, str(data.terminator.term_list.index (self)))
def on_drag_data_delete(self, widget, drag_context):
print "Drag data delete"
def on_drag_end(self, widget, drag_context):
print "Drag end"
def on_drag_motion(self, widget, drag_context, x, y, time):
return
print "Drag Motion on ",
parent = widget.get_parent()
print "%dx%d -> %dx%d" % (parent.allocation.width, parent.allocation.height , x, y)
coef1 = float(parent.allocation.height)/float(parent.allocation.width)
coef2 = -float(parent.allocation.height)/float(parent.allocation.width)
b1 = 0
b2 = parent.allocation.height
print "%f %f %d %d" %(coef1, coef2, b1,b2)
if (x*coef1 + b1 > y ) and (x*coef2 + b2 < y ):
print "right"
if (x*coef1 + b1 > y ) and (x*coef2 + b2 > y ):
print "top"
if (x*coef1 + b1 < y ) and (x*coef2 + b2 > y ):
print "left"
if (x*coef1 + b1 < y ) and (x*coef2 + b2 < y ):
print "bottom"
def on_drag_drop(self, widget, drag_context, x, y, time):
print "Drag Drop on",
parent = widget.get_parent()
print parent,
def on_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time, data):
print "Drag Data Received on "
widgetsrc = data.terminator.term_list[int(selection_data.data)]
srchbox = widgetsrc.get_box()
dsthbox = widget.get_parent()
coef1 = float(widget.allocation.height)/float(widget.allocation.width)
coef2 = -float(widget.allocation.height)/float(widget.allocation.width)
b1 = 0
b2 = dsthbox.allocation.height
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"
if dsthbox == srchbox:
print "on itself"
return
dstpaned = dsthbox.get_parent()
srcpaned = srchbox.get_parent()
if isinstance(dstpaned, gtk.Window) and isinstance(srcpaned, gtk.Window):
print "Only one terminal"
return
"""remove src pane"""
if srcpaned.get_child1() == srchbox:
srcsibling = srcpaned.get_child2()
else:
srcsibling = srcpaned.get_child1()
srcgdparent = srcpaned.get_parent()
if isinstance (srcgdparent, gtk.Window):
print "SRC = window"
srcpaned.remove(srchbox)
srcpaned.remove(srcsibling)
srcgdparent.remove(srcpaned)
srcpaned.destroy()
srcgdparent.add(srcsibling)
srcsibling.reparent(srcgdparent)
if isinstance (srcgdparent, gtk.Paned):
print "SRC = paned"
srcpaned.remove(srchbox)
srcgdparent.remove(srcpaned)
srcsibling.reparent(srcgdparent)
srcpaned.destroy()
dstpaned = dsthbox.get_parent()
if isinstance (dstpaned, gtk.Window):
print "DST = window"
# We have just one term
pane = (pos in ("top", "bottom")) and gtk.VPaned() or gtk.HPaned()
dsthbox.reparent (pane)
#srchbox.reparent(pane)
pane.pack1 (dsthbox, True, True)
pane.pack2 (srchbox, True, True)
dstpaned.add(pane)
position = (pos in ("top", "bottom")) and dstpaned.allocation.height or dstpaned.allocation.width
if isinstance (dstpaned, gtk.Paned):
print "DST = paned"
pane = (pos in ("top", "bottom")) and gtk.VPaned() or gtk.HPaned()
position = (pos in ("top", "bottom")) and dstpaned.allocation.height or dstpaned.allocation.width
print "%s %d" % (pos, position)
if (dsthbox == dstpaned.get_child1 ()):
dsthbox.reparent (pane)
dstpaned.pack1 (pane, True, True)
else:
dsthbox.reparent (pane)
dstpaned.pack2 (pane, True, True)
pane.pack1 (dsthbox, True, True)
pane.pack2 (srchbox, True, True)
pane.show_all()
pane.set_position (position / 2)
def spawn_child (self, event=None):
update_records = self.gconf_client.get_bool (self.profile + "/update_records") or True
login = self.gconf_client.get_bool (self.profile + "/login_shell") or False
if self.command:
args = self.command
shell = self.command[0]
elif self.gconf_client.get_bool (self.profile + "/use_custom_command") == True:
args = self.gconf_client.get_string (self.profile + "/custom_command").split ()
shell = args[0]
else:
2007-08-26 23:24:32 +00:00
shell = pwd.getpwuid (os.getuid ())[6]
args = [os.path.basename (shell)]
self._vte.fork_command (command = shell, argv = args, envv = [], loglastlog = login, logwtmp = update_records, logutmp = update_records)
def reconf (self, property):
value = self.gconf_client.get ('%s/%s'%(self.profile, property))
ret = None
if not value:
try:
ret = self.defaults[property]
except:
pass
else:
if value.type == gconf.VALUE_STRING:
ret = value.get_string ()
elif value.type == gconf.VALUE_INT:
ret = value.get_int ()
elif value.type == gconf.VALUE_FLOAT:
ret = value.get_float ()
elif value.type == gconf.VALUE_BOOL:
ret = value.get_bool ()
if ret == None:
print >> sys.stderr, _('Unknown value requested. Unable to find in gconf profile or default settings: ') + property
sys.exit (1)
return ret
def reconfigure_vte (self):
# Set our emulation
self._vte.set_emulation (self.defaults['emulation'])
# Set our wordchars
self._vte.set_word_chars (self.reconf ('word_chars'))
# Set our mouselation
self._vte.set_mouse_autohide (self.defaults['mouse_autohide'])
# Set our compatibility
backspace = self.reconf ('backspace_binding')
delete = self.reconf ('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, preferably from gconf settings
if self.gconf_client.get_bool (self.profile + "/use_system_font"):
font_name = (self.gconf_client.get_string ("/desktop/gnome/interface/monospace_font_name") or self.defaults['font'])
else:
font_name = self.reconf ('font')
try:
self._vte.set_font (pango.FontDescription (font_name))
except:
pass
# Set our boldness
self._vte.set_allow_bold (self.reconf ('allow_bold'))
# Set our color scheme, preferably from gconf settings
palette = self.reconf ('palette')
if self.gconf_client.get_bool (self.profile + "/use_theme_colors"):
fg_color = self._vte.get_style ().text[gtk.STATE_NORMAL]
bg_color = self._vte.get_style ().base[gtk.STATE_NORMAL]
2007-07-27 23:33:43 +00:00
else:
fg_color = gtk.gdk.color_parse (self.reconf ('foreground_color'))
bg_color = gtk.gdk.color_parse (self.reconf ('background_color'))
# Set our background image, transparency and type
background_type = self.reconf ('background_type')
if background_type == "solid":
self._vte.set_background_image_file ('')
self._vte.set_background_transparent (False)
if background_type == "image":
self._vte.set_background_image_file (self.reconf ('background_image'))
self._vte.set_scroll_background (self.reconf ('scroll_background'))
self._vte.set_background_transparent (False)
if background_type == "transparent":
self._vte.set_background_transparent (True)
self._vte.set_background_saturation (1 - (self.reconf ('background_darkness')))
colors = palette.split (':')
palette = []
for color in colors:
if color:
palette.append (gtk.gdk.color_parse (color))
2007-07-27 23:33:43 +00:00
self._vte.set_colors (fg_color, bg_color, palette)
# Set our cursor blinkiness
self._vte.set_cursor_blinks = (self.reconf ('cursor_blink'))
# Set our audible belliness
silent_bell = self.reconf ('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.reconf ('scrollback_lines'))
self._vte.set_scroll_on_keystroke (self.reconf ('scroll_on_keystroke'))
self._vte.set_scroll_on_output (self.reconf ('scroll_on_output'))
scrollbar_position = self.reconf ('scrollbar_position')
if scrollbar_position != self.scrollbar_position:
if scrollbar_position == 'hidden':
self._scrollbar.hide ()
else:
self._scrollbar.show ()
if scrollbar_position == 'right':
self._box.remove (self._scrollbar)
self._box.remove (self._vte)
self._box.pack_start (self._vte)
self._box.pack_start (self._scrollbar)
elif scrollbar_position == 'left':
self._box.remove (self._vte)
self._box.remove (self._scrollbar)
self._box.pack_start(self._scrollbar)
self._box.pack_start(self._vte)
self.scrollbar_position = scrollbar_position
# Set our sloppiness
self.focus = self.gconf_client.get_string ("/apps/metacity/general/focus_mode") or self.defaults['focus']
def on_gconf_notification (self, client, cnxn_id, entry, what):
self.reconfigure_vte ()
def on_vte_button_press (self, term, event):
# Left mouse button should transfer focus to this vte widget
if event.button == 1:
self._vte.grab_focus ()
return False
2008-02-24 20:42:08 +00:00
# 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):
if self._scrollbar.get_property ('visible'):
self._scrollbar.hide ()
else:
self._scrollbar.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)
# 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 in ('Up', 'Down', 'Left', 'Right'):
self.terminator.resizeterm (self, keyname)
return (True)
if keyname and (keyname == 'Tab' or keyname.endswith('_Tab')):
if event.state == gtk.gdk.CONTROL_MASK:
self.terminator.go_next (self)
return (True)
if (event.state & mask) == mask:
self.terminator.go_prev (self)
return (True)
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):
self.do_popup ()
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]
2007-12-29 03:01:28 +00:00
nameopen = _("_Open Link")
namecopy = _("_Copy Link Address")
else:
2007-11-09 04:36:01 +00:00
if url[0][0:7] != "mailto:":
address = "mailto:" + url[0]
else:
address = url[0]
2007-12-29 03:01:28 +00:00
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.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 ()
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_vte_title_change(self, vte):
vte.set_property ("has-tooltip", True)
vte.set_property ("tooltip-text", vte.get_window_title ())
def get_box (self):
return self._box
class Terminator:
def __init__ (self, profile, command = None):
self.profile = profile
self.gconf_client = gconf.client_get_default ()
self.command = command
self._fullscreen = False
self.window = gtk.Window ()
self.window.set_title ("Terminator")
2008-02-08 11:05:19 +00:00
# FIXME: This really shouldn't be a hardcoded path
try:
self.window.set_icon_from_file ("/usr/share/icons/hicolor/48x48/apps/terminator.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)
2007-11-09 03:19:25 +00:00
self.window.set_property ('allow-shrink', True)
# 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.get_box ())
self.window.show ()
def maximize (self):
""" Maximize the Terminator."""
self.window.maximize ()
def toggle_fullscreen (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 ()
self._fullscreen = not self._fullscreen
def on_delete_event (self, window, event, data=None):
if len (self.term_list) == 1:
return False
# show dialog
2007-12-29 03:01:28 +00:00
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)
2007-12-29 03:01:28 +00:00
primairy = gtk.Label (_('<big><b>Close all terminals?</b></big>'))
primairy.set_use_markup (True)
primairy.set_alignment (0, 0.5)
2007-12-29 03:01:28 +00:00
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.toggle_fullscreen ()
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 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)
pane = (vertical) and gtk.VPaned () or gtk.HPaned ()
# get the parent of the provided terminal
parent = widget.get_box ().get_parent ()
if isinstance (parent, gtk.Window):
# We have just one term
widget.get_box ().reparent (pane)
pane.pack1 (widget.get_box (), True, True)
pane.pack2 (terminal.get_box (), True, True)
parent.add (pane)
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.get_box().allocation.height or widget.get_box().allocation.width
if (widget.get_box () == parent.get_child1 ()):
widget.get_box ().reparent (pane)
parent.pack1 (pane, True, True)
else:
widget.get_box ().reparent (pane)
parent.pack2 (pane, True, True)
pane.pack1 (widget.get_box (), True, True)
pane.pack2 (terminal.get_box (), True, True)
# show all, set position of the divider
pane.show ()
pane.set_position (position / 2)
terminal.get_box ().show ()
# insert the term reference into the list
index = self.term_list.index (widget)
self.term_list.insert (index + 1, terminal)
# make the new terminal grab the focus
terminal._vte.grab_focus ()
return (terminal)
def closeterm (self, widget):
parent = widget.get_box ().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.get_box () == parent.get_child1 ():
sibling = parent.get_child2 ()
if widget.get_box () == 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
self.term_list.remove (widget)
grandparent.remove (parent)
sibling.reparent (grandparent)
widget.get_box ().destroy ()
parent.destroy ()
if not isinstance (sibling, gtk.Paned):
for term in self.term_list:
if term.get_box () == sibling:
term._vte.grab_focus ()
break
else:
if index == 0: index = 1
self.term_list[index - 1]._vte.grab_focus ()
return
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
self.term_list[next]._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())
self.term_list[previous]._vte.grab_focus ()
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.get_box (),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 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 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)
if __name__ == '__main__':
usage = "usage: %prog [options]"
parser = OptionParser (usage)
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 Terminator window maximised")
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))
command = []
if (options.command):
command.append (options.command)
if (options.execute):
command = options.execute
term = Terminator (options.profile, command)
# Set the Terminator in fullscreen state or maximize it.
# Fullscreen and maximise are mutually exclusive, with
# fullscreen taking precedence over maximise.
if options.fullscreen:
term.toggle_fullscreen ()
elif options.maximise:
term.maximize ()
if options.borderless:
term.window.set_decorated (False)
gtk.main ()