2006-11-10 05:18:31 +00:00
#!/usr/bin/python
2007-07-28 01:33:48 +00:00
# Terminator - multiple gnome terminals in one window
# Copyright (C) 2006-2007 cmsj@tenshu.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2006-11-10 05:18:31 +00:00
import sys
import string
import gtk
import vte
import gconf
import pango
2007-02-25 02:37:56 +00:00
import gnome
2007-05-08 23:15:26 +00:00
import time
2006-11-10 05:18:31 +00:00
class TerminatorTerm :
2007-05-08 23:15:26 +00:00
lastreconfigure = 0
2007-02-24 14:53:10 +00:00
# Our settings
2007-05-08 23:15:26 +00:00
# FIXME: Add commandline and/or gconf options to change these
2006-11-10 05:18:31 +00:00
defaults = {
2007-02-24 14:53:10 +00:00
' profile_dir ' : ' /apps/gnome-terminal/profiles/ ' ,
' profile ' : ' Default ' ,
2006-11-10 05:18:31 +00:00
' allow_bold ' : True ,
' audible_bell ' : False ,
' background ' : None ,
' background_color ' : ' #000000 ' ,
' backspace_binding ' : ' ascii-del ' ,
2007-07-28 00:44:32 +00:00
' delete_binding ' : ' delete-sequence ' ,
2006-11-10 05:18:31 +00:00
' cursor_blinks ' : False ,
' emulation ' : ' xterm ' ,
' font_name ' : ' Serif 10 ' ,
' foreground_color ' : ' #AAAAAA ' ,
2007-02-24 14:53:10 +00:00
' scrollbar ' : True ,
2006-11-10 05:18:31 +00:00
' scroll_on_keystroke ' : False ,
' scroll_on_output ' : False ,
' scrollback_lines ' : 100 ,
2007-05-08 23:15:26 +00:00
' focus ' : ' sloppy ' ,
2007-02-24 14:53:10 +00:00
' visible_bell ' : False ,
' child_restart ' : True ,
' link_scheme ' : ' (news|telnet|nttp|file|http|ftp|https) ' ,
' _link_user ' : ' [ %s ]+(:[ %s ]+)? ' ,
' link_hostchars ' : ' -A-Za-z0-9 ' ,
' link_userchars ' : ' -A-Za-z0-9 ' ,
2007-07-27 22:31:58 +00:00
' link_passchars ' : ' -A-Za-z0-9,?;.:/! % $^*&~ " # \' ' ,
' palette ' : ' /apps/gnome-terminal/profiles/Default/palette ' ,
2006-11-10 05:18:31 +00:00
}
2007-02-24 14:53:10 +00:00
def __init__ ( self , term , settings = { } ) :
self . defaults [ ' link_user ' ] = self . defaults [ ' _link_user ' ] % ( self . defaults [ ' link_userchars ' ] , self . defaults [ ' link_passchars ' ] )
# Set up any overridden settings
for key in settings . keys ( ) :
defaults [ key ] = settings [ key ]
2007-02-25 03:30:29 +00:00
self . term = term
2007-02-24 14:53:10 +00:00
self . profile = self . defaults [ ' profile_dir ' ] + self . defaults [ ' profile ' ]
2006-11-10 05:18:31 +00:00
self . gconf_client = gconf . client_get_default ( )
2007-02-24 14:53:10 +00:00
self . gconf_client . add_dir ( self . profile , gconf . CLIENT_PRELOAD_RECURSIVE )
2007-05-08 23:15:26 +00:00
self . gconf_client . add_dir ( ' /apps/metacity/general ' , gconf . CLIENT_PRELOAD_RECURSIVE )
2006-11-10 05:18:31 +00:00
2007-07-28 18:36:51 +00:00
self . clipboard = gtk . clipboard_get ( gtk . gdk . SELECTION_CLIPBOARD )
2007-02-25 02:37:56 +00:00
2006-11-10 05:18:31 +00:00
self . _vte = vte . Terminal ( )
2007-07-29 02:06:52 +00:00
self . _vte . set_size ( 5 , 5 )
2006-11-10 05:18:31 +00:00
self . reconfigure_vte ( )
2007-02-24 14:53:10 +00:00
self . _vte . show ( )
2006-11-10 05:18:31 +00:00
self . _box = gtk . HBox ( )
self . _scrollbar = gtk . VScrollbar ( self . _vte . get_adjustment ( ) )
2007-02-24 14:53:10 +00:00
if self . defaults [ ' scrollbar ' ] :
self . _scrollbar . show ( )
2006-11-10 05:18:31 +00:00
self . _box . pack_start ( self . _vte )
self . _box . pack_start ( self . _scrollbar , False )
2007-02-24 14:53:10 +00:00
self . gconf_client . notify_add ( self . profile , self . on_gconf_notification )
2007-05-08 23:15:26 +00:00
self . gconf_client . notify_add ( ' /apps/metacity/general/focus_mode ' , self . on_gconf_notification )
2006-11-10 05:18:31 +00:00
self . _vte . connect ( " button-press-event " , self . on_vte_button_press )
2006-11-17 07:23:04 +00:00
self . _vte . connect ( " popup-menu " , self . on_vte_popup_menu )
2007-07-28 01:17:37 +00:00
if self . gconf_client . get_string ( self . profile + " /exit_action " ) == " restart " :
self . _vte . connect ( " child-exited " , self . spawn_child )
2006-11-10 05:18:31 +00:00
2007-05-08 23:15:26 +00:00
self . _vte . add_events ( gtk . gdk . ENTER_NOTIFY_MASK )
self . _vte . connect ( " enter_notify_event " , self . on_vte_notify_enter )
2006-11-14 08:04:06 +00:00
2007-02-24 14:53:10 +00:00
self . _vte . match_add ( ' (( %s ://( %s @)?)|(www|ftp)[ %s ]* \\ .)[ %s .]+(:[0-9]*)? ' % ( self . defaults [ ' link_scheme ' ] , self . defaults [ ' link_user ' ] , self . defaults [ ' link_hostchars ' ] , self . defaults [ ' link_hostchars ' ] ) )
self . _vte . match_add ( ' (( %s ://( %s @)?)|(www|ftp)[ %s ]* \\ .)[ %s .]+(:[0-9]+)?/[-A-Za-z0-9_$.+!*(),;:@&=?/~# %% ]*[^] \' .}>) \t \r \n , \\ \ ] ' % ( self . defaults [ ' link_scheme ' ] , self . defaults [ ' link_userchars ' ] , self . defaults [ ' link_hostchars ' ] , self . defaults [ ' link_hostchars ' ] ) )
2007-07-28 01:17:37 +00:00
self . spawn_child ( )
def spawn_child ( self , event = None ) :
2007-07-28 18:36:51 +00:00
if self . gconf_client . get_bool ( self . profile + " /use_custom_command " ) == True :
2007-07-28 01:17:37 +00:00
self . _vte . fork_command ( self . gconf_client . get_string ( self . profile + " /custom_command " ) )
else :
self . _vte . fork_command ( )
2006-11-10 05:18:31 +00:00
def reconfigure_vte ( self ) :
2007-05-08 23:15:26 +00:00
if ( ( self . lastreconfigure != 0 ) and ( time . time ( ) - self . lastreconfigure ) < 5 ) :
# Rate limit
return
self . lastreconfigure = time . time ( )
2006-11-10 20:15:33 +00:00
# Set our emulation
2006-11-10 05:18:31 +00:00
self . _vte . set_emulation ( self . defaults [ ' emulation ' ] )
2007-07-16 22:59:17 +00:00
# Set our wordchars
# FIXME: This shouldn't be hardcoded
self . _vte . set_word_chars ( ' -A-Za-z0-9./? % &#_+ ' )
# Set our mouselation
# FIXME: This shouldn't be hardcoded
self . _vte . set_mouse_autohide ( True )
# Set our compatibility
2007-07-28 00:44:32 +00:00
backspace = self . gconf_client . get_string ( self . profile + " /backspace_binding " ) or self . defaults [ ' backspace_binding ' ]
2007-07-28 18:36:51 +00:00
delete = self . gconf_client . get_string ( self . profile + " /delete_binding " ) or self . defaults [ ' delete_binding ' ]
2007-07-28 00:44:32 +00:00
# Note, each of the 4 following comments should replace the line beneath it, but the python-vte bindings don't appear to support this constant, so the magic values are being assumed from the C enum :/
if backspace == " ascii-del " :
# backbind = vte.ERASE_ASCII_BACKSPACE
backbind = 2
else :
# backbind = vte.ERASE_AUTO_BACKSPACE
backbind = 1
if delete == " escape-sequence " :
# delbind = vte.ERASE_DELETE_SEQUENCE
delbind = 3
else :
# delbind = vte.ERASE_AUTO
delbind = 0
self . _vte . set_backspace_binding ( backbind )
self . _vte . set_delete_binding ( delbind )
2007-07-16 22:59:17 +00:00
2006-11-10 20:15:33 +00:00
# Set our font, preferably from gconf settings
2007-02-24 14:53:10 +00:00
if self . gconf_client . get_bool ( self . profile + " /use_system_font " ) :
2006-11-10 05:18:31 +00:00
font_name = ( self . gconf_client . get_string ( " /desktop/gnome/interface/monospace_font_name " ) or self . defaults [ ' font_name ' ] )
else :
2007-02-24 14:53:10 +00:00
font_name = ( self . gconf_client . get_string ( self . profile + " /font " ) or self . defaults [ ' font_name ' ] )
2006-11-10 05:18:31 +00:00
try :
self . _vte . set_font ( pango . FontDescription ( font_name ) )
except :
pass
2006-11-10 20:15:33 +00:00
# Set our boldness
2007-02-24 14:53:10 +00:00
self . _vte . set_allow_bold ( self . gconf_client . get_bool ( self . profile + " /allow_bold " ) or self . defaults [ ' allow_bold ' ] )
2006-11-10 20:15:33 +00:00
# Set our color scheme, preferably from gconf settings
2007-07-27 22:31:58 +00:00
palette = self . gconf_client . get_string ( self . profile + " /palette " ) or self . defaults [ ' palette ' ]
2007-07-27 23:33:43 +00:00
if self . gconf_client . get_bool ( self . profile + " /use_theme_colors " ) == True :
2007-07-28 18:36:51 +00:00
# FIXME: For some reason this isn't working properly, but the code appears to be analogous to what gnome-terminal does in C
2007-07-28 00:44:32 +00:00
fg_color = self . _vte . get_style ( ) . text [ gtk . STATE_NORMAL ]
bg_color = self . _vte . get_style ( ) . base [ gtk . STATE_NORMAL ]
2007-07-27 23:33:43 +00:00
else :
fg_color = gtk . gdk . color_parse ( self . gconf_client . get_string ( self . profile + " /foreground_color " ) or self . defaults [ ' foreground_color ' ] )
bg_color = gtk . gdk . color_parse ( self . gconf_client . get_string ( self . profile + " /background_color " ) or self . defaults [ ' background_color ' ] )
2007-07-27 22:31:58 +00:00
colors = palette . split ( ' : ' )
palette = [ ]
for color in colors :
palette . append ( gtk . gdk . color_parse ( color ) )
2007-07-27 23:33:43 +00:00
self . _vte . set_colors ( fg_color , bg_color , palette )
2006-11-10 20:15:33 +00:00
# Set our cursor blinkiness
2007-02-24 14:53:10 +00:00
self . _vte . set_cursor_blinks = ( self . gconf_client . get_bool ( self . profile + " /cursor_blinks " ) or self . defaults [ ' cursor_blinks ' ] )
2006-11-10 20:15:33 +00:00
# Set our audible belliness
2007-02-24 14:53:10 +00:00
self . _vte . set_audible_bell = not ( self . gconf_client . get_bool ( self . profile + " /silent_bell " ) or self . defaults [ ' audible_bell ' ] )
2006-11-10 20:15:33 +00:00
self . _vte . set_visible_bell ( self . defaults [ ' visible_bell ' ] )
# Set our scrolliness
2007-02-24 14:53:10 +00:00
self . _vte . set_scrollback_lines ( self . gconf_client . get_int ( self . profile + " /scrollback_lines " ) or self . defaults [ ' scrollback_lines ' ] )
self . _vte . set_scroll_on_keystroke ( self . gconf_client . get_bool ( self . profile + " /scroll_on_keystroke " ) or self . defaults [ ' scroll_on_keystroke ' ] )
self . _vte . set_scroll_on_output ( self . gconf_client . get_bool ( self . profile + " /scroll_on_output " ) or self . defaults [ ' scroll_on_output ' ] )
2006-11-10 20:15:33 +00:00
2007-05-08 23:15:26 +00:00
# Set our sloppiness
self . focus = self . gconf_client . get_string ( " /apps/metacity/general/focus_mode " ) or self . defaults [ ' focus ' ]
2006-11-10 05:18:31 +00:00
def on_gconf_notification ( self , client , cnxn_id , entry , what ) :
self . reconfigure_vte ( )
def on_vte_button_press ( self , term , event ) :
2006-11-10 20:15:33 +00:00
# Left mouse button should transfer focus to this vte widget
2006-11-10 05:18:31 +00:00
if event . button == 1 :
self . _vte . grab_focus ( )
2006-11-17 04:34:15 +00:00
return False
2006-11-10 20:15:33 +00:00
2007-02-27 20:47:02 +00:00
# Right mouse button should display a context menu
2006-11-10 05:18:31 +00:00
if event . button == 3 :
2006-11-17 07:23:04 +00:00
self . do_popup ( event )
2006-11-10 05:18:31 +00:00
return True
2006-11-14 08:04:06 +00:00
def on_vte_notify_enter ( self , term , event ) :
2007-05-08 23:15:26 +00:00
if ( self . focus == " sloppy " or self . focus == " mouse " ) :
term . grab_focus ( )
# FIXME: Should we eat this event or let it propagate further?
return False
2006-11-14 08:04:06 +00:00
2007-02-24 14:53:10 +00:00
def do_scrollbar_toggle ( self ) :
if self . _scrollbar . get_property ( ' visible ' ) :
self . _scrollbar . hide ( )
else :
self . _scrollbar . show ( )
2006-11-17 07:23:04 +00:00
def on_vte_popup_menu ( self , term ) :
self . do_popup ( )
def do_popup ( self , event = None ) :
2007-02-24 14:53:10 +00:00
menu = self . create_popup_menu ( event )
2006-11-17 07:23:04 +00:00
menu . popup ( None , None , None , event . button , event . time )
2007-02-24 14:53:10 +00:00
def create_popup_menu ( self , event ) :
2006-11-17 07:23:04 +00:00
menu = gtk . Menu ( )
2007-02-25 02:37:56 +00:00
url = self . _vte . match_check ( int ( event . x / self . _vte . get_char_width ( ) ) , int ( event . y / self . _vte . get_char_height ( ) ) )
if url :
2007-02-25 03:30:29 +00:00
item = gtk . MenuItem ( " _Open Link " )
2007-02-25 02:37:56 +00:00
item . connect ( " activate " , lambda menu_item : gnome . url_show ( url [ 0 ] ) )
menu . append ( item )
2007-02-25 03:30:29 +00:00
item = gtk . MenuItem ( " _Copy Link Address " )
item . connect ( " activate " , lambda menu_item : self . clipboard . set_text ( url [ 0 ] ) )
menu . append ( item )
2007-02-25 02:37:56 +00:00
2007-02-25 03:30:29 +00:00
item = gtk . MenuItem ( )
2007-02-25 02:37:56 +00:00
menu . append ( item )
2006-11-17 07:23:04 +00:00
2007-02-25 03:30:29 +00:00
item = gtk . ImageMenuItem ( gtk . STOCK_COPY )
item . connect ( " activate " , lambda menu_item : self . _vte . copy_clipboard ( ) )
item . set_sensitive ( self . _vte . get_has_selection ( ) )
menu . append ( item )
2006-11-17 07:23:04 +00:00
item = gtk . ImageMenuItem ( gtk . STOCK_PASTE )
item . connect ( " activate " , lambda menu_item : self . _vte . paste_clipboard ( ) )
menu . append ( item )
2007-02-25 03:30:29 +00:00
item = gtk . MenuItem ( )
menu . append ( item )
2007-02-24 14:53:10 +00:00
item = gtk . CheckMenuItem ( " Show scrollbar " )
item . set_active ( self . _scrollbar . get_property ( ' visible ' ) )
item . connect ( " toggled " , lambda menu_item : self . do_scrollbar_toggle ( ) )
menu . append ( item )
2007-07-29 02:06:52 +00:00
item = gtk . MenuItem ( )
menu . append ( item )
item = gtk . MenuItem ( " Split _Horizontally " )
item . connect ( " activate " , lambda menu_item : self . term . splithoriz ( self ) )
menu . append ( item )
item = gtk . MenuItem ( " Split _Vertically " )
item . connect ( " activate " , lambda menu_item : self . term . splitvert ( self ) )
menu . append ( item )
2006-11-17 07:23:04 +00:00
menu . show_all ( )
return menu
2006-11-10 05:18:31 +00:00
def get_box ( self ) :
return self . _box
class Terminator :
def __init__ ( self ) :
2006-11-14 08:04:06 +00:00
self . gconf_client = gconf . client_get_default ( )
2006-11-10 05:18:31 +00:00
self . window = gtk . Window ( )
2006-11-13 06:47:26 +00:00
self . icon = self . window . render_icon ( gtk . STOCK_DIALOG_INFO , gtk . ICON_SIZE_BUTTON )
self . window . set_icon ( self . icon )
2006-11-10 05:18:31 +00:00
self . window . connect ( " delete_event " , self . on_delete_event )
self . window . connect ( " destroy " , self . on_destroy_event )
2007-07-15 23:24:42 +00:00
self . window . maximize ( )
2006-11-10 05:18:31 +00:00
2007-07-29 02:06:52 +00:00
# Start out with just one terminal
# FIXME: This should be really be decided from some kind of profile
term = ( TerminatorTerm ( self ) )
self . window . add ( term . get_box ( ) )
self . window . show_all ( )
2006-11-10 05:18:31 +00:00
def on_delete_event ( self , widget , event , data = None ) :
2007-02-25 03:30:29 +00:00
dialog = gtk . Dialog ( " Quit? " , self . window , gtk . DIALOG_MODAL , ( gtk . STOCK_CANCEL , gtk . RESPONSE_REJECT , gtk . STOCK_QUIT , gtk . RESPONSE_ACCEPT ) )
label = gtk . Label ( " Do you really want to quit? " )
dialog . vbox . pack_start ( label , True , True , 0 )
label . show ( )
res = dialog . run ( )
if res == gtk . RESPONSE_ACCEPT :
return False
dialog . destroy ( )
return True
2006-11-10 05:18:31 +00:00
def on_destroy_event ( self , widget , data = None ) :
gtk . main_quit ( )
2007-07-29 02:06:52 +00:00
def splithoriz ( self , widget ) :
term2 = TerminatorTerm ( self )
parent = widget . get_box ( ) . get_parent ( )
pane = gtk . HPaned ( )
2007-07-29 02:49:19 +00:00
cols = widget . _vte . get_column_count ( )
rows = widget . _vte . get_row_count ( )
allowance = widget . _scrollbar . allocation . width + pane . style_get_property ( ' handle-size ' )
widget . _vte . set_size ( ( cols / 2 ) - ( allowance / widget . _vte . get_char_width ( ) ) , rows )
term2 . _vte . set_size ( ( cols / 2 ) - ( allowance / widget . _vte . get_char_width ( ) ) , rows )
2007-07-29 02:06:52 +00:00
if isinstance ( parent , gtk . Window ) :
# We just have one term
termwidth = parent . allocation . width / 2
widget . get_box ( ) . reparent ( pane )
2007-07-29 02:16:18 +00:00
pane . pack1 ( widget . get_box ( ) , True , True )
pane . pack2 ( term2 . get_box ( ) , True , True )
2006-11-10 05:18:31 +00:00
2007-07-29 02:06:52 +00:00
parent . add ( pane )
pane . set_position ( termwidth )
2006-11-10 05:18:31 +00:00
2007-07-29 02:06:52 +00:00
if isinstance ( parent , gtk . Paned ) :
# We are inside a split term
if ( widget . get_box ( ) == parent . get_child1 ( ) ) :
widget . get_box ( ) . reparent ( pane )
2007-07-29 02:16:18 +00:00
parent . pack1 ( pane , True , True )
2007-07-29 02:06:52 +00:00
else :
widget . get_box ( ) . reparent ( pane )
2007-07-29 02:16:18 +00:00
parent . pack2 ( pane , True , True )
2006-11-10 05:18:31 +00:00
2007-07-29 02:16:18 +00:00
pane . pack1 ( widget . get_box ( ) , True , True )
pane . pack2 ( term2 . get_box ( ) , True , True )
2006-11-10 05:18:31 +00:00
2007-07-29 02:06:52 +00:00
parent . show_all ( )
2007-07-29 02:12:04 +00:00
return ( term2 )
2007-07-29 02:06:52 +00:00
def splitvert ( self , widget ) :
2007-07-29 02:12:04 +00:00
term2 = TerminatorTerm ( self )
parent = widget . get_box ( ) . get_parent ( )
pane = gtk . VPaned ( )
2007-07-29 02:49:19 +00:00
cols = widget . _vte . get_column_count ( )
rows = widget . _vte . get_row_count ( )
allowance = widget . _scrollbar . allocation . width + pane . style_get_property ( ' handle-size ' )
widget . _vte . set_size ( cols , ( rows / 2 ) - ( allowance / widget . _vte . get_char_height ( ) ) )
term2 . _vte . set_size ( cols , ( rows / 2 ) - ( allowance / widget . _vte . get_char_height ( ) ) )
2007-07-29 02:12:04 +00:00
if isinstance ( parent , gtk . Window ) :
# We just have one term
termheight = parent . allocation . height / 2
widget . get_box ( ) . reparent ( pane )
2007-07-29 02:16:18 +00:00
pane . pack1 ( widget . get_box ( ) , True , True )
pane . pack2 ( term2 . get_box ( ) , True , True )
2007-07-29 02:12:04 +00:00
parent . add ( pane )
pane . set_position ( termheight )
if isinstance ( parent , gtk . Paned ) :
# We are inside a split term
2007-07-29 02:49:19 +00:00
term2 . _vte . set_size ( cols , ( rows / 2 ) - 1 )
2007-07-29 02:12:04 +00:00
if ( widget . get_box ( ) == parent . get_child1 ( ) ) :
widget . get_box ( ) . reparent ( pane )
2007-07-29 02:16:18 +00:00
parent . pack1 ( pane , True , True )
2007-07-29 02:12:04 +00:00
else :
widget . get_box ( ) . reparent ( pane )
2007-07-29 02:16:18 +00:00
parent . pack2 ( pane , True , True )
2007-07-29 02:12:04 +00:00
2007-07-29 02:16:18 +00:00
pane . pack1 ( widget . get_box ( ) , True , True )
pane . pack2 ( term2 . get_box ( ) , True , True )
2007-07-29 02:12:04 +00:00
parent . show_all ( )
return ( term2 )
2007-07-29 02:06:52 +00:00
if __name__ == ' __main__ ' :
term = Terminator ( )
2006-11-10 05:18:31 +00:00
gtk . main ( )