1# Terminator by Chris Jones <cmsj@tenshu.net>
2# GPL v2 only
3"""terminal.py - classes necessary to provide Terminal widgets"""
4
5
6import os
7import signal
8import gi
9from gi.repository import GLib, GObject, Pango, Gtk, Gdk, GdkPixbuf
10gi.require_version('Vte', '2.91')  # vte-0.38 (gnome-3.14)
11from gi.repository import Vte
12import subprocess
13try:
14    from urllib.parse import unquote as urlunquote
15except ImportError:
16    from urllib import unquote as urlunquote
17
18from .util import dbg, err, spawn_new_terminator, make_uuid, manual_lookup, display_manager
19from . import util
20from .config import Config
21from .cwd import get_pid_cwd
22from .factory import Factory
23from .terminator import Terminator
24from .titlebar import Titlebar
25from .terminal_popup_menu import TerminalPopupMenu
26from .prefseditor import PrefsEditor
27from .searchbar import Searchbar
28from .translation import _
29from .signalman import Signalman
30from . import plugin
31from terminatorlib.layoutlauncher import LayoutLauncher
32from . import regex
33
34# pylint: disable-msg=R0904
35class Terminal(Gtk.VBox):
36    """Class implementing the VTE widget and its wrappings"""
37
38    __gsignals__ = {
39        'close-term': (GObject.SignalFlags.RUN_LAST, None, ()),
40        'title-change': (GObject.SignalFlags.RUN_LAST, None,
41            (GObject.TYPE_STRING,)),
42        'enumerate': (GObject.SignalFlags.RUN_LAST, None,
43            (GObject.TYPE_INT,)),
44        'group-tab': (GObject.SignalFlags.RUN_LAST, None, ()),
45        'group-tab-toggle': (GObject.SignalFlags.RUN_LAST, None, ()),
46        'ungroup-tab': (GObject.SignalFlags.RUN_LAST, None, ()),
47        'ungroup-all': (GObject.SignalFlags.RUN_LAST, None, ()),
48        'split-horiz': (GObject.SignalFlags.RUN_LAST, None,
49            (GObject.TYPE_STRING,)),
50        'split-vert': (GObject.SignalFlags.RUN_LAST, None,
51            (GObject.TYPE_STRING,)),
52        'rotate-cw': (GObject.SignalFlags.RUN_LAST, None, ()),
53        'rotate-ccw': (GObject.SignalFlags.RUN_LAST, None, ()),
54        'tab-new': (GObject.SignalFlags.RUN_LAST, None,
55            (GObject.TYPE_BOOLEAN, GObject.TYPE_OBJECT)),
56        'tab-top-new': (GObject.SignalFlags.RUN_LAST, None, ()),
57        'focus-in': (GObject.SignalFlags.RUN_LAST, None, ()),
58        'focus-out': (GObject.SignalFlags.RUN_LAST, None, ()),
59        'zoom': (GObject.SignalFlags.RUN_LAST, None, ()),
60        'maximise': (GObject.SignalFlags.RUN_LAST, None, ()),
61        'unzoom': (GObject.SignalFlags.RUN_LAST, None, ()),
62        'resize-term': (GObject.SignalFlags.RUN_LAST, None,
63            (GObject.TYPE_STRING,)),
64        'navigate': (GObject.SignalFlags.RUN_LAST, None,
65            (GObject.TYPE_STRING,)),
66        'tab-change': (GObject.SignalFlags.RUN_LAST, None,
67            (GObject.TYPE_INT,)),
68        'group-all': (GObject.SignalFlags.RUN_LAST, None, ()),
69        'group-all-toggle': (GObject.SignalFlags.RUN_LAST, None, ()),
70        'move-tab': (GObject.SignalFlags.RUN_LAST, None,
71            (GObject.TYPE_STRING,)),
72    }
73
74    TARGET_TYPE_VTE = 8
75    TARGET_TYPE_MOZ = 9
76
77    MOUSEBUTTON_LEFT = 1
78    MOUSEBUTTON_MIDDLE = 2
79    MOUSEBUTTON_RIGHT = 3
80
81    terminator = None
82    vte = None
83    terminalbox = None
84    scrollbar = None
85    titlebar = None
86    searchbar = None
87
88    group = None
89    cwd = None
90    origcwd = None
91    command = None
92    clipboard = None
93    pid = None
94
95    matches = None
96    regex_flags = None
97    config = None
98    default_encoding = None
99    custom_encoding = None
100    custom_font_size = None
101    layout_command = None
102    relaunch_command = None
103    directory = None
104
105    is_held_open = False
106
107    fgcolor_active = None
108    fgcolor_inactive = None
109    bgcolor = None
110    palette_active = None
111    palette_inactive = None
112
113    composite_support = None
114
115    cnxids = None
116    targets_for_new_group = None
117
118    def __init__(self):
119        """Class initialiser"""
120        GObject.GObject.__init__(self)
121
122        self.terminator = Terminator()
123        self.terminator.register_terminal(self)
124
125        # FIXME: Surely these should happen in Terminator::register_terminal()?
126        self.connect('enumerate', self.terminator.do_enumerate)
127        self.connect('focus-in', self.terminator.focus_changed)
128        self.connect('focus-out', self.terminator.focus_left)
129
130        self.matches = {}
131        self.cnxids = Signalman()
132
133        self.config = Config()
134
135        self.cwd = get_pid_cwd()
136        self.origcwd = self.terminator.origcwd
137        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
138
139        self.pending_on_vte_size_allocate = False
140
141        self.vte = Vte.Terminal()
142        self.background_image = None
143        if self.config['background_image'] != '':
144            try:
145                self.background_image = GdkPixbuf.Pixbuf.new_from_file(self.config['background_image'])
146                self.vte.set_clear_background(False)
147                self.vte.connect("draw",self.background_draw)
148            except Exception as e:
149                self.background_image = None
150                self.vte.set_clear_background(True)
151                err('error loading background image: %s' % e)
152
153        self.background_alpha = self.config['background_darkness']
154        self.vte.set_allow_hyperlink(True)
155        self.vte._draw_data = None
156        if not hasattr(self.vte, "set_opacity") or \
157           not hasattr(self.vte, "is_composited"):
158            self.composite_support = False
159        else:
160            self.composite_support = True
161        dbg('composite_support: %s' % self.composite_support)
162
163
164        self.vte.show()
165        self.default_encoding = self.vte.get_encoding()
166        self.update_url_matches()
167
168        self.terminalbox = self.create_terminalbox()
169
170        self.titlebar = Titlebar(self)
171        self.titlebar.connect_icon(self.on_group_button_press)
172        self.titlebar.connect('edit-done', self.on_edit_done)
173        self.connect('title-change', self.titlebar.set_terminal_title)
174        self.titlebar.connect('create-group', self.really_create_group)
175        self.titlebar.show_all()
176
177        self.searchbar = Searchbar()
178        self.searchbar.connect('end-search', self.on_search_done)
179
180        self.show()
181        if self.config['title_at_bottom']:
182            self.pack_start(self.terminalbox, True, True, 0)
183            self.pack_start(self.titlebar, False, True, 0)
184        else:
185            self.pack_start(self.titlebar, False, True, 0)
186            self.pack_start(self.terminalbox, True, True, 0)
187
188        self.pack_end(self.searchbar, True, True, 0)
189
190        self.connect_signals()
191
192        os.putenv('TERM', self.config['term'])
193        os.putenv('COLORTERM', self.config['colorterm'])
194
195        env_proxy = os.getenv('http_proxy')
196        if not env_proxy:
197            if self.config['http_proxy'] and self.config['http_proxy'] != '':
198                os.putenv('http_proxy', self.config['http_proxy'])
199        self.reconfigure()
200        self.vte.set_size(80, 24)
201
202    def get_vte(self):
203        """This simply returns the vte widget we are using"""
204        return(self.vte)
205
206    def force_set_profile(self, widget, profile):
207        """Forcibly set our profile"""
208        self.set_profile(widget, profile, True)
209
210    def set_profile(self, _widget, profile, force=False):
211        """Set our profile"""
212        if profile != self.config.get_profile():
213            self.config.set_profile(profile, force)
214            self.reconfigure()
215
216    def get_profile(self):
217        """Return our profile name"""
218        return(self.config.profile)
219
220    def switch_to_next_profile(self):
221        profilelist = self.config.list_profiles()
222        list_length = len(profilelist)
223
224        if list_length > 1:
225            if profilelist.index(self.get_profile()) + 1 == list_length:
226                self.force_set_profile(False, profilelist[0])
227            else:
228                self.force_set_profile(False, profilelist[profilelist.index(self.get_profile()) + 1])
229
230    def switch_to_previous_profile(self):
231        profilelist = self.config.list_profiles()
232        list_length = len(profilelist)
233
234        if list_length > 1:
235            if profilelist.index(self.get_profile()) == 0:
236                self.force_set_profile(False, profilelist[list_length - 1])
237            else:
238                self.force_set_profile(False, profilelist[profilelist.index(self.get_profile()) - 1])
239
240    def get_cwd(self):
241        """Return our cwd"""
242        vte_cwd = self.vte.get_current_directory_uri()
243        if vte_cwd:
244            # OSC7 pwd gives an answer
245            return(GLib.filename_from_uri(vte_cwd)[0])
246        else:
247            # Fall back to old gtk2 method
248            dbg('calling get_pid_cwd')
249            return(get_pid_cwd(self.pid))
250
251    def close(self):
252        """Close ourselves"""
253        dbg('close: called')
254        self.cnxids.remove_widget(self.vte)
255        self.emit('close-term')
256        if self.pid is not None:
257            try:
258                dbg('close: killing %d' % self.pid)
259                os.kill(self.pid, signal.SIGHUP)
260            except Exception as ex:
261                # We really don't want to care if this failed. Deep OS voodoo is
262                # not what we should be doing.
263                dbg('os.kill failed: %s' % ex)
264                pass
265
266        if self.vte:
267            self.terminalbox.remove(self.vte)
268            del(self.vte)
269
270    def create_terminalbox(self):
271        """Create a GtkHBox containing the terminal and a scrollbar"""
272
273        terminalbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
274        self.scrollbar = Gtk.Scrollbar.new(Gtk.Orientation.VERTICAL, adjustment=self.vte.get_vadjustment())
275        self.scrollbar.set_no_show_all(True)
276
277        terminalbox.pack_start(self.vte, True, True, 0)
278        terminalbox.pack_start(self.scrollbar, False, True, 0)
279        terminalbox.show_all()
280
281        return(terminalbox)
282
283    def _add_regex(self, name, re):
284        match = -1
285        if regex.FLAGS_PCRE2:
286            try:
287                reg = Vte.Regex.new_for_match(re, len(re), self.regex_flags or regex.FLAGS_PCRE2)
288                match = self.vte.match_add_regex(reg, 0)
289            except GLib.Error:
290                # happens when PCRE2 support is not builtin (Ubuntu < 19.10)
291                pass
292
293        # try the "old" glib regex
294        if match < 0:
295            reg = GLib.Regex.new(re, self.regex_flags or regex.FLAGS_GLIB, 0)
296            match = self.vte.match_add_gregex(reg, 0)
297
298        self.matches[name] = match
299        self.vte.match_set_cursor_name(self.matches[name], 'pointer')
300
301    def update_url_matches(self):
302        """Update the regexps used to match URLs"""
303        userchars = "-A-Za-z0-9"
304        passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
305        hostchars = "-A-Za-z0-9:\[\]"
306        pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'"
307        schemes   = "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:|ssh:)"
308        user      = "[" + userchars + "]+(:[" + passchars + "]+)?"
309        urlpath   = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]"
310
311        lboundry = "\\b"
312        rboundry = "\\b"
313
314        re = (lboundry + schemes +
315                "//(" + user + "@)?[" + hostchars  +".]+(:[0-9]+)?(" +
316                urlpath + ")?" + rboundry + "/?")
317        self._add_regex('full_uri', re)
318
319        if self.matches['full_uri'] == -1:
320            err ('Terminal::update_url_matches: Failed adding URL matches')
321        else:
322            re = (lboundry +
323                    '(callto:|h323:|sip:)' + "[" + userchars + "+][" +
324                    userchars + ".]*(:[0-9]+)?@?[" + pathchars + "]+" +
325                    rboundry)
326            self._add_regex('voip', re)
327
328            re = (lboundry +
329                    "(www|ftp)[" + hostchars + "]*\.[" + hostchars +
330                    ".]+(:[0-9]+)?(" + urlpath + ")?" + rboundry + "/?")
331            self._add_regex('addr_only', re)
332
333            re = (lboundry +
334                    "(mailto:)?[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9]" +
335                            "[a-zA-Z0-9-]*\.[a-zA-Z0-9][a-zA-Z0-9-]+" +
336                            "[.a-zA-Z0-9-]*" + rboundry)
337            self._add_regex('email', re)
338
339            re = (lboundry +
340                  """news:[-A-Z\^_a-z{|}~!"#$%&'()*+,./0-9;:=?`]+@""" +
341                            "[-A-Za-z0-9.]+(:[0-9]+)?" + rboundry)
342            self._add_regex('nntp', re)
343
344            # Now add any matches from plugins
345            try:
346                registry = plugin.PluginRegistry()
347                registry.load_plugins()
348                plugins = registry.get_plugins_by_capability('url_handler')
349
350                for urlplugin in plugins:
351                    name = urlplugin.handler_name
352                    match = urlplugin.match
353                    if name in self.matches:
354                        dbg('refusing to add duplicate match %s' % name)
355                        continue
356
357                    self._add_regex(name, match)
358
359                    dbg('added plugin URL handler for %s (%s) as %d' %
360                        (name, urlplugin.__class__.__name__,
361                        self.matches[name]))
362            except Exception as ex:
363                err('Exception occurred adding plugin URL match: %s' % ex)
364
365    def match_add(self, name, match):
366        """Register a URL match"""
367        if name in self.matches:
368            err('Terminal::match_add: Refusing to create duplicate match %s' % name)
369            return
370
371        self._add_regex(name, match)
372
373    def match_remove(self, name):
374        """Remove a previously registered URL match"""
375        if name not in self.matches:
376            err('Terminal::match_remove: Unable to remove non-existent match %s' % name)
377            return
378        self.vte.match_remove(self.matches[name])
379        del(self.matches[name])
380
381    def maybe_copy_clipboard(self):
382        if self.config['copy_on_selection'] and self.vte.get_has_selection():
383            self.vte.copy_clipboard()
384
385    def connect_signals(self):
386        """Connect all the gtk signals and drag-n-drop mechanics"""
387
388        self.scrollbar.connect('button-press-event', self.on_buttonpress)
389
390        self.cnxids.new(self.vte, 'key-press-event', self.on_keypress)
391        self.cnxids.new(self.vte, 'button-press-event', self.on_buttonpress)
392        self.cnxids.new(self.vte, 'scroll-event', self.on_mousewheel)
393        self.cnxids.new(self.vte, 'popup-menu', self.popup_menu)
394
395        srcvtetargets = [("vte", Gtk.TargetFlags.SAME_APP, self.TARGET_TYPE_VTE)]
396        dsttargets = [("vte", Gtk.TargetFlags.SAME_APP, self.TARGET_TYPE_VTE),
397                      ('text/x-moz-url', 0, self.TARGET_TYPE_MOZ),
398                      ('_NETSCAPE_URL', 0, 0)]
399        '''
400        The following should work, but on my system it corrupts the returned
401        TargetEntry's in the newdstargets with binary crap, causing "Segmentation
402        fault (core dumped)" when the later drag_dest_set gets called.
403
404        dsttargetlist = Gtk.TargetList.new([])
405        dsttargetlist.add_text_targets(0)
406        dsttargetlist.add_uri_targets(0)
407        dsttargetlist.add_table(dsttargets)
408
409        newdsttargets = Gtk.target_table_new_from_list(dsttargetlist)
410        '''
411        # FIXME: Temporary workaround for the problems with the correct way of doing things
412        dsttargets.extend([('text/plain', 0, 0),
413                           ('text/plain;charset=utf-8', 0, 0),
414                           ('TEXT', 0, 0),
415                           ('STRING', 0, 0),
416                           ('UTF8_STRING', 0, 0),
417                           ('COMPOUND_TEXT', 0, 0),
418                           ('text/uri-list', 0, 0)])
419        # Convert to target entries
420        srcvtetargets = [Gtk.TargetEntry.new(*tgt) for tgt in srcvtetargets]
421        dsttargets = [Gtk.TargetEntry.new(*tgt) for tgt in dsttargets]
422
423        dbg('Finalised drag targets: %s' % dsttargets)
424
425        for (widget, mask) in [
426            (self.vte, Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.BUTTON3_MASK),
427            (self.titlebar, Gdk.ModifierType.BUTTON1_MASK)]:
428            widget.drag_source_set(mask, srcvtetargets, Gdk.DragAction.MOVE)
429
430        self.vte.drag_dest_set(Gtk.DestDefaults.MOTION |
431                Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
432                dsttargets, Gdk.DragAction.COPY | Gdk.DragAction.MOVE)
433
434        for widget in [self.vte, self.titlebar]:
435            self.cnxids.new(widget, 'drag-begin', self.on_drag_begin, self)
436            self.cnxids.new(widget, 'drag-data-get', self.on_drag_data_get,
437            self)
438
439        self.cnxids.new(self.vte, 'drag-motion', self.on_drag_motion, self)
440        self.cnxids.new(self.vte, 'drag-data-received',
441            self.on_drag_data_received, self)
442
443        self.cnxids.new(self.vte, 'selection-changed',
444            lambda widget: self.maybe_copy_clipboard())
445
446        if self.composite_support:
447            self.cnxids.new(self.vte, 'composited-changed', self.reconfigure)
448
449        self.cnxids.new(self.vte, 'window-title-changed', lambda x:
450            self.emit('title-change', self.get_window_title()))
451        self.cnxids.new(self.vte, 'grab-focus', self.on_vte_focus)
452        self.cnxids.new(self.vte, 'focus-in-event', self.on_vte_focus_in)
453        self.cnxids.new(self.vte, 'focus-out-event', self.on_vte_focus_out)
454        self.cnxids.new(self.vte, 'size-allocate', self.deferred_on_vte_size_allocate)
455
456        self.vte.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK)
457        self.cnxids.new(self.vte, 'enter_notify_event',
458            self.on_vte_notify_enter)
459
460        self.cnxids.new(self.vte, 'realize', self.reconfigure)
461
462    def create_popup_group_menu(self, widget, event = None):
463        """Pop up a menu for the group widget"""
464        if event:
465            button = event.button
466            time = event.time
467        else:
468            button = 0
469            time = 0
470
471        menu = self.populate_group_menu()
472        menu.show_all()
473        menu.popup_at_widget(widget,Gdk.Gravity.SOUTH_WEST,Gdk.Gravity.NORTH_WEST,None)
474        return(True)
475
476    def populate_group_menu(self):
477        """Fill out a group menu"""
478        menu = Gtk.Menu()
479        self.group_menu = menu
480        groupitems = []
481
482        item = Gtk.MenuItem.new_with_mnemonic(_('N_ew group...'))
483        item.connect('activate', self.create_group)
484        menu.append(item)
485
486        if len(self.terminator.groups) > 0:
487            cnxs = []
488            item = Gtk.RadioMenuItem.new_with_mnemonic(groupitems, _('_None'))
489            groupitems = item.get_group()
490            item.set_active(self.group == None)
491            cnxs.append([item, 'toggled', self.set_group, None])
492            menu.append(item)
493
494            for group in self.terminator.groups:
495                item = Gtk.RadioMenuItem.new_with_label(groupitems, group)
496                groupitems = item.get_group()
497                item.set_active(self.group == group)
498                cnxs.append([item, 'toggled', self.set_group, group])
499                menu.append(item)
500
501            for cnx in cnxs:
502                cnx[0].connect(cnx[1], cnx[2], cnx[3])
503
504        if self.group != None or len(self.terminator.groups) > 0:
505            menu.append(Gtk.SeparatorMenuItem())
506
507        if self.group != None:
508            item = Gtk.MenuItem(_('Remove group %s') % self.group)
509            item.connect('activate', self.ungroup, self.group)
510            menu.append(item)
511
512        if util.has_ancestor(self, Gtk.Notebook):
513            item = Gtk.MenuItem.new_with_mnemonic(_('G_roup all in tab'))
514            item.connect('activate', lambda x: self.emit('group_tab'))
515            menu.append(item)
516
517            if len(self.terminator.groups) > 0:
518                item = Gtk.MenuItem.new_with_mnemonic(_('Ungro_up all in tab'))
519                item.connect('activate', lambda x: self.emit('ungroup_tab'))
520                menu.append(item)
521
522        if len(self.terminator.groups) > 0:
523            item = Gtk.MenuItem(_('Remove all groups'))
524            item.connect('activate', lambda x: self.emit('ungroup-all'))
525            menu.append(item)
526
527        if self.group != None:
528            menu.append(Gtk.SeparatorMenuItem())
529
530            item = Gtk.MenuItem(_('Close group %s') % self.group)
531            item.connect('activate', lambda x:
532                         self.terminator.closegroupedterms(self.group))
533            menu.append(item)
534
535        menu.append(Gtk.SeparatorMenuItem())
536
537        groupitems = []
538        cnxs = []
539
540        for key, value in list({_('Broadcast _all'):'all',
541                          _('Broadcast _group'):'group',
542                          _('Broadcast _off'):'off'}.items()):
543            item = Gtk.RadioMenuItem.new_with_mnemonic(groupitems, key)
544            groupitems = item.get_group()
545            dbg('Terminal::populate_group_menu: %s active: %s' %
546                    (key, self.terminator.groupsend ==
547                        self.terminator.groupsend_type[value]))
548            item.set_active(self.terminator.groupsend ==
549                    self.terminator.groupsend_type[value])
550            cnxs.append([item, 'activate', self.set_groupsend, self.terminator.groupsend_type[value]])
551            menu.append(item)
552
553        for cnx in cnxs:
554            cnx[0].connect(cnx[1], cnx[2], cnx[3])
555
556        menu.append(Gtk.SeparatorMenuItem())
557
558        item = Gtk.CheckMenuItem.new_with_mnemonic(_('_Split to this group'))
559        item.set_active(self.config['split_to_group'])
560        item.connect('toggled', lambda x: self.do_splittogroup_toggle())
561        menu.append(item)
562
563        item = Gtk.CheckMenuItem.new_with_mnemonic(_('Auto_clean groups'))
564        item.set_active(self.config['autoclean_groups'])
565        item.connect('toggled', lambda x: self.do_autocleangroups_toggle())
566        menu.append(item)
567
568        menu.append(Gtk.SeparatorMenuItem())
569
570        item = Gtk.MenuItem.new_with_mnemonic(_('_Insert terminal number'))
571        item.connect('activate', lambda x: self.emit('enumerate', False))
572        menu.append(item)
573
574        item = Gtk.MenuItem.new_with_mnemonic(_('Insert _padded terminal number'))
575        item.connect('activate', lambda x: self.emit('enumerate', True))
576        menu.append(item)
577
578        return(menu)
579
580    def set_group(self, _item, name):
581        """Set a particular group"""
582        if self.group == name:
583            # already in this group, no action needed
584            return
585        dbg('Terminal::set_group: Setting group to %s' % name)
586        self.group = name
587        self.titlebar.set_group_label(name)
588        self.terminator.group_hoover()
589
590    def create_group(self, _item):
591        """Trigger the creation of a group via the titlebar (because popup
592        windows are really lame)"""
593        self.titlebar.create_group()
594
595    def really_create_group(self, _widget, groupname):
596        """The titlebar has spoken, let a group be created"""
597        self.terminator.create_group(groupname)
598        self.set_group(None, groupname)
599
600    def ungroup(self, _widget, data):
601        """Remove a group"""
602        # FIXME: Could we emit and have Terminator do this?
603        for term in self.terminator.terminals:
604            if term.group == data:
605                term.set_group(None, None)
606        self.terminator.group_hoover()
607
608    def set_groupsend(self, _widget, value):
609        """Set the groupsend mode"""
610        # FIXME: Can we think of a smarter way of doing this than poking?
611        if value in list(self.terminator.groupsend_type.values()):
612            dbg('Terminal::set_groupsend: setting groupsend to %s' % value)
613            self.terminator.groupsend = value
614
615    def do_splittogroup_toggle(self):
616        """Toggle the splittogroup mode"""
617        self.config['split_to_group'] = not self.config['split_to_group']
618
619    def do_autocleangroups_toggle(self):
620        """Toggle the autocleangroups mode"""
621        self.config['autoclean_groups'] = not self.config['autoclean_groups']
622
623    def reconfigure(self, _widget=None):
624        """Reconfigure our settings"""
625        dbg('Terminal::reconfigure')
626        self.cnxids.remove_signal(self.vte, 'realize')
627
628        # Handle child command exiting
629        self.cnxids.remove_signal(self.vte, 'child-exited')
630
631        if self.config['exit_action'] == 'restart':
632            self.cnxids.new(self.vte, 'child-exited', self.spawn_child, True)
633        elif self.config['exit_action'] == 'hold':
634            self.cnxids.new(self.vte, 'child-exited', self.held_open, True)
635        elif self.config['exit_action'] in ('close', 'left'):
636            self.cnxids.new(self.vte, 'child-exited',
637                                            lambda x, y: self.emit('close-term'))
638
639        if self.custom_encoding != True:
640            self.vte.set_encoding(self.config['encoding'])
641        # Word char support was missing from vte 0.38, silently skip this setting
642        if hasattr(self.vte, 'set_word_char_exceptions'):
643            self.vte.set_word_char_exceptions(self.config['word_chars'])
644        self.vte.set_mouse_autohide(self.config['mouse_autohide'])
645
646        backspace = self.config['backspace_binding']
647        delete = self.config['delete_binding']
648
649        try:
650            if backspace == 'ascii-del':
651                backbind = Vte.ERASE_ASCII_DELETE
652            elif backspace == 'control-h':
653                backbind = Vte.ERASE_ASCII_BACKSPACE
654            elif backspace == 'escape-sequence':
655                backbind = Vte.ERASE_DELETE_SEQUENCE
656            else:
657                backbind = Vte.ERASE_AUTO
658        except AttributeError:
659            if backspace == 'ascii-del':
660                backbind = 2
661            elif backspace == 'control-h':
662                backbind = 1
663            elif backspace == 'escape-sequence':
664                backbind = 3
665            else:
666                backbind = 0
667
668        try:
669            if delete == 'ascii-del':
670                delbind = Vte.ERASE_ASCII_DELETE
671            elif delete == 'control-h':
672                delbind = Vte.ERASE_ASCII_BACKSPACE
673            elif delete == 'escape-sequence':
674                delbind = Vte.ERASE_DELETE_SEQUENCE
675            else:
676                delbind = Vte.ERASE_AUTO
677        except AttributeError:
678            if delete == 'ascii-del':
679                delbind = 2
680            elif delete == 'control-h':
681                delbind = 1
682            elif delete == 'escape-sequence':
683                delbind = 3
684            else:
685                delbind = 0
686
687        self.vte.set_backspace_binding(backbind)
688        self.vte.set_delete_binding(delbind)
689
690        if not self.custom_font_size:
691            try:
692                if self.config['use_system_font'] == True:
693                    font = self.config.get_system_mono_font()
694                else:
695                    font = self.config['font']
696                self.set_font(Pango.FontDescription(font))
697            except:
698                pass
699        self.vte.set_allow_bold(self.config['allow_bold'])
700        if hasattr(self.vte,'set_cell_height_scale'):
701            self.vte.set_cell_height_scale(self.config['line_height'])
702        if hasattr(self.vte, 'set_bold_is_bright'):
703            self.vte.set_bold_is_bright(self.config['bold_is_bright'])
704
705        if self.config['use_theme_colors']:
706            self.fgcolor_active = self.vte.get_style_context().get_color(Gtk.StateType.NORMAL)  # VERIFY FOR GTK3: do these really take the theme colors?
707            self.bgcolor = self.vte.get_style_context().get_background_color(Gtk.StateType.NORMAL)
708        else:
709            self.fgcolor_active = Gdk.RGBA()
710            self.fgcolor_active.parse(self.config['foreground_color'])
711            self.bgcolor = Gdk.RGBA()
712            self.bgcolor.parse(self.config['background_color'])
713
714        if self.config['background_type'] == 'transparent' or self.config['background_type'] == 'image':
715            self.bgcolor.alpha = self.config['background_darkness']
716        else:
717            self.bgcolor.alpha = 1
718
719        factor = self.config['inactive_color_offset']
720        if factor > 1.0:
721          factor = 1.0
722        self.fgcolor_inactive = self.fgcolor_active.copy()
723        dbg(("fgcolor_inactive set to: RGB(%s,%s,%s)", getattr(self.fgcolor_inactive, "red"),
724                                                      getattr(self.fgcolor_inactive, "green"),
725                                                      getattr(self.fgcolor_inactive, "blue")))
726
727        for bit in ['red', 'green', 'blue']:
728            setattr(self.fgcolor_inactive, bit,
729                    getattr(self.fgcolor_inactive, bit) * factor)
730
731        dbg(("fgcolor_inactive set to: RGB(%s,%s,%s)", getattr(self.fgcolor_inactive, "red"),
732                                                      getattr(self.fgcolor_inactive, "green"),
733                                                      getattr(self.fgcolor_inactive, "blue")))
734        colors = self.config['palette'].split(':')
735        self.palette_active = []
736        for color in colors:
737            if color:
738                newcolor = Gdk.RGBA()
739                newcolor.parse(color)
740                self.palette_active.append(newcolor)
741        if len(colors) == 16:
742            # RGB values for indices 16..255 copied from vte source in order to dim them
743            shades = [0, 95, 135, 175, 215, 255]
744            for r in range(0, 6):
745                for g in range(0, 6):
746                    for b in range(0, 6):
747                        newcolor = Gdk.RGBA()
748                        setattr(newcolor, "red",   shades[r] / 255.0)
749                        setattr(newcolor, "green", shades[g] / 255.0)
750                        setattr(newcolor, "blue",  shades[b] / 255.0)
751                        self.palette_active.append(newcolor)
752            for y in range(8, 248, 10):
753                newcolor = Gdk.RGBA()
754                setattr(newcolor, "red",   y / 255.0)
755                setattr(newcolor, "green", y / 255.0)
756                setattr(newcolor, "blue",  y / 255.0)
757                self.palette_active.append(newcolor)
758        self.palette_inactive = []
759        for color in self.palette_active:
760            newcolor = Gdk.RGBA()
761            for bit in ['red', 'green', 'blue']:
762                setattr(newcolor, bit,
763                        getattr(color, bit) * factor)
764            self.palette_inactive.append(newcolor)
765        if self.terminator.last_focused_term == self:
766            self.vte.set_colors(self.fgcolor_active, self.bgcolor,
767                                self.palette_active)
768        else:
769            self.vte.set_colors(self.fgcolor_inactive, self.bgcolor,
770                                self.palette_inactive)
771        profiles = self.config.base.profiles
772        terminal_box_style_context = self.terminalbox.get_style_context()
773        for profile in list(profiles.keys()):
774            munged_profile = "terminator-profile-%s" % (
775                "".join([c if c.isalnum() else "-" for c in profile]))
776            if terminal_box_style_context.has_class(munged_profile):
777                terminal_box_style_context.remove_class(munged_profile)
778        munged_profile = "".join([c if c.isalnum() else "-" for c in self.get_profile()])
779        css_class_name = "terminator-profile-%s" % (munged_profile)
780        terminal_box_style_context.add_class(css_class_name)
781        self.set_cursor_color()
782        self.vte.set_cursor_shape(getattr(Vte.CursorShape,
783                                          self.config['cursor_shape'].upper()));
784
785        if self.config['cursor_blink'] == True:
786            self.vte.set_cursor_blink_mode(Vte.CursorBlinkMode.ON)
787        else:
788            self.vte.set_cursor_blink_mode(Vte.CursorBlinkMode.OFF)
789
790        if self.config['force_no_bell'] == True:
791            self.vte.set_audible_bell(False)
792            self.cnxids.remove_signal(self.vte, 'bell')
793        else:
794            self.vte.set_audible_bell(self.config['audible_bell'])
795            self.cnxids.remove_signal(self.vte, 'bell')
796            if self.config['urgent_bell'] == True or \
797               self.config['icon_bell'] == True or \
798               self.config['visible_bell'] == True:
799                try:
800                    self.cnxids.new(self.vte, 'bell', self.on_bell)
801                except TypeError:
802                    err('bell signal unavailable with this version of VTE')
803
804        if self.config['scrollback_infinite'] == True:
805            scrollback_lines = -1
806        else:
807            scrollback_lines = self.config['scrollback_lines']
808        self.vte.set_scrollback_lines(scrollback_lines)
809        self.vte.set_scroll_on_keystroke(self.config['scroll_on_keystroke'])
810        self.vte.set_scroll_on_output(self.config['scroll_on_output'])
811
812        if self.config['scrollbar_position'] in ['disabled', 'hidden']:
813            self.scrollbar.hide()
814        else:
815            self.scrollbar.show()
816            if self.config['scrollbar_position'] == 'left':
817                self.terminalbox.reorder_child(self.scrollbar, 0)
818            elif self.config['scrollbar_position'] == 'right':
819                self.terminalbox.reorder_child(self.vte, 0)
820
821        self.titlebar.update()
822        self.vte.queue_draw()
823
824    def set_cursor_color(self):
825        """Set the cursor color appropriately"""
826        if self.config['cursor_color_fg']:
827            self.vte.set_color_cursor(None)
828        else:
829            cursor_color = Gdk.RGBA()
830            cursor_color.parse(self.config['cursor_color'])
831            self.vte.set_color_cursor(cursor_color)
832
833    def get_window_title(self):
834        """Return the window title"""
835        return self.vte.get_window_title() or str(self.command)
836
837    def on_group_button_press(self, widget, event):
838        """Handler for the group button"""
839        if event.button == 1:
840            if event.type == Gdk.EventType._2BUTTON_PRESS or \
841               event.type == Gdk.EventType._3BUTTON_PRESS:
842                # Ignore these, or they make the interaction bad
843                return True
844            # Super key applies interaction to all terms in group
845            include_siblings=event.get_state() & Gdk.ModifierType.MOD4_MASK == Gdk.ModifierType.MOD4_MASK
846            if include_siblings:
847                targets=self.terminator.get_sibling_terms(self)
848            else:
849                targets=[self]
850            if event.get_state() & Gdk.ModifierType.CONTROL_MASK == Gdk.ModifierType.CONTROL_MASK:
851                dbg('on_group_button_press: toggle terminal to focused terminals group')
852                focused=self.get_toplevel().get_focussed_terminal()
853                if focused in targets: targets.remove(focused)
854                if self != focused:
855                    if self.group == focused.group:
856                        new_group = None
857                    else:
858                        new_group = focused.group
859                    [term.set_group(None, new_group) for term in targets]
860                    [term.titlebar.update(focused) for term in targets]
861                return True
862            elif event.get_state() & Gdk.ModifierType.SHIFT_MASK == Gdk.ModifierType.SHIFT_MASK:
863                dbg('on_group_button_press: rename of terminals group')
864                self.targets_for_new_group = targets
865                self.titlebar.create_group()
866                return True
867            elif event.type == Gdk.EventType.BUTTON_PRESS:
868                # Single Click gives popup
869                dbg('on_group_button_press: group menu popup')
870                window = self.get_toplevel()
871                window.preventHide = True
872                self.create_popup_group_menu(widget, event)
873                return True
874            else:
875                dbg('on_group_button_press: unknown group button interaction')
876        return False
877
878    def on_keypress(self, widget, event):
879        """Handler for keyboard events"""
880        if not event:
881            dbg('Terminal::on_keypress: Called on %s with no event' % widget)
882            return False
883
884        # FIXME: Does keybindings really want to live in Terminator()?
885        mapping = self.terminator.keybindings.lookup(event)
886
887        # Just propagate tab-swictch events if there is only one tab
888        if (
889                mapping and (
890                    mapping.startswith('switch_to_tab') or
891                    mapping in ('next_tab', 'prev_tab')
892                )
893        ):
894            window = self.get_toplevel()
895            child = window.get_children()[0]
896            if isinstance(child, Terminal):
897                # not a Notebook instance => a single tab is used
898                # .get_n_pages() can not be used
899                return False
900
901        if mapping == "hide_window":
902            return False
903
904        if mapping and mapping not in ['close_window',
905                                       'full_screen']:
906            dbg('Terminal::on_keypress: lookup found: %r' % mapping)
907            # handle the case where user has re-bound copy to ctrl+<key>
908            # we only copy if there is a selection otherwise let it fall through
909            # to ^<key>
910            if (mapping == "copy" and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
911                if self.vte.get_has_selection():
912                    getattr(self, "key_" + mapping)()
913                    return True
914                elif not self.config['smart_copy']:
915                    return True
916            else:
917                getattr(self, "key_" + mapping)()
918                return True
919
920        # FIXME: This is all clearly wrong. We should be doing this better
921        #         maybe we can emit the key event and let Terminator() care?
922        groupsend = self.terminator.groupsend
923        groupsend_type = self.terminator.groupsend_type
924        window_focussed = self.vte.get_toplevel().get_property('has-toplevel-focus')
925        if groupsend != groupsend_type['off'] and window_focussed and self.vte.is_focus():
926            if self.group and groupsend == groupsend_type['group']:
927                self.terminator.group_emit(self, self.group, 'key-press-event',
928                                           event)
929            if groupsend == groupsend_type['all']:
930                self.terminator.all_emit(self, 'key-press-event', event)
931
932        return False
933
934    def on_buttonpress(self, widget, event):
935        """Handler for mouse events"""
936        # Any button event should grab focus
937        widget.grab_focus()
938
939        if type(widget) == Gtk.VScrollbar and event.type == Gdk.EventType._2BUTTON_PRESS:
940            # Suppress double-click behavior
941            return True
942
943        if self.config['putty_paste_style']:
944            middle_click = [self.popup_menu, (widget, event)]
945            right_click = [self.paste_clipboard, (not self.config['putty_paste_style_source_clipboard'], )]
946        else:
947            middle_click = [self.paste_clipboard, (True, )]
948            right_click = [self.popup_menu, (widget, event)]
949
950        # Ctrl-click event here.
951        if event.button == self.MOUSEBUTTON_LEFT:
952            # Ctrl+leftclick on a URL should open it
953            if event.get_state() & Gdk.ModifierType.CONTROL_MASK == Gdk.ModifierType.CONTROL_MASK:
954                # Check new OSC-8 method first
955                url = self.vte.hyperlink_check_event(event)
956                dbg('url: %s' % url)
957                if url:
958                    self.open_url(url, prepare=False)
959                else:
960                    dbg('OSC-8 URL not detected dropping back to regex match')
961                    url = self.vte.match_check_event(event)
962                    if url[0]:
963                        self.open_url(url, prepare=True)
964        elif event.button == self.MOUSEBUTTON_MIDDLE:
965            # middleclick should paste the clipboard
966            # try to pass it to vte widget first though
967            if event.get_state() & Gdk.ModifierType.CONTROL_MASK == 0:
968                if event.get_state() & Gdk.ModifierType.SHIFT_MASK == 0:
969                    gtk_settings=Gtk.Settings().get_default()
970                    primary_state = gtk_settings.get_property('gtk-enable-primary-paste')
971                    gtk_settings.set_property('gtk-enable-primary-paste',  False)
972                    if not Vte.Terminal.do_button_press_event(self.vte, event):
973                        middle_click[0](*middle_click[1])
974                    gtk_settings.set_property('gtk-enable-primary-paste', primary_state)
975                else:
976                    middle_click[0](*middle_click[1])
977                return True
978            return Vte.Terminal.do_button_press_event(self.vte, event)
979        elif event.button == self.MOUSEBUTTON_RIGHT:
980            # rightclick should display a context menu if Ctrl is not pressed,
981            # plus either the app is not interested in mouse events or Shift is pressed
982            if event.get_state() & Gdk.ModifierType.CONTROL_MASK == 0:
983                if event.get_state() & Gdk.ModifierType.SHIFT_MASK == 0:
984                    if not Vte.Terminal.do_button_press_event(self.vte, event):
985                        right_click[0](*right_click[1])
986                else:
987                    right_click[0](*right_click[1])
988                return True
989        return False
990
991    def on_mousewheel(self, widget, event):
992        """Handler for modifier + mouse wheel scroll events"""
993        SMOOTH_SCROLL_UP = event.direction == Gdk.ScrollDirection.SMOOTH and event.delta_y <= 0.
994        SMOOTH_SCROLL_DOWN = event.direction == Gdk.ScrollDirection.SMOOTH and event.delta_y > 0.
995        if event.state & Gdk.ModifierType.CONTROL_MASK == Gdk.ModifierType.CONTROL_MASK:
996            # Zoom the terminal(s) in or out if not disabled in config
997            if self.config["disable_mousewheel_zoom"] is True:
998                return False
999            # Choice of target terminals depends on Shift and Super modifiers
1000            if event.state & Gdk.ModifierType.MOD4_MASK == Gdk.ModifierType.MOD4_MASK:
1001                targets = self.terminator.terminals
1002            elif event.state & Gdk.ModifierType.SHIFT_MASK == Gdk.ModifierType.SHIFT_MASK:
1003                targets = self.terminator.get_target_terms(self)
1004            else:
1005                targets = [self]
1006            if event.direction == Gdk.ScrollDirection.UP or SMOOTH_SCROLL_UP:
1007                for target in targets:
1008                    target.zoom_in()
1009                return True
1010            elif event.direction == Gdk.ScrollDirection.DOWN or SMOOTH_SCROLL_DOWN:
1011                for target in targets:
1012                    target.zoom_out()
1013                return True
1014        if event.state & Gdk.ModifierType.SHIFT_MASK == Gdk.ModifierType.SHIFT_MASK:
1015            # Shift + mouse wheel up/down
1016            if event.direction == Gdk.ScrollDirection.UP or SMOOTH_SCROLL_UP:
1017                self.scroll_by_page(-1)
1018                return True
1019            elif event.direction == Gdk.ScrollDirection.DOWN or SMOOTH_SCROLL_DOWN:
1020                self.scroll_by_page(1)
1021                return True
1022        return False
1023
1024    def popup_menu(self, widget, event=None):
1025        """Display the context menu"""
1026        window = self.get_toplevel()
1027        window.preventHide = True
1028        menu = TerminalPopupMenu(self)
1029        menu.show(widget, event)
1030
1031    def do_scrollbar_toggle(self):
1032        """Show or hide the terminal scrollbar"""
1033        self.toggle_widget_visibility(self.scrollbar)
1034
1035    def toggle_widget_visibility(self, widget):
1036        """Show or hide a widget"""
1037        if widget.get_property('visible'):
1038            widget.hide()
1039        else:
1040            widget.show()
1041
1042    def on_encoding_change(self, _widget, encoding):
1043        """Handle the encoding changing"""
1044        current = self.vte.get_encoding()
1045        if current != encoding:
1046            dbg('on_encoding_change: setting encoding to: %s' % encoding)
1047            self.custom_encoding = not (encoding == self.config['encoding'])
1048            self.vte.set_encoding(encoding)
1049
1050    def on_drag_begin(self, widget, drag_context, _data):
1051        """Handle the start of a drag event"""
1052        Gtk.drag_set_icon_pixbuf(drag_context, util.widget_pixbuf(self, 512), 0, 0)
1053
1054    def on_drag_data_get(self, _widget, _drag_context, selection_data, info,
1055                         _time, data):
1056        """I have no idea what this does, drag and drop is a mystery. sorry."""
1057        selection_data.set(Gdk.atom_intern('vte', False), info,
1058                           bytes(str(data.terminator.terminals.index(self)),
1059                                 'utf-8'))
1060
1061    def on_drag_motion(self, widget, drag_context, x, y, _time, _data):
1062        """*shrug*"""
1063        if not drag_context.list_targets() == [Gdk.atom_intern('vte', False)] and \
1064           (Gtk.targets_include_text(drag_context.list_targets()) or
1065           Gtk.targets_include_uri(drag_context.list_targets())):
1066            # copy text from another widget
1067            return
1068        srcwidget = Gtk.drag_get_source_widget(drag_context)
1069        if(isinstance(srcwidget, Gtk.EventBox) and
1070           srcwidget == self.titlebar) or widget == srcwidget:
1071            # on self
1072            return
1073
1074        alloc = widget.get_allocation()
1075
1076        if self.config['use_theme_colors']:
1077            color = self.vte.get_style_context().get_color(Gtk.StateType.NORMAL)  # VERIFY FOR GTK3 as above
1078        else:
1079            color = Gdk.RGBA()
1080            color.parse(self.config['foreground_color'])  # VERIFY FOR GTK3
1081
1082        pos = self.get_location(widget, x, y)
1083        topleft = (0, 0)
1084        topright = (alloc.width, 0)
1085        topmiddle = (alloc.width/2, 0)
1086        bottomleft = (0, alloc.height)
1087        bottomright = (alloc.width, alloc.height)
1088        bottommiddle = (alloc.width/2, alloc.height)
1089        middleleft = (0, alloc.height/2)
1090        middleright = (alloc.width, alloc.height/2)
1091
1092        coord = ()
1093        if pos == "right":
1094            coord = (topright, topmiddle, bottommiddle, bottomright)
1095        elif pos == "top":
1096            coord = (topleft, topright, middleright , middleleft)
1097        elif pos == "left":
1098            coord = (topleft, topmiddle, bottommiddle, bottomleft)
1099        elif pos == "bottom":
1100            coord = (bottomleft, bottomright, middleright , middleleft)
1101
1102        # here, we define some widget internal values
1103        widget._draw_data = { 'color': color, 'coord' : coord }
1104        # redraw by forcing an event
1105        connec = widget.connect_after('draw', self.on_draw)
1106        widget.queue_draw_area(0, 0, alloc.width, alloc.height)
1107        widget.get_window().process_updates(True)
1108        # finaly reset the values
1109        widget.disconnect(connec)
1110        widget._draw_data = None
1111
1112    def background_draw(self, widget, cr):
1113        if not self.config['background_type'] == 'image' or not self.background_image:
1114            return False
1115
1116        rect = self.vte.get_allocation()
1117        xratio = float(rect.width) / float(self.background_image.get_width())
1118        yratio = float(rect.height) / float(self.background_image.get_height())
1119        cr.save()
1120        cr.scale(xratio,yratio)
1121        Gdk.cairo_set_source_pixbuf(cr, self.background_image, 0, 0)
1122        cr.paint()
1123        Gdk.cairo_set_source_rgba(cr,self.bgcolor)
1124        cr.paint()
1125        cr.restore()
1126
1127    def on_draw(self, widget, context):
1128        if not widget._draw_data:
1129            return False
1130
1131        color = widget._draw_data['color']
1132        coord = widget._draw_data['coord']
1133
1134        context.set_source_rgba(color.red, color.green, color.blue, 0.5)
1135        if len(coord) > 0:
1136            context.move_to(coord[len(coord)-1][0], coord[len(coord)-1][1])
1137            for i in coord:
1138                context.line_to(i[0], i[1])
1139
1140        context.fill()
1141        return False
1142
1143    def on_drag_data_received(self, widget, drag_context, x, y, selection_data,
1144            info, _time, data):
1145        """Something has been dragged into the terminal. Handle it as either a
1146        URL or another terminal."""
1147        # FIXME this code is a mess that I don't quite understand how it works.
1148        dbg('drag data received of type: %s' % (selection_data.get_data_type()))
1149        # print(selection_data.get_urls())
1150        if Gtk.targets_include_text(drag_context.list_targets()) or \
1151           Gtk.targets_include_uri(drag_context.list_targets()):
1152            # copy text with no modification yet to destination
1153            txt = selection_data.get_data()
1154            # https://bugs.launchpad.net/terminator/+bug/1518705
1155            if info == self.TARGET_TYPE_MOZ:
1156                 txt = txt.decode('utf-16')
1157                 # KDE ends it's text/x-moz-url text with CRLF, :shrug:
1158                 if not txt.endswith('\r\n'):
1159                   txt = txt.split('\n')[0]
1160            else:
1161                 txt = txt.decode()
1162
1163            txt_lines = txt.split( "\r\n" )
1164            if txt_lines[-1] == '':
1165                for line in txt_lines[:-1]:
1166                    if line[0:7] != 'file://':
1167                        txt = txt.replace('\r\n','\n')
1168                        break
1169                else:
1170                    # It is a list of crlf terminated file:// URL. let's
1171                    # iterate over all elements except the last one.
1172                    str=''
1173                    for fname in txt_lines[:-1]:
1174                        fname = "'%s'" % urlunquote(fname[7:].replace("'",
1175                                                                      '\'\\\'\''))
1176                        str += fname + ' '
1177                    txt = str
1178            # Never send a CRLF to the terminal from here
1179            txt = txt.rstrip('\r\n')
1180            for term in self.terminator.get_target_terms(self):
1181                term.feed(txt.encode())
1182            return
1183
1184        widgetsrc = data.terminator.terminals[int(selection_data.get_data())]
1185        srcvte = Gtk.drag_get_source_widget(drag_context)
1186        # check if computation requireds
1187        if (isinstance(srcvte, Gtk.EventBox) and
1188                srcvte == self.titlebar) or srcvte == widget:
1189            return
1190
1191        srchbox = widgetsrc
1192
1193        # The widget argument is actually a Vte.Terminal(). Turn that into a
1194        # terminatorlib Terminal()
1195        maker = Factory()
1196        while True:
1197            widget = widget.get_parent()
1198            if not widget:
1199                # We've run out of widgets. Something is wrong.
1200                err('Failed to find Terminal from vte')
1201                return
1202            if maker.isinstance(widget, 'Terminal'):
1203                break
1204
1205        dsthbox = widget
1206
1207        dstpaned = dsthbox.get_parent()
1208        srcpaned = srchbox.get_parent()
1209
1210        pos = self.get_location(widget, x, y)
1211
1212        srcpaned.remove(widgetsrc)
1213        dstpaned.split_axis(dsthbox, pos in ['top', 'bottom'], None, widgetsrc, pos in ['bottom', 'right'])
1214        srcpaned.hoover()
1215        widgetsrc.ensure_visible_and_focussed()
1216
1217    def get_location(self, term, x, y):
1218        """Get our location within the terminal"""
1219        pos = ''
1220        # get the diagonales function for the receiving widget
1221        term_alloc = term.get_allocation()
1222        coef1 = float(term_alloc.height)/float(term_alloc.width)
1223        coef2 = -float(term_alloc.height)/float(term_alloc.width)
1224        b1 = 0
1225        b2 = term_alloc.height
1226        #determine position in rectangle
1227        #--------
1228        #|\    /|
1229        #| \  / |
1230        #|  \/  |
1231        #|  /\  |
1232        #| /  \ |
1233        #|/    \|
1234        #--------
1235        if (x*coef1 + b1 > y) and (x*coef2 + b2 < y):
1236            pos = "right"
1237        if (x*coef1 + b1 > y) and (x*coef2 + b2 > y):
1238            pos = "top"
1239        if (x*coef1 + b1 < y) and (x*coef2 + b2 > y):
1240            pos = "left"
1241        if (x*coef1 + b1 < y) and (x*coef2 + b2 < y):
1242            pos = "bottom"
1243        return pos
1244
1245    def grab_focus(self):
1246        """Steal focus for this terminal"""
1247        if self.vte and not self.vte.has_focus():
1248            self.vte.grab_focus()
1249
1250    def ensure_visible_and_focussed(self):
1251        """Make sure that we're visible and focussed"""
1252        window = self.get_toplevel()
1253        try:
1254            topchild = window.get_children()[0]
1255        except IndexError:
1256            dbg('unable to get top child')
1257            return
1258        maker = Factory()
1259
1260        if maker.isinstance(topchild, 'Notebook'):
1261            # Find which page number this term is on
1262            tabnum = topchild.page_num_descendant(self)
1263            # If terms page number is not the current one, switch to it
1264            current_page = topchild.get_current_page()
1265            if tabnum != current_page:
1266                topchild.set_current_page(tabnum)
1267
1268        self.grab_focus()
1269
1270    def on_vte_focus(self, _widget):
1271        """Update our UI when we get focus"""
1272        self.emit('title-change', self.get_window_title())
1273
1274    def on_vte_focus_in(self, _widget, _event):
1275        """Inform other parts of the application when focus is received"""
1276        self.vte.set_colors(self.fgcolor_active, self.bgcolor,
1277                            self.palette_active)
1278        self.set_cursor_color()
1279        if not self.terminator.doing_layout:
1280            self.terminator.last_focused_term = self
1281            if self.get_toplevel().is_child_notebook():
1282                notebook = self.get_toplevel().get_children()[0]
1283                notebook.set_last_active_term(self.uuid)
1284                notebook.clean_last_active_term()
1285                self.get_toplevel().last_active_term = None
1286            else:
1287                self.get_toplevel().last_active_term = self.uuid
1288        self.emit('focus-in')
1289
1290    def on_vte_focus_out(self, _widget, _event):
1291        """Inform other parts of the application when focus is lost"""
1292        self.vte.set_colors(self.fgcolor_inactive, self.bgcolor,
1293                            self.palette_inactive)
1294        self.set_cursor_color()
1295        self.emit('focus-out')
1296
1297    def on_window_focus_out(self):
1298        """Update our UI when the window loses focus"""
1299        self.titlebar.update('window-focus-out')
1300
1301    def scrollbar_jump(self, position):
1302        """Move the scrollbar to a particular row"""
1303        self.scrollbar.set_value(position)
1304
1305    def on_search_done(self, _widget):
1306        """We've finished searching, so clean up"""
1307        self.searchbar.hide()
1308        self.scrollbar.set_value(self.vte.get_cursor_position()[1])
1309        self.vte.grab_focus()
1310
1311    def on_edit_done(self, _widget):
1312        """A child widget is done editing a label, return focus to VTE"""
1313        self.vte.grab_focus()
1314
1315    def deferred_on_vte_size_allocate(self, widget, allocation):
1316        # widget & allocation are not used in on_vte_size_allocate, so we
1317        # can use the on_vte_size_allocate instead of duplicating the code
1318        if self.pending_on_vte_size_allocate:
1319            return
1320        self.pending_on_vte_size_allocate = True
1321        GObject.idle_add(self.do_deferred_on_vte_size_allocate, widget, allocation)
1322
1323    def do_deferred_on_vte_size_allocate(self, widget, allocation):
1324        self.pending_on_vte_size_allocate = False
1325        self.on_vte_size_allocate(widget, allocation)
1326
1327    def on_vte_size_allocate(self, widget, allocation):
1328        self.titlebar.update_terminal_size(self.vte.get_column_count(),
1329                self.vte.get_row_count())
1330        if self.config['geometry_hinting']:
1331            window = self.get_toplevel()
1332            window.deferred_set_rough_geometry_hints()
1333
1334    def on_vte_notify_enter(self, term, event):
1335        """Handle the mouse entering this terminal"""
1336        # FIXME: This shouldn't be looking up all these values every time
1337        sloppy = False
1338        if self.config['focus'] == 'system':
1339            sloppy = self.config.get_system_focus() in ['sloppy', 'mouse']
1340        elif self.config['focus'] in ['sloppy', 'mouse']:
1341            sloppy = True
1342        if sloppy and self.titlebar.editing() == False:
1343            term.grab_focus()
1344            return False
1345
1346    def get_zoom_data(self):
1347        """Return a dict of information for Window"""
1348        data = {'old_font': self.vte.get_font().copy(),
1349                'old_char_height': self.vte.get_char_height(),
1350                'old_char_width': self.vte.get_char_width(),
1351                'old_allocation': self.vte.get_allocation(),
1352                'old_columns': self.vte.get_column_count(),
1353                'old_rows': self.vte.get_row_count(),
1354                'old_parent': self.get_parent()}
1355
1356        return data
1357
1358    def zoom_scale(self, widget, allocation, old_data):
1359        """Scale our font correctly based on how big we are not vs before"""
1360        self.cnxids.remove_signal(self, 'size-allocate')
1361        # FIXME: Is a zoom signal actualy used anywhere?
1362        self.cnxids.remove_signal(self, 'zoom')
1363
1364        new_columns = self.vte.get_column_count()
1365        new_rows = self.vte.get_row_count()
1366        new_font = self.vte.get_font()
1367
1368        dbg('Terminal::zoom_scale: Resized from %dx%d to %dx%d' % (
1369             old_data['old_columns'],
1370             old_data['old_rows'],
1371             new_columns,
1372             new_rows))
1373
1374        if new_rows == old_data['old_rows'] or \
1375           new_columns == old_data['old_columns']:
1376            dbg('Terminal::zoom_scale: One axis unchanged, not scaling')
1377            return
1378
1379        scale_factor = min ( (new_columns / old_data['old_columns'] * 0.97),
1380                             (new_rows / old_data['old_rows'] * 1.05) )
1381
1382        new_size = int(old_data['old_font'].get_size() * scale_factor)
1383        if new_size == 0:
1384            err('refusing to set a zero sized font')
1385            return
1386        new_font.set_size(new_size)
1387        dbg('setting new font: %s' % new_font)
1388        self.set_font(new_font)
1389
1390    def is_zoomed(self):
1391        """Determine if we are a zoomed terminal"""
1392        prop = None
1393        window = self.get_toplevel()
1394
1395        try:
1396            prop = window.get_property('term-zoomed')
1397        except TypeError:
1398            prop = False
1399
1400        return prop
1401
1402    def zoom(self, widget=None):
1403        """Zoom ourself to fill the window"""
1404        self.emit('zoom')
1405
1406    def maximise(self, widget=None):
1407        """Maximise ourself to fill the window"""
1408        self.emit('maximise')
1409
1410    def unzoom(self, widget=None):
1411        """Restore normal layout"""
1412        self.emit('unzoom')
1413
1414    def set_cwd(self, cwd=None):
1415        """Set our cwd"""
1416        if cwd is not None:
1417            self.cwd = cwd
1418
1419    def held_open(self, widget=None, respawn=False, debugserver=False):
1420        self.is_held_open = True
1421        self.titlebar.update()
1422
1423    def spawn_child(self, widget=None, respawn=False, debugserver=False):
1424        args = []
1425        shell = None
1426        command = None
1427
1428        if self.terminator.doing_layout:
1429            dbg('still laying out, refusing to spawn a child')
1430            return
1431
1432        if respawn is False:
1433            self.vte.grab_focus()
1434
1435        self.is_held_open = False
1436
1437        options = self.config.options_get()
1438        if options and options.command:
1439            command = options.command
1440            self.relaunch_command = command
1441            options.command = None
1442        elif options and options.execute:
1443            command = options.execute
1444            self.relaunch_command = command
1445            options.execute = None
1446        elif self.relaunch_command:
1447            command = self.relaunch_command
1448        elif self.config['use_custom_command']:
1449            command = self.config['custom_command']
1450        elif self.layout_command:
1451            command = self.layout_command
1452        elif debugserver is True:
1453            details = self.terminator.debug_address
1454            dbg('spawning debug session with: %s:%s' % (details[0],
1455                details[1]))
1456            command = 'telnet %s %s' % (details[0], details[1])
1457
1458        # working directory set in layout config
1459        if self.directory:
1460            self.set_cwd(self.directory)
1461        # working directory given as argument
1462        elif options and options.working_directory and \
1463                options.working_directory != '':
1464            self.set_cwd(options.working_directory)
1465            options.working_directory = ''
1466
1467        if type(command) is list:
1468            shell = util.path_lookup(command[0])
1469            args = command
1470        else:
1471            shell = util.shell_lookup()
1472
1473            if self.config['login_shell']:
1474                args.insert(0, "-l")
1475            else:
1476                args.insert(0, shell)
1477
1478            if command is not None:
1479                args += ['-c', command]
1480
1481        if shell is None:
1482            self.vte.feed(_('Unable to find a shell'))
1483            return -1
1484
1485        try:
1486            os.putenv('WINDOWID', '%s' % self.vte.get_parent_window().xid)
1487        except AttributeError:
1488            pass
1489
1490        envv = ['TERM=%s' % self.config['term'],
1491                'COLORTERM=%s' % self.config['colorterm'], 'PWD=%s' % self.cwd,
1492                'TERMINATOR_UUID=%s' % self.uuid.urn]
1493        if self.terminator.dbus_name:
1494            envv.append('TERMINATOR_DBUS_NAME=%s' % self.terminator.dbus_name)
1495        if self.terminator.dbus_path:
1496            envv.append('TERMINATOR_DBUS_PATH=%s' % self.terminator.dbus_path)
1497
1498        dbg('Forking shell: "%s" with args: %s' % (shell, args))
1499        args.insert(0, shell)
1500        result,  self.pid = self.vte.spawn_sync(Vte.PtyFlags.DEFAULT,
1501                                                self.cwd,
1502                                                args,
1503                                                envv,
1504                                                GLib.SpawnFlags.FILE_AND_ARGV_ZERO,
1505                                                None,
1506                                                None,
1507                                                None)
1508        self.command = shell
1509
1510        self.titlebar.update()
1511
1512        if self.pid == -1:
1513            self.vte.feed(_('Unable to start shell:') + shell)
1514            return -1
1515
1516    def prepare_url(self, urlmatch):
1517        """Prepare a URL from a VTE match"""
1518        url = urlmatch[0]
1519        match = urlmatch[1]
1520
1521        if match == self.matches['addr_only'] and url[0:3] == 'ftp':
1522            url = 'ftp://' + url
1523        elif match == self.matches['addr_only']:
1524            url = 'http://' + url
1525        elif match in list(self.matches.values()):
1526            # We have a match, but it's not a hard coded one, so it's a plugin
1527            try:
1528                registry = plugin.PluginRegistry()
1529                registry.load_plugins()
1530                plugins = registry.get_plugins_by_capability('url_handler')
1531
1532                for urlplugin in plugins:
1533                    if match == self.matches[urlplugin.handler_name]:
1534                        newurl = urlplugin.callback(url)
1535                        if newurl is not None:
1536                            dbg('Terminal::prepare_url: URL prepared by \
1537%s plugin' % urlplugin.handler_name)
1538                            url = newurl
1539                        break
1540            except Exception as ex:
1541                err('Exception occurred preparing URL: %s' % ex)
1542
1543        return url
1544
1545    def open_url(self, url, prepare=False):
1546        """Open a given URL, conditionally unpacking it from a VTE match"""
1547        if prepare:
1548            url = self.prepare_url(url)
1549        dbg('open_url: URL: %s (prepared: %s)' % (url, prepare))
1550
1551        if self.config['use_custom_url_handler']:
1552            dbg("Using custom URL handler: %s" %
1553                self.config['custom_url_handler'])
1554            try:
1555                subprocess.Popen([self.config['custom_url_handler'], url])
1556                return
1557            except:
1558                dbg('custom url handler did not work, falling back to defaults')
1559
1560        try:
1561            Gtk.show_uri(None, url, Gdk.CURRENT_TIME)
1562            return
1563        except:
1564            dbg('Gtk.show_uri did not work, falling through to xdg-open')
1565
1566        try:
1567            subprocess.Popen(["xdg-open", url])
1568        except:
1569            dbg('xdg-open did not work, falling back to webbrowser.open')
1570            import webbrowser
1571            webbrowser.open(url)
1572
1573    def paste_clipboard(self, primary=False):
1574        """Paste one of the two clipboards"""
1575        for term in self.terminator.get_target_terms(self):
1576            if primary:
1577                term.vte.paste_primary()
1578            else:
1579                term.vte.paste_clipboard()
1580        self.vte.grab_focus()
1581
1582    def feed(self, text):
1583        """Feed the supplied text to VTE"""
1584        self.vte.feed_child(text)
1585
1586    def zoom_in(self):
1587        """Increase the font size"""
1588        self.zoom_font(True)
1589
1590    def zoom_out(self):
1591        """Decrease the font size"""
1592        self.zoom_font(False)
1593
1594    def zoom_font(self, zoom_in):
1595        """Change the font size"""
1596        pangodesc = self.vte.get_font()
1597        fontsize = pangodesc.get_size()
1598
1599        if fontsize > Pango.SCALE and not zoom_in:
1600            fontsize -= Pango.SCALE
1601        elif zoom_in:
1602            fontsize += Pango.SCALE
1603
1604        pangodesc.set_size(fontsize)
1605        self.set_font(pangodesc)
1606        self.custom_font_size = fontsize
1607
1608    def zoom_orig(self):
1609        """Restore original font size"""
1610        if self.config['use_system_font']:
1611            font = self.config.get_system_mono_font()
1612        else:
1613            font = self.config['font']
1614        dbg("Terminal::zoom_orig: restoring font to: %s" % font)
1615        self.set_font(Pango.FontDescription(font))
1616        self.custom_font_size = None
1617
1618    def set_font(self, fontdesc):
1619        """Set the font we want in VTE"""
1620        self.vte.set_font(fontdesc)
1621
1622    def get_cursor_position(self):
1623        """Return the co-ordinates of our cursor"""
1624        # FIXME: THIS METHOD IS DEPRECATED AND UNUSED
1625        col, row = self.vte.get_cursor_position()
1626        width = self.vte.get_char_width()
1627        height = self.vte.get_char_height()
1628        return col * width, row * height
1629
1630    def get_font_size(self):
1631        """Return the width/height of our font"""
1632        return self.vte.get_char_width(), self.vte.get_char_height()
1633
1634    def get_size(self):
1635        """Return the column/rows of the terminal"""
1636        return self.vte.get_column_count(), self.vte.get_row_count()
1637
1638    def on_bell(self, widget):
1639        """Set the urgency hint/icon/flash for our window"""
1640        if self.config['urgent_bell']:
1641            window = self.get_toplevel()
1642            if window.is_toplevel():
1643                window.set_urgency_hint(True)
1644        if self.config['icon_bell']:
1645            self.titlebar.icon_bell()
1646        if self.config['visible_bell']:
1647            # Repurposed the code used for drag and drop overlay to provide a visual terminal flash
1648            alloc = widget.get_allocation()
1649
1650            if self.config['use_theme_colors']:
1651                color = self.vte.get_style_context().get_color(Gtk.StateType.NORMAL)  # VERIFY FOR GTK3 as above
1652            else:
1653                color = Gdk.RGBA()
1654                color.parse(self.config['foreground_color'])  # VERIFY FOR GTK3
1655
1656            coord = ((0, 0), (alloc.width, 0), (alloc.width, alloc.height), (0, alloc.height))
1657
1658            # here, we define some widget internal values
1659            widget._draw_data = { 'color': color, 'coord' : coord }
1660            # redraw by forcing an event
1661            connec = widget.connect_after('draw', self.on_draw)
1662            widget.queue_draw_area(0, 0, alloc.width, alloc.height)
1663            widget.get_window().process_updates(True)
1664            # finaly reset the values
1665            widget.disconnect(connec)
1666            widget._draw_data = None
1667
1668            # Add timeout to clean up display
1669            GObject.timeout_add(100, self.on_bell_cleanup, widget, alloc)
1670
1671    def on_bell_cleanup(self, widget, alloc):
1672        """Queue a redraw to clear the visual flash overlay"""
1673        widget.queue_draw_area(0, 0, alloc.width, alloc.height)
1674        widget.get_window().process_updates(True)
1675        return False
1676
1677    def describe_layout(self, count, parent, global_layout, child_order):
1678        """Describe our layout"""
1679        layout = {'type': 'Terminal', 'parent': parent, 'order': child_order}
1680        if self.group:
1681            layout['group'] = self.group
1682        profile = self.get_profile()
1683        if layout != "default":
1684            # There's no point explicitly noting default profiles
1685            layout['profile'] = profile
1686        title = self.titlebar.get_custom_string()
1687        if title:
1688            layout['title'] = title
1689        layout['uuid'] = self.uuid
1690        name = 'terminal%d' % count
1691        count = count + 1
1692        global_layout[name] = layout
1693        return count
1694
1695    def create_layout(self, layout):
1696        """Apply our layout"""
1697        dbg('Setting layout')
1698        if 'command' in layout and layout['command'] != '':
1699            self.layout_command = layout['command']
1700        if 'profile' in layout and layout['profile'] != '':
1701            if layout['profile'] in self.config.list_profiles():
1702                self.set_profile(self, layout['profile'])
1703        if 'group' in layout and layout['group'] != '':
1704            # This doesn't need/use self.titlebar, but it's safer than sending
1705            # None
1706            self.really_create_group(self.titlebar, layout['group'])
1707        if 'title' in layout and layout['title'] != '':
1708            self.titlebar.set_custom_string(layout['title'])
1709        if 'directory' in layout and layout['directory'] != '':
1710            self.directory = layout['directory']
1711        if 'uuid' in layout and layout['uuid'] != '':
1712            self.uuid = make_uuid(layout['uuid'])
1713
1714    def scroll_by_page(self, pages):
1715        """Scroll up or down in pages"""
1716        amount = pages * self.vte.get_vadjustment().get_page_increment()
1717        self.scroll_by(int(amount))
1718
1719    def scroll_by_line(self, lines):
1720        """Scroll up or down in lines"""
1721        amount = lines * self.vte.get_vadjustment().get_step_increment()
1722        self.scroll_by(int(amount))
1723
1724    def scroll_by(self, amount):
1725        """Scroll up or down by an amount of lines"""
1726        adjustment = self.vte.get_vadjustment()
1727        bottom = adjustment.get_upper() - adjustment.get_page_size()
1728        value = adjustment.get_value() + amount
1729        adjustment.set_value(min(value, bottom))
1730
1731    def get_allocation(self):
1732        """Get a real allocation which includes the bloody x and y coordinates
1733        (grumble, grumble) """
1734        alloc = super(Terminal, self).get_allocation()
1735        rv = self.translate_coordinates(self.get_toplevel(), 0, 0)
1736        if rv:
1737            alloc.x, alloc.y = rv
1738        return alloc
1739
1740    # There now begins a great list of keyboard event handlers
1741    def key_zoom_in(self):
1742        self.zoom_in()
1743
1744    def key_next_profile(self):
1745        self.switch_to_next_profile()
1746
1747    def key_previous_profile(self):
1748        self.switch_to_previous_profile()
1749
1750    def key_zoom_out(self):
1751        self.zoom_out()
1752
1753    def key_copy(self):
1754        self.vte.copy_clipboard()
1755        if self.config['clear_select_on_copy']:
1756            self.vte.unselect_all()
1757
1758    def key_paste(self):
1759        self.paste_clipboard()
1760
1761    def key_toggle_scrollbar(self):
1762        self.do_scrollbar_toggle()
1763
1764    def key_zoom_normal(self):
1765        self.zoom_orig ()
1766
1767    def key_search(self):
1768        self.searchbar.start_search()
1769
1770    # bindings that should be moved to Terminator as they all just call
1771    # a function of Terminator. It would be cleaner if TerminatorTerm
1772    # has absolutely no reference to Terminator.
1773    # N (next) - P (previous) - O (horizontal) - E (vertical) - W (close)
1774    def key_zoom_in_all(self):
1775        self.terminator.zoom_in_all()
1776
1777    def key_zoom_out_all(self):
1778        self.terminator.zoom_out_all()
1779
1780    def key_zoom_normal_all(self):
1781        self.terminator.zoom_orig_all()
1782
1783    def key_cycle_next(self):
1784        self.key_go_next()
1785
1786    def key_cycle_prev(self):
1787        self.key_go_prev()
1788
1789    def key_go_next(self):
1790        self.emit('navigate', 'next')
1791
1792    def key_go_prev(self):
1793        self.emit('navigate', 'prev')
1794
1795    def key_go_up(self):
1796        self.emit('navigate', 'up')
1797
1798    def key_go_down(self):
1799        self.emit('navigate', 'down')
1800
1801    def key_go_left(self):
1802        self.emit('navigate', 'left')
1803
1804    def key_go_right(self):
1805        self.emit('navigate', 'right')
1806
1807    def key_split_horiz(self):
1808        self.emit('split-horiz', self.get_cwd())
1809
1810    def key_split_vert(self):
1811        self.emit('split-vert', self.get_cwd())
1812
1813    def key_rotate_cw(self):
1814        self.emit('rotate-cw')
1815
1816    def key_rotate_ccw(self):
1817        self.emit('rotate-ccw')
1818
1819    def key_close_term(self):
1820        self.close()
1821
1822    def key_resize_up(self):
1823        self.emit('resize-term', 'up')
1824
1825    def key_resize_down(self):
1826        self.emit('resize-term', 'down')
1827
1828    def key_resize_left(self):
1829        self.emit('resize-term', 'left')
1830
1831    def key_resize_right(self):
1832        self.emit('resize-term', 'right')
1833
1834    def key_move_tab_right(self):
1835        self.emit('move-tab', 'right')
1836
1837    def key_move_tab_left(self):
1838        self.emit('move-tab', 'left')
1839
1840    def key_toggle_zoom(self):
1841        if self.is_zoomed():
1842            self.unzoom()
1843        else:
1844            self.maximise()
1845
1846    def key_scaled_zoom(self):
1847        if self.is_zoomed():
1848            self.unzoom()
1849        else:
1850            self.zoom()
1851
1852    def key_next_tab(self):
1853        self.emit('tab-change', -1)
1854
1855    def key_prev_tab(self):
1856        self.emit('tab-change', -2)
1857
1858    def key_switch_to_tab_1(self):
1859        self.emit('tab-change', 0)
1860
1861    def key_switch_to_tab_2(self):
1862        self.emit('tab-change', 1)
1863
1864    def key_switch_to_tab_3(self):
1865        self.emit('tab-change', 2)
1866
1867    def key_switch_to_tab_4(self):
1868        self.emit('tab-change', 3)
1869
1870    def key_switch_to_tab_5(self):
1871        self.emit('tab-change', 4)
1872
1873    def key_switch_to_tab_6(self):
1874        self.emit('tab-change', 5)
1875
1876    def key_switch_to_tab_7(self):
1877        self.emit('tab-change', 6)
1878
1879    def key_switch_to_tab_8(self):
1880        self.emit('tab-change', 7)
1881
1882    def key_switch_to_tab_9(self):
1883        self.emit('tab-change', 8)
1884
1885    def key_switch_to_tab_10(self):
1886        self.emit('tab-change', 9)
1887
1888    def key_reset(self):
1889        self.vte.reset (True, False)
1890
1891    def key_reset_clear(self):
1892        self.vte.reset (True, True)
1893
1894    def key_create_group(self):
1895        self.titlebar.create_group()
1896
1897    def key_group_all(self):
1898        self.emit('group-all')
1899
1900    def key_group_all_toggle(self):
1901        self.emit('group-all-toggle')
1902
1903    def key_ungroup_all(self):
1904        self.emit('ungroup-all')
1905
1906    def key_group_tab(self):
1907        self.emit('group-tab')
1908
1909    def key_group_tab_toggle(self):
1910        self.emit('group-tab-toggle')
1911
1912    def key_ungroup_tab(self):
1913        self.emit('ungroup-tab')
1914
1915    def key_new_window(self):
1916        self.terminator.new_window(self.get_cwd(), self.get_profile())
1917
1918    def key_new_tab(self):
1919        self.get_toplevel().tab_new(self)
1920
1921    def key_new_terminator(self):
1922        spawn_new_terminator(self.origcwd, ['-u'])
1923
1924    def key_broadcast_off(self):
1925        self.set_groupsend(None, self.terminator.groupsend_type['off'])
1926        self.terminator.focus_changed(self)
1927
1928    def key_broadcast_group(self):
1929        self.set_groupsend(None, self.terminator.groupsend_type['group'])
1930        self.terminator.focus_changed(self)
1931
1932    def key_broadcast_all(self):
1933        self.set_groupsend(None, self.terminator.groupsend_type['all'])
1934        self.terminator.focus_changed(self)
1935
1936    def key_insert_number(self):
1937        self.emit('enumerate', False)
1938
1939    def key_insert_padded(self):
1940        self.emit('enumerate', True)
1941
1942    def key_edit_window_title(self):
1943        window = self.get_toplevel()
1944        dialog = Gtk.Dialog(_('Rename Window'), window,
1945                            Gtk.DialogFlags.MODAL,
1946                            (Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
1947                             Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT))
1948        dialog.set_default_response(Gtk.ResponseType.ACCEPT)
1949        dialog.set_resizable(False)
1950        dialog.set_border_width(8)
1951
1952        label = Gtk.Label(label=_('Enter a new title for the Terminator window...'))
1953        name = Gtk.Entry()
1954        name.set_activates_default(True)
1955        if window.title.text != self.vte.get_window_title():
1956            name.set_text(self.get_toplevel().title.text)
1957
1958        dialog.vbox.pack_start(label, False, False, 6)
1959        dialog.vbox.pack_start(name, False, False, 6)
1960
1961        dialog.show_all()
1962        res = dialog.run()
1963        if res == Gtk.ResponseType.ACCEPT:
1964            if name.get_text():
1965                window.title.force_title(None)
1966                window.title.force_title(name.get_text())
1967            else:
1968                window.title.force_title(None)
1969        dialog.destroy()
1970        return
1971
1972    def key_edit_tab_title(self):
1973        window = self.get_toplevel()
1974        if not window.is_child_notebook():
1975            return
1976
1977        notebook = window.get_children()[0]
1978        n_page = notebook.get_current_page()
1979        page = notebook.get_nth_page(n_page)
1980        label = notebook.get_tab_label(page)
1981        label.edit()
1982
1983    def key_edit_terminal_title(self):
1984        self.titlebar.label.edit()
1985
1986    def key_layout_launcher(self):
1987        LAYOUTLAUNCHER=LayoutLauncher()
1988
1989    def key_page_up(self):
1990        self.scroll_by_page(-1)
1991
1992    def key_page_down(self):
1993        self.scroll_by_page(1)
1994
1995    def key_page_up_half(self):
1996        self.scroll_by_page(-0.5)
1997
1998    def key_page_down_half(self):
1999        self.scroll_by_page(0.5)
2000
2001    def key_line_up(self):
2002        self.scroll_by_line(-1)
2003
2004    def key_line_down(self):
2005        self.scroll_by_line(1)
2006
2007    def key_preferences(self):
2008        PrefsEditor(self)
2009
2010    def key_help(self):
2011        manual_index_page = manual_lookup()
2012        if manual_index_page:
2013            self.open_url(manual_index_page)
2014
2015# End key events
2016
2017
2018GObject.type_register(Terminal)
2019# vim: set expandtab ts=4 sw=4:
2020