def __init__(self): self.terminator = Terminator() self.terminator.register_launcher_window(self) self.config = config.Config() self.config.base.reload() self.builder = Gtk.Builder() try: # Figure out where our library is on-disk so we can open our UI (head, _tail) = os.path.split(config.__file__) librarypath = os.path.join(head, 'layoutlauncher.glade') with open(librarypath, mode='rt') as f: gladedata = f.read() except Exception as ex: print("Failed to find layoutlauncher.glade") print(ex) return self.builder.add_from_string(gladedata) self.window = self.builder.get_object('layoutlauncherwin') icon_theme = Gtk.IconTheme.get_default() if icon_theme.lookup_icon('terminator-layout', 48, 0): self.window.set_icon_name('terminator-layout') else: dbg('Unable to load Terminator layout launcher icon') icon = self.window.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON) self.window.set_icon(icon) self.builder.connect_signals(self) self.window.connect('destroy', self.on_destroy_event) self.window.show_all() self.layouttreeview = self.builder.get_object('layoutlist') self.layouttreestore = self.builder.get_object('layoutstore') self.update_layouts()
def __init__(self, window): """Class initialiser""" if isinstance(window.get_child(), Gtk.Notebook): err('There is already a Notebook at the top of this window') raise(ValueError) Container.__init__(self) GObject.GObject.__init__(self) self.terminator = Terminator() self.window = window GObject.type_register(Notebook) self.register_signals(Notebook) self.connect('switch-page', self.deferred_on_tab_switch) self.connect('scroll-event', self.on_scroll_event) self.configure() child = window.get_child() window.remove(child) window.add(self) window_last_active_term = window.last_active_term self.newtab(widget=child) if window_last_active_term: self.set_last_active_term(window_last_active_term) window.last_active_term = None self.show_all()
def __init__(self): self.config = Config().plugin_get_config(self.__class__.__name__) self.watches = {} self.images = self.load_images() dbg(self.images) global_config = Terminator().config # Create ssh profiles self.ssh_profiles = {} for f in glob.glob(self.images): dbg(f) name = os.path.splitext(os.path.basename(f))[0] self.ssh_profiles[name] = f global_config.add_profile(name) profile = global_config.base.profiles[name] profile["background_darkness"] = 0.88 profile["background_image"] = f profile["background_type"] = "image" dbg(repr(self.ssh_profiles)) for v in global_config.list_profiles(): dbg(repr(v)) self.failback_profile = self.get_failback() self.last_profile = self.failback_profile self.load_patterns() self.update_watches()
def __init__(self, terminal): """Class initialiser""" GObject.GObject.__init__(self) self.terminator = Terminator() self.terminal = terminal self.config = self.terminal.config self.label = EditableLabel() self.label.connect('edit-done', self.on_edit_done) self.ebox = Gtk.EventBox() grouphbox = Gtk.HBox() self.grouplabel = Gtk.Label(ellipsize='end') self.groupicon = Gtk.Image() self.bellicon = Gtk.Image() self.bellicon.set_no_show_all(True) self.groupentry = Gtk.Entry() self.groupentry.set_no_show_all(True) self.groupentry.connect('focus-out-event', self.groupentry_cancel) self.groupentry.connect('activate', self.groupentry_activate) self.groupentry.connect('key-press-event', self.groupentry_keypress) groupsend_type = self.terminator.groupsend_type if self.terminator.groupsend == groupsend_type['all']: icon_name = 'all' elif self.terminator.groupsend == groupsend_type['group']: icon_name = 'group' elif self.terminator.groupsend == groupsend_type['off']: icon_name = 'off' self.set_from_icon_name('_active_broadcast_%s' % icon_name, Gtk.IconSize.MENU) grouphbox.pack_start(self.groupicon, False, True, 2) grouphbox.pack_start(self.grouplabel, False, True, 2) grouphbox.pack_start(self.groupentry, False, True, 2) self.ebox.add(grouphbox) self.ebox.show_all() self.bellicon.set_from_icon_name('terminal-bell', Gtk.IconSize.MENU) viewport = Gtk.Viewport(hscroll_policy='natural') viewport.add(self.label) hbox = Gtk.HBox() hbox.pack_start(self.ebox, False, True, 0) hbox.pack_start(Gtk.VSeparator(), False, True, 0) hbox.pack_start(viewport, True, True, 0) hbox.pack_end(self.bellicon, False, False, 2) self.add(hbox) hbox.show_all() self.set_no_show_all(True) self.show() self.connect('button-press-event', self.on_clicked)
class JumpUp(plugin.MenuItem): capabilities = ['terminal_menu'] last_cursor_pos = 0 def __init__(self): plugin.MenuItem.__init__(self) self.entry = Terminator().windows[0] self.entry.connect('key-release-event', self.onKeyRelease) self.entry.connect('key-press-event', self.onKeyPress) def callback(self, menuitems, menu, terminal): item = Gtk.MenuItem(_('JumpUp!')) item.connect("activate", self.jumpUp) menuitems.append(item) def jumpUp(self, widget): t = Terminator().last_focused_term t.scrollbar_jump(self.last_cursor_pos) def onKeyRelease(self, widget, event): if float(APP_VERSION) <= 0.98: if (event.state & Gtk.gdk.MOD1_MASK == Gtk.gdk.MOD1_MASK) and ( event.keyval == 74 or event.keyval == 106): # Alt+J or Alt+j self.jumpUp(widget) else: if (event.state & Gdk.ModifierType.MOD1_MASK == Gdk.ModifierType.MOD1_MASK) and ( event.keyval == 74 or event.keyval == 106): # Alt+J or Alt+j self.jumpUp(widget) def onKeyPress(self, widget, event): if event.keyval == 65293: t = Terminator().last_focused_term col, row = t.get_vte().get_cursor_position() if float(APP_VERSION) <= 0.98: content = t.get_vte().get_text_range( row - 3, 0, row, col, lambda *a: True).split("\n") if re.match("\w+@\w+", content[-2].split(":") [0]) and not content[-2].endswith("$ "): self.last_cursor_pos = row else: content = t.get_vte().get_text_range( row - 3, 0, row, col, lambda *a: True)[0].split("\n") if re.match("\w+@\w+", content[-1].split(":") [0]) and not content[-1].endswith("$ "): self.last_cursor_pos = row
def onKeyPress(self, widget, event): if event.keyval == 65293: t = Terminator().last_focused_term col, row = t.get_vte().get_cursor_position() if float(APP_VERSION) <= 0.98: content = t.get_vte().get_text_range( row - 3, 0, row, col, lambda *a: True).split("\n") if re.match("\w+@\w+", content[-2].split(":") [0]) and not content[-2].endswith("$ "): self.last_cursor_pos = row else: content = t.get_vte().get_text_range( row - 3, 0, row, col, lambda *a: True)[0].split("\n") if re.match("\w+@\w+", content[-1].split(":") [0]) and not content[-1].endswith("$ "): self.last_cursor_pos = row
def multiKill(self, widget): for t in Terminator().terminals: try: t.vte.feed_child("\x03", len("\x03")) except Exception, ex: err('\033[1;31mMultikill failed: %s\033[0m' % ex) pass
def multiSource(self, widget): for t in Terminator().terminals: try: command = "source ~/.bashrc\n" t.vte.feed_child(command, len(command)) except Exception, ex: err('\033[1;31mMultisource failed: %s\033[0m' % ex) pass
def unload(self): """Handle being removed""" if not self.match: err('unload called without self.handler_name being set') return terminator = Terminator() for terminal in terminator.terminals: terminal.match_remove(self.handler_name)
def prepare_attributes(self): """Ensure we are populated""" if not self.bus_name: dbg('Checking for bus name availability: %s' % BUS_NAME) bus = dbus.SessionBus() proxy = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') flags = 1 | 4 # allow replacement | do not queue if not proxy.RequestName(BUS_NAME, dbus.UInt32(flags)) in (1, 4): dbg('bus name unavailable: %s' % BUS_NAME) raise dbus.exceptions.DBusException( "Couldn't get DBus name %s: Name exists" % BUS_NAME) self.bus_name = dbus.service.BusName(BUS_NAME, bus=dbus.SessionBus()) if not self.bus_path: self.bus_path = BUS_PATH if not self.terminator: self.terminator = Terminator()
def __init__(self): """Class initialiser""" self.terminator = Terminator() self.maker = Factory() Container.__init__(self) self.signals.append({'name' : 'resize-term', 'flags' : GObject.SignalFlags.RUN_LAST, 'return_type': None, 'param_types': (GObject.TYPE_STRING,)})
def __init__(self): """ Class initialiser """ self.terminator = Terminator() self.terminator.register_window(self) Container.__init__(self) GObject.GObject.__init__(self) GObject.type_register(Window) self.register_signals(Window) self.get_style_context().add_class("terminator-terminal-window") # self.set_property('allow-shrink', True) # FIXME FOR GTK3, or do we need this actually? icon_to_apply = '' self.register_callbacks() self.apply_config() self.title = WindowTitle(self) self.title.update() self.preventHide = False options = self.config.options_get() if options: if options.forcedtitle: self.title.force_title(options.forcedtitle) if options.role: self.set_role(options.role) if options.forcedicon is not None: icon_to_apply = options.forcedicon if options.geometry: if not self.parse_geometry(options.geometry): err('Window::__init__: Unable to parse geometry: %s' % options.geometry) self.apply_icon(icon_to_apply) self.pending_set_rough_geometry_hint = False
class MultiKill(plugin.MenuItem): capabilities = ['terminal_menu'] def __init__(self): plugin.MenuItem.__init__(self) self.entry = Terminator().windows[0] self.entry.connect('key-release-event', self.onKeyPress) def callback(self, menuitems, menu, terminal): item = Gtk.MenuItem(_('MultiKill!')) item.connect("activate", self.multiKill) menuitems.append(item) def multiKill(self, widget): for t in Terminator().terminals: try: t.vte.feed_child("\x03", len("\x03")) except Exception, ex: err('\033[1;31mMultikill failed: %s\033[0m' % ex) pass
class MultiSource(plugin.MenuItem): capabilities = ['terminal_menu'] def __init__(self): plugin.MenuItem.__init__(self) self.entry = Terminator().windows[0] self.entry.connect('key-release-event', self.onKeyPress) def callback(self, menuitems, menu, terminal): item = Gtk.MenuItem(_('MultiSource!')) item.connect("activate", self.multiSource) menuitems.append(item) def multiSource(self, widget): for t in Terminator().terminals: try: command = "source ~/.bashrc\n" t.vte.feed_child(command, len(command)) except Exception, ex: err('\033[1;31mMultisource failed: %s\033[0m' % ex) pass
def get_terminal_container(self, terminal, container=None): terminator = Terminator() if not container: for window in terminator.windows: owner = self.get_terminal_container(terminal, window) if owner: return owner else: for child in container.get_children(): if isinstance(child, Terminal) and child == terminal: return container if isinstance(child, Container): owner = self.get_terminal_container(terminal, child) if owner: return owner
def update_watched(self): """Updates the list of watched terminals""" new_watched = set() for term in Terminator().terminals: new_watched.add(term) if not term in self.watched: vte = term.get_vte() term.connect('focus-out', self.update_watched_delayed, None) vte.connect('focus-out-event', self.update_watched_delayed, None) notify = vte.connect('notification-received', self.notification_received, term) else: notify = None self.watched = new_watched
def __init__(self, title, notebook): """Class initialiser""" GObject.GObject.__init__(self) self.notebook = notebook self.terminator = Terminator() self.config = Config() self.label = EditableLabel(title) self.update_angle() self.pack_start(self.label, True, True, 0) self.update_button() self.show_all()
def wrapcloseterm(self, widget): """A child terminal has closed, so this container must die""" dbg('Paned::wrapcloseterm: Called on %s' % widget) if self.closeterm(widget): # At this point we only have one child, which is the surviving term sibling = self.children[0] first_term_sibling = sibling cur_tabnum = None focus_sibling = True if self.get_toplevel().is_child_notebook(): notebook = self.get_toplevel().get_children()[0] cur_tabnum = notebook.get_current_page() tabnum = notebook.page_num_descendant(self) nth_page = notebook.get_nth_page(tabnum) exiting_term_was_last_active = ( notebook.last_active_term[nth_page] == widget.uuid) if exiting_term_was_last_active: first_term_sibling = enumerate_descendants(self)[1][0] notebook.set_last_active_term(first_term_sibling.uuid) notebook.clean_last_active_term() self.get_toplevel().last_active_term = None if cur_tabnum != tabnum: focus_sibling = False elif self.get_toplevel().last_active_term != widget.uuid: focus_sibling = False self.remove(sibling) metadata = None parent = self.get_parent() metadata = parent.get_child_metadata(self) dbg('metadata obtained for %s: %s' % (self, metadata)) parent.remove(self) self.cnxids.remove_all() parent.add(sibling, metadata) if cur_tabnum: notebook.set_current_page(cur_tabnum) if focus_sibling: first_term_sibling.grab_focus() elif not sibling.get_toplevel().is_child_notebook(): Terminator().find_terminal_by_uuid( sibling.get_toplevel().last_active_term.urn).grab_focus() else: dbg("Paned::wrapcloseterm: self.closeterm failed")
def load_profile_mappings(self): """ get profile mapping as declared with profile_patterns config key and append profile names as patterns profiles are saved as compiled patterns in an ordered dictionary so patterns mappings are parsed prior to profiles """ if self.config and 'profile_patterns' in self.config: # we have to parse and create dict since configuration doesnt allow this for pre in self.config['profile_patterns']: kv = pre.split(":") if len(kv) == 2: # config recovered as ugly string with leading and trailing quotes removed, must remove ' and " dbg("profile mapping : %s -> %s" % (kv[0].replace("'", "").replace( '"', ''), kv[1].replace("'", "").replace('"', ''))) self.profile_mappings[re.compile(kv[0].replace( "'", "").replace('"', ''))] = kv[1].replace("'", "").replace('"', '') # we load profile name as plain regex for v in Terminator().config.list_profiles(): self.profile_mappings[re.compile(v)] = v
def __init__(self): plugin.MenuItem.__init__(self) self.entry = Terminator().windows[0] self.entry.connect('key-release-event', self.onKeyRelease) self.entry.connect('key-press-event', self.onKeyPress)
class LayoutLauncher: """Class implementing the various parts of the preferences editor""" terminator = None config = None registry = None plugins = None keybindings = None window = None builder = None layouttreeview = None layouttreestore = None def __init__(self): self.terminator = Terminator() self.terminator.register_launcher_window(self) self.config = config.Config() self.config.base.reload() self.builder = Gtk.Builder() try: # Figure out where our library is on-disk so we can open our UI (head, _tail) = os.path.split(config.__file__) librarypath = os.path.join(head, 'layoutlauncher.glade') with open(librarypath, mode='rt') as f: gladedata = f.read() except Exception as ex: print('Failed to find layoutlauncher.glade') print(ex) return self.builder.add_from_string(gladedata) self.window = self.builder.get_object('layoutlauncherwin') icon_theme = Gtk.IconTheme.get_default() if icon_theme.lookup_icon('terminator-layout', 48, 0): self.window.set_icon_name('terminator-layout') else: dbg('Unable to load Terminator layout launcher icon') icon = self.window.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON) self.window.set_icon(icon) self.builder.connect_signals(self) self.window.connect('destroy', self.on_destroy_event) self.window.show_all() self.layouttreeview = self.builder.get_object('layoutlist') self.layouttreestore = self.builder.get_object('layoutstore') self.update_layouts() def on_destroy_event(self, widget, data=None): """Handle window destruction""" dbg('destroying self') self.terminator.deregister_launcher_window(self) self.window.destroy() del self.window def update_layouts(self): """Update the contents of the layout""" self.layouttreestore.clear() layouts = self.config.list_layouts() for layout in sorted(layouts, key=lambda x, y: cmp(x.lower(), y.lower())): if layout != "default": self.layouttreestore.append([layout]) else: self.layouttreestore.prepend([layout]) def on_launchbutton_clicked(self, widget): """Handle button click""" self.launch_layout() def on_row_activated(self, widget, path, view_column): """Handle item double-click and return""" self.launch_layout() def launch_layout(self): """Launch the selected layout as new instance""" dbg('We have takeoff!') selection = self.layouttreeview.get_selection() (listmodel, rowiter) = selection.get_selected() if not rowiter: # Something is wrong, just jump to the first item in the list selection.select_iter(self.layouttreestore.get_iter_first()) (listmodel, rowiter) = selection.get_selected() layout = listmodel.get_value(rowiter, 0) dbg(f'Clicked for {layout}') spawn_new_terminator(self.terminator.origcwd, ['-u', '-l', layout])
def __init__(self, terminal): """Class initialiser""" self.terminal = terminal self.terminator = Terminator() self.config = Config()
if OPTIONS.nodbus: dbg('dbus disabled by command line') raise ImportError from terminatorlib import ipc try: dbus_service = ipc.DBusService() except ipc.DBusException: dbg('Unable to become master process, requesting a new window') ipc.new_window(OPTIONS.layout) sys.exit() except ImportError: dbg('dbus not imported') pass MAKER = Factory() TERMINATOR = Terminator() TERMINATOR.set_origcwd(ORIGCWD) TERMINATOR.set_dbus_data(dbus_service) TERMINATOR.reconfigure() try: dbg('Creating a terminal with layout: %s' % OPTIONS.layout) TERMINATOR.create_layout(OPTIONS.layout) except (KeyError,ValueError), ex: err('layout creation failed, creating a window ("%s")' % ex) TERMINATOR.new_window() TERMINATOR.layout_done() if OPTIONS.debug > 2: import terminatorlib.debugserver as debugserver # pylint: disable-msg=W0611 import threading
def __init__(self): plugin.Plugin.__init__(self) self.terminator = Terminator() gobject.timeout_add_seconds(5, self.update_clock)
class Window(Container, Gtk.Window): # pylint: disable-msg=R0904 """ Class implementing a top-level Terminator window """ terminator = None title = None isfullscreen = None ismaximised = None hidebound = None hidefunc = None losefocus_time = 0 position = None ignore_startup_show = None set_pos_by_ratio = None last_active_term = None preventHide = None zoom_data = None term_zoomed = False __gproperties__ = { 'term_zoomed': (GObject.TYPE_BOOLEAN, 'terminal zoomed', 'whether the terminal is zoomed', False, GObject.PARAM_READWRITE) } def __init__(self): """ Class initialiser """ self.terminator = Terminator() self.terminator.register_window(self) Container.__init__(self) GObject.GObject.__init__(self) GObject.type_register(Window) self.register_signals(Window) self.get_style_context().add_class("terminator-terminal-window") # self.set_property('allow-shrink', True) # FIXME FOR GTK3, or do we need this actually? icon_to_apply = '' self.register_callbacks() self.apply_config() self.title = WindowTitle(self) self.title.update() self.preventHide = False options = self.config.options_get() if options: if options.forcedtitle: self.title.force_title(options.forcedtitle) if options.role: self.set_role(options.role) if options.forcedicon is not None: icon_to_apply = options.forcedicon if options.geometry: if not self.parse_geometry(options.geometry): err('Window::__init__: Unable to parse geometry: %s' % options.geometry) self.apply_icon(icon_to_apply) self.pending_set_rough_geometry_hint = False def do_get_property(self, prop): """Handle gobject getting a property""" if prop.name in ['term_zoomed', 'term-zoomed']: return self.term_zoomed else: raise AttributeError('unknown property %s' % prop.name) def do_set_property(self, prop, value): """Handle gobject setting a property""" if prop.name in ['term_zoomed', 'term-zoomed']: self.term_zoomed = value else: raise AttributeError('unknown property %s' % prop.name) def register_callbacks(self): """ Connect the GTK+ signals we care about """ self.connect('key-press-event', self.on_key_press) self.connect('button-press-event', self.on_button_press) self.connect('delete_event', self.on_delete_event) self.connect('destroy', self.on_destroy_event) self.connect('window-state-event', self.on_window_state_changed) self.connect('focus-out-event', self.on_focus_out) self.connect('focus-in-event', self.on_focus_in) # Attempt to grab a global hotkey for hiding the window. # If we fail, we'll never hide the window, iconifying instead. if self.config['keybindings']['hide_window'] != None: if display_manager() == 'X11': try: self.hidebound = Keybinder.bind( self.config['keybindings']['hide_window'].replace( '<Shift>', ''), self.on_hide_window) except (KeyError, NameError): pass if not self.hidebound: err('Unable to bind hide_window key, another instance/window has it.' ) self.hidefunc = self.iconify else: self.hidefunc = self.hide def apply_config(self): """Apply various configuration options""" options = self.config.options_get() maximise = self.config['window_state'] == 'maximise' fullscreen = self.config['window_state'] == 'fullscreen' hidden = self.config['window_state'] == 'hidden' borderless = self.config['borderless'] skiptaskbar = self.config['hide_from_taskbar'] alwaysontop = self.config['always_on_top'] sticky = self.config['sticky'] if options: if options.maximise: maximise = True if options.fullscreen: fullscreen = True if options.hidden: hidden = True if options.borderless: borderless = True self.set_fullscreen(fullscreen) self.set_maximised(maximise) self.set_borderless(borderless) self.set_always_on_top(alwaysontop) self.set_real_transparency() self.set_sticky(sticky) if self.hidebound: self.set_hidden(hidden) self.set_skip_taskbar_hint(skiptaskbar) else: self.set_iconified(hidden) def apply_icon(self, requested_icon): """Set the window icon""" icon_theme = Gtk.IconTheme.get_default() icon_name_list = [APP_NAME] # disable self.wmclass_name, n/a in GTK3 if requested_icon: try: self.set_icon_from_file(requested_icon) return except (NameError, GObject.GError): dbg('Unable to load %s icon as file' % (repr(requested_icon))) icon_name_list.insert(0, requested_icon) for icon_name in icon_name_list: # Test if the icon is available first if icon_theme.lookup_icon(icon_name, 48, 0): self.set_icon_name(icon_name) return # Success! We're done. else: dbg('Unable to load %s icon' % (icon_name)) icon = self.render_icon(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.BUTTON) self.set_icon(icon) def on_key_press(self, window, event): """Handle a keyboard event""" maker = Factory() self.set_urgency_hint(False) mapping = self.terminator.keybindings.lookup(event) if mapping: dbg('Window::on_key_press: looked up %r' % mapping) if mapping == 'full_screen': self.set_fullscreen(not self.isfullscreen) elif mapping == 'close_window': if not self.on_delete_event( window, Gdk.Event.new(Gdk.EventType.DELETE)): self.on_destroy_event(window, Gdk.Event.new(Gdk.EventType.DESTROY)) else: return False return True def on_button_press(self, window, event): """Handle a mouse button event. Mainly this is just a clean way to cancel any urgency hints that are set.""" self.set_urgency_hint(False) return False def on_focus_out(self, window, event): """Focus has left the window""" for terminal in self.get_visible_terminals(): terminal.on_window_focus_out() self.losefocus_time = time.time() if self.preventHide: self.preventHide = False else: if self.config['hide_on_lose_focus'] and self.get_property( 'visible'): self.position = self.get_position() self.hidefunc() def on_focus_in(self, window, event): """Focus has entered the window""" self.set_urgency_hint(False) if not self.terminator.doing_layout: self.terminator.last_active_window = self.uuid # FIXME: Cause the terminal titlebars to update here def is_child_notebook(self): """Returns True if this Window's child is a Notebook""" maker = Factory() return maker.isinstance(self.get_child(), 'Notebook') def tab_new(self, widget=None, debugtab=False, _param1=None, _param2=None): """Make a new tab""" cwd = None profile = None if self.get_property('term_zoomed') == True: err("You can't create a tab while a terminal is maximised/zoomed") return if widget: cwd = widget.get_cwd() profile = widget.get_profile() maker = Factory() if not self.is_child_notebook(): dbg('Making a new Notebook') notebook = maker.make('Notebook', window=self) self.show() self.present() return self.get_child().newtab(debugtab, cwd=cwd, profile=profile) def on_delete_event(self, window, event, data=None): """Handle a window close request""" maker = Factory() if maker.isinstance(self.get_child(), 'Terminal'): if self.get_property('term_zoomed') == True: return self.confirm_close(window, _('window')) else: dbg('Window::on_delete_event: Only one child, closing is fine') return False elif maker.isinstance(self.get_child(), 'Container'): return self.confirm_close(window, _('window')) else: dbg('unknown child: %s' % self.get_child()) def confirm_close(self, window, type): """Display a confirmation dialog when the user is closing multiple terminals in one window""" return not (self.construct_confirm_close(window, type) == Gtk.ResponseType.ACCEPT) def on_destroy_event(self, widget, data=None): """Handle window destruction""" dbg('destroying self') for terminal in self.get_visible_terminals(): terminal.close() self.cnxids.remove_all() self.terminator.deregister_window(self) self.destroy() del (self) def on_hide_window(self, data=None): """Handle a request to hide/show the window""" if not self.get_property('visible'): # Don't show if window has just been hidden because of # lost focus if (time.time() - self.losefocus_time < 0.1) and \ self.config['hide_on_lose_focus']: return if self.position: self.move(self.position[0], self.position[1]) self.show() self.grab_focus() try: t = GdkX11.x11_get_server_time(self.get_window()) except (TypeError, AttributeError): t = 0 self.get_window().focus(t) else: self.position = self.get_position() self.hidefunc() # pylint: disable-msg=W0613 def on_window_state_changed(self, window, event): """Handle the state of the window changing""" self.isfullscreen = bool(event.new_window_state & Gdk.WindowState.FULLSCREEN) self.ismaximised = bool(event.new_window_state & Gdk.WindowState.MAXIMIZED) dbg('Window::on_window_state_changed: fullscreen=%s, maximised=%s' % (self.isfullscreen, self.ismaximised)) return False def set_maximised(self, value): """Set the maximised state of the window from the supplied value""" if value == True: self.maximize() else: self.unmaximize() def set_fullscreen(self, value): """Set the fullscreen state of the window from the supplied value""" if value == True: self.fullscreen() else: self.unfullscreen() def set_borderless(self, value): """Set the state of the window border from the supplied value""" self.set_decorated(not value) def set_hidden(self, value): """Set the visibility of the window from the supplied value""" if value == True: self.ignore_startup_show = True else: self.ignore_startup_show = False def set_iconified(self, value): """Set the minimised state of the window from the supplied value""" if value == True: self.iconify() def set_always_on_top(self, value): """Set the always on top window hint from the supplied value""" self.set_keep_above(value) def set_sticky(self, value): """Set the sticky hint from the supplied value""" if value == True: self.stick() def set_real_transparency(self, value=True): """Enable RGBA if supported on the current screen""" if self.is_composited() == False: value = False screen = self.get_screen() if value: dbg('setting rgba visual') visual = screen.get_rgba_visual() if visual: self.set_visual(visual) def show(self, startup=False): """Undo the startup show request if started in hidden mode""" # Present is necessary to grab focus when window is hidden from taskbar. # It is important to call present() before show(), otherwise the window # won't be brought to front if an another application has the focus. # Last note: present() will implicitly call Gtk.Window.show() self.present() # Window must be shown, then hidden for the hotkeys to be registered if (self.ignore_startup_show and startup == True): self.position = self.get_position() self.hide() def add(self, widget, metadata=None): """Add a widget to the window by way of Gtk.Window.add()""" maker = Factory() Gtk.Window.add(self, widget) if maker.isinstance(widget, 'Terminal'): signals = { 'close-term': self.closeterm, 'title-change': self.title.set_title, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, 'unzoom': self.unzoom, 'tab-change': self.tab_change, 'group-all': self.group_all, 'group-all-toggle': self.group_all_toggle, 'ungroup-all': self.ungroup_all, 'group-tab': self.group_tab, 'group-tab-toggle': self.group_tab_toggle, 'ungroup-tab': self.ungroup_tab, 'move-tab': self.move_tab, 'tab-new': [self.tab_new, widget], 'navigate': self.navigate_terminal } for signal in signals: args = [] handler = signals[signal] if isinstance(handler, list): args = handler[1:] handler = handler[0] self.connect_child(widget, signal, handler, *args) widget.grab_focus() def remove(self, widget): """Remove our child widget by way of Gtk.Window.remove()""" Gtk.Window.remove(self, widget) self.disconnect_child(widget) return True def get_children(self): """Return a single list of our child""" children = [] children.append(self.get_child()) return children def hoover(self): """Ensure we still have a reason to exist""" if not self.get_child(): self.emit('destroy') def closeterm(self, widget): """Handle a terminal closing""" Container.closeterm(self, widget) self.hoover() def split_axis(self, widget, vertical=True, cwd=None, sibling=None, widgetfirst=True): """Split the window""" if self.get_property('term_zoomed') == True: err("You can't split while a terminal is maximised/zoomed") return order = None maker = Factory() self.remove(widget) if vertical: container = maker.make('VPaned') else: container = maker.make('HPaned') self.set_pos_by_ratio = True if not sibling: sibling = maker.make('Terminal') sibling.set_cwd(cwd) if self.config['always_split_with_profile']: sibling.force_set_profile(None, widget.get_profile()) sibling.spawn_child() if widget.group and self.config['split_to_group']: sibling.set_group(None, widget.group) elif self.config['always_split_with_profile']: sibling.force_set_profile(None, widget.get_profile()) self.add(container) container.show_all() order = [widget, sibling] if widgetfirst is False: order.reverse() for term in order: container.add(term) container.show_all() while Gtk.events_pending(): Gtk.main_iteration_do(False) sibling.grab_focus() self.set_pos_by_ratio = False def zoom(self, widget, font_scale=True): """Zoom a terminal widget""" children = self.get_children() if widget in children: # This widget is a direct child of ours and we're a Window # so zooming is a no-op return self.zoom_data = widget.get_zoom_data() self.zoom_data['widget'] = widget self.zoom_data['old_child'] = children[0] self.zoom_data['font_scale'] = font_scale self.remove(self.zoom_data['old_child']) self.zoom_data['old_parent'].remove(widget) self.add(widget) self.set_property('term_zoomed', True) if font_scale: widget.cnxids.new(widget, 'size-allocate', widget.zoom_scale, self.zoom_data) widget.grab_focus() def unzoom(self, widget): """Restore normal terminal layout""" if not self.get_property('term_zoomed'): # We're not zoomed anyway dbg('Window::unzoom: not zoomed, no-op') return widget = self.zoom_data['widget'] if self.zoom_data['font_scale']: widget.vte.set_font(self.zoom_data['old_font']) self.remove(widget) self.add(self.zoom_data['old_child']) self.zoom_data['old_parent'].add(widget) widget.grab_focus() self.zoom_data = None self.set_property('term_zoomed', False) def rotate(self, widget, clockwise): """Rotate children in this window""" self.set_pos_by_ratio = True maker = Factory() child = self.get_child() # If our child is a Notebook, reset to work from its visible child if maker.isinstance(child, 'Notebook'): pagenum = child.get_current_page() child = child.get_nth_page(pagenum) if maker.isinstance(child, 'Paned'): parent = child.get_parent() # Need to get the allocation before we remove the child, # otherwise _sometimes_ we get incorrect values. alloc = child.get_allocation() parent.remove(child) child.rotate_recursive(parent, alloc.width, alloc.height, clockwise) self.show_all() while Gtk.events_pending(): Gtk.main_iteration_do(False) widget.grab_focus() self.set_pos_by_ratio = False def get_visible_terminals(self): """Walk down the widget tree to find all of the visible terminals. Mostly using Container::get_visible_terminals()""" terminals = {} if not hasattr(self, 'cached_maker'): self.cached_maker = Factory() maker = self.cached_maker child = self.get_child() if not child: return [] # If our child is a Notebook, reset to work from its visible child if maker.isinstance(child, 'Notebook'): pagenum = child.get_current_page() child = child.get_nth_page(pagenum) if maker.isinstance(child, 'Container'): terminals.update(child.get_visible_terminals()) elif maker.isinstance(child, 'Terminal'): terminals[child] = child.get_allocation() else: err('Unknown child type %s' % type(child)) return terminals def get_focussed_terminal(self): """Find which terminal we want to have focus""" terminals = self.get_visible_terminals() for terminal in terminals: if terminal.vte.is_focus(): return terminal return None def deferred_set_rough_geometry_hints(self): # no parameters are used in set_rough_geometry_hints, so we can # use the set_rough_geometry_hints if self.pending_set_rough_geometry_hint == True: return self.pending_set_rough_geometry_hint = True GObject.idle_add(self.do_deferred_set_rough_geometry_hints) def do_deferred_set_rough_geometry_hints(self): self.pending_set_rough_geometry_hint = False self.set_rough_geometry_hints() def set_rough_geometry_hints(self): """Walk all the terminals along the top and left edges to fake up how many columns/rows we sort of have""" if self.ismaximised == True: return if not hasattr(self, 'cached_maker'): self.cached_maker = Factory() maker = self.cached_maker if maker.isinstance(self.get_child(), 'Notebook'): dbg("We don't currently support geometry hinting with tabs") return terminals = self.get_visible_terminals() column_sum = 0 row_sum = 0 for terminal in terminals: rect = terminal.get_allocation() if rect.x == 0: cols, rows = terminal.get_size() row_sum = row_sum + rows if rect.y == 0: cols, rows = terminal.get_size() column_sum = column_sum + cols if column_sum == 0 or row_sum == 0: dbg('column_sum=%s,row_sum=%s. No terminals found in >=1 axis' % (column_sum, row_sum)) return # FIXME: I don't think we should just use whatever font size info is on # the last terminal we inspected. Looking up the default profile font # size and calculating its character sizes would be rather expensive # though. font_width, font_height = terminal.get_font_size() total_font_width = font_width * column_sum total_font_height = font_height * row_sum win_width, win_height = self.get_size() extra_width = win_width - total_font_width extra_height = win_height - total_font_height dbg('setting geometry hints: (ewidth:%s)(eheight:%s),\ (fwidth:%s)(fheight:%s)' % (extra_width, extra_height, font_width, font_height)) geometry = Gdk.Geometry() geometry.base_width = extra_width geometry.base_height = extra_height geometry.width_inc = font_width geometry.height_inc = font_height self.set_geometry_hints( self, geometry, Gdk.WindowHints.BASE_SIZE | Gdk.WindowHints.RESIZE_INC) def tab_change(self, widget, num=None): """Change to a specific tab""" if num is None: err('must specify a tab to change to') maker = Factory() child = self.get_child() if not maker.isinstance(child, 'Notebook'): dbg('child is not a notebook, nothing to change to') return if num == -1: # Go to the next tab cur = child.get_current_page() pages = child.get_n_pages() if cur == pages - 1: num = 0 else: num = cur + 1 elif num == -2: # Go to the previous tab cur = child.get_current_page() if cur > 0: num = cur - 1 else: num = child.get_n_pages() - 1 child.set_current_page(num) # Work around strange bug in gtk-2.12.11 and pygtk-2.12.1 # Without it, the selection changes, but the displayed page doesn't # change child.set_current_page(child.get_current_page()) def set_groups(self, new_group, term_list): """Set terminals in term_list to new_group""" for terminal in term_list: terminal.set_group(None, new_group) self.terminator.focus_changed(self.terminator.last_focused_term) def group_all(self, widget): """Group all terminals""" # FIXME: Why isn't this being done by Terminator() ? group = _('All') self.terminator.create_group(group) self.set_groups(group, self.terminator.terminals) def group_all_toggle(self, widget): """Toggle grouping to all""" if widget.group == 'All': self.ungroup_all(widget) else: self.group_all(widget) def ungroup_all(self, widget): """Ungroup all terminals""" self.set_groups(None, self.terminator.terminals) def group_tab(self, widget): """Group all terminals in the current tab""" maker = Factory() notebook = self.get_child() if not maker.isinstance(notebook, 'Notebook'): dbg('not in a notebook, refusing to group tab') return pagenum = notebook.get_current_page() while True: group = _('Tab %d') % pagenum if group not in self.terminator.groups: break pagenum += 1 self.set_groups(group, self.get_visible_terminals()) def group_tab_toggle(self, widget): """Blah""" if widget.group and widget.group[:4] == 'Tab ': self.ungroup_tab(widget) else: self.group_tab(widget) def ungroup_tab(self, widget): """Ungroup all terminals in the current tab""" maker = Factory() notebook = self.get_child() if not maker.isinstance(notebook, 'Notebook'): dbg('note in a notebook, refusing to ungroup tab') return self.set_groups(None, self.get_visible_terminals()) def move_tab(self, widget, direction): """Handle a keyboard shortcut for moving tab positions""" maker = Factory() notebook = self.get_child() if not maker.isinstance(notebook, 'Notebook'): dbg('not in a notebook, refusing to move tab %s' % direction) return dbg('moving tab %s' % direction) numpages = notebook.get_n_pages() page = notebook.get_current_page() child = notebook.get_nth_page(page) if direction == 'left': if page == 0: page = numpages else: page = page - 1 elif direction == 'right': if page == numpages - 1: page = 0 else: page = page + 1 else: err('unknown direction: %s' % direction) return notebook.reorder_child(child, page) def navigate_terminal(self, terminal, direction): """Navigate around terminals""" _containers, terminals = enumerate_descendants(self) visibles = self.get_visible_terminals() current = terminals.index(terminal) length = len(terminals) next = None if length <= 1 or len(visibles) <= 1: return if direction in ['next', 'prev']: tmpterms = copy.copy(terminals) tmpterms = tmpterms[current + 1:] tmpterms.extend(terminals[0:current]) if direction == 'next': tmpterms.reverse() next = 0 while len(tmpterms) > 0: tmpitem = tmpterms.pop() if tmpitem in visibles: next = terminals.index(tmpitem) break elif direction in ['left', 'right', 'up', 'down']: layout = self.get_visible_terminals() allocation = terminal.get_allocation() possibles = [] # Get the co-ordinate of the appropriate edge for this direction edge, p1, p2 = get_edge(allocation, direction) # Find all visible terminals which are, in their entirity, in the # direction we want to move, and are at least partially spanning # p1 to p2 for term in layout: rect = layout[term] if get_nav_possible(edge, rect, direction, p1, p2): possibles.append(term) if len(possibles) == 0: return # Find out how far away each of the possible terminals is, then # find the smallest distance. The winning terminals are all of # those who are that distance away. offsets = {} for term in possibles: rect = layout[term] offsets[term] = get_nav_offset(edge, rect, direction) keys = offsets.values() keys.sort() winners = [k for k, v in offsets.iteritems() if v == keys[0]] next = terminals.index(winners[0]) if len(winners) > 1: # Break an n-way tie using the cursor position term_alloc = terminal.get_allocation() cursor_x = term_alloc.x + term_alloc.width / 2 cursor_y = term_alloc.y + term_alloc.height / 2 for term in winners: rect = layout[term] if get_nav_tiebreak(direction, cursor_x, cursor_y, rect): next = terminals.index(term) break else: err('Unknown navigation direction: %s' % direction) if next is not None: terminals[next].grab_focus() def create_layout(self, layout): """Apply any config items from our layout""" if 'children' not in layout: err('layout describes no children: %s' % layout) return children = layout['children'] if len(children) != 1: # We're a Window, we can only have one child err('incorrect number of children for Window: %s' % layout) return child = children[children[0]] terminal = self.get_children()[0] dbg('Making a child of type: %s' % child['type']) if child['type'] == 'VPaned': self.split_axis(terminal, True) elif child['type'] == 'HPaned': self.split_axis(terminal, False) elif child['type'] == 'Notebook': self.tab_new() i = 2 while i < len(child['children']): self.tab_new() i = i + 1 elif child['type'] == 'Terminal': pass else: err('unknown child type: %s' % child['type']) return self.get_children()[0].create_layout(child) if 'last_active_term' in layout and layout['last_active_term'] not in [ '', None ]: self.last_active_term = make_uuid(layout['last_active_term']) if 'last_active_window' in layout and layout[ 'last_active_window'] == 'True': self.terminator.last_active_window = self.uuid
class Titlebar(Gtk.EventBox): # pylint: disable-msg=R0904 # pylint: disable-msg=W0613 """Class implementing the Titlebar widget""" terminator = None terminal = None config = None oldtitle = None termtext = None sizetext = None label = None ebox = None groupicon = None grouplabel = None groupentry = None bellicon = None __gsignals__ = { 'clicked': (GObject.SignalFlags.RUN_LAST, None, ()), 'edit-done': (GObject.SignalFlags.RUN_LAST, None, ()), 'create-group': (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_STRING, )), } def __init__(self, terminal): """Class initialiser""" GObject.GObject.__init__(self) self.terminator = Terminator() self.terminal = terminal self.config = self.terminal.config self.label = EditableLabel() self.label.connect('edit-done', self.on_edit_done) self.ebox = Gtk.EventBox() grouphbox = Gtk.HBox() self.grouplabel = Gtk.Label(ellipsize='end') self.groupicon = Gtk.Image() self.bellicon = Gtk.Image() self.bellicon.set_no_show_all(True) self.groupentry = Gtk.Entry() self.groupentry.set_no_show_all(True) self.groupentry.connect('focus-out-event', self.groupentry_cancel) self.groupentry.connect('activate', self.groupentry_activate) self.groupentry.connect('key-press-event', self.groupentry_keypress) groupsend_type = self.terminator.groupsend_type if self.terminator.groupsend == groupsend_type['all']: icon_name = 'all' elif self.terminator.groupsend == groupsend_type['group']: icon_name = 'group' elif self.terminator.groupsend == groupsend_type['off']: icon_name = 'off' self.set_from_icon_name('_active_broadcast_%s' % icon_name, Gtk.IconSize.MENU) grouphbox.pack_start(self.groupicon, False, True, 2) grouphbox.pack_start(self.grouplabel, False, True, 2) grouphbox.pack_start(self.groupentry, False, True, 2) self.ebox.add(grouphbox) self.ebox.show_all() self.bellicon.set_from_icon_name('terminal-bell', Gtk.IconSize.MENU) viewport = Gtk.Viewport(hscroll_policy='natural') viewport.add(self.label) hbox = Gtk.HBox() hbox.pack_start(self.ebox, False, True, 0) hbox.pack_start(Gtk.VSeparator(), False, True, 0) hbox.pack_start(viewport, True, True, 0) hbox.pack_end(self.bellicon, False, False, 2) self.add(hbox) hbox.show_all() self.set_no_show_all(True) self.show() self.connect('button-press-event', self.on_clicked) def connect_icon(self, func): """Connect the supplied function to clicking on the group icon""" self.ebox.connect('button-press-event', func) def update(self, other=None): """Update our contents""" default_bg = False if self.config['title_hide_sizetext']: self.label.set_text("%s" % self.termtext) else: self.label.set_text("%s %s" % (self.termtext, self.sizetext)) if (not self.config['title_use_system_font'] ) and self.config['title_font']: title_font = Pango.FontDescription(self.config['title_font']) else: title_font = Pango.FontDescription( self.config.get_system_prop_font()) self.label.modify_font(title_font) self.grouplabel.modify_font(title_font) if other: term = self.terminal terminator = self.terminator if other == 'window-focus-out': title_fg = self.config['title_inactive_fg_color'] title_bg = self.config['title_inactive_bg_color'] icon = '_receive_off' default_bg = True group_fg = self.config['title_inactive_fg_color'] group_bg = self.config['title_inactive_bg_color'] elif term != other and term.group and term.group == other.group: if terminator.groupsend == terminator.groupsend_type['off']: title_fg = self.config['title_inactive_fg_color'] title_bg = self.config['title_inactive_bg_color'] icon = '_receive_off' default_bg = True else: title_fg = self.config['title_receive_fg_color'] title_bg = self.config['title_receive_bg_color'] icon = '_receive_on' group_fg = self.config['title_receive_fg_color'] group_bg = self.config['title_receive_bg_color'] elif term != other and not term.group or term.group != other.group: if terminator.groupsend == terminator.groupsend_type['all']: title_fg = self.config['title_receive_fg_color'] title_bg = self.config['title_receive_bg_color'] icon = '_receive_on' else: title_fg = self.config['title_inactive_fg_color'] title_bg = self.config['title_inactive_bg_color'] icon = '_receive_off' default_bg = True group_fg = self.config['title_inactive_fg_color'] group_bg = self.config['title_inactive_bg_color'] else: # We're the active terminal title_fg = self.config['title_transmit_fg_color'] title_bg = self.config['title_transmit_bg_color'] if terminator.groupsend == terminator.groupsend_type['all']: icon = '_active_broadcast_all' elif terminator.groupsend == terminator.groupsend_type[ 'group']: icon = '_active_broadcast_group' else: icon = '_active_broadcast_off' group_fg = self.config['title_transmit_fg_color'] group_bg = self.config['title_transmit_bg_color'] self.label.modify_fg(Gtk.StateType.NORMAL, Gdk.color_parse(title_fg)) self.grouplabel.modify_fg(Gtk.StateType.NORMAL, Gdk.color_parse(group_fg)) self.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse(title_bg)) if not self.get_desired_visibility(): if default_bg == True: color = term.get_style_context().get_background_color( Gtk.StateType.NORMAL) # VERIFY FOR GTK3 else: color = Gdk.color_parse(title_bg) self.update_visibility() self.ebox.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse(group_bg)) self.set_from_icon_name(icon, Gtk.IconSize.MENU) def update_visibility(self): """Make the titlebar be visible or not""" if not self.get_desired_visibility(): dbg('hiding titlebar') self.hide() self.label.hide() else: dbg('showing titlebar') self.show() self.label.show() def get_desired_visibility(self): """Returns True if the titlebar is supposed to be visible. False if not""" if self.editing() == True or self.terminal.group: dbg('implicit desired visibility') return (True) else: dbg('configured visibility: %s' % self.config['show_titlebar']) return (self.config['show_titlebar']) def set_from_icon_name(self, name, size=Gtk.IconSize.MENU): """Set an icon for the group label""" if not name: self.groupicon.hide() return self.groupicon.set_from_icon_name(APP_NAME + name, size) self.groupicon.show() def update_terminal_size(self, width, height): """Update the displayed terminal size""" self.sizetext = "%sx%s" % (width, height) self.update() def set_terminal_title(self, widget, title): """Update the terminal title""" self.termtext = title self.update() # Return False so we don't interrupt any chains of signal handling return False def set_group_label(self, name): """Set the name of the group""" if name: self.grouplabel.set_text(name) self.grouplabel.show() else: self.grouplabel.set_text('') self.grouplabel.hide() self.update_visibility() def on_clicked(self, widget, event): """Handle a click on the label""" self.show() self.label.show() self.emit('clicked') def on_edit_done(self, widget): """Re-emit an edit-done signal from an EditableLabel""" self.emit('edit-done') def editing(self): """Determine if we're currently editing a group name or title""" return (self.groupentry.get_property('visible') or self.label.editing()) def create_group(self): """Create a new group""" if self.terminal.group: self.groupentry.set_text(self.terminal.group) else: defaultmembers = [ _('Alpha'), _('Beta'), _('Gamma'), _('Delta'), _('Epsilon'), _('Zeta'), _('Eta'), _('Theta'), _('Iota'), _('Kappa'), _('Lambda'), _('Mu'), _('Nu'), _('Xi'), _('Omicron'), _('Pi'), _('Rho'), _('Sigma'), _('Tau'), _('Upsilon'), _('Phi'), _('Chi'), _('Psi'), _('Omega') ] currentgroups = set(self.terminator.groups) for i in range(1, 4): defaultgroups = set( map(''.join, list(itertools.product(defaultmembers, repeat=i)))) freegroups = list(defaultgroups - currentgroups) if freegroups: self.groupentry.set_text(random.choice(freegroups)) break else: self.groupentry.set_text('') self.groupentry.show() self.grouplabel.hide() self.groupentry.grab_focus() self.update_visibility() def groupentry_cancel(self, widget, event): """Hide the group name entry""" self.groupentry.set_text('') self.groupentry.hide() self.grouplabel.show() self.get_parent().grab_focus() def groupentry_activate(self, widget): """Actually cause a group to be created""" groupname = self.groupentry.get_text() or None dbg('Titlebar::groupentry_activate: creating group: %s' % groupname) self.groupentry_cancel(None, None) last_focused_term = self.terminator.last_focused_term if self.terminal.targets_for_new_group: [ term.titlebar.emit('create-group', groupname) for term in self.terminal.targets_for_new_group ] self.terminal.targets_for_new_group = None else: self.emit('create-group', groupname) last_focused_term.grab_focus() self.terminator.focus_changed(last_focused_term) def groupentry_keypress(self, widget, event): """Handle keypresses on the entry widget""" key = Gdk.keyval_name(event.keyval) if key == 'Escape': self.groupentry_cancel(None, None) def icon_bell(self): """A bell signal requires we display our bell icon""" self.bellicon.show() GObject.timeout_add(1000, self.icon_bell_hide) def icon_bell_hide(self): """Handle a timeout which means we now hide the bell icon""" self.bellicon.hide() return (False) def get_custom_string(self): """If we have a custom string set, return it, otherwise None""" if self.label.is_custom(): return (self.label.get_text()) else: return (None) def set_custom_string(self, string): """Set a custom string""" self.label.set_text(string) self.label.set_custom()
def __init__(self): """Class initialiser""" Plugin.__init__(self) terminator = Terminator() for terminal in terminator.terminals: terminal.match_add(self.handler_name, self.match)
class Notebook(Container, Gtk.Notebook): """Class implementing a Gtk.Notebook container""" window = None last_active_term = None pending_on_tab_switch = None pending_on_tab_switch_args = None def __init__(self, window): """Class initialiser""" if isinstance(window.get_child(), Gtk.Notebook): err('There is already a Notebook at the top of this window') raise(ValueError) Container.__init__(self) GObject.GObject.__init__(self) self.terminator = Terminator() self.window = window GObject.type_register(Notebook) self.register_signals(Notebook) self.connect('switch-page', self.deferred_on_tab_switch) self.connect('scroll-event', self.on_scroll_event) self.configure() child = window.get_child() window.remove(child) window.add(self) window_last_active_term = window.last_active_term self.newtab(widget=child) if window_last_active_term: self.set_last_active_term(window_last_active_term) window.last_active_term = None self.show_all() def configure(self): """Apply widget-wide settings""" # FIXME: The old reordered handler updated Terminator.terminals with # the new order of terminals. We probably need to preserve this for # navigation to next/prev terminals. #self.connect('page-reordered', self.on_page_reordered) self.set_scrollable(self.config['scroll_tabbar']) if self.config['tab_position'] == 'hidden' or self.config['hide_tabbar']: self.set_show_tabs(False) else: self.set_show_tabs(True) pos = getattr(Gtk.PositionType, self.config['tab_position'].upper()) self.set_tab_pos(pos) for tab in range(0, self.get_n_pages()): label = self.get_tab_label(self.get_nth_page(tab)) label.update_angle() # style = Gtk.RcStyle() # FIXME FOR GTK3 how to do it there? actually do we really want to override the theme? # style.xthickness = 0 # style.ythickness = 0 # self.modify_style(style) self.last_active_term = {} def create_layout(self, layout): """Apply layout configuration""" def child_compare(a, b): order_a = children[a]['order'] order_b = children[b]['order'] if (order_a == order_b): return 0 if (order_a < order_b): return -1 if (order_a > order_b): return 1 if not layout.has_key('children'): err('layout specifies no children: %s' % layout) return children = layout['children'] if len(children) <= 1: #Notebooks should have two or more children err('incorrect number of children for Notebook: %s' % layout) return num = 0 keys = children.keys() keys.sort(child_compare) for child_key in keys: child = children[child_key] dbg('Making a child of type: %s' % child['type']) if child['type'] == 'Terminal': pass elif child['type'] == 'VPaned': page = self.get_nth_page(num) self.split_axis(page, True) elif child['type'] == 'HPaned': page = self.get_nth_page(num) self.split_axis(page, False) num = num + 1 num = 0 for child_key in keys: page = self.get_nth_page(num) if not page: # This page does not yet exist, so make it self.newtab(children[child_key]) page = self.get_nth_page(num) if layout.has_key('labels'): labeltext = layout['labels'][num] if labeltext and labeltext != "None": label = self.get_tab_label(page) label.set_custom_label(labeltext) page.create_layout(children[child_key]) if layout.get('last_active_term', None): self.last_active_term[page] = make_uuid(layout['last_active_term'][num]) num = num + 1 if layout.has_key('active_page'): # Need to do it later, or layout changes result GObject.idle_add(self.set_current_page, int(layout['active_page'])) else: self.set_current_page(0) def split_axis(self, widget, vertical=True, cwd=None, sibling=None, widgetfirst=True): """Split the axis of a terminal inside us""" dbg('called for widget: %s' % widget) order = None page_num = self.page_num(widget) if page_num == -1: err('Notebook::split_axis: %s not found in Notebook' % widget) return label = self.get_tab_label(widget) self.remove(widget) maker = Factory() if vertical: container = maker.make('vpaned') else: container = maker.make('hpaned') self.get_toplevel().set_pos_by_ratio = True if not sibling: sibling = maker.make('terminal') sibling.set_cwd(cwd) if self.config['always_split_with_profile']: sibling.force_set_profile(None, widget.get_profile()) sibling.spawn_child() if widget.group and self.config['split_to_group']: sibling.set_group(None, widget.group) elif self.config['always_split_with_profile']: sibling.force_set_profile(None, widget.get_profile()) self.insert_page(container, None, page_num) self.child_set_property(container, 'tab-expand', True) self.child_set_property(container, 'tab-fill', True) self.set_tab_reorderable(container, True) self.set_tab_label(container, label) self.show_all() order = [widget, sibling] if widgetfirst is False: order.reverse() for terminal in order: container.add(terminal) self.set_current_page(page_num) self.show_all() while Gtk.events_pending(): Gtk.main_iteration_do(False) self.get_toplevel().set_pos_by_ratio = False GObject.idle_add(terminal.ensure_visible_and_focussed) def add(self, widget, metadata=None): """Add a widget to the container""" dbg('adding a new tab') self.newtab(widget=widget, metadata=metadata) def remove(self, widget): """Remove a widget from the container""" page_num = self.page_num(widget) if page_num == -1: err('%s not found in Notebook. Actual parent is: %s' % (widget, widget.get_parent())) return False self.remove_page(page_num) self.disconnect_child(widget) return True def replace(self, oldwidget, newwidget): """Replace a tab's contents with a new widget""" page_num = self.page_num(oldwidget) self.remove(oldwidget) self.add(newwidget) self.reorder_child(newwidget, page_num) def get_child_metadata(self, widget): """Fetch the relevant metadata for a widget which we'd need to recreate it when it's readded""" metadata = {} metadata['tabnum'] = self.page_num(widget) label = self.get_tab_label(widget) if not label: dbg('unable to find label for widget: %s' % widget) elif label.get_custom_label(): metadata['label'] = label.get_custom_label() else: dbg('don\'t grab the label as it was not customised') return metadata def get_children(self): """Return an ordered list of our children""" children = [] for page in range(0,self.get_n_pages()): children.append(self.get_nth_page(page)) return(children) def newtab(self, debugtab=False, widget=None, cwd=None, metadata=None, profile=None): """Add a new tab, optionally supplying a child widget""" dbg('making a new tab') maker = Factory() top_window = self.get_toplevel() if not widget: widget = maker.make('Terminal') if cwd: widget.set_cwd(cwd) if profile and self.config['always_split_with_profile']: widget.force_set_profile(None, profile) widget.spawn_child(debugserver=debugtab) elif profile and self.config['always_split_with_profile']: widget.force_set_profile(None, profile) signals = {'close-term': self.wrapcloseterm, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, 'title-change': self.propagate_title_change, 'unzoom': self.unzoom, 'tab-change': top_window.tab_change, 'group-all': top_window.group_all, 'group-all-toggle': top_window.group_all_toggle, 'ungroup-all': top_window.ungroup_all, 'group-tab': top_window.group_tab, 'group-tab-toggle': top_window.group_tab_toggle, 'ungroup-tab': top_window.ungroup_tab, 'move-tab': top_window.move_tab, 'tab-new': [top_window.tab_new, widget], 'navigate': top_window.navigate_terminal} if maker.isinstance(widget, 'Terminal'): for signal in signals: args = [] handler = signals[signal] if isinstance(handler, list): args = handler[1:] handler = handler[0] self.connect_child(widget, signal, handler, *args) if metadata and metadata.has_key('tabnum'): tabpos = metadata['tabnum'] else: tabpos = -1 label = TabLabel(self.window.get_title(), self) if metadata and metadata.has_key('label'): dbg('creating TabLabel with text: %s' % metadata['label']) label.set_custom_label(metadata['label']) label.connect('close-clicked', self.closetab) label.show_all() widget.show_all() dbg('inserting page at position: %s' % tabpos) self.insert_page(widget, None, tabpos) if maker.isinstance(widget, 'Terminal'): containers, objects = ([], [widget]) else: containers, objects = enumerate_descendants(widget) term_widget = None for term_widget in objects: if maker.isinstance(term_widget, 'Terminal'): self.set_last_active_term(term_widget.uuid) break self.set_tab_label(widget, label) self.child_set_property(widget, 'tab-expand', True) self.child_set_property(widget, 'tab-fill', True) self.set_tab_reorderable(widget, True) self.set_current_page(tabpos) self.show_all() if maker.isinstance(term_widget, 'Terminal'): widget.grab_focus() def wrapcloseterm(self, widget): """A child terminal has closed""" dbg('Notebook::wrapcloseterm: called on %s' % widget) if self.closeterm(widget): dbg('Notebook::wrapcloseterm: closeterm succeeded') self.hoover() else: dbg('Notebook::wrapcloseterm: closeterm failed') def closetab(self, widget, label): """Close a tab""" tabnum = None try: nb = widget.notebook except AttributeError: err('TabLabel::closetab: called on non-Notebook: %s' % widget) return for i in range(0, nb.get_n_pages() + 1): if label == nb.get_tab_label(nb.get_nth_page(i)): tabnum = i break if tabnum is None: err('TabLabel::closetab: %s not in %s. Bailing.' % (label, nb)) return maker = Factory() child = nb.get_nth_page(tabnum) if maker.isinstance(child, 'Terminal'): dbg('Notebook::closetab: child is a single Terminal') del nb.last_active_term[child] child.close() # FIXME: We only do this del and return here to avoid removing the # page below, which child.close() implicitly does del(label) return elif maker.isinstance(child, 'Container'): dbg('Notebook::closetab: child is a Container') result = self.construct_confirm_close(self.window, _('tab')) if result == Gtk.ResponseType.ACCEPT: containers = None objects = None containers, objects = enumerate_descendants(child) while len(objects) > 0: descendant = objects.pop() descendant.close() while Gtk.events_pending(): Gtk.main_iteration() return else: dbg('Notebook::closetab: user cancelled request') return else: err('Notebook::closetab: child is unknown type %s' % child) return def resizeterm(self, widget, keyname): """Handle a keyboard event requesting a terminal resize""" raise NotImplementedError('resizeterm') def zoom(self, widget, fontscale = False): """Zoom a terminal""" raise NotImplementedError('zoom') def unzoom(self, widget): """Unzoom a terminal""" raise NotImplementedError('unzoom') def find_tab_root(self, widget): """Look for the tab child which is or ultimately contains the supplied widget""" parent = widget.get_parent() previous = parent while parent is not None and parent is not self: previous = parent parent = parent.get_parent() if previous == self: return(widget) else: return(previous) def update_tab_label_text(self, widget, text): """Update the text of a tab label""" notebook = self.find_tab_root(widget) label = self.get_tab_label(notebook) if not label: err('Notebook::update_tab_label_text: %s not found' % widget) return label.set_label(text) def hoover(self): """Clean up any empty tabs and if we only have one tab left, die""" numpages = self.get_n_pages() while numpages > 0: numpages = numpages - 1 page = self.get_nth_page(numpages) if not page: dbg('Removing empty page: %d' % numpages) self.remove_page(numpages) if self.get_n_pages() == 1: dbg('Last page, removing self') child = self.get_nth_page(0) self.remove_page(0) parent = self.get_parent() parent.remove(self) self.cnxids.remove_all() parent.add(child) del(self) # Find the last terminal in the new parent and give it focus terms = parent.get_visible_terminals() terms.keys()[-1].grab_focus() def page_num_descendant(self, widget): """Find the tabnum of the tab containing a widget at any level""" tabnum = self.page_num(widget) dbg("widget is direct child if not equal -1 - tabnum: %d" % tabnum) while tabnum == -1 and widget.get_parent(): widget = widget.get_parent() tabnum = self.page_num(widget) dbg("found tabnum containing widget: %d" % tabnum) return tabnum def set_last_active_term(self, uuid): """Set the last active term for uuid""" widget = self.terminator.find_terminal_by_uuid(uuid.urn) if not widget: err("Cannot find terminal with uuid: %s, so cannot make it active" % (uuid.urn)) return tabnum = self.page_num_descendant(widget) if tabnum == -1: err("No tabnum found for terminal with uuid: %s" % (uuid.urn)) return nth_page = self.get_nth_page(tabnum) self.last_active_term[nth_page] = uuid def clean_last_active_term(self): """Clean up old entries in last_active_term""" if self.terminator.doing_layout == True: return last_active_term = {} for tabnum in range(0, self.get_n_pages()): nth_page = self.get_nth_page(tabnum) if nth_page in self.last_active_term: last_active_term[nth_page] = self.last_active_term[nth_page] self.last_active_term = last_active_term def deferred_on_tab_switch(self, notebook, page, page_num, data=None): """Prime a single idle tab switch signal, using the most recent set of params""" tabs_last_active_term = self.last_active_term.get(self.get_nth_page(page_num), None) data = {'tabs_last_active_term': tabs_last_active_term} self.pending_on_tab_switch_args = (notebook, page, page_num, data) if self.pending_on_tab_switch == True: return GObject.idle_add(self.do_deferred_on_tab_switch) self.pending_on_tab_switch = True def do_deferred_on_tab_switch(self): """Perform the latest tab switch signal, and resetting the pending flag""" self.on_tab_switch(*self.pending_on_tab_switch_args) self.pending_on_tab_switch = False self.pending_on_tab_switch_args = None def on_tab_switch(self, notebook, page, page_num, data=None): """Do the real work for a tab switch""" tabs_last_active_term = data['tabs_last_active_term'] if tabs_last_active_term: term = self.terminator.find_terminal_by_uuid(tabs_last_active_term.urn) GObject.idle_add(term.ensure_visible_and_focussed) return True def on_scroll_event(self, notebook, event): '''Handle scroll events for scrolling through tabs''' #print "self: %s" % self #print "event: %s" % event child = self.get_nth_page(self.get_current_page()) if child == None: print("Child = None, return false") return False event_widget = Gtk.get_event_widget(event) if event_widget == None or \ event_widget == child or \ event_widget.is_ancestor(child): print("event_widget is wrong one, return false") return False # Not sure if we need these. I don't think wehave any action widgets # at this point. action_widget = self.get_action_widget(Gtk.PackType.START) if event_widget == action_widget or \ (action_widget != None and event_widget.is_ancestor(action_widget)): return False action_widget = self.get_action_widget(Gtk.PackType.END) if event_widget == action_widget or \ (action_widget != None and event_widget.is_ancestor(action_widget)): return False if event.direction in [Gdk.ScrollDirection.RIGHT, Gdk.ScrollDirection.DOWN]: self.next_page() elif event.direction in [Gdk.ScrollDirection.LEFT, Gdk.ScrollDirection.UP]: self.prev_page() elif event.direction == Gdk.ScrollDirection.SMOOTH: if self.get_tab_pos() in [Gtk.PositionType.LEFT, Gtk.PositionType.RIGHT]: if event.delta_y > 0: self.next_page() elif event.delta_y < 0: self.prev_page() elif self.get_tab_pos() in [Gtk.PositionType.TOP, Gtk.PositionType.BOTTOM]: if event.delta_x > 0: self.next_page() elif event.delta_x < 0: self.prev_page() return True
def update_watches(self): for terminal in Terminator().terminals: if terminal not in self.watches: self.watches[terminal] = terminal.get_vte().connect( 'contents-changed', self._on_terminal_change, terminal)
def parse_options(): """Parse the command line options""" is_x_terminal_emulator = os.path.basename( sys.argv[0]) == 'x-terminal-emulator' parser = argparse.ArgumentParser() parser.add_argument('-v', '--version', action='store_true', dest='version', help=_('Display program version')) parser.add_argument('-m', '--maximise', action='store_true', dest='maximise', help=_('Maximize the window')) parser.add_argument('-M', '--maximize', action='store_true', dest='maximise', help=_('Maximize the window')) parser.add_argument('-f', '--fullscreen', action='store_true', dest='fullscreen', help=_('Make the window fill the screen')) parser.add_argument('-b', '--borderless', action='store_true', dest='borderless', help=_('Disable window borders')) parser.add_argument('-H', '--hidden', action='store_true', dest='hidden', help=_('Hide the window at startup')) parser.add_argument('-T', '--title', dest='forcedtitle', help=_('Specify a title for the window')) parser.add_argument('--geometry', dest='geometry', type=str, help=_( 'Set the preferred size and position of the window' '(see X man page)')) if not is_x_terminal_emulator: parser.add_argument( '-e', '--command', dest='command', help=_('Specify a command to execute inside the terminal')) else: parser.add_argument( '--command', dest='command', help=_('Specify a command to execute inside the terminal')) parser.add_argument( '-e', '--execute2', dest='execute', action=ExecuteCallback, help=_('Use the rest of the command line as a command to ' 'execute inside the terminal, and its arguments')) parser.add_argument('-g', '--config', dest='config', help=_('Specify a config file')) parser.add_argument('-j', '--config-json', dest='configjson', help=_('Specify a partial config json file')) parser.add_argument( '-x', '--execute', dest='execute', action=ExecuteCallback, help=_('Use the rest of the command line as a command to execute ' 'inside the terminal, and its arguments')) parser.add_argument('--working-directory', metavar='DIR', dest='working_directory', help=_('Set the working directory')) parser.add_argument('-i', '--icon', dest='forcedicon', help=_('Set a custom \ icon for the window (by file or name)')) parser.add_argument( '-r', '--role', dest='role', help=_('Set a custom WM_WINDOW_ROLE property on the window')) parser.add_argument('-l', '--layout', dest='layout', help=_('Launch with the given layout')) parser.add_argument('-s', '--select-layout', action='store_true', dest='select', help=_('Select a layout from a list')) parser.add_argument('-p', '--profile', dest='profile', help=_('Use a different profile as the default')) parser.add_argument('-u', '--no-dbus', action='store_true', dest='nodbus', help=_('Disable DBus')) parser.add_argument( '-d', '--debug', action='count', dest='debug', help=_('Enable debugging information (twice for debug server)')) parser.add_argument( '--debug-classes', action='store', dest='debug_classes', help=_('Comma separated list of classes to limit debugging to')) parser.add_argument( '--debug-methods', action='store', dest='debug_methods', help=_('Comma separated list of methods to limit debugging to')) parser.add_argument( '--new-tab', action='store_true', dest='new_tab', help=_('If Terminator is already running, just open a new tab')) parser.add_argument( '--unhide', action='store_true', dest='unhide', help=_( 'If Terminator is already running, just unhide all hidden windows') ) parser.add_argument('--list-profiles', action='store_true', dest='list_profiles', help=_('List all profiles')) parser.add_argument('--list-layouts', action='store_true', dest='list_layouts', help=_('List all layouts')) for item in [ '--sm-client-id', '--sm-config-prefix', '--screen', '-n', '--no-gconf' ]: parser.add_argument(item, dest='dummy', action='store', help=argparse.SUPPRESS) global options options = parser.parse_args() if options.version: print('%s %s' % (version.APP_NAME, version.APP_VERSION)) sys.exit(0) if options.list_profiles: for p in Terminator().config.list_profiles(): print(p) sys.exit(0) if options.list_layouts: for l in Terminator().config.list_layouts(): print(l) sys.exit(0) if options.debug_classes or options.debug_methods: if not options.debug > 0: options.debug = 1 if options.debug: util.DEBUG = True if options.debug > 1: util.DEBUGFILES = True if options.debug_classes: classes = options.debug_classes.split(',') for item in classes: util.DEBUGCLASSES.append(item.strip()) if options.debug_methods: methods = options.debug_methods.split(',') for item in methods: util.DEBUGMETHODS.append(item.strip()) if options.working_directory: if os.path.exists(os.path.expanduser(options.working_directory)): options.working_directory = os.path.expanduser( options.working_directory) os.chdir(options.working_directory) else: err('OptionParse::parse_options: %s does not exist' % options.working_directory) options.working_directory = '' if options.layout is None: options.layout = 'default' configobj = config.Config() if options.profile and options.profile not in configobj.list_profiles(): options.profile = None configobj.options_set(options) optionslist = {} for opt, val in list(options.__dict__.items()): if type(val) == type([]): val = ' '.join(val) if val == True: val = 'True' optionslist[opt] = val and '%s' % val or '' # optionslist = dbus.Dictionary(optionslist, signature='ss') if util.DEBUG == True: dbg('command line options: %s' % options) return (options, optionslist)
def jumpUp(self, widget): t = Terminator().last_focused_term t.scrollbar_jump(self.last_cursor_pos)