diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 00000000..0d20b648 --- /dev/null +++ b/.bzrignore @@ -0,0 +1 @@ +*.pyc diff --git a/ChangeLog b/ChangeLog index 7e180444..4f3ae2c8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,9 @@ terminator 0.9: * Added support for ~/.terminatorrc * Added kwybindings for terms size and scrollbar manipulation. Thanks Emmanuel Bretelle. - + * Completely revamped config system which now transparently makes use + of gconf settings if they are available, falls back to sensible + defaults if not, and can be overridden entirely by ~/.terminatorrc terminator 0.8.1: * Fixed ChangeLog diff --git a/README b/README index a111ce8b..bc7cd21d 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Terminator 0.8.1 +Terminator 0.9 by Chris Jones This is a little python script to give me lots of terminals in a single window, saving me valuable laptop screen space otherwise wasted on window decorations and not quite being able to fill the screen with terminals. @@ -20,4 +20,5 @@ the gedit terminal plugin is part of the gedit-plugins package, which is licence I am thus licensing Terminator as GPL v2 only. -Cristian Grada provided the icon under the same licence. +Cristian Grada provided the old icon under the same licence. +Cory Kontros provided the new icon under the CC-by-SA licence. diff --git a/TODO b/TODO index ea15c09f..64a16735 100644 --- a/TODO +++ b/TODO @@ -1,4 +1 @@ * Edit doc/terminatorrc.5 manpage to contain the information about the options -* Write a Tab feature for terminator - -* Drag and Drop terms diff --git a/data/icons/16x16/apps/terminator.png b/data/icons/16x16/apps/terminator.png index df543b12..9c649a40 100644 Binary files a/data/icons/16x16/apps/terminator.png and b/data/icons/16x16/apps/terminator.png differ diff --git a/data/icons/22x22/apps/terminator.png b/data/icons/22x22/apps/terminator.png index 891bdc73..d5319a6b 100644 Binary files a/data/icons/22x22/apps/terminator.png and b/data/icons/22x22/apps/terminator.png differ diff --git a/data/icons/24x24/apps/terminator.png b/data/icons/24x24/apps/terminator.png index 57c43eca..81fc54fb 100644 Binary files a/data/icons/24x24/apps/terminator.png and b/data/icons/24x24/apps/terminator.png differ diff --git a/data/icons/48x48/apps/terminator.png b/data/icons/48x48/apps/terminator.png index 9f815996..4125c9fc 100644 Binary files a/data/icons/48x48/apps/terminator.png and b/data/icons/48x48/apps/terminator.png differ diff --git a/data/icons/scalable/apps/terminator.svg b/data/icons/scalable/apps/terminator.svg index 4883dbf3..f85b606c 100644 --- a/data/icons/scalable/apps/terminator.svg +++ b/data/icons/scalable/apps/terminator.svg @@ -2,551 +2,524 @@ - - - - - - - - + id="defs1308"> + + + id="linearGradient5060"> + id="stop5062" /> + id="stop5064" /> + + id="linearGradient5048"> + id="stop5050" /> - - - - - + style="stop-color:black;stop-opacity:1;" /> - - - - + id="stop5052" /> + xlink:href="#linearGradient5048" + id="linearGradient5027" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)" + x1="302.85715" + y1="366.64789" + x2="302.85715" + y2="609.50507" /> + + + + + + + + + + + + + + + + + + + id="stop2240" /> + id="stop2242" /> + id="linearGradient2224"> + style="stop-color:#32342f;stop-opacity:0.54639173;" + offset="0.0000000" + id="stop2226" /> + id="stop2228" /> + id="linearGradient2214"> + style="stop-color:#a9aaa7;stop-opacity:1.0000000;" + offset="0.0000000" + id="stop2216" /> - + style="stop-color:#676964;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop2218" /> + id="linearGradient2206"> + + + + + id="stop2200" /> + id="stop2202" /> + gradientTransform="matrix(0.9500924,0,0,0.9650936,1.1833929,0.2767092)" /> + gradientTransform="matrix(0.957412,0.000000,0.000000,0.952331,1.022766,0.133307)" /> + + + gradientTransform="matrix(0.953506,0.000000,0.000000,0.947873,1.141528,1.205591)" /> + gradientTransform="matrix(0.9975908,0,0,0.9893614,4.354704e-2,9.1764254e-2)" /> - - + xlink:href="#linearGradient2214" + id="linearGradient5719" + x1="40.253334" + y1="42.318577" + x2="36.451904" + y2="37.999615" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.000000,0.000000,0.000000,0.744756,0.000000,9.569132)" /> - - - - - - - - - - + showgrid="false" + inkscape:grid-bbox="true" + inkscape:document-units="px" + inkscape:window-width="1460" + inkscape:window-height="1129" + inkscape:window-x="321" + inkscape:window-y="50" + showguides="true" + inkscape:guide-bbox="true" + inkscape:showpageshadow="false" /> + id="metadata1311"> image/svg+xml + Terminal + 2005-10-15 + + + Andreas Nilsson + + + + + terminal + emulator + term + command line + + + + + + Jakub Steiner + + + + + + + + + + - - - - + inkscape:groupmode="layer"> + + + + + + style="opacity:1.0000000;fill:url(#linearGradient2212);fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient2220);stroke-width:0.99999946;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000" + id="rect1316" + width="44.996037" + height="38.998734" + x="1.5026338" + y="3.5015533" + rx="4.8517075" + ry="4.8517079" /> + style="opacity:1;fill:url(#linearGradient2204);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient4260);stroke-width:0.99467027;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect1314" + width="37.088291" + height="29.005329" + x="5.4357266" + y="7.4998446" + rx="1.6452278" + ry="1.6442512" /> + + + + + + + + + + + + + + - > | - > | - > | + style="opacity:0.76373626;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient2244);stroke-width:0.99999946;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect2232" + width="42.945141" + height="37.000587" + x="2.5542557" + y="4.5007114" + rx="3.7910469" + ry="3.7910469" /> - > | + style="font-size:18.58501053px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#ff0000;stroke-width:0.36798642pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.2786885;font-family:Bitstream Vera Sans Mono" + d="M 8.5171086,12.191731 L 8.5171086,11.067756 L 11.823403,12.562073 L 11.823403,13.245026 L 8.5171086,14.749146 L 8.5171086,13.628511 L 11.079912,12.946316 L 8.5171086,12.191731 z M 15.469341,15.881295 L 15.469341,16.607773 L 11.457036,16.607773 L 11.457036,15.881295 L 15.469341,15.881295" + id="text1340" + sodipodi:nodetypes="ccccccccccccc" /> + style="opacity:0.72413791;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.2786885" + d="M 23 9.9375 L 23 21 L 7.96875 21 L 7.96875 23 L 23 23 L 23 34.09375 L 25 34.09375 L 25 23 L 40.03125 23 L 40.03125 21 L 25 21 L 25 9.9375 L 23 9.9375 z " + id="rect3228" /> - + - - - + sodipodi:nodetypes="ccccccc" + style="opacity:0.53142856;fill:url(#linearGradient2673);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 7.625388,8 C 7.102102,8 6.05153,8.190188 6.05153,9.0259761 L 6.16958,25.542519 C 23.841567,24.579133 20.294433,17.286426 42,13.633318 L 41.937264,9.2913791 C 41.859002,8.1662868 41.397947,8.0594548 40.327115,8.066071 L 7.625388,8 z " + id="path2443" /> + style="opacity:0.71428572999999995;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect1340" + width="34.026031" + height="26.057468" + x="6.9894562" + y="8.9805145" + rx="0.11773217" + ry="0.11773217" /> + + + style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" + id="path2300" + sodipodi:cx="28.3125" + sodipodi:cy="38.75" + sodipodi:rx="0.5625" + sodipodi:ry="0.5625" + d="M 28.875 38.75 A 0.5625 0.5625 0 1 1 27.75,38.75 A 0.5625 0.5625 0 1 1 28.875 38.75 z" + transform="translate(4.375000,-6.250000e-2)" /> + + + diff --git a/debian/changelog b/debian/changelog index a7ca8212..dd499cc6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +terminator (0.9-0ubuntu1) hardy; urgency=low + + * New upstream release + + -- Chris Jones Wed, 19 Mar 2008 17:16:25 +0000 + terminator (0.8.1-0ubuntu1) hardy; urgency=low * New upstream release diff --git a/debian/copyright b/debian/copyright index 79527aef..70c0d016 100644 --- a/debian/copyright +++ b/debian/copyright @@ -9,9 +9,13 @@ Upstream Authors: Huang He Kees Cook Thomas Meire + Nicolas Valcarcel + Emmanuel Bretelle + Chris Oattes Artwork: - Cristian Grada - Drew our icon and licenced it to us under this licence + Cory Kontros - Produced our current icon under the CC-by-SA licence + Cristian Grada - Drew our original icon and licenced it to us under this licence Translations: Thomas Meire diff --git a/debian/watch b/debian/watch index e1a61e46..649e09ec 100644 --- a/debian/watch +++ b/debian/watch @@ -1,2 +1,2 @@ version=3 -https://launchpad.net/terminator/+download .*/terminator_(.+)\.tar\.gz +https://launchpad.net/terminator/+download http://launchpad.net/terminator/.*/terminator_(.+)\.tar\.gz diff --git a/doc/terminator.1 b/doc/terminator.1 index 5a3d5366..9dd051d0 100644 --- a/doc/terminator.1 +++ b/doc/terminator.1 @@ -85,7 +85,7 @@ Toggle fullscreen .SH "SEE ALSO" .BR gnome\-terminal(1),terminatorrc(5) .SH "AUTHOR" -Terminator was written by Chris Jones +Terminator was written by Chris Jones and others. .PP This manual page was written by Chris Jones for the Ubuntu project (but may be used by others). diff --git a/setup.py b/setup.py index a47609d2..842107e5 100755 --- a/setup.py +++ b/setup.py @@ -2,12 +2,49 @@ from distutils.core import setup from distutils.command.install_data import install_data +from distutils.command.build import build from distutils.dep_util import newer from distutils.log import info import glob import os import sys +def import_terminator(): + from types import ModuleType + module = ModuleType('terminator') + module_file = open('terminator', 'r') + exec module_file in module.__dict__ + return module + +APP_VERSION = import_terminator().APP_VERSION + +PO_DIR = 'po' +MO_DIR = os.path.join('build', 'mo') +WITHOUT_NLS = sys.platform == 'win32' or os.environ.has_key("WITHOUT_NLS") + +class BuildData(build): + def run (self): + build.run (self) + + if WITHOUT_NLS: + return + + for po in glob.glob (os.path.join (PO_DIR, '*.po')): + lang = os.path.basename(po[:-3]) + mo = os.path.join(MO_DIR, lang + '.mo') + + directory = os.path.dirname(mo) + if not os.path.exists(directory): + info('creating %s' % directory) + os.makedirs(directory) + + if newer(po, mo): + cmd = 'msgfmt -o %s %s' % (mo, po) + info('compiling %s -> %s' % (po, mo)) + if os.system(cmd) != 0: + raise SystemExit('Error while running msgfmt') + + class InstallData(install_data): def run (self): self.data_files.extend (self._compile_po_files ()) @@ -17,39 +54,24 @@ class InstallData(install_data): data_files = [] # Don't install language files on win32 - if sys.platform == 'win32': + if WITHOUT_NLS: return data_files - PO_DIR = 'po' - for po in glob.glob (os.path.join (PO_DIR,'*.po')): - lang = os.path.basename(po[:-3]) - mo = os.path.join('build', 'mo', lang, 'terminator.mo') - - directory = os.path.dirname(mo) - if not os.path.exists(directory): - info('creating %s' % directory) - os.makedirs(directory) - - if newer(po, mo): - # True if mo doesn't exist - cmd = 'msgfmt -o %s %s' % (mo, po) - info('compiling %s -> %s' % (po, mo)) - if os.system(cmd) != 0: - raise SystemExit('Error while running msgfmt') - - dest = os.path.dirname(os.path.join('share', 'locale', lang, 'LC_MESSAGES', 'terminator.mo')) - data_files.append((dest, [mo])) + for mo in glob.glob (os.path.join (MO_DIR, '*.mo')): + lang = os.path.basename(mo[:-3]) + dest = os.path.dirname(os.path.join('share', 'locale', lang, 'LC_MESSAGES', 'terminator.mo')) + data_files.append((dest, [mo])) return data_files setup(name='Terminator', - version='0.8.1', + version=APP_VERSION, description='Terminator, the robot future of terminals', author='Chris Jones', author_email='cmsj@tenshu.net', url='http://www.tenshu.net/terminator/', - license='GPL v2', + license='GNU GPL v2', scripts=['terminator'], data_files=[ ('share/applications', ['data/terminator.desktop']), @@ -62,6 +84,7 @@ setup(name='Terminator', ('share/icons/hicolor/24x24/apps', glob.glob('data/icons/24x24/apps/*.png')), ('share/icons/hicolor/48x48/apps', glob.glob('data/icons/48x48/apps/*.png')), ], - cmdclass={'install_data': InstallData} + packages=['terminatorlib'], + cmdclass={'build': BuildData, 'install_data': InstallData} ) diff --git a/terminator b/terminator index 218c9ec6..1d8d7410 100755 --- a/terminator +++ b/terminator @@ -17,47 +17,43 @@ """Terminator by Chris Jones """ +# Global defines +APP_NAME = 'terminator' +APP_VERSION = '0.9' + # import standard python libs import os, platform, sys, string, time, math from optparse import OptionParser -import gettext -gettext.install ('terminator') +try: + import gettext + gettext.install (APP_NAME) +except: + def _ (text): + return text # import unix-lib import pwd - TARGET_TYPE_VTE = 8 -# import gconf if possible, if not construct a fake replacement -class fakegconfclient: - def get_string (self, key): - return ("") - def get_list (self, key, type): - return ([]) - def add_dir (self, profile, path): - return (True) - def notify_add (self, profile, callback): - return (True) - def get_bool (self, key): - return (False) - def get (self, key): - return (0) -class fakegconf: - CLIENT_PRELOAD_RECURSIVE = False - VALUE_STRING = "" - VALUE_INT = 0 - VALUE_FLOAT = 0.0 - VALUE_BOOL = False - def client_get_default (self): - foo = fakegconfclient () - return (foo) - -try: - import gconf -except: - pass +# 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) + except: + pass +elif platform.system == 'Linux': + pid_get_cwd = lambda pid: os.path.realpath ('/proc/%s/cwd' % pid) # import gtk libs # check just in case anyone runs it on a non-gnome system. @@ -93,93 +89,22 @@ def openurl (url): except: pass -class TerminatorTerm: - - # Our settings - defaults = { - 'gt_dir' : '/apps/gnome-terminal', - '_profile_dir' : '%s/profiles', - 'titlebars' : True, - 'titletips' : False, - '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', - 'exit_action' : 'close', - 'overlay_type' : "rectangle", - '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, - } +class TerminatorTerm (gtk.VBox): matches = {} def __init__ (self, terminator, profile = None, command = None, cwd = None): - self.defaults['profile_dir'] = self.defaults['_profile_dir']%(self.defaults['gt_dir']) - + gtk.VBox.__init__ (self) self.terminator = terminator - self.gconf_client = gconf.client_get_default () + 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] - 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 self.profile: - self.gconf_client.add_dir (self.profile, gconf.CLIENT_PRELOAD_RECURSIVE) - self.gconf_client.notify_add (self.profile, self.on_gconf_notification) - - 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: - try: - line = line.strip () - if line[0] == '#': - pass - elif line: - (key,value) = line.split ("=") - print >> sys.stderr, _('''Overriding setting '%s' from value '%s' to: '%s' ''')%(key.strip (), self.defaults[key.strip ()], value.strip ()) - if value.strip() == "True": - self.defaults[key.strip ()] = True - elif value.strip() == "False": - self.defaults[key.strip ()] = False - else: - self.defaults[key.strip ()] = value.strip () - except: - pass - - 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.scrollbar_position = self.conf.scrollbar_position self._vte = vte.Terminal () self._vte.set_size (80, 24) @@ -192,20 +117,16 @@ class TerminatorTerm: self._title.show() self._titlebox = gtk.EventBox() self._titlebox.add(self._title) - self._box = gtk.VBox () - self._box.show() - self._box.pack_start(self._titlebox, False) - self._box.pack_start(self._termbox) - - if self.reconf('titlebars'): - if len(self.terminator.term_list) > 0 and self.reconf('titlebars'): - if len(self.terminator.term_list) == 1: - self.terminator.term_list[0]._titlebox.show() - self._titlebox.show() + self.show() + self.pack_start(self._titlebox, False) + self.pack_start(self._termbox) + if len(self.terminator.term_list) > 0 and self.conf.titlebars: + if len(self.terminator.term_list) == 1: + self.terminator.term_list[0]._titlebox.show() + self._titlebox.show() else: self._titlebox.hide() - self._titlebox.set_property ('visible', False) - + self._scrollbar = gtk.VScrollbar (self._vte.get_adjustment ()) if self.scrollbar_position != "hidden" and self.scrollbar_position != "disabled": self._scrollbar.show () @@ -238,35 +159,30 @@ class TerminatorTerm: 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.gconf_client.get_string (self.profile + "/exit_action") - exit_action = self.reconf ("exit_action") + 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 - if exit_action in ("close", "left"): + 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.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.add_matches() - 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-]*)+\>''') + env_proxy = os.getenv ('http_proxy') + if not env_proxy and self.conf.http_proxy: + os.putenv ('http_proxy', self.conf.http_proxy) - self.matches['nntp'] = self._vte.match_add ('''\''') + os.putenv ('COLORTERM', 'gnome-terminal') - self.spawn_child () - def on_drag_begin(self, widget, drag_context, data): 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")) @@ -306,10 +222,10 @@ text/plain widget.window.process_updates(True) context = widget.window.cairo_create() - if self.gconf_client.get_bool (self.profile + "/use_theme_colors"): + if self.conf.use_theme_colors: color = self._vte.get_style ().text[gtk.STATE_NORMAL] else: - color = gtk.gdk.color_parse (self.reconf ('foreground_color')) + color = gtk.gdk.color_parse (self.conf.foreground_color) context.set_source_rgba(color.red, color.green, color.blue, 0.5) @@ -324,7 +240,7 @@ text/plain middle = (alloc.width/2, alloc.height/2) middleleft = (0, alloc.height/2) middleright = (alloc.width, alloc.height/2) - overlay_type = self.reconf ('overlay_type') + overlay_type = self.conf.overlay_type #print "%f %f %d %d" %(coef1, coef2, b1,b2) coord = () if pos == "right": @@ -383,7 +299,7 @@ text/plain print "on itself" return - srchbox = widgetsrc.get_box() + srchbox = widgetsrc dsthbox = widget.get_parent().get_parent() dstpaned = dsthbox.get_parent() @@ -424,74 +340,93 @@ text/plain 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.gconf_client.get_bool (self.profile + "/update_records") or True - login = self.gconf_client.get_bool (self.profile + "/login_shell") or False + 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.gconf_client.get_bool (self.profile + "/use_custom_command") == True: - args = self.gconf_client.get_string (self.profile + "/custom_command").split () + elif self.conf.use_custom_command: + args = self.conf.custom_command.split () shell = args[0] - else: - shell = pwd.getpwuid (os.getuid ())[6] - args = [os.path.basename (shell)] + + 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 """ - system = platform.system () - - if system == 'Linux': - cwd = os.path.realpath ('/proc/%s/cwd' % self._pid) - else: - # We don't have a child cwd getter for this platform, so let - # TerminatorTerm use its default - cwd = None - return (cwd) - - 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 - - return (ret) + return (pid_get_cwd(self._pid)) def reconfigure_vte (self): # Set our emulation - self._vte.set_emulation (self.defaults['emulation']) + self._vte.set_emulation (self.conf.emulation) # Set our wordchars - self._vte.set_word_chars (self.reconf ('word_chars')) + self._vte.set_word_chars (self.conf.word_chars) # Set our mouselation - self._vte.set_mouse_autohide (self.defaults['mouse_autohide']) + self._vte.set_mouse_autohide (self.conf.mouse_autohide) # Set our compatibility - backspace = self.reconf ('backspace_binding') - delete = self.reconf ('delete_binding') + 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": @@ -511,28 +446,23 @@ text/plain 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') - + # Set our font try: - self._vte.set_font (pango.FontDescription (font_name)) + self._vte.set_font (pango.FontDescription (self.conf.font)) except: pass # Set our boldness - self._vte.set_allow_bold (self.reconf ('allow_bold')) + self._vte.set_allow_bold (self.conf.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"): + # 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.reconf ('foreground_color')) - bg_color = gtk.gdk.color_parse (self.reconf ('background_color')) + fg_color = gtk.gdk.color_parse (self.conf.foreground_color) + bg_color = gtk.gdk.color_parse (self.conf.background_color) colors = palette.split (':') palette = [] @@ -543,12 +473,12 @@ text/plain # Set our background image, transparency and type # Many thanks to the authors of gnome-terminal, on which this code is based. - background_type = self.reconf ('background_type') + background_type = self.conf.background_type # set background image settings 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_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) @@ -556,8 +486,8 @@ text/plain # 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.reconf ('background_darkness'))) - self._vte.set_opacity(int(self.reconf('background_darkness') * 65535)) + 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) @@ -568,52 +498,55 @@ text/plain self._vte.set_background_transparent (False) # Set our cursor blinkiness - self._vte.set_cursor_blinks = (self.reconf ('cursor_blink')) + self._vte.set_cursor_blinks = (self.conf.cursor_blink) # Set our audible belliness - silent_bell = self.reconf ('silent_bell') + 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.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')) + 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) - scrollbar_position = self.reconf ('scrollbar_position') + if self.scrollbar_position != self.conf.scrollbar_position: + self.scrollbar_position = self.conf.scrollbar_position - if scrollbar_position != self.scrollbar_position: - if scrollbar_position == 'hidden' or scrollbar_position == 'disabled': + if self.scrollbar_position == 'hidden' or self.scrollbar_position == 'disabled': 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 + 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.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 () + 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 + 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) @@ -625,17 +558,20 @@ text/plain return False def do_scrollbar_toggle (self): - if self._scrollbar.get_property ('visible'): - self._scrollbar.hide () - else: - self._scrollbar.show () + self.toggle_widget_visibility (self._scrollbar) def do_title_toggle (self): - if self._titlebox.get_property ('visible'): - self._titlebox.hide () + 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: - self._titlebox.show () - #self._title.show () + widget.show () + #keybindings for the individual splited terminals (affects only the #the selected terminal) def on_vte_key_press (self, term, event): @@ -687,7 +623,7 @@ text/plain elif keyname in ('Up', 'Down', 'Left', 'Right'): self.terminator.resizeterm (self, keyname) return (True) - + mask = gtk.gdk.CONTROL_MASK if (event.state & mask) == mask: if keyname == 'Page_Down': @@ -696,7 +632,6 @@ text/plain elif keyname == 'Page_Up': self.terminator.previous_tab(self) return (True) - if keyname and (keyname == 'Tab' or keyname.endswith('_Tab')): if event.state == gtk.gdk.CONTROL_MASK: @@ -710,7 +645,7 @@ text/plain def zoom (self, zoom_in): pangodesc = self._vte.get_font () - fontsize = pangodesc.get_size () + fontsize = pangodesc.get_size () if fontsize > pango.SCALE and not zoom_in: fontsize -= pango.SCALE @@ -720,8 +655,8 @@ text/plain pangodesc.set_size (fontsize) self._vte.set_font (pangodesc) - def on_vte_popup_menu (self, term): - self.do_popup () + def on_vte_popup_menu (self, term, event): + self.do_popup (event) def do_popup (self, event = None): menu = self.create_popup_menu (event) @@ -775,11 +710,13 @@ text/plain item.connect ("toggled", lambda menu_item: self.do_scrollbar_toggle ()) menu.append (item) - item = gtk.CheckMenuItem (_("Show Title")) + 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) @@ -805,13 +742,68 @@ text/plain 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.reconf ('titletips'): + 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("Terminator: %s" % 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 ()) @@ -829,34 +821,41 @@ text/plain def on_vte_focus(self, vte): if vte.get_window_title (): - self.terminator.set_window_title("Terminator: %s" % 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 get_box (self): - return self._box def destroy(self): - self.get_box().destroy() self._vte.destroy() - + class Terminator: def __init__ (self, profile, command = None, fullscreen = False, maximise = False, borderless = False): self.profile = profile - self.gconf_client = gconf.client_get_default () 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 ("Terminator") - self.term_list = [] + 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/terminator.png") + 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) @@ -888,14 +887,15 @@ class Terminator: term = (TerminatorTerm (self, self.profile, self.command)) self.term_list = [term] - self.window.add (term.get_box ()) + self.window.add (term) self.window.show () + term.spawn_child () def maximize (self): - """ Maximize the Terminator.""" + """ Maximize the Terminator window.""" self.window.maximize () - def toggle_fullscreen (self): + 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. @@ -955,7 +955,7 @@ class Terminator: mask = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK if (keyname == 'F11'): - self.toggle_fullscreen () + self.fullscreen_toggle () return (True) if (event.state & mask) == mask: @@ -975,19 +975,20 @@ class Terminator: """ vertical = pos in ("top", "bottom") pane = (vertical) and gtk.VPaned () or gtk.HPaned () + # get the parent of the provided terminal - parent = widget.get_box ().get_parent () + parent = widget.get_parent () if isinstance (parent, gtk.Window): # We have just one term - widget.get_box ().reparent (pane) + widget.reparent (pane) if pos in ("top", "left"): - pane.remove(widget.get_box ()) - pane.pack1 (terminal.get_box (), True, True) - pane.pack2 (widget.get_box (), True, True) + pane.remove(widget) + pane.pack1 (terminal, True, True) + pane.pack2 (widget, True, True) else: - pane.pack1 (widget.get_box (), True, True) - pane.pack2 (terminal.get_box (), True, True) + pane.pack1 (widget, True, True) + pane.pack2 (terminal, True, True) parent.add (pane) position = (vertical) and parent.allocation.height \ @@ -996,17 +997,17 @@ class Terminator: if isinstance (parent, gtk.Notebook): page = -1 for i in range(0, parent.get_n_pages()): - if parent.get_nth_page(i) == widget.get_box(): + if parent.get_nth_page(i) == widget: page = i break - widget.get_box ().reparent (pane) + widget.reparent (pane) if pos in ("top", "left"): - pane.remove(widget.get_box ()) - pane.pack1 (terminal.get_box (), True, True) - pane.pack2 (widget.get_box (), True, True) + pane.remove(widget) + pane.pack1 (terminal, True, True) + pane.pack2 (widget, True, True) else: - pane.pack1 (widget.get_box (), True, True) - pane.pack2 (terminal.get_box (), True, True) + pane.pack1 (widget, True, True) + pane.pack2 (terminal, True, True) #parent.remove_page(page) pane.show() parent.insert_page(pane, None, page) @@ -1019,30 +1020,31 @@ class Terminator: 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 + position = (vertical) and widget.allocation.height \ + or widget.allocation.width - if (widget.get_box () == parent.get_child1 ()): - widget.get_box ().reparent (pane) + if (widget == parent.get_child1 ()): + widget.reparent (pane) parent.pack1 (pane, True, True) else: - widget.get_box ().reparent (pane) + widget.reparent (pane) parent.pack2 (pane, True, True) if pos in ("top", "left"): - pane.remove(widget.get_box ()) - pane.pack1 (terminal.get_box (), True, True) - pane.pack2 (widget.get_box (), True, True) + pane.remove(widget) + pane.pack1 (terminal, True, True) + pane.pack2 (widget, True, True) else: - pane.pack1 (widget.get_box (), True, True) - pane.pack2 (terminal.get_box (), True, True) + pane.pack1 (widget, True, True) + pane.pack2 (terminal, True, True) - pane.pack1 (widget.get_box (), True, True) - pane.pack2 (terminal.get_box (), 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.get_box ().show () + terminal.show () + terminal.spawn_child () # insert the term reference into the list index = self.term_list.index (widget) @@ -1052,6 +1054,7 @@ class Terminator: terminal._vte.grab_focus () return (terminal) + def on_page_reordered(self, notebook, child, page_num): print page_num @@ -1059,7 +1062,7 @@ class Terminator: def newtab(self,widget): terminal = TerminatorTerm (self, self.profile, None, widget.get_cwd()) - widgetbox = widget.get_box () + widgetbox = widget parent = widgetbox.get_parent () if isinstance(parent, gtk.Paned) or isinstance(parent, gtk.Window): @@ -1091,13 +1094,14 @@ class Terminator: else: return (False) - notebook.append_page(terminal.get_box(),terminal._vte.get_window_title()) - notebook. set_tab_label_packing(terminal.get_box(), True, True, gtk.PACK_START) - notebook.set_tab_reorderable(terminal.get_box(),True) + notebook.append_page(terminal,terminal._vte.get_window_title()) + notebook. set_tab_label_packing(terminal, True, True, gtk.PACK_START) + notebook.set_tab_reorderable(terminal,True) notebook.set_current_page(-1) index = self.term_list.index(widget) self.term_list.insert (index + 1, terminal) - terminal.get_box ().show () + terminal.show () + terminal.spawn_child () terminal._vte.grab_focus () return (True) @@ -1110,13 +1114,14 @@ class Terminator: terminal = TerminatorTerm (self, self.profile, None, widget.get_cwd()) pos = vertical and "bottom" or "right" self.add(widget, terminal, pos) - terminal.get_box ().show () + 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_box ().get_parent () + parent = widget.get_parent () sibling = None if isinstance (parent, gtk.Window): @@ -1130,9 +1135,9 @@ class Terminator: grandparent = parent.get_parent () # Discover sibling while all objects exist - if widget.get_box () == parent.get_child1 (): + if widget == parent.get_child1 (): sibling = parent.get_child2 () - if widget.get_box () == parent.get_child2 (): + if widget == parent.get_child2 (): sibling = parent.get_child1 () if not sibling: @@ -1140,7 +1145,7 @@ class Terminator: print >> sys.stderr, "Error: %s is not a child of %s"%(widget, parent) return False - parent.remove(widget.get_box()) + parent.remove(widget) if isinstance(grandparent, gtk.Notebook): page = -1 for i in range(0, grandparent.get_n_pages()): @@ -1158,17 +1163,17 @@ class Terminator: grandparent.resize_children() parent.destroy () self.term_list.remove (widget) - + if not isinstance (sibling, gtk.Paned): for term in self.term_list: - if term.get_box () == sibling: + 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.get_box()) + parent.remove(widget) nbpages = parent.get_n_pages() index = self.term_list.index (widget) self.term_list.remove (widget) @@ -1212,14 +1217,14 @@ class Terminator: nextterm = self.term_list[next] - if isinstance(nextterm.get_box().get_parent(), gtk.Notebook): - box = nextterm.get_box() + if isinstance(nextterm.get_parent(), gtk.Notebook): + box = nextterm parent = box.get_parent() for i in range(0, parent.get_n_pages()): if box == parent.get_nth_page(i): parent.set_current_page(i) break - notebookpage = self.get_first_notebook_page(nextterm.get_box()) + notebookpage = self.get_first_notebook_page(nextterm) if notebookpage: child = None for i in range(0, notebookpage[0].get_n_pages()): @@ -1240,14 +1245,14 @@ class Terminator: #self.window.set_title(self.term_list[previous]._vte.get_window_title()) previousterm = self.term_list[previous] - if isinstance(previousterm.get_box().get_parent(), gtk.Notebook): - box = previousterm.get_box() + if isinstance(previousterm.get_parent(), gtk.Notebook): + box = previousterm parent = box.get_parent() for i in range(0, parent.get_n_pages()): if box == parent.get_nth_page(i): parent.set_current_page(i) break - notebookpage = self.get_first_notebook_page(previousterm.get_box()) + notebookpage = self.get_first_notebook_page(previousterm) if notebookpage: child = None for i in range(0, notebookpage[0].get_n_pages()): @@ -1267,36 +1272,35 @@ class Terminator: vertical = False else: return - parent = self.get_first_parent_paned(widget.get_box (),vertical) + 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.get_box()) + notebook = self.get_first_parent_notebook(term) notebook.prev_page() return def next_tab(self, term): - notebook = self.get_first_parent_notebook(term.get_box()) + notebook = self.get_first_parent_notebook(term) notebook.next_page() return @@ -1323,7 +1327,7 @@ class Terminator: 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 @@ -1334,14 +1338,12 @@ class Terminator: 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__': - try: - if (gconf): - pass - except: - # Install a fake gconf setup - gconf = fakegconf () def execute_cb (option, opt, value, parser): assert value is None @@ -1363,27 +1365,28 @@ if __name__ == '__main__': 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 Terminator window maximised") + 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") - parser.add_option ("-g", "--no-gconf", dest="nogconf", action="store_true", help="Disable gconf usage, falling back on ~/.terminatorrc and defaults") (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 (options.nogconf): - del (gconf) - gconf = fakegconf () term = Terminator (options.profile, command, options.fullscreen, options.maximise, options.borderless) diff --git a/terminatorlib/__init__.py b/terminatorlib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/terminatorconfig.py b/terminatorlib/config.py similarity index 54% rename from terminatorconfig.py rename to terminatorlib/config.py index 0b880fb0..82dad7cf 100755 --- a/terminatorconfig.py +++ b/terminatorlib/config.py @@ -58,21 +58,17 @@ class TerminatorConfig: self.sources.append (source) def __getattr__ (self, keyname): - dbg ("Config: Looking for: %s"%keyname) + dbg ("TConfig: Looking for: '%s'"%keyname) for source in self.sources: try: val = getattr (source, keyname) - dbg ("Config: got: %s from a %s"%(val, source.type)) + dbg (" TConfig: got: '%s' from a '%s'"%(val, source.type)) return (val) except: - dbg ("Config: no value found in %s."%source.type) pass - dbg ("Config: Out of sources") - raise (AttributeError) - def set_reconfigure_callback (self, function): - self.reconfigure_callback = function - return (True) + dbg (" TConfig: Out of sources") + raise (AttributeError) class TerminatorConfValuestore: type = "Base" @@ -80,39 +76,48 @@ class TerminatorConfValuestore: reconfigure_callback = None # Our settings - # FIXME: Is it acceptable to not explicitly store the type, but - # instead infer it from defaults[key].__class__.__name__ defaults = { - 'gt_dir' : [str, '/apps/gnome-terminal'], - 'profile_dir' : [str, '/apps/gnome-terminal/profiles'], - 'titlebars' : [bool, True], - 'titletips' : [bool, False], - 'allow_bold' : [bool, False], - 'silent_bell' : [bool, True], - 'background_color' : [str, '#000000'], - 'background_darkness' : [float, 0.5], - 'background_type' : [str, 'solid'], - 'backspace_binding' : [str, 'ascii-del'], - 'delete_binding' : [str, 'delete-sequence'], - 'cursor_blink' : [bool, False], - 'emulation' : [str, 'xterm'], - 'font' : [str, 'Serif 10'], - 'foreground_color' : [str, '#AAAAAA'], - 'scrollbar_position' : [str, "right"], - 'scroll_background' : [bool, True], - 'scroll_on_keystroke' : [bool, False], - 'scroll_on_output' : [bool, False], - 'scrollback_lines' : [int, 100], - 'focus' : [str, 'sloppy'], - 'exit_action' : [str, 'close'], - 'palette' : [str, '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF'], - 'word_chars' : [str, '-A-Za-z0-9,./?%&#:_'], - 'mouse_autohide' : [bool, True], + 'gt_dir' : '/apps/gnome-terminal', + 'profile_dir' : '/apps/gnome-terminal/profiles', + 'titlebars' : True, + 'titletips' : False, + 'allow_bold' : False, + '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', + 'exit_action' : 'close', + '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, + 'update_records' : True, + 'login_shell' : False, + 'use_custom_command' : False, + 'custom_command' : '', + 'use_system_font' : True, + 'use_theme_colors' : True, + 'http_proxy' : '', + 'ignore_hosts' : ['localhost','127.0.0.0/8','*.local'], + 'encoding' : 'UTF-8', + 'active_encodings' : ['UTF-8', 'ISO-8859-1'], + 'overlay_type' : 'rectangle', } def __getattr__ (self, keyname): if self.values.has_key (keyname): - return self.values[keyname][1] + return self.values[keyname] else: raise (AttributeError) @@ -127,7 +132,7 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore): # that can be re-used when rc changes. def __init__ (self): self.type = "RCFile" - self.rcfilename = pwd.getpwuid (os.getuid ())[5] + "/.terminatorrc" + self.rcfilename = os.path.join(os.path.expanduser("~"), ".terminatorrc") if os.path.exists (self.rcfilename): rcfile = open (self.rcfilename) rc = rcfile.readlines () @@ -138,33 +143,44 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore): item = item.strip () if item and item[0] != '#': (key, value) = item.split ("=") - dbg ("VS_RCFile: Setting value %s to %s"%(key, value)) - self.values[key] = [self.defaults[key][0], self.defaults[key][0](value)] + dbg (" VS_RCFile: Setting value %s to %s"%(key, value)) + if value == 'True': + self.values[key] = True + elif value == 'False': + self.values[key] = False except: + dbg (" VS_RCFile: Exception handling: %s"%item) pass class TerminatorConfValuestoreGConf (TerminatorConfValuestore): profile = "" client = None + cache = {} def __init__ (self, profile = None): self.type = "GConf" + import gconf + self.client = gconf.client_get_default () # Grab a couple of values from base class to avoid recursing with our __getattr__ - self._gt_dir = self.defaults['gt_dir'][1] - self._profile_dir = self.defaults['profile_dir'][1] + self._gt_dir = self.defaults['gt_dir'] + self._profile_dir = self.defaults['profile_dir'] if not profile: profile = self.client.get_string (self._gt_dir + '/global/default_profile') profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string') + #set up the active encoding list + self.active_encodings = self.client.get_list (self._gt_dir + '/global/active_encodings', 'string') + + #need to handle the list of Gconf.value if profile in profiles: - dbg ("VSGConf: Found profile '%s' in profile_list"%profile) + dbg (" VSGConf: Found profile '%s' in profile_list"%profile) self.profile = '%s/%s'%(self._profile_dir, profile) elif "Default" in profiles: - dbg ("VSGConf: profile '%s' not found, but 'Default' exists"%profile) + dbg (" VSGConf: profile '%s' not found, but 'Default' exists"%profile) self.profile = '%s/%s'%(self._profile_dir, "Default") else: # We're a bit stuck, there is no profile in the list @@ -178,25 +194,69 @@ class TerminatorConfValuestoreGConf (TerminatorConfValuestore): self.client.add_dir ('/apps/metacity/general', gconf.CLIENT_PRELOAD_RECURSIVE) self.client.notify_add ('/apps/metacity/general/focus_mode', self.on_gconf_notify) + self.client.add_dir ('/desktop/gnome/interface', gconf.CLIENT_PRELOAD_RECURSIVE) + self.client.notify_add ('/desktop/gnome/interface/monospace_font_name', self.on_gconf_notify) + # FIXME: Do we need to watch more non-profile stuff here? + + def set_reconfigure_callback (self, function): + dbg (" VSConf: setting callback to: %s"%function) + self.reconfigure_callback = function + return (True) def on_gconf_notify (self, client, cnxn_id, entry, what): + dbg (" VSGConf: invalidating cache") + self.cache = {} + dbg (" VSGConf: gconf changed, callback is: %s"%self.reconfigure_callback) if self.reconfigure_callback: self.reconfigure_callback () def __getattr__ (self, key = ""): - ret = None + if self.cache.has_key (key): + dbg (" VSGConf: returning cached value: %s"%self.cache[key]) + return (self.cache[key]) + + ret = None + value = None + + dbg (' VSGConf: preparing: %s/%s'%(self.profile, key)) + + # FIXME: Ugly special cases we should look to fix in some other way. + if key == 'font' and self.use_system_font: + value = self.client.get ('/desktop/gnome/interface/monospace_font_name') + elif key == 'focus': + value = self.client.get ('/apps/metacity/general/focus_mode') + elif key == 'http_proxy': + if self.client.get_bool ('/system/http_proxy/use_http_proxy'): + dbg ('HACK: Mangling http_proxy') + + if self.client.get_bool ('use_authentication'): + dbg ('HACK: Using proxy authentication') + value = 'http://%s:%s@%s:%s/'%( + self.client.get_string ('/system/http_proxy/authentication_user'), + self.client.get_string ('/system/http_proxy/authentication_password'), + self.client.get_string ('/system/http_proxy/host'), + self.client.get_int ('/system/http_proxy/port')) + else: + dbg ('HACK: Not using proxy authentication') + value = 'http://%s:%s/'%( + self.client.get_string ('/system/http_proxy/host'), + self.client.get_int ('/system/http_proxy/port')) + else: + value = self.client.get ('%s/%s'%(self.profile, key)) - dbg ('VSGConf: preparing: %s/%s'%(self.profile, key)) - value = self.client.get ('%s/%s'%(self.profile, key)) - dbg ('VSGConf: getting: %s'%value) if value: - funcname = "get_" + self.defaults[key][0].__name__ + funcname = "get_" + self.defaults[key].__class__.__name__ + dbg (' GConf: picked function: %s'%funcname) # Special case for str if funcname == "get_str": funcname = "get_string" + # Special case for strlist + if funcname == "get_strlist": + funcname = "get_list" typefunc = getattr (value, funcname) ret = typefunc () + self.cache[key] = ret return (ret) else: raise (AttributeError) @@ -230,4 +290,7 @@ if __name__ == '__main__': print foo.titletips # This should raise AttributeError - print foo.blimnle + #print foo.blimnle + + # http_proxy is a value that is allowed to not exist + print "final proxy: %s"%foo.http_proxy diff --git a/terminatorlib/encoding.py b/terminatorlib/encoding.py new file mode 100644 index 00000000..5851b6be --- /dev/null +++ b/terminatorlib/encoding.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# TerminatorEncoding - charset encoding classes +# Copyright (C) 2006-2008 chantra@debuntu.org +# +# 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 + +"""TerminatorEncoding by Emmanuel Bretelle + +TerminatorEncoding supplies a list of possible encoding + values. +This list is taken from gnome-terminal's src/encoding.h + and src/encoding.c +""" + +class TerminatorEncoding: + + encodings = [ + [True, None, _("Current Locale")], + [False, "ISO-8859-1", _("Western")], + [False, "ISO-8859-2", _("Central European")], + [False, "ISO-8859-3", _("South European") ], + [False, "ISO-8859-4", _("Baltic") ], + [False,"ISO-8859-5", _("Cyrillic") ], + [False, "ISO-8859-6", _("Arabic") ], + [False, "ISO-8859-7", _("Greek") ], + [False, "ISO-8859-8", _("Hebrew Visual") ], + [False, "ISO-8859-8-I", _("Hebrew") ], + [False, "ISO-8859-9", _("Turkish") ], + [False, "ISO-8859-10", _("Nordic") ], + [False, "ISO-8859-13", _("Baltic") ], + [False, "ISO-8859-14", _("Celtic") ], + [False, "ISO-8859-15", _("Western") ], + [False, "ISO-8859-16", _("Romanian") ], + [False, "UTF-7", _("Unicode") ], + [False, "UTF-8", _("Unicode") ], + [False, "UTF-16", _("Unicode") ], + [False, "UCS-2", _("Unicode") ], + [False, "UCS-4", _("Unicode") ], + [False, "ARMSCII-8", _("Armenian") ], + [False, "BIG5", _("Chinese Traditional") ], + [False, "BIG5-HKSCS", _("Chinese Traditional") ], + [False, "CP866", _("Cyrillic/Russian") ], + [False, "EUC-JP", _("Japanese") ], + [False, "EUC-KR", _("Korean") ], + [False, "EUC-TW", _("Chinese Traditional") ], + [False, "GB18030", _("Chinese Simplified") ], + [False, "GB2312", _("Chinese Simplified") ], + [False, "GBK", _("Chinese Simplified") ], + [False, "GEORGIAN-PS", _("Georgian") ], + [False, "HZ", _("Chinese Simplified") ], + [False, "IBM850", _("Western") ], + [False, "IBM852", _("Central European") ], + [False, "IBM855", _("Cyrillic") ], + [False, "IBM857", _("Turkish") ], + [False, "IBM862", _("Hebrew") ], + [False, "IBM864", _("Arabic") ], + [False, "ISO2022JP", _("Japanese") ], + [False, "ISO2022KR", _("Korean") ], + [False, "ISO-IR-111", _("Cyrillic") ], + [False, "JOHAB", _("Korean") ], + [False, "KOI8-R", _("Cyrillic") ], + [False, "KOI8-U", _("Cyrillic/Ukrainian") ], + [False, "MAC_ARABIC", _("Arabic") ], + [False, "MAC_CE", _("Central European") ], + [False, "MAC_CROATIAN", _("Croatian") ], + [False, "MAC-CYRILLIC", _("Cyrillic") ], + [False, "MAC_DEVANAGARI", _("Hindi") ], + [False, "MAC_FARSI", _("Persian") ], + [False, "MAC_GREEK", _("Greek") ], + [False, "MAC_GUJARATI", _("Gujarati") ], + [False, "MAC_GURMUKHI", _("Gurmukhi") ], + [False, "MAC_HEBREW", _("Hebrew") ], + [False, "MAC_ICELANDIC", _("Icelandic") ], + [False, "MAC_ROMAN", _("Western") ], + [False, "MAC_ROMANIAN", _("Romanian") ], + [False, "MAC_TURKISH", _("Turkish") ], + [False, "MAC_UKRAINIAN", _("Cyrillic/Ukrainian") ], + [False, "SHIFT-JIS", _("Japanese") ], + [False, "TCVN", _("Vietnamese") ], + [False, "TIS-620", _("Thai") ], + [False, "UHC", _("Korean") ], + [False, "VISCII", _("Vietnamese") ], + [False, "WINDOWS-1250", _("Central European") ], + [False, "WINDOWS-1251", _("Cyrillic") ], + [False, "WINDOWS-1252", _("Western") ], + [False, "WINDOWS-1253", _("Greek") ], + [False, "WINDOWS-1254", _("Turkish") ], + [False, "WINDOWS-1255", _("Hebrew") ], + [False, "WINDOWS-1256", _("Arabic") ], + [False, "WINDOWS-1257", _("Baltic") ], + [False, "WINDOWS-1258", _("Vietnamese") ] + ] + + def get_list(): + return TerminatorEncoding.encodings + get_list = staticmethod(get_list) + \ No newline at end of file diff --git a/terminatorlib/freebsd.py b/terminatorlib/freebsd.py new file mode 100644 index 00000000..221d5146 --- /dev/null +++ b/terminatorlib/freebsd.py @@ -0,0 +1,83 @@ +#!/usr/local/bin/python +# +# Use sysctl() to retrieve the cwd of an arbitrary process on FreeBSD. +# Tested on FreeBSD 7-STABLE/amd64 from April 11 2008. +# +# Be prepared for excitement if the structs are changed. +# +# Blame: Thomas Hurst +# + +from ctypes import * + +# This is padded awkwardly, see /usr/include/sys/socket.h +class sockaddr_storage(Structure): + _fields_ = [ + ('ss_len', c_char), + ('ss_family', c_char), # /usr/include/sys/_types.h; _uint8_t + ('__ss_pad1', c_char * 6), # (sizeof(int64) - sizeof(char) - sizeof(ss_family_t)) + ('__ss_align', c_longlong), + ('__ss_pad2', c_char * 112), # (128(maxsize) - sizeof(char) - sizeof(ss_family_t) - + # sizeof(ss_pad1) - sizeof(int64)) + ] + +# struct kinfo_file, defined in /usr/include/sys/user.h +class kinfo_file(Structure): + _fields_ = [ + ('kf_structsize', c_int), + ('kf_type', c_int), + ('kf_fd', c_int), + ('kf_ref_count', c_int), + ('kf_flags', c_int), + ('kf_offset', c_long), # this is a off_t, a pointer + ('kf_vnode_type', c_int), + ('kf_sock_domain', c_int), + ('kf_sock_type', c_int), + ('kf_sock_protocol', c_int), + ('kf_path', c_char * 1024), # PATH_MAX + ('kf_sa_local', sockaddr_storage), + ('kf_sa_peer', sockaddr_storage), + ] + + +def get_process_cwd(pid): + libc = CDLL('libc.so') + + len = c_uint(sizeof(c_uint)) + ver = c_uint(0) + + if (libc.sysctlbyname('kern.osreldate', byref(ver), byref(len), None, 0) < 0): + return None + + # kern.proc.filedesc added for procstat(1) after these __FreeBSD_versions + if ver.value < 700104 and ver.value < 800019: + return None + + # /usr/include/sys/sysctl.h + # CTL_KERN, KERN_PROC, KERN_PROC_FILEDESC + oid = (c_uint * 4)(1, 14, 14, pid) + + if libc.sysctl(oid, 4, None, byref(len), None, 0) < 0: + return None + + buf = c_char_p(" " * len.value) + if libc.sysctl(oid, 4, buf, byref(len), None, 0) < 0: + return None + + kifs = cast(buf, POINTER(kinfo_file)) + for i in range(0, len.value / sizeof(kinfo_file)): + kif = kifs[i] + if kif.kf_fd == -1: # KF_FD_TYPE_CWD + return kif.kf_path + +if __name__ == '__main__': + import os, sys + print " => %d cwd = %s" % (os.getpid(), get_process_cwd(os.getpid())) + for pid in sys.argv: + try: + pid = int(pid) + except: + pass + else: + print " => %d cwd = %s" % (pid, get_process_cwd(pid)) +