1# Terminator by Chris Jones <cmsj@tenshu.net> 2# GPL v2 only 3"""ipc.py - DBus server and API calls""" 4 5import sys 6import hashlib 7from gi.repository import Gdk 8import dbus.service 9from dbus.exceptions import DBusException 10import dbus.glib 11from .borg import Borg 12from .terminator import Terminator 13from .config import Config 14from .factory import Factory 15from .util import dbg, err, enumerate_descendants 16 17CONFIG = Config() 18if not CONFIG['dbus']: 19 # The config says we are not to load dbus, so pretend like we can't 20 dbg('dbus disabled') 21 raise ImportError 22 23BUS_BASE = 'net.tenshu.Terminator2' 24BUS_PATH = '/net/tenshu/Terminator2' 25try: 26 # Try and include the X11 display name in the dbus bus name 27 DISPLAY = Gdk.get_display().partition('.')[0] 28 # In Python 3, hash() uses a different seed on each run, so use hashlib 29 DISPLAY = hashlib.md5(DISPLAY.encode('utf-8')).hexdigest() 30 BUS_NAME = '%s%s' % (BUS_BASE, DISPLAY) 31except: 32 BUS_NAME = BUS_BASE 33 34class DBusService(Borg, dbus.service.Object): 35 """DBus Server class. This is implemented as a Borg""" 36 bus_name = None 37 bus_path = None 38 terminator = None 39 40 def __init__(self): 41 """Class initialiser""" 42 Borg.__init__(self, self.__class__.__name__) 43 self.prepare_attributes() 44 dbus.service.Object.__init__(self, self.bus_name, BUS_PATH) 45 46 def prepare_attributes(self): 47 """Ensure we are populated""" 48 if not self.bus_name: 49 dbg('Checking for bus name availability: %s' % BUS_NAME) 50 try: 51 bus = dbus.SessionBus() 52 except Exception as e: 53 err('Unable to connect to DBUS Server, proceeding as standalone') 54 raise ImportError 55 proxy = bus.get_object('org.freedesktop.DBus', 56 '/org/freedesktop/DBus') 57 flags = 1 | 4 # allow replacement | do not queue 58 if not proxy.RequestName(BUS_NAME, dbus.UInt32(flags)) in (1, 4): 59 dbg('bus name unavailable: %s' % BUS_NAME) 60 raise dbus.exceptions.DBusException( 61 "Couldn't get DBus name %s: Name exists" % BUS_NAME) 62 self.bus_name = dbus.service.BusName(BUS_NAME, 63 bus=dbus.SessionBus()) 64 if not self.bus_path: 65 self.bus_path = BUS_PATH 66 if not self.terminator: 67 self.terminator = Terminator() 68 69 @dbus.service.method(BUS_NAME, in_signature='a{ss}') 70 def new_window_cmdline(self, options=dbus.Dictionary()): 71 """Create a new Window""" 72 dbg('dbus method called: new_window with parameters %s'%(options)) 73 oldopts = self.terminator.config.options_get() 74 oldopts.__dict__ = options 75 self.terminator.config.options_set(oldopts) 76 self.terminator.create_layout(oldopts.layout) 77 self.terminator.layout_done() 78 79 @dbus.service.method(BUS_NAME, in_signature='a{ss}') 80 def new_tab_cmdline(self, options=dbus.Dictionary()): 81 """Create a new tab""" 82 dbg('dbus method called: new_tab with parameters %s'%(options)) 83 oldopts = self.terminator.config.options_get() 84 oldopts.__dict__ = options 85 self.terminator.config.options_set(oldopts) 86 window = self.terminator.get_windows()[0] 87 window.tab_new() 88 89 @dbus.service.method(BUS_NAME, in_signature='a{ss}') 90 def unhide_cmdline(self,options=dbus.Dictionary): 91 dbg('unhide_cmdline') 92 for window in self.terminator.get_windows(): 93 if not window.get_property('visible'): 94 window.on_hide_window() 95 96 @dbus.service.method(BUS_NAME) 97 def new_window(self): 98 """Create a new Window""" 99 terminals_before = set(self.get_terminals()) 100 self.terminator.new_window() 101 terminals_after = set(self.get_terminals()) 102 new_terminal_set = list(terminals_after - terminals_before) 103 if len(new_terminal_set) != 1: 104 return "ERROR: Cannot determine the UUID of the added terminal" 105 else: 106 return new_terminal_set[0] 107 108 @dbus.service.method(BUS_NAME) 109 def new_tab(self, uuid=None): 110 """Create a new tab""" 111 return self.new_terminal(uuid, 'tab') 112 113 @dbus.service.method(BUS_NAME) 114 def hsplit(self, uuid=None): 115 """Split a terminal horizontally, by UUID""" 116 return self.new_terminal(uuid, 'hsplit') 117 118 @dbus.service.method(BUS_NAME) 119 def vsplit(self, uuid=None): 120 """Split a terminal vertically, by UUID""" 121 return self.new_terminal(uuid, 'vsplit') 122 123 def new_terminal(self, uuid, type): 124 """Split a terminal horizontally or vertically, by UUID""" 125 dbg('dbus method called: %s' % type) 126 if not uuid: 127 return "ERROR: No UUID specified" 128 terminal = self.terminator.find_terminal_by_uuid(uuid) 129 terminals_before = set(self.get_terminals()) 130 if not terminal: 131 return "ERROR: Terminal with supplied UUID not found" 132 elif type == 'tab': 133 terminal.key_new_tab() 134 elif type == 'hsplit': 135 terminal.key_split_horiz() 136 elif type == 'vsplit': 137 terminal.key_split_vert() 138 else: 139 return "ERROR: Unknown type \"%s\" specified" % (type) 140 terminals_after = set(self.get_terminals()) 141 # Detect the new terminal UUID 142 new_terminal_set = list(terminals_after - terminals_before) 143 if len(new_terminal_set) != 1: 144 return "ERROR: Cannot determine the UUID of the added terminal" 145 else: 146 return new_terminal_set[0] 147 148 @dbus.service.method(BUS_NAME) 149 def get_terminals(self): 150 """Return a list of all the terminals""" 151 return [x.uuid.urn for x in self.terminator.terminals] 152 153 @dbus.service.method(BUS_NAME) 154 def get_window(self, uuid=None): 155 """Return the UUID of the parent window of a given terminal""" 156 terminal = self.terminator.find_terminal_by_uuid(uuid) 157 window = terminal.get_toplevel() 158 return window.uuid.urn 159 160 @dbus.service.method(BUS_NAME) 161 def get_window_title(self, uuid=None): 162 """Return the title of a parent window of a given terminal""" 163 terminal = self.terminator.find_terminal_by_uuid(uuid) 164 window = terminal.get_toplevel() 165 return window.get_title() 166 167 @dbus.service.method(BUS_NAME) 168 def get_tab(self, uuid=None): 169 """Return the UUID of the parent tab of a given terminal""" 170 maker = Factory() 171 terminal = self.terminator.find_terminal_by_uuid(uuid) 172 window = terminal.get_toplevel() 173 root_widget = window.get_children()[0] 174 if maker.isinstance(root_widget, 'Notebook'): 175 #return root_widget.uuid.urn 176 for tab_child in root_widget.get_children(): 177 terms = [tab_child] 178 if not maker.isinstance(terms[0], "Terminal"): 179 terms = enumerate_descendants(tab_child)[1] 180 if terminal in terms: 181 # FIXME: There are no uuid's assigned to the the notebook, or the actual tabs! 182 # This would fail: return root_widget.uuid.urn 183 return "" 184 185 @dbus.service.method(BUS_NAME) 186 def get_tab_title(self, uuid=None): 187 """Return the title of a parent tab of a given terminal""" 188 maker = Factory() 189 terminal = self.terminator.find_terminal_by_uuid(uuid) 190 window = terminal.get_toplevel() 191 root_widget = window.get_children()[0] 192 if maker.isinstance(root_widget, "Notebook"): 193 for tab_child in root_widget.get_children(): 194 terms = [tab_child] 195 if not maker.isinstance(terms[0], "Terminal"): 196 terms = enumerate_descendants(tab_child)[1] 197 if terminal in terms: 198 return root_widget.get_tab_label(tab_child).get_label() 199 200 @dbus.service.method(BUS_NAME) 201 def switch_profile(self, uuid=None, options=dbus.Dictionary()): 202 """Switch profile of a given terminal""" 203 terminal = self.terminator.find_terminal_by_uuid(uuid) 204 profile_name = options.get('profile') 205 terminal.force_set_profile(False, profile_name) 206 207def with_proxy(func): 208 """Decorator function to connect to the session dbus bus""" 209 dbg('dbus client call: %s' % func.__name__) 210 def _exec(*args, **argd): 211 bus = dbus.SessionBus() 212 try: 213 proxy = bus.get_object(BUS_NAME, BUS_PATH) 214 215 except dbus.DBusException as e: 216 sys.exit( 217 "Remotinator can't connect to terminator. " + 218 "May be terminator is not running.") 219 220 func(proxy, *args, **argd) 221 return _exec 222 223@with_proxy 224def new_window_cmdline(session, options): 225 """Call the dbus method to open a new window""" 226 session.new_window_cmdline(options) 227 228@with_proxy 229def new_tab_cmdline(session, options): 230 """Call the dbus method to open a new tab in the first window""" 231 session.new_tab_cmdline(options) 232 233@with_proxy 234def unhide_cmdline(session,options): 235 session.unhide_cmdline(options) 236 237@with_proxy 238def new_window(session, options): 239 """Call the dbus method to open a new window""" 240 print(session.new_window()) 241 242@with_proxy 243def new_tab(session, uuid, options): 244 """Call the dbus method to open a new tab in the first window""" 245 print(session.new_tab(uuid)) 246 247@with_proxy 248def hsplit(session, uuid, options): 249 """Call the dbus method to horizontally split a terminal""" 250 print(session.hsplit(uuid)) 251 252@with_proxy 253def vsplit(session, uuid, options): 254 """Call the dbus method to vertically split a terminal""" 255 print(session.vsplit(uuid)) 256 257@with_proxy 258def get_terminals(session, options): 259 """Call the dbus method to return a list of all terminals""" 260 print('\n'.join(session.get_terminals())) 261 262@with_proxy 263def get_window(session, uuid, options): 264 """Call the dbus method to return the toplevel tab for a terminal""" 265 print(session.get_window(uuid)) 266 267@with_proxy 268def get_window_title(session, uuid, options): 269 """Call the dbus method to return the title of a tab""" 270 print(session.get_window_title(uuid)) 271 272@with_proxy 273def get_tab(session, uuid, options): 274 """Call the dbus method to return the toplevel tab for a terminal""" 275 print(session.get_tab(uuid)) 276 277@with_proxy 278def get_tab_title(session, uuid, options): 279 """Call the dbus method to return the title of a tab""" 280 print(session.get_tab_title(uuid)) 281 282@with_proxy 283def switch_profile(session, uuid, options): 284 """Call the dbus method to return the title of a tab""" 285 session.switch_profile(uuid, options) 286 287