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