Merge changes from trunk - diverged

This commit is contained in:
Stephen Boddy 2012-10-22 14:34:00 +02:00
commit 183914c7dc
22 changed files with 410 additions and 68 deletions

View File

@ -1,9 +1,24 @@
terminator 0.97: terminator 1.0:
* Allow font dimming in inactive terminals * Allow font dimming in inactive terminals
* Allow URL handler plugins to override label text for URL context * Allow URL handler plugins to override label text for URL context
menus menus
* When copying a URL, run it through the URL handler first so the * When copying a URL, run it through the URL handler first so the
resulting URL is copied, rather than the original text resulting URL is copied, rather than the original text
* Allow users to configure a custom URL handler, since the
default GTK library option is failing a lot of users in non-GNOME
environments.
* Allow rotation of a group of terminals (Andre Hilsendeger)
* Add a keyboard shortcut to insert a terminal's number (Stephen J
Boddy)
* Add a keyboard shortcut to edit the window title (Stephen J Boddy)
* Add an easy way to balance terminals by double clicking on their
separator (Stephen J Boddy)
* Add a plugin by Sinan Nalkaya to log the contents of terminals.
* Support configuration of TERM and COLORTERM, via a patch from
John Feuerstein
* Support reading configuration from alternate files, via a patch
from Pavel Khlebovich
* Bug fixes
terminator 0.96: terminator 0.96:
* Unity support for opening new windows (Lucian Adrian Grijincu) * Unity support for opening new windows (Lucian Adrian Grijincu)

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
terminator (1.0) precise; urgency=low
* New upstream release of 1.0
-- Chris Jones <cmsj@tenshu.net> Fri, 19 Oct 2012 12:57:42 -0700
terminator (0.96ppa6) oneiric; urgency=low terminator (0.96ppa6) oneiric; urgency=low
* No-change rebuild for oneiric PPA * No-change rebuild for oneiric PPA

View File

@ -115,6 +115,11 @@ Controls how much to reduce the colour values of fonts in terminals that do not
factor. A font colour that was RGB(200,200,200) with an inactive_color_offset of 0.5 would set inactive terminals to factor. A font colour that was RGB(200,200,200) with an inactive_color_offset of 0.5 would set inactive terminals to
RGB(100,100,100). RGB(100,100,100).
.TP .TP
.B always_split_with_profile
Controls whether splits/tabs will continue to use the profile of their peer terminal. If set to False, they will always use
the default profile.
Default value: \fBFalse\fR
.TP
.B enabled_plugins .B enabled_plugins
A list of plugins which should be loaded by default. All other plugin classes will be ignored. The default value includes two A list of plugins which should be loaded by default. All other plugin classes will be ignored. The default value includes two
plugins related to Launchpad, which are enabled by default to provide continuity with earlier releases where these were the plugins related to Launchpad, which are enabled by default to provide continuity with earlier releases where these were the
@ -377,6 +382,14 @@ Default value: \fBblock\fR
Sets what type of terminal should be emulated. Sets what type of terminal should be emulated.
Default value: \fBxterm\fR Default value: \fBxterm\fR
.TP .TP
.B xterm
This translates into the value that will be set for TERM in the environment of your terminals.
Default value: \fBxterm\fR
.TP
.B colorterm
This translates into the value that will be set for COLORTERM in the environment of your terminals.
Default value: \fBgnome-terminal\fR
.TP
.B use_system_font .B use_system_font
Whether or not to use the GNOME default monospace font for terminals. Whether or not to use the GNOME default monospace font for terminals.
Default value: \fBTrue\fR Default value: \fBTrue\fR
@ -423,7 +436,7 @@ Default value: \fBFalse\fR
.TP .TP
.B focus_on_close .B focus_on_close
Sets which terminal should get the focus when another terminal is closed. Values can be "prev", "next" or "auto". Sets which terminal should get the focus when another terminal is closed. Values can be "prev", "next" or "auto".
Using "auto", if the closed terminal is within a splitted window, the focus will be on the sibling terminal rather than another tab. Using "auto", if the closed terminal is within a split window, the focus will be on the sibling terminal rather than another tab.
Default value: \fBauto\fR Default value: \fBauto\fR
.TP .TP
.B exit_action .B exit_action

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-08-21 01:31+0100\n" "POT-Creation-Date: 2011-08-21 01:31+0100\n"
"PO-Revision-Date: 2012-05-04 22:28+0000\n" "PO-Revision-Date: 2012-10-05 03:56+0000\n"
"Last-Translator: Juan Pablo <Unknown>\n" "Last-Translator: Paco Molinero <paco@byasl.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-05-24 10:30+0000\n" "X-Launchpad-Export-Date: 2012-10-06 04:33+0000\n"
"X-Generator: Launchpad (build 15288)\n" "X-Generator: Launchpad (build 16061)\n"
#: ../data/terminator.desktop.in.h:1 #: ../data/terminator.desktop.in.h:1
msgid "Multiple terminals in one window" msgid "Multiple terminals in one window"
@ -333,7 +333,7 @@ msgstr "Captura de terminal"
#: ../terminatorlib/prefseditor.py:942 ../terminatorlib/prefseditor.py:947 #: ../terminatorlib/prefseditor.py:942 ../terminatorlib/prefseditor.py:947
msgid "New Profile" msgid "New Profile"
msgstr "Nuevo Perfil" msgstr "Perfil nuevo"
#: ../terminatorlib/prefseditor.py:987 ../terminatorlib/prefseditor.py:992 #: ../terminatorlib/prefseditor.py:987 ../terminatorlib/prefseditor.py:992
msgid "New Layout" msgid "New Layout"

View File

@ -24,7 +24,7 @@ import sys
from terminatorlib.util import dbg, err from terminatorlib.util import dbg, err
try: try:
from terminatorlib import ipc from terminatorlib import ipc
except ImportErrror: except ImportError:
err('Unable to initialise Terminator remote library. This probably means dbus is not available') err('Unable to initialise Terminator remote library. This probably means dbus is not available')
sys.exit(1) sys.exit(1)
@ -35,6 +35,8 @@ COMMANDS={
'hsplit': ['terminal_hsplit', 'Split the current terminal horizontally'], 'hsplit': ['terminal_hsplit', 'Split the current terminal horizontally'],
'vsplit': ['terminal_vsplit', 'Split the current terminal vertically'], 'vsplit': ['terminal_vsplit', 'Split the current terminal vertically'],
'terminals': ['get_terminals', 'Get a list of all terminals'], 'terminals': ['get_terminals', 'Get a list of all terminals'],
'terminal_tab': ['get_terminal_tab', 'Get the UUID of a parent tab'],
'terminal_tab_title': ['get_terminal_tab_title', 'Get the title of a parent tab'],
} }
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -62,11 +62,23 @@ if __name__ == '__main__':
dbg('dbus disabled by command line') dbg('dbus disabled by command line')
raise ImportError raise ImportError
from terminatorlib import ipc from terminatorlib import ipc
import dbus
try: try:
dbus_service = ipc.DBusService() dbus_service = ipc.DBusService()
except ipc.DBusException: except ipc.DBusException:
dbg('Unable to become master process, requesting a new window') dbg('Unable to become master process, requesting a new window')
ipc.new_window(OPTIONS.layout) # get rid of the None and True types so dbus can handle them (empty
# and 'True' strings are used instead), also arrays are joined
# (the -x argument for example)
optionslist = {}
for opt, val in OPTIONS.__dict__.items():
if type(val) == type([]):
val = ' '.join(val)
if val == True:
val = 'True'
optionslist[opt] = val and '%s'%val or ''
optionslist = dbus.Dictionary(optionslist, signature='ss')
ipc.new_window(optionslist)
sys.exit() sys.exit()
except ImportError: except ImportError:
dbg('dbus not imported') dbg('dbus not imported')

View File

@ -1,7 +1,7 @@
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
Name: terminator Name: terminator
Version: 0.96 Version: 1.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: Store and run multiple GNOME terminals in one window Summary: Store and run multiple GNOME terminals in one window

View File

@ -105,6 +105,7 @@ DEFAULTS = {
'LaunchpadCodeURLHandler', 'LaunchpadCodeURLHandler',
'APTURLHandler'], 'APTURLHandler'],
'suppress_multiple_term_dialog': False, 'suppress_multiple_term_dialog': False,
'always_split_with_profile': False,
}, },
'keybindings': { 'keybindings': {
'zoom_in' : '<Control>plus', 'zoom_in' : '<Control>plus',
@ -185,6 +186,8 @@ DEFAULTS = {
'cursor_shape' : 'block', 'cursor_shape' : 'block',
'cursor_color' : '#aaaaaa', 'cursor_color' : '#aaaaaa',
'emulation' : 'xterm', 'emulation' : 'xterm',
'term' : 'xterm',
'colorterm' : 'gnome-terminal',
'font' : 'Mono 10', 'font' : 'Mono 10',
'foreground_color' : '#aaaaaa', 'foreground_color' : '#aaaaaa',
'show_titlebar' : True, 'show_titlebar' : True,
@ -351,9 +354,10 @@ class Config(object):
self.gconf = gconf.client_get_default() self.gconf = gconf.client_get_default()
value = self.gconf.get('/apps/metacity/general/focus_mode') value = self.gconf.get('/apps/metacity/general/focus_mode')
self.system_focus = value.get_string() if value:
self.gconf.notify_add('/apps/metacity/general/focus_mode', self.system_focus = value.get_string()
self.on_gconf_notify) self.gconf.notify_add('/apps/metacity/general/focus_mode',
self.on_gconf_notify)
return(self.system_focus) return(self.system_focus)
def on_gconf_notify(self, _client, _cnxn_id, _entry, _what): def on_gconf_notify(self, _client, _cnxn_id, _entry, _what):
@ -427,6 +431,8 @@ class ConfigBase(Borg):
Borg.__init__(self, self.__class__.__name__) Borg.__init__(self, self.__class__.__name__)
self.prepare_attributes() self.prepare_attributes()
import optionparse
self.command_line_options = optionparse.options
self.load() self.load()
def prepare_attributes(self): def prepare_attributes(self):
@ -474,6 +480,9 @@ class ConfigBase(Borg):
keytype = '%s(default=%s)' % (keytype, value) keytype = '%s(default=%s)' % (keytype, value)
if key == 'custom_url_handler':
keytype = 'string(default="")'
section[key] = keytype section[key] = keytype
configspecdata['global_config'] = section configspecdata['global_config'] = section
@ -528,7 +537,10 @@ class ConfigBase(Borg):
dbg('ConfigBase::load: config already loaded') dbg('ConfigBase::load: config already loaded')
return return
filename = os.path.join(get_config_dir(), 'config') if not self.command_line_options.config:
self.command_line_options.config = os.path.join(get_config_dir(), 'config')
filename = self.command_line_options.config
dbg('looking for config file: %s' % filename) dbg('looking for config file: %s' % filename)
try: try:
configfile = open(filename, 'r') configfile = open(filename, 'r')
@ -624,7 +636,7 @@ class ConfigBase(Borg):
if not os.path.isdir(config_dir): if not os.path.isdir(config_dir):
os.makedirs(config_dir) os.makedirs(config_dir)
try: try:
parser.write(open(os.path.join(config_dir, 'config'), 'w')) parser.write(open(self.command_line_options.config, 'w'))
except Exception, ex: except Exception, ex:
err('ConfigBase::save: Unable to save config: %s' % ex) err('ConfigBase::save: Unable to save config: %s' % ex)

View File

@ -20,7 +20,7 @@ True
""" """
from borg import Borg from borg import Borg
from util import dbg, err from util import dbg, err, inject_uuid
# pylint: disable-msg=R0201 # pylint: disable-msg=R0201
# pylint: disable-msg=W0613 # pylint: disable-msg=W0613
@ -91,7 +91,9 @@ class Factory(Borg):
return(None) return(None)
dbg('Factory::make: created a %s' % product) dbg('Factory::make: created a %s' % product)
return(func(**kwargs)) output = func(**kwargs)
inject_uuid(output)
return(output)
def make_window(self, **kwargs): def make_window(self, **kwargs):
"""Make a Window""" """Make a Window"""

View File

@ -10,6 +10,7 @@ import dbus.glib
from borg import Borg from borg import Borg
from terminator import Terminator from terminator import Terminator
from config import Config from config import Config
from factory import Factory
from util import dbg from util import dbg
CONFIG = Config() CONFIG = Config()
@ -58,12 +59,16 @@ class DBusService(Borg, dbus.service.Object):
if not self.terminator: if not self.terminator:
self.terminator = Terminator() self.terminator = Terminator()
@dbus.service.method(BUS_NAME) @dbus.service.method(BUS_NAME, in_signature='a{ss}')
def new_window(self, layout='default'): def new_window(self, options=dbus.Dictionary()):
"""Create a new Window""" """Create a new Window"""
dbg('dbus method called: new_window') dbg('dbus method called: new_window with parameters %s'%(options))
self.terminator.create_layout(layout) oldopts = self.terminator.config.options_get()
oldopts.__dict__ = options
self.terminator.config.options_set(oldopts)
self.terminator.create_layout(oldopts.layout)
self.terminator.layout_done() self.terminator.layout_done()
@dbus.service.method(BUS_NAME) @dbus.service.method(BUS_NAME)
def terminal_hsplit(self, uuid=None): def terminal_hsplit(self, uuid=None):
@ -93,6 +98,26 @@ class DBusService(Borg, dbus.service.Object):
"""Return a list of all the terminals""" """Return a list of all the terminals"""
return [x.uuid.urn for x in self.terminator.terminals] return [x.uuid.urn for x in self.terminator.terminals]
@dbus.service.method(BUS_NAME)
def get_terminal_tab(self, uuid):
"""Return the UUID of the parent tab of a given terminal"""
maker = Factory()
terminal = self.terminator.find_terminal_by_uuid(uuid)
window = terminal.get_toplevel()
root_widget = window.get_children()[0]
if maker.isinstance(root_widget, 'Notebook'):
return root_widget.uuid.urn
@dbus.service.method(BUS_NAME)
def get_terminal_tab_title(self, uuid):
"""Return the title of a parent tab of a given terminal"""
maker = Factory()
terminal = self.terminator.find_terminal_by_uuid(uuid)
window = terminal.get_toplevel()
root_widget = window.get_children()[0]
if maker.isinstance(root_widget, "Notebook"):
return root_widget.get_tab_label(terminal).get_label()
def with_proxy(func): def with_proxy(func):
"""Decorator function to connect to the session dbus bus""" """Decorator function to connect to the session dbus bus"""
dbg('dbus client call: %s' % func.func_name) dbg('dbus client call: %s' % func.func_name)
@ -103,9 +128,9 @@ def with_proxy(func):
return _exec return _exec
@with_proxy @with_proxy
def new_window(session, layout='default'): def new_window(session, options):
"""Call the dbus method to open a new window""" """Call the dbus method to open a new window"""
session.new_window(layout) session.new_window(options)
@with_proxy @with_proxy
def terminal_hsplit(session, uuid): def terminal_hsplit(session, uuid):
@ -122,3 +147,13 @@ def get_terminals(session, uuid):
"""Call the dbus method to return a list of all terminals""" """Call the dbus method to return a list of all terminals"""
print '\n'.join(session.get_terminals(uuid)) print '\n'.join(session.get_terminals(uuid))
@with_proxy
def get_terminal_tab(session, uuid):
"""Call the dbus method to return the toplevel tab for a terminal"""
print session.get_terminal_tab(uuid)
@with_proxy
def get_terminal_tab_title(session, uuid):
"""Call the dbus method to return the title of a tab"""
print session.get_terminal_tab_title(uuid)

View File

@ -136,6 +136,10 @@ class Notebook(Container, gtk.Notebook):
sibling = maker.make('terminal') sibling = maker.make('terminal')
sibling.set_cwd(cwd) sibling.set_cwd(cwd)
sibling.spawn_child() sibling.spawn_child()
if widget.group and self.config['split_to_group']:
sibling.set_group(None, widget.group)
if self.config['always_split_with_profile']:
sibling.force_set_profile(None, widget.get_profile())
self.insert_page(container, None, page_num) self.insert_page(container, None, page_num)
self.set_tab_reorderable(container, True) self.set_tab_reorderable(container, True)
@ -195,7 +199,7 @@ class Notebook(Container, gtk.Notebook):
children.append(self.get_nth_page(page)) children.append(self.get_nth_page(page))
return(children) return(children)
def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None): def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None):
"""Add a new tab, optionally supplying a child widget""" """Add a new tab, optionally supplying a child widget"""
dbg('making a new tab') dbg('making a new tab')
maker = Factory() maker = Factory()
@ -206,6 +210,8 @@ class Notebook(Container, gtk.Notebook):
if cwd: if cwd:
widget.set_cwd(cwd) widget.set_cwd(cwd)
widget.spawn_child(debugserver=debugtab) widget.spawn_child(debugserver=debugtab)
if profile and self.config['always_split_with_profile']:
widget.force_set_profile(None, profile)
signals = {'close-term': self.wrapcloseterm, signals = {'close-term': self.wrapcloseterm,
'split-horiz': self.split_horiz, 'split-horiz': self.split_horiz,
@ -472,6 +478,8 @@ class TabLabel(gtk.HBox):
self.set_orientation(gtk.ORIENTATION_VERTICAL) self.set_orientation(gtk.ORIENTATION_VERTICAL)
self.label.set_angle(90) self.label.set_angle(90)
elif position == gtk.POS_RIGHT: elif position == gtk.POS_RIGHT:
if hasattr(self, 'set_orientation'):
self.set_orientation(gtk.ORIENTATION_VERTICAL)
self.label.set_angle(270) self.label.set_angle(270)
else: else:
if hasattr(self, 'set_orientation'): if hasattr(self, 'set_orientation'):

View File

@ -26,6 +26,8 @@ import config
import version import version
from translation import _ from translation import _
options = None
def execute_cb(option, opt, value, lparser): def execute_cb(option, opt, value, lparser):
"""Callback for use in parsing execute options""" """Callback for use in parsing execute options"""
assert value is None assert value is None
@ -40,7 +42,6 @@ def parse_options():
"""Parse the command line options""" """Parse the command line options"""
usage = "usage: %prog [options]" usage = "usage: %prog [options]"
configobj = config.Config()
parser = OptionParser(usage) parser = OptionParser(usage)
parser.add_option('-v', '--version', action='store_true', dest='version', parser.add_option('-v', '--version', action='store_true', dest='version',
@ -53,26 +54,31 @@ def parse_options():
dest='borderless', help=_('Disable window borders')) dest='borderless', help=_('Disable window borders'))
parser.add_option('-H', '--hidden', action='store_true', dest='hidden', parser.add_option('-H', '--hidden', action='store_true', dest='hidden',
help=_('Hide the window at startup')) help=_('Hide the window at startup'))
parser.add_option('-T', '--title', dest='forcedtitle', help=_('Specify a \ parser.add_option('-T', '--title', dest='forcedtitle',
title for the window')) help=_('Specify a title for the window'))
parser.add_option('--geometry', dest='geometry', type='string', help=_('Set \ parser.add_option('--geometry', dest='geometry', type='string',
the preferred size and position of the window (see X man page)')) help=_('Set the preferred size and position of the window'
parser.add_option('-e', '--command', dest='command', help=_('Specify a \ '(see X man page)'))
command to execute inside the terminal')) parser.add_option('-e', '--command', dest='command',
help=_('Specify a command to execute inside the terminal'))
parser.add_option('-g', '--config', dest='config',
help=_('Specify a config file'))
parser.add_option('-x', '--execute', dest='execute', action='callback', parser.add_option('-x', '--execute', dest='execute', action='callback',
callback=execute_cb, help=_('Use the rest of the command line as a \ callback=execute_cb,
command to execute inside the terminal, and its arguments')) help=_('Use the rest of the command line as a command to execute'
'nside the terminal, and its arguments'))
parser.add_option('--working-directory', metavar='DIR', parser.add_option('--working-directory', metavar='DIR',
dest='working_directory', help=_('Set the working directory')) dest='working_directory', help=_('Set the working directory'))
parser.add_option('-r', '--role', dest='role', help=_('Set a custom \
WM_WINDOW_ROLE property on the window'))
parser.add_option('-c', '--classname', dest='classname', help=_('Set a \ parser.add_option('-c', '--classname', dest='classname', help=_('Set a \
custom name (WM_CLASS) property on the window')) custom name (WM_CLASS) property on the window'))
parser.add_option('-l', '--layout', dest='layout', help=_('Select a layout'))
parser.add_option('-p', '--profile', dest='profile', help=_('Use a \
different profile as the default'))
parser.add_option('-i', '--icon', dest='forcedicon', help=_('Set a custom \ parser.add_option('-i', '--icon', dest='forcedicon', help=_('Set a custom \
icon for the window (by file or name)')) icon for the window (by file or name)'))
parser.add_option('-r', '--role', dest='role',
help=_('Set a custom WM_WINDOW_ROLE property on the window'))
parser.add_option('-l', '--layout', dest='layout',
help=_('Select a layout'))
parser.add_option('-p', '--profile', dest='profile',
help=_('Use a different profile as the default'))
parser.add_option('-u', '--no-dbus', action='store_true', dest='nodbus', parser.add_option('-u', '--no-dbus', action='store_true', dest='nodbus',
help=_('Disable DBus')) help=_('Disable DBus'))
parser.add_option('-d', '--debug', action='count', dest='debug', parser.add_option('-d', '--debug', action='count', dest='debug',
@ -82,10 +88,11 @@ icon for the window (by file or name)'))
parser.add_option('--debug-methods', action='store', dest='debug_methods', parser.add_option('--debug-methods', action='store', dest='debug_methods',
help=_('Comma separated list of methods to limit debugging to')) help=_('Comma separated list of methods to limit debugging to'))
for item in ['--sm-client-id', '--sm-config-prefix', '--screen', '-n', for item in ['--sm-client-id', '--sm-config-prefix', '--screen', '-n',
'--no-gconf' ]: '--no-gconf' ]:
parser.add_option(item, dest='dummy', action='store', parser.add_option(item, dest='dummy', action='store',
help=SUPPRESS_HELP) help=SUPPRESS_HELP)
global options
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if len(args) != 0: if len(args) != 0:
parser.error('Additional unexpected arguments found: %s' % args) parser.error('Additional unexpected arguments found: %s' % args)
@ -122,6 +129,7 @@ icon for the window (by file or name)'))
if options.layout is None: if options.layout is None:
options.layout = 'default' options.layout = 'default'
configobj = config.Config()
if options.profile and options.profile not in configobj.list_profiles(): if options.profile and options.profile not in configobj.list_profiles():
options.profile = None options.profile = None

View File

@ -50,6 +50,10 @@ class Paned(Container):
sibling = self.maker.make('terminal') sibling = self.maker.make('terminal')
sibling.set_cwd(cwd) sibling.set_cwd(cwd)
sibling.spawn_child() sibling.spawn_child()
if widget.group and self.config['split_to_group']:
sibling.set_group(None, widget.group)
if self.config['always_split_with_profile']:
sibling.force_set_profile(None, widget.get_profile())
self.add(container) self.add(container)
self.show_all() self.show_all()
@ -111,7 +115,10 @@ class Paned(Container):
handler = handler[0] handler = handler[0]
self.connect_child(widget, signal, handler, *args) self.connect_child(widget, signal, handler, *args)
widget.grab_focus() if metadata and \
metadata.has_key('had_focus') and \
metadata['had_focus'] == True:
widget.grab_focus()
elif isinstance(widget, gtk.Paned): elif isinstance(widget, gtk.Paned):
try: try:
@ -220,9 +227,15 @@ class Paned(Container):
children.append(self.get_child2()) children.append(self.get_child2())
return(children) return(children)
def get_child_metadata(self, widget):
"""Return metadata about a child"""
metadata = {}
metadata['had_focus'] = widget.has_focus()
def wrapcloseterm(self, widget): def wrapcloseterm(self, widget):
"""A child terminal has closed, so this container must die""" """A child terminal has closed, so this container must die"""
dbg('Paned::wrapcloseterm: Called on %s' % widget) dbg('Paned::wrapcloseterm: Called on %s' % widget)
if self.closeterm(widget): if self.closeterm(widget):
# At this point we only have one child, which is the surviving term # At this point we only have one child, which is the surviving term
sibling = self.children[0] sibling = self.children[0]
@ -235,7 +248,6 @@ class Paned(Container):
parent.remove(self) parent.remove(self)
self.cnxids.remove_all() self.cnxids.remove_all()
parent.add(sibling, metadata) parent.add(sibling, metadata)
sibling.grab_focus()
del(self) del(self)
else: else:
dbg("Paned::wrapcloseterm: self.closeterm failed") dbg("Paned::wrapcloseterm: self.closeterm failed")

View File

@ -0,0 +1,108 @@
#!/usr/bin/python
# Plugin by Sinan Nalkaya <sardok@gmail.com>
# See LICENSE of Terminator package.
""" logger.py - Terminator Plugin to log 'content' of individual
terminals """
import os
import sys
import gtk
import terminatorlib.plugin as plugin
from terminatorlib.translation import _
AVAILABLE = ['Logger']
class Logger(plugin.MenuItem):
""" Add custom command to the terminal menu"""
capabilities = ['terminal_menu']
loggers = None
dialog_action = gtk.FILE_CHOOSER_ACTION_SAVE
dialog_buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_SAVE, gtk.RESPONSE_OK)
def __init__(self):
plugin.MenuItem.__init__(self)
if not self.loggers:
self.loggers = {}
def callback(self, menuitems, menu, terminal):
""" Add save menu item to the menu"""
vte_terminal = terminal.get_vte()
if not self.loggers.has_key(vte_terminal):
item = gtk.MenuItem(_('Start Logger'))
item.connect("activate", self.start_logger, terminal)
else:
item = gtk.MenuItem(_('Stop Logger'))
item.connect("activate", self.stop_logger, terminal)
item.set_has_tooltip(True)
item.set_tooltip_text("Saving at '" + self.loggers[vte_terminal]["filepath"] + "'")
menuitems.append(item)
def write_content(self, terminal, row_start, col_start, row_end, col_end):
""" Final function to write a file """
content = terminal.get_text_range(row_start, col_start, row_end, col_end,
lambda *a: True)
fd = self.loggers[terminal]["fd"]
# Don't write the last char which is always '\n'
fd.write(content[:-1])
self.loggers[terminal]["col"] = col_end
self.loggers[terminal]["row"] = row_end
def save(self, terminal):
""" 'contents-changed' callback """
last_saved_col = self.loggers[terminal]["col"]
last_saved_row = self.loggers[terminal]["row"]
(col, row) = terminal.get_cursor_position()
# Save only when buffer is nearly full,
# for the sake of efficiency
if row - last_saved_row < terminal.get_row_count():
return
self.write_content(terminal, last_saved_row, last_saved_col, row, col)
def start_logger(self, _widget, Terminal):
""" Handle menu item callback by saving text to a file"""
savedialog = gtk.FileChooserDialog(title="Save Log File As",
action=self.dialog_action,
buttons=self.dialog_buttons)
savedialog.set_do_overwrite_confirmation(True)
savedialog.set_local_only(True)
savedialog.show_all()
response = savedialog.run()
if response == gtk.RESPONSE_OK:
try:
logfile = os.path.join(savedialog.get_current_folder(),
savedialog.get_filename())
fd = open(logfile, 'w+')
# Save log file path,
# associated file descriptor, signal handler id
# and last saved col,row positions respectively.
vte_terminal = Terminal.get_vte()
(col, row) = vte_terminal.get_cursor_position()
self.loggers[vte_terminal] = {"filepath":logfile,
"handler_id":0, "fd":fd,
"col":col, "row":row}
# Add contents-changed callback
self.loggers[vte_terminal]["handler_id"] = vte_terminal.connect('contents-changed', self.save)
except:
e = sys.exc_info()[1]
error = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
gtk.BUTTONS_OK, e.strerror)
error.run()
error.destroy()
savedialog.destroy()
def stop_logger(self, _widget, terminal):
vte_terminal = terminal.get_vte()
last_saved_col = self.loggers[vte_terminal]["col"]
last_saved_row = self.loggers[vte_terminal]["row"]
(col, row) = vte_terminal.get_cursor_position()
if last_saved_col != col or last_saved_row != row:
# Save unwritten bufer to the file
self.write_content(vte_terminal, last_saved_row, last_saved_col, row, col)
fd = self.loggers[vte_terminal]["fd"]
fd.close()
vte_terminal.disconnect(self.loggers[vte_terminal]["handler_id"])
del(self.loggers[vte_terminal])

View File

@ -382,7 +382,7 @@
<object class="GtkTable" id="global_config_table"> <object class="GtkTable" id="global_config_table">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="n_rows">15</property> <property name="n_rows">16</property>
<property name="n_columns">2</property> <property name="n_columns">2</property>
<property name="column_spacing">6</property> <property name="column_spacing">6</property>
<child> <child>
@ -857,6 +857,36 @@
<property name="bottom_attach">15</property> <property name="bottom_attach">15</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkLabel" id="label34">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Re-use profiles for new terminals</property>
</object>
<packing>
<property name="top_attach">15</property>
<property name="bottom_attach">16</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="always_split_with_profile">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_always_split_with_profile_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">15</property>
<property name="bottom_attach">16</property>
<property name="x_options">GTK_EXPAND</property>
<property name="y_options">GTK_EXPAND</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>

View File

@ -238,6 +238,9 @@ class PrefsEditor:
#Hide size text from the title bar #Hide size text from the title bar
widget = guiget('title_hide_sizetextcheck') widget = guiget('title_hide_sizetextcheck')
widget.set_active(self.config['title_hide_sizetext']) widget.set_active(self.config['title_hide_sizetext'])
#Always split with profile
widget = guiget('always_split_with_profile')
widget.set_active(self.config['always_split_with_profile'])
## Profile tab ## Profile tab
# Populate the profile list # Populate the profile list
@ -610,6 +613,11 @@ class PrefsEditor:
self.config['title_hide_sizetext'] = widget.get_active() self.config['title_hide_sizetext'] = widget.get_active()
self.config.save() self.config.save()
def on_always_split_with_profile_toggled(self, widget):
"""Always split with profile setting changed"""
self.config['always_split_with_profile'] = widget.get_active()
self.config.save()
def on_allow_bold_checkbutton_toggled(self, widget): def on_allow_bold_checkbutton_toggled(self, widget):
"""Allow bold setting changed""" """Allow bold setting changed"""
self.config['allow_bold'] = widget.get_active() self.config['allow_bold'] = widget.get_active()

View File

@ -14,7 +14,6 @@ import gobject
import pango import pango
import subprocess import subprocess
import urllib import urllib
import uuid
from util import dbg, err, gerr from util import dbg, err, gerr
import util import util
@ -89,7 +88,6 @@ class Terminal(gtk.VBox):
command = None command = None
clipboard = None clipboard = None
pid = None pid = None
uuid = None
matches = None matches = None
config = None config = None
@ -107,6 +105,7 @@ class Terminal(gtk.VBox):
composite_support = None composite_support = None
cnxids = None cnxids = None
targets_for_new_group = None
def __init__(self): def __init__(self):
"""Class initialiser""" """Class initialiser"""
@ -119,6 +118,7 @@ class Terminal(gtk.VBox):
# FIXME: Surely these should happen in Terminator::register_terminal()? # FIXME: Surely these should happen in Terminator::register_terminal()?
self.connect('enumerate', self.terminator.do_enumerate) self.connect('enumerate', self.terminator.do_enumerate)
self.connect('focus-in', self.terminator.focus_changed) self.connect('focus-in', self.terminator.focus_changed)
self.connect('focus-out', self.terminator.focus_left)
self.matches = {} self.matches = {}
self.cnxids = Signalman() self.cnxids = Signalman()
@ -131,9 +131,6 @@ class Terminal(gtk.VBox):
self.pending_on_vte_size_allocate = False self.pending_on_vte_size_allocate = False
self.uuid = uuid.uuid4()
dbg('assigning Terminal a TERMINATOR_UUID of: %s' % self.uuid.urn)
self.vte = vte.Terminal() self.vte = vte.Terminal()
self.vte._expose_data = None self.vte._expose_data = None
if not hasattr(self.vte, "set_opacity") or \ if not hasattr(self.vte, "set_opacity") or \
@ -167,8 +164,8 @@ class Terminal(gtk.VBox):
self.connect_signals() self.connect_signals()
os.putenv('TERM', 'xterm') os.putenv('TERM', self.config['term'])
os.putenv('COLORTERM', 'gnome-terminal') os.putenv('COLORTERM', self.config['colorterm'])
env_proxy = os.getenv('http_proxy') env_proxy = os.getenv('http_proxy')
if not env_proxy: if not env_proxy:
@ -770,7 +767,40 @@ class Terminal(gtk.VBox):
def on_group_button_press(self, widget, event): def on_group_button_press(self, widget, event):
"""Handler for the group button""" """Handler for the group button"""
if event.button == 1: if event.button == 1:
self.create_popup_group_menu(widget, event) if event.type == gtk.gdk._2BUTTON_PRESS or \
event.type == gtk.gdk._3BUTTON_PRESS:
# Ignore these, or they make the interaction bad
return False
# Super key applies interaction to all terms in group
include_siblings=event.state & gtk.gdk.MOD4_MASK == gtk.gdk.MOD4_MASK
if include_siblings:
targets=self.terminator.get_sibling_terms(self)
else:
targets=[self]
if event.state & gtk.gdk.CONTROL_MASK == gtk.gdk.CONTROL_MASK:
dbg('on_group_button_press: toggle terminal to focused terminals group')
focused=self.get_toplevel().get_focussed_terminal()
if focused in targets: targets.remove(focused)
if self != focused:
if self.group==focused.group:
new_group=None
else:
new_group=focused.group
[term.set_group(None, new_group) for term in targets]
[term.titlebar.update(focused) for term in targets]
return True
elif event.state & gtk.gdk.SHIFT_MASK == gtk.gdk.SHIFT_MASK:
dbg('on_group_button_press: rename of terminals group')
self.targets_for_new_group = targets
self.titlebar.create_group()
return True
elif event.type == gtk.gdk.BUTTON_PRESS:
# Single Click gives popup
dbg('on_group_button_press: group menu popup')
self.create_popup_group_menu(widget, event)
return True
else:
dbg('on_group_button_press: unknown group button interaction')
return(False) return(False)
def on_keypress(self, widget, event): def on_keypress(self, widget, event):
@ -947,9 +977,10 @@ class Terminal(gtk.VBox):
if gtk.targets_include_text(drag_context.targets) or \ if gtk.targets_include_text(drag_context.targets) or \
gtk.targets_include_uri(drag_context.targets): gtk.targets_include_uri(drag_context.targets):
# copy text to destination # copy text to destination
txt = selection_data.data.strip() txt = selection_data.data.strip(' ')
if txt[0:7] == 'file://': if txt[0:7] == 'file://':
txt = "'%s'" % urllib.unquote(txt[7:]) txt = "'%s'" % urllib.unquote(txt[7:])
txt = selection_data.data.strip('\n')
for term in self.terminator.get_target_terms(self): for term in self.terminator.get_target_terms(self):
term.feed(txt) term.feed(txt)
return return
@ -1247,6 +1278,8 @@ class Terminal(gtk.VBox):
pass pass
envv = [] envv = []
envv.append('TERM=%s' % self.config['term'])
envv.append('COLORTERM=%s' % self.config['colorterm'])
envv.append('TERMINATOR_UUID=%s' % self.uuid.urn) envv.append('TERMINATOR_UUID=%s' % self.uuid.urn)
if self.terminator.dbus_name: if self.terminator.dbus_name:
envv.append('TERMINATOR_DBUS_NAME=%s' % self.terminator.dbus_name) envv.append('TERMINATOR_DBUS_NAME=%s' % self.terminator.dbus_name)

View File

@ -349,19 +349,21 @@ class Terminator(Borg):
idx = terminals.index(term) idx = terminals.index(term)
term.feed(numstr % (idx + 1)) term.feed(numstr % (idx + 1))
def get_sibling_terms(self, widget):
termset = []
for term in self.terminals:
if term.group == widget.group:
termset.append(term)
return(termset)
def get_target_terms(self, widget): def get_target_terms(self, widget):
"""Get the terminals we should currently be broadcasting to""" """Get the terminals we should currently be broadcasting to"""
if self.groupsend == self.groupsend_type['all']: if self.groupsend == self.groupsend_type['all']:
return(self.terminals) return(self.terminals)
elif self.groupsend == self.groupsend_type['group']: elif self.groupsend == self.groupsend_type['group']:
termset = [] if widget.group != None:
for term in self.terminals: return(self.get_sibling_terms(widget))
if term == widget or (term.group != None and term.group == return([widget])
widget.group):
termset.append(term)
return(termset)
else:
return([widget])
def get_focussed_terminal(self): def get_focussed_terminal(self):
"""iterate over all the terminals to find which, if any, has focus""" """iterate over all the terminals to find which, if any, has focus"""
@ -376,6 +378,9 @@ class Terminator(Borg):
terminal.titlebar.update(widget) terminal.titlebar.update(widget)
return return
def focus_left(self, widget):
self.last_focused_term=widget
def describe_layout(self): def describe_layout(self):
"""Describe our current layout""" """Describe our current layout"""
layout = {} layout = {}

View File

@ -94,7 +94,7 @@ class Titlebar(gtk.EventBox):
def connect_icon(self, func): def connect_icon(self, func):
"""Connect the supplied function to clicking on the group icon""" """Connect the supplied function to clicking on the group icon"""
self.ebox.connect('button-release-event', func) self.ebox.connect('button-press-event', func)
def update(self, other=None): def update(self, other=None):
"""Update our contents""" """Update our contents"""
@ -247,6 +247,7 @@ class Titlebar(gtk.EventBox):
if self.groupentry.get_text()=='' and freegroups: if self.groupentry.get_text()=='' and freegroups:
self.groupentry.set_text(freegroups.pop()) self.groupentry.set_text(freegroups.pop())
self.groupentry.show() self.groupentry.show()
self.grouplabel.hide()
self.groupentry.grab_focus() self.groupentry.grab_focus()
self.update_visibility() self.update_visibility()
@ -254,6 +255,7 @@ class Titlebar(gtk.EventBox):
"""Hide the group name entry""" """Hide the group name entry"""
self.groupentry.set_text('') self.groupentry.set_text('')
self.groupentry.hide() self.groupentry.hide()
self.grouplabel.show()
self.get_parent().grab_focus() self.get_parent().grab_focus()
def groupentry_activate(self, widget): def groupentry_activate(self, widget):
@ -261,7 +263,14 @@ class Titlebar(gtk.EventBox):
groupname = self.groupentry.get_text() groupname = self.groupentry.get_text()
dbg('Titlebar::groupentry_activate: creating group: %s' % groupname) dbg('Titlebar::groupentry_activate: creating group: %s' % groupname)
self.groupentry_cancel(None, None) self.groupentry_cancel(None, None)
self.emit('create-group', groupname) last_focused_term=self.terminator.last_focused_term
if self.terminal.targets_for_new_group:
[term.titlebar.emit('create-group', groupname) for term in self.terminal.targets_for_new_group]
self.terminal.targets_for_new_group = None
else:
self.emit('create-group', groupname)
last_focused_term.grab_focus()
self.terminator.focus_changed(last_focused_term)
def groupentry_keypress(self, widget, event): def groupentry_keypress(self, widget, event):
"""Handle keypresses on the entry widget""" """Handle keypresses on the entry widget"""

View File

@ -28,6 +28,7 @@ import gtk
import os import os
import pwd import pwd
import inspect import inspect
import uuid
# set this to true to enable debugging output # set this to true to enable debugging output
DEBUG = False DEBUG = False
@ -276,3 +277,16 @@ def enumerate_descendants(parent):
len(terminals), parent)) len(terminals), parent))
return(containers, terminals) return(containers, terminals)
def make_uuid():
"""Generate a UUID for an object"""
return uuid.uuid4()
def inject_uuid(target):
"""Inject a UUID into an existing object"""
uuid = make_uuid()
if not hasattr(target, "uuid") or target.uuid == None:
dbg("Injecting UUID %s into: %s" % (uuid, target))
target.uuid = uuid
else:
dbg("Object already has a UUID: %s" % target)

View File

@ -21,4 +21,4 @@ TerminatorVersion supplies our version number.
""" """
APP_NAME = 'terminator' APP_NAME = 'terminator'
APP_VERSION = '0.96' APP_VERSION = '1.0'

View File

@ -71,10 +71,10 @@ class Window(Container, gtk.Window):
options = self.config.options_get() options = self.config.options_get()
if options: if options:
if options.forcedtitle is not None: if options.forcedtitle:
self.title.force_title(options.forcedtitle) self.title.force_title(options.forcedtitle)
if options.role is not None: if options.role:
self.set_role(options.role) self.set_role(options.role)
if options.classname is not None: if options.classname is not None:
@ -83,7 +83,7 @@ class Window(Container, gtk.Window):
if options.forcedicon is not None: if options.forcedicon is not None:
icon_to_apply = options.forcedicon icon_to_apply = options.forcedicon
if options.geometry is not None: if options.geometry:
if not self.parse_geometry(options.geometry): if not self.parse_geometry(options.geometry):
err('Window::__init__: Unable to parse geometry: %s' % err('Window::__init__: Unable to parse geometry: %s' %
options.geometry) options.geometry)
@ -248,6 +248,7 @@ class Window(Container, gtk.Window):
def tab_new(self, widget=None, debugtab=False, _param1=None, _param2=None): def tab_new(self, widget=None, debugtab=False, _param1=None, _param2=None):
"""Make a new tab""" """Make a new tab"""
cwd = None cwd = None
profile = None
if self.get_property('term_zoomed') == True: if self.get_property('term_zoomed') == True:
err("You can't create a tab while a terminal is maximised/zoomed") err("You can't create a tab while a terminal is maximised/zoomed")
@ -255,11 +256,13 @@ class Window(Container, gtk.Window):
if widget: if widget:
cwd = widget.get_cwd() cwd = widget.get_cwd()
profile = widget.get_profile()
maker = Factory() maker = Factory()
if not self.is_child_notebook(): if not self.is_child_notebook():
dbg('Making a new Notebook') dbg('Making a new Notebook')
notebook = maker.make('Notebook', window=self) notebook = maker.make('Notebook', window=self)
self.get_child().newtab(debugtab, cwd=cwd) self.get_child().newtab(debugtab, cwd=cwd, profile=profile)
def on_delete_event(self, window, event, data=None): def on_delete_event(self, window, event, data=None):
"""Handle a window close request""" """Handle a window close request"""
@ -373,8 +376,10 @@ class Window(Container, gtk.Window):
def show(self, startup=False): def show(self, startup=False):
"""Undo the startup show request if started in hidden mode""" """Undo the startup show request if started in hidden mode"""
gtk.Window.show(self) #Present is necessary to grab focus when window is hidden from taskbar.
#Present is necessary to grab focus when window is hidden from taskbar #It is important to call present() before show(), otherwise the window
#won't be brought to front if an another application has the focus.
#Last note: present() will implicitly call gtk.Window.show()
self.present() self.present()
#Window must be shown, then hidden for the hotkeys to be registered #Window must be shown, then hidden for the hotkeys to be registered
@ -454,6 +459,11 @@ class Window(Container, gtk.Window):
sibling = maker.make('Terminal') sibling = maker.make('Terminal')
sibling.set_cwd(cwd) sibling.set_cwd(cwd)
sibling.spawn_child() sibling.spawn_child()
if widget.group and self.config['split_to_group']:
sibling.set_group(None, widget.group)
if self.config['always_split_with_profile']:
sibling.force_set_profile(None, widget.get_profile())
self.add(container) self.add(container)
container.show_all() container.show_all()