def on_css_parsing_error(provider, section, error, user_data=None): """Report CSS parsing issues""" file_path = section.get_file().get_path() line_no = section.get_end_line() + 1 col_no = section.get_end_position() + 1 err(f'{error.message}, at line {line_no}, column {col_no}, of file {file_path}' )
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 reload(self): """Parse bindings and mangle into an appropriate form""" self._lookup = {} self._masks = 0 for action, bindings in self.keys.items(): if not isinstance(bindings, tuple): bindings = (bindings,) for binding in bindings: if not binding or binding == 'None': continue try: keyval, mask = self._parsebinding(binding) # Does much the same, but with poorer error handling. # keyval, mask = Gtk.accelerator_parse(binding) except KeymapError as e: err(f'keybindings.reload failed to parse binding <{binding}>: {e}') else: if mask & Gdk.ModifierType.SHIFT_MASK: if keyval == Gdk.KEY_Tab: keyval = Gdk.KEY_ISO_Left_Tab mask &= ~Gdk.ModifierType.SHIFT_MASK else: keyvals = Gdk.keyval_convert_case(keyval) if keyvals[0] != keyvals[1]: keyval = keyvals[1] mask &= ~Gdk.ModifierType.SHIFT_MASK else: keyval = Gdk.keyval_to_lower(keyval) self._lookup.setdefault(mask, {}) self._lookup[mask][keyval] = action self._masks |= mask
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 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 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 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 on_css_parsing_error(self, provider, section, error, user_data=None): """Report CSS parsing issues""" file_path = section.get_file().get_path() line_no = section.get_end_line() + 1 col_no = section.get_end_position() + 1 err('%s, at line %d, column %d, of file %s' % (error.message, line_no, col_no, file_path))
def save(self): """Save the config to a file""" dbg('ConfigBase::save: saving config') parser = ConfigObj() parser.indent_type = ' ' for section_name in ['global_config', 'keybindings']: dbg('ConfigBase::save: Processing section: %s' % section_name) section = getattr(self, section_name) parser[section_name] = dict_diff(DEFAULTS[section_name], section) parser['profiles'] = {} for profile in self.profiles: dbg('ConfigBase::save: Processing profile: %s' % profile) parser['profiles'][profile] = dict_diff( DEFAULTS['profiles']['default'], self.profiles[profile]) parser['layouts'] = {} for layout in self.layouts: dbg('ConfigBase::save: Processing layout: %s' % layout) parser['layouts'][layout] = self.layouts[layout] parser['plugins'] = {} for plugin in self.plugins: dbg('ConfigBase::save: Processing plugin: %s' % plugin) parser['plugins'][plugin] = self.plugins[plugin] config_dir = get_config_dir() if not os.path.isdir(config_dir): os.makedirs(config_dir) try: parser.write(open(self.command_line_options.config, 'w')) except Exception as ex: err('ConfigBase::save: Unable to save config: %s' % ex)
def isinstance(self, product, classtype): """Check if a given product is a particular type of object""" if classtype in self.types_keys: # This is now very ugly, but now it's fast :-) # Someone with real Python skills should fix this to be less insane. # Optimisations: # - swap order of imports, otherwise we throw ImportError # almost every time # - cache everything we can try: type_key = 'terminatorlib.%s' % self.types[classtype] if type_key not in self.instance_types_keys: self.instance_types[type_key] = __import__( type_key, None, None, ['']) self.instance_types_keys.append(type_key) module = self.instance_types[type_key] except ImportError: type_key = self.types[classtype] if type_key not in self.instance_types_keys: self.instance_types[type_key] = __import__( type_key, None, None, ['']) self.instance_types_keys.append(type_key) module = self.instance_types[type_key] return isinstance(product, getattr(module, classtype)) else: err('Factory::isinstance: unknown class type: %s' % classtype) return False
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 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 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 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 GLib.idle_add(terminal.ensure_visible_and_focussed)
def add(self, widget, metadata=None): """Add a widget to the container""" if len(self.children) == 0: self.pack1(widget, False, True) self.children.append(widget) elif len(self.children) == 1: if self.get_child1(): self.pack2(widget, False, True) else: self.pack1(widget, False, True) self.children.append(widget) else: raise ValueError('Paned widgets can only have two children') if self.maker.isinstance(widget, 'Terminal'): top_window = self.get_toplevel() signals = { 'close-term': self.wrapcloseterm, 'split-horiz': self.split_horiz, 'split-vert': self.split_vert, 'title-change': self.propagate_title_change, 'resize-term': self.resizeterm, 'size-allocate': self.new_size, 'zoom': top_window.zoom, '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, 'maximise': [top_window.zoom, False], 'tab-new': [top_window.tab_new, widget], 'navigate': top_window.navigate_terminal, 'rotate-cw': [top_window.rotate, True], 'rotate-ccw': [top_window.rotate, False] } 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 \ 'had_focus' in metadata and \ metadata['had_focus'] == True: widget.grab_focus() elif isinstance(widget, Gtk.Paned): try: self.connect_child(widget, 'resize-term', self.resizeterm) self.connect_child(widget, 'size-allocate', self.new_size) except TypeError: err('Paned::add: %s has no signal resize-term' % widget)
def isVerticalOrientation(self, orientation): if orientation is None: err("orientation is None; use default") elif orientation == HORIZONTAL_VALUE: return False elif not orientation == VERTICAL_VALUE: err("unknown orientation [%s]; use default" % orientation) return True
def executeStep(self, step, terminal, terminalElement): if step == DIRECTORY_ATTRIBUTE: self.setDirectory(terminal, terminalElement) elif step == EXPORT_TERMINAL_NUMBER_ATTRIBUTE: self.exportTerminalNumber(terminal, self.exportVariable) elif step == COMMAND_ATTRIBUTE: self.executeTerminalCommand(terminal, terminalElement) else: err("ignoring unknown step [%s]" % step)
def getOrientation(self, paned): if isinstance(paned, VPaned): orientation = VERTICAL_VALUE else: if not isinstance(paned, HPaned): err("unknown Paned type; use %s" % HORIZONTAL_VALUE) orientation = HORIZONTAL_VALUE return orientation
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 execute_step(self, step, terminal, terminal_element): if step == DIRECTORY_ATTRIBUTE: self.set_directory(terminal, terminal_element) elif step == EXPORT_TERMINAL_NUMBER_ATTRIBUTE: self.export_terminal_number(terminal, self.export_variable) elif step == COMMAND_ATTRIBUTE: self.execute_terminal_command(terminal, terminal_element) else: err('ignoring unknown step [%s]' % step)
def get_orientation(paned): if isinstance(paned, VPaned): orientation = VERTICAL_VALUE else: if not isinstance(paned, HPaned): err('unknown Paned type; will use: %s' % HORIZONTAL_VALUE) orientation = HORIZONTAL_VALUE return orientation
def is_vertical_orientation(orientation): if orientation is None: err('orientation is None; use default') elif orientation == HORIZONTAL_VALUE: return False elif not orientation == VERTICAL_VALUE: err('unknown orientation [%s]; use default' % orientation) return True
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 replace(self, oldwidget, newwidget): """Replace the child oldwidget with newwidget. This is the bare minimum required for this operation. Containers should override it if they have more complex requirements""" if not oldwidget in self.get_children(): err('%s is not a child of %s' % (oldwidget, self)) return self.remove(oldwidget) self.add(newwidget)
def loadChildRecursive(self,terminal,childElement): targetElement = childElement.find(SPLIT_ELEMENT) handled = self.tryLoadSplitRecursive(terminal, targetElement) if not handled: targetElement = childElement.find(TERMINAL_ELEMENT) handled = self.tryLoadTerminal(terminal, targetElement) if not handled: err("neither split, nor terminal found.")
def proc_get_pid_cwd(pid, path): """Extract the cwd of a PID from proc, given the PID and the /proc path to insert it into, e.g. /proc/%s/cwd""" try: cwd = os.path.realpath(path % pid) except Exception as ex: err('Unable to get cwd for PID %s: %s' % (pid, ex)) cwd = '/' return (cwd)
def toggle_zoom(self, widget, fontscale=False): """Toggle the existing zoom state""" try: if self.get_property('term_zoomed'): self.unzoom(widget) else: self.zoom(widget, fontscale) except TypeError: err('Container::toggle_zoom: %s is unable to handle zooming, for \ %s' % (self, widget))
def loadChildRecursive(self, terminal, childElement): targetElement = childElement.find(SPLIT_ELEMENT) handled = self.tryLoadSplitRecursive(terminal, targetElement) if not handled: targetElement = childElement.find(TERMINAL_ELEMENT) handled = self.tryLoadTerminal(terminal, targetElement) if not handled: err("neither split, nor terminal found.")
def load_child_recursive(self, terminal, child_element): target_element = self.try_get_xml_child(child_element, SPLIT_ELEMENT) handled = self.try_load_split_recursive(terminal, target_element) if not handled: target_element = self.try_get_xml_child(child_element, TERMINAL_ELEMENT) handled = self.try_load_terminal(terminal, target_element) if not handled: err('neither split, nor terminal found: %s' % child_element)
def try_load_split_recursive(self, terminal, split_element): if split_element is None: return False split_children = list(self.try_get_xml_children(split_element, CHILD_ELEMENT)) if len(split_children) == 2: orientation = self.try_get_xml_attribute(split_element, ORIENTATION_ATTRIBUTE) self.split_and_load_axis_recursive(terminal, orientation, split_children[0], split_children[1]) else: err('split element needs exactly two child elements. You have: %d' % len(split_children)) return True
def save_recursive(self, target, element, terminal=None): if isinstance(target, Terminal): self.save_terminal(target, element) elif isinstance(target, Paned): self.save_paned_recursive(target, element) elif isinstance(target, Window): self.save_window_recursive(target, element, terminal) elif isinstance(target, Notebook): self.save_notebook_recursive(target, element, terminal) else: err('ignoring unknown target type %s' % target.__class__)
def saveRecursive(self, target, element, terminal = None): if isinstance(target, Terminal): self.saveTerminal(target, element) elif isinstance(target, Paned): self.savePanedRecursive(target, element) elif isinstance(target, Window): self.saveWindowRecursiv(target, element, terminal) elif isinstance(target, Notebook): self.saveNotebookRecursiv(target, element, terminal) else: err("ignoring unknown target type")
def insertCommandParameter(self, command): if not command: return None if not self.parameter: err("no parameter left for terminal; ignoring command") return None parameter = self.parameter.pop() return command.replace(self.parameterPlaceholder, parameter)
def parsePluginConfig(config): '''merge the default settings with settings from terminator's config''' ret = DEFAULT_SETTINGS pluginConfig = config.plugin_get_config(EXPORTER_NAME) if pluginConfig: for currentKey in ret.keys(): if currentKey in pluginConfig: ret[currentKey] = pluginConfig[currentKey] for currentKey in pluginConfig: if not currentKey in ret: err("invalid config parameter: %s" % currentKey) return ret
def parse_plugin_config(config): """merge the default settings with settings from terminator's config""" ret = DEFAULT_SETTINGS plugin_config = config.plugin_get_config(EXPORTER_NAME) if plugin_config: for current_key in ret.keys(): if current_key in plugin_config: ret[current_key] = plugin_config[current_key] for current_key in plugin_config: if current_key not in ret: err('invalid config parameter: %s' % current_key) return ret
def tryLoadSplitRecursive(self, terminal, splitElement): if splitElement == None: return False #TODO: pass the position to terminator's pane #position = self.tryGetXmlAttribute(splitElement, POSITION_ATTRIBUTE) splitChildren = list(splitElement.findall(CHILD_ELEMENT)) if len(splitChildren) == 2: orientation = self.tryGetXmlAttribute(splitElement, ORIENTATION_ATTRIBUTE) self.splitAndLoadAxisRecursive(terminal, orientation, splitChildren[0], splitChildren[1]) else: err("split element does not have exactly 2 child elements as children") return True
def insert_command_parameter(self, command, terminal_element): if not command: return None parameter = self.try_get_xml_attribute(terminal_element, PARAMETER_ATTRIBUTE) if not parameter: if not self.parameter: err('no parameter left for terminal; ignoring command') return None parameter = self.parameter.pop() return command.replace(self.parameter_placeholder, parameter)
from terminatorlib.config import Config import terminatorlib.plugin as plugin from terminatorlib.translation import _ from terminatorlib.util import err, dbg from terminatorlib.version import APP_NAME try: gi.require_version('Notify', '0.7') from gi.repository import Notify # Every plugin you want Terminator to load *must* be listed in 'AVAILABLE' # This is inside this try so we only make the plugin available if pynotify # is present on this computer. AVAILABLE = ['ActivityWatch', 'InactivityWatch'] except (ImportError, ValueError): err('ActivityWatch plugin unavailable as we cannot import Notify') config = Config() inactive_period = float(config.plugin_get('InactivityWatch', 'inactive_period', 10.0)) watch_interval = int(config.plugin_get('InactivityWatch', 'watch_interval', 5000)) hush_period = float(config.plugin_get('ActivityWatch', 'hush_period', 10.0)) class ActivityWatch(plugin.MenuItem): """Add custom commands to the terminal menu""" capabilities = ['terminal_menu'] watches = None last_notifies = None timers = None
def loadLayout(self, terminal, rootElement): childElement = rootElement.find(CHILD_ELEMENT) if not childElement is None: self.loadChildRecursive(terminal, childElement) else: err("rootElement has no child childElement; abort loading")
import re import terminatorlib.plugin as plugin from terminatorlib.util import err, dbg from terminatorlib.terminator import Terminator from terminatorlib.config import Config try: import pynotify # Every plugin you want Terminator to load *must* be listed in 'AVAILABLE' # This is inside this try so we only make the plugin available if pynotify # is present on this computer. AVAILABLE = ['HostWatch'] except ImportError: err(_('HostWatch plugin unavailable')) class HostWatch(plugin.Plugin): watches = {} profiles = {} def __init__(self): self.watches = {} self.profiles = Terminator().config.list_profiles() self.update_watches() 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.check_host, terminal)
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 gtk.gdk.threads_init() (DEBUGTHREAD, DEBUGSVR) = debugserver.spawn(locals()) TERMINATOR.debug_address = DEBUGSVR.server_address try: gtk.main() except KeyboardInterrupt:
def load_layout(self, terminal, root_element): child_element = self.try_get_xml_child(root_element, CHILD_ELEMENT) if child_element is not None: return self.load_child_recursive(terminal, child_element) err('rootElement has no childElement; abort loading')
import gtk import gobject import pyglet # for playing sounds import terminatorlib.plugin as plugin from terminatorlib.translation import _ from terminatorlib.util import err, dbg from terminatorlib.version import APP_NAME try: import pynotify # Every plugin you want Terminator to load *must* be listed in 'AVAILABLE' # This is inside this try so we only make the plugin available if pynotify # is present on this computer. AVAILABLE = ['ActivityWatch', 'InactivityWatch'] except ImportError: err(_('ActivityWatch plugin unavailable: please install python-notify')) class ActivityWatch(plugin.MenuItem): """Add custom commands to the terminal menu""" capabilities = ['terminal_menu'] watches = None last_notifies = None timers = None def __init__(self): plugin.MenuItem.__init__(self) if not self.watches: self.watches = {} if not self.last_notifies: self.last_notifies = {}