root/branches/gajim_0.11.1/src/remote_control.py

Revision 8626, 19.8 kB (checked in by asterix, 15 months ago)

[Øystein] fix dbus signature of gajim-remote list_contacts. fixes #3408

  • Property svn:eol-style set to LF
Line 
1##      remote_control.py
2##
3## Copyright (C) 2005-2006 Yann Le Boulanger <asterix@lagaule.org>
4## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
5## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
6## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
7##
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published
10## by the Free Software Foundation; version 2 only.
11##
12## This program is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15## GNU General Public License for more details.
16##
17
18import gobject
19import os
20
21from common import gajim
22from common import helpers
23from time import time
24from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
25
26from common import dbus_support
27if dbus_support.supported:
28        import dbus
29        if dbus_support:
30                import dbus.service
31                import dbus.glib
32
33INTERFACE = 'org.gajim.dbus.RemoteInterface'
34OBJ_PATH = '/org/gajim/dbus/RemoteObject'
35SERVICE = 'org.gajim.dbus'
36
37# type mapping
38
39# in most cases it is a utf-8 string
40DBUS_STRING = dbus.String
41
42# general type (for use in dicts, where all values should have the same type)
43DBUS_BOOLEAN = dbus.Boolean
44DBUS_DOUBLE = dbus.Double
45DBUS_INT32 = dbus.Int32
46# dictionary with string key and binary value
47DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv")
48# dictionary with string key and value
49DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
50# empty type (there is no equivalent of None on D-Bus, but historically gajim
51# used 0 instead)
52DBUS_NONE = lambda : dbus.Int32(0)
53
54def get_dbus_struct(obj):
55        ''' recursively go through all the items and replace
56        them with their casted dbus equivalents
57        '''
58        if obj is None:
59                return DBUS_NONE()
60        if isinstance(obj, (unicode, str)):
61                return DBUS_STRING(obj)
62        if isinstance(obj, int):
63                return DBUS_INT32(obj)
64        if isinstance(obj, float):
65                return DBUS_DOUBLE(obj)
66        if isinstance(obj, bool):
67                return DBUS_BOOLEAN(obj)
68        if isinstance(obj, (list, tuple)):
69                result = dbus.Array([get_dbus_struct(i) for i in obj],
70                        signature='v')
71                if result == []:
72                        return DBUS_NONE()
73                return result
74        if isinstance(obj, dict):
75                result = DBUS_DICT_SV()
76                for key, value in obj.items():
77                        result[DBUS_STRING(key)] = get_dbus_struct(value)
78                if result == {}:
79                        return DBUS_NONE()
80                return result
81        # unknown type
82        return DBUS_NONE() 
83
84class Remote:
85        def __init__(self):
86                self.signal_object = None
87                session_bus = dbus_support.session_bus.SessionBus()
88
89                bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
90                self.signal_object = SignalObject(bus_name)
91
92        def raise_signal(self, signal, arg):
93                if self.signal_object:
94                        getattr(self.signal_object, signal)(get_dbus_struct(arg))
95
96
97class SignalObject(dbus.service.Object):
98        ''' Local object definition for /org/gajim/dbus/RemoteObject.
99        (This docstring is not be visible, because the clients can access only the remote object.)'''
100
101        def __init__(self, bus_name):
102                self.first_show = True
103                self.vcard_account = None
104
105                # register our dbus API
106                dbus.service.Object.__init__(self, bus_name, OBJ_PATH)
107
108        @dbus.service.signal(INTERFACE, signature='av')
109        def Roster(self, account_and_data):
110                pass
111
112        @dbus.service.signal(INTERFACE, signature='av')
113        def AccountPresence(self, status_and_account):
114                pass
115
116        @dbus.service.signal(INTERFACE, signature='av')
117        def ContactPresence(self, account_and_array):
118                pass
119
120        @dbus.service.signal(INTERFACE, signature='av')
121        def ContactAbsence(self, account_and_array):
122                pass
123
124        @dbus.service.signal(INTERFACE, signature='av')
125        def NewMessage(self, account_and_array):
126                pass
127
128        @dbus.service.signal(INTERFACE, signature='av')
129        def Subscribe(self, account_and_array):
130                pass
131
132        @dbus.service.signal(INTERFACE, signature='av')
133        def Subscribed(self, account_and_array):
134                pass
135
136        @dbus.service.signal(INTERFACE, signature='av')
137        def Unsubscribed(self, account_and_jid):
138                pass
139
140        @dbus.service.signal(INTERFACE, signature='av')
141        def NewAccount(self, account_and_array):
142                pass
143
144        @dbus.service.signal(INTERFACE, signature='av')
145        def VcardInfo(self, account_and_vcard):
146                pass
147
148        @dbus.service.signal(INTERFACE, signature='av')
149        def LastStatusTime(self, account_and_array):
150                pass
151
152        @dbus.service.signal(INTERFACE, signature='av')
153        def OsInfo(self, account_and_array):
154                pass
155
156        @dbus.service.signal(INTERFACE, signature='av')
157        def GCPresence(self, account_and_array):
158                pass
159
160        @dbus.service.signal(INTERFACE, signature='av')
161        def GCMessage(self, account_and_array):
162                pass
163
164        @dbus.service.signal(INTERFACE, signature='av')
165        def RosterInfo(self, account_and_array):
166                pass
167
168        @dbus.service.signal(INTERFACE, signature='av')
169        def NewGmail(self, account_and_array):
170                pass
171
172        def raise_signal(self, signal, arg):
173                '''raise a signal, with a single argument of unspecified type
174                Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).'''
175                getattr(self, signal)(arg)
176
177        @dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
178        def get_status(self, account):
179                '''Returns status (show to be exact) which is the global one
180                unless account is given'''
181                if not account:
182                        # If user did not ask for account, returns the global status
183                        return DBUS_STRING(helpers.get_global_show())
184                # return show for the given account
185                index = gajim.connections[account].connected
186                return DBUS_STRING(gajim.SHOW_LIST[index])
187
188        @dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
189        def get_status_message(self, account):
190                '''Returns status which is the global one
191                unless account is given'''
192                if not account:
193                        # If user did not ask for account, returns the global status
194                        return DBUS_STRING(str(helpers.get_global_status()))
195                # return show for the given account
196                status = gajim.connections[account].status
197                return DBUS_STRING(status)
198
199        def _get_account_and_contact(self, account, jid):
200                '''get the account (if not given) and contact instance from jid'''
201                connected_account = None
202                contact = None
203                accounts = gajim.contacts.get_accounts()
204                # if there is only one account in roster, take it as default
205                # if user did not ask for account
206                if not account and len(accounts) == 1:
207                        account = accounts[0]
208                if account:
209                        if gajim.connections[account].connected > 1: # account is connected
210                                connected_account = account
211                                contact = gajim.contacts.get_contact_with_highest_priority(account,
212                                        jid)
213                else:
214                        for account in accounts:
215                                contact = gajim.contacts.get_contact_with_highest_priority(account,
216                                        jid)
217                                if contact and gajim.connections[account].connected > 1:
218                                        # account is connected
219                                        connected_account = account
220                                        break
221                if not contact:
222                        contact = jid
223
224                return connected_account, contact
225
226        @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
227        def send_file(self, file_path, jid, account):
228                '''send file, located at 'file_path' to 'jid', using account
229                (optional) 'account' '''
230                jid = self._get_real_jid(jid, account)
231                connected_account, contact = self._get_account_and_contact(account, jid)
232
233                if connected_account:
234                        if file_path[:7] == 'file://':
235                                file_path=file_path[7:]
236                        if os.path.isfile(file_path): # is it file?
237                                gajim.interface.instances['file_transfers'].send_file(
238                                        connected_account, contact, file_path)
239                                return DBUS_BOOLEAN(True)
240                return DBUS_BOOLEAN(False)
241
242        def _send_message(self, jid, message, keyID, account, type = 'chat',
243        subject = None):
244                '''can be called from send_chat_message (default when send_message)
245                or send_single_message'''
246                if not jid or not message:
247                        return DBUS_BOOLEAN(False)
248                if not keyID:
249                        keyID = ''
250
251                connected_account, contact = self._get_account_and_contact(account, jid)
252                if connected_account:
253                        connection = gajim.connections[connected_account]
254                        connection.send_message(jid, message, keyID, type, subject)
255                        return DBUS_BOOLEAN(True)
256                return DBUS_BOOLEAN(False)
257
258        @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b')
259        def send_chat_message(self, jid, message, keyID, account):
260                '''Send chat 'message' to 'jid', using account (optional) 'account'.
261                if keyID is specified, encrypt the message with the pgp key '''
262                jid = self._get_real_jid(jid, account)
263                return self._send_message(jid, message, keyID, account)
264
265        @dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b')
266        def send_single_message(self, jid, subject, message, keyID, account):
267                '''Send single 'message' to 'jid', using account (optional) 'account'.
268                if keyID is specified, encrypt the message with the pgp key '''
269                jid = self._get_real_jid(jid, account)
270                return self._send_message(jid, message, keyID, account, type, subject)
271
272        @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
273        def open_chat(self, jid, account):
274                '''Shows the tabbed window for new message to 'jid', using account
275                (optional) 'account' '''
276                if not jid:
277                        raise MissingArgument
278                        return DBUS_BOOLEAN(False)
279                jid = self._get_real_jid(jid, account)
280                try:
281                        jid = helpers.parse_jid(jid)
282                except:
283                        # Jid is not conform, ignore it
284                        return DBUS_BOOLEAN(False)
285
286                if account:
287                        accounts = [account]
288                else:
289                        accounts = gajim.connections.keys()
290                        if len(accounts) == 1:
291                                account = accounts[0]
292                connected_account = None
293                first_connected_acct = None
294                for acct in accounts:
295                        if gajim.connections[acct].connected > 1: # account is  online
296                                contact = gajim.contacts.get_first_contact_from_jid(acct, jid)
297                                if gajim.interface.msg_win_mgr.has_window(jid, acct):
298                                        connected_account = acct
299                                        break
300                                # jid is in roster
301                                elif contact:
302                                        connected_account = acct
303                                        break
304                                # we send the message to jid not in roster, because account is
305                                # specified, or there is only one account
306                                elif account: 
307                                        connected_account = acct
308                                elif first_connected_acct is None:
309                                        first_connected_acct = acct
310
311                # if jid is not a conntact, open-chat with first connected account
312                if connected_account is None and first_connected_acct:
313                        connected_account = first_connected_acct
314
315                if connected_account:
316                        gajim.interface.roster.new_chat_from_jid(connected_account, jid)
317                        # preserve the 'steal focus preservation'
318                        win = gajim.interface.msg_win_mgr.get_window(jid,
319                                connected_account).window
320                        if win.get_property('visible'):
321                                win.window.focus()
322                        return DBUS_BOOLEAN(True)
323                return DBUS_BOOLEAN(False)
324
325        @dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
326        def change_status(self, status, message, account):
327                ''' change_status(status, message, account). account is optional -
328                if not specified status is changed for all accounts. '''
329                if status not in ('offline', 'online', 'chat', 
330                        'away', 'xa', 'dnd', 'invisible'):
331                        raise InvalidArgument
332                        return DBUS_BOOLEAN(False)
333                if account:
334                        gobject.idle_add(gajim.interface.roster.send_status, account,
335                                status, message)
336                else:
337                        # account not specified, so change the status of all accounts
338                        for acc in gajim.contacts.get_accounts():
339                                if not gajim.config.get_per('accounts', acc,
340                                'sync_with_global_status'):
341                                        continue
342                                gobject.idle_add(gajim.interface.roster.send_status, acc,
343                                        status, message)
344                return DBUS_BOOLEAN(False)
345
346        @dbus.service.method(INTERFACE, in_signature='', out_signature='')
347        def show_next_pending_event(self):
348                '''Show the window(s) with next pending event in tabbed/group chats.'''
349                if gajim.events.get_nb_events():
350                        gajim.interface.systray.handle_first_event()
351
352        @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}')
353        def contact_info(self, jid):
354                '''get vcard info for a contact. Return cached value of the vcard.
355                '''
356                if not isinstance(jid, unicode):
357                        jid = unicode(jid)
358                if not jid:
359                        raise MissingArgument
360                        return DBUS_DICT_SV()
361                jid = self._get_real_jid(jid)
362
363                cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
364                if cached_vcard:
365                        return get_dbus_struct(cached_vcard)
366
367                # return empty dict
368                return DBUS_DICT_SV()
369
370        @dbus.service.method(INTERFACE, in_signature='', out_signature='as')
371        def list_accounts(self):
372                '''list register accounts'''
373                result = gajim.contacts.get_accounts()
374                result_array = dbus.Array([], signature='s')
375                if result and len(result) > 0:
376                        for account in result:
377                                result_array.append(DBUS_STRING(account))
378                return result_array
379
380        @dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}')
381        def account_info(self, account):
382                '''show info on account: resource, jid, nick, prio, message'''
383                result = DBUS_DICT_SS()
384                if gajim.connections.has_key(account):
385                        # account is valid
386                        con = gajim.connections[account]
387                        index = con.connected
388                        result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
389                        result['name'] = DBUS_STRING(con.name)
390                        result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name))
391                        result['message'] = DBUS_STRING(con.status)
392                        result['priority'] = DBUS_STRING(unicode(con.priority))
393                        result['resource'] = DBUS_STRING(unicode(gajim.config.get_per(
394                                'accounts', con.name, 'resource')))
395                return result
396
397        @dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}')
398        def list_contacts(self, account):
399                '''list all contacts in the roster. If the first argument is specified,
400                then return the contacts for the specified account'''
401                result = dbus.Array([], signature='aa{sv}')
402                accounts = gajim.contacts.get_accounts()
403                if len(accounts) == 0:
404                        return result
405                if account:
406                        accounts_to_search = [account]
407                else:
408                        accounts_to_search = accounts
409                for acct in accounts_to_search:
410                        if acct in accounts:
411                                for jid in gajim.contacts.get_jid_list(acct):
412                                        item = self._contacts_as_dbus_structure(
413                                                gajim.contacts.get_contact(acct, jid))
414                                        if item:
415                                                result.append(item)
416                return result
417
418        @dbus.service.method(INTERFACE, in_signature='', out_signature='')
419        def toggle_roster_appearance(self):
420                ''' shows/hides the roster window '''
421                win = gajim.interface.roster.window
422                if win.get_property('visible'):
423                        gobject.idle_add(win.hide)
424                else:
425                        win.present()
426                        # preserve the 'steal focus preservation'
427                        if self._is_first():
428                                win.window.focus()
429                        else:
430                                win.window.focus(long(time()))
431
432        @dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}')
433        def prefs_list(self):
434                prefs_dict = DBUS_DICT_SS()
435                def get_prefs(data, name, path, value):
436                        if value is None:
437                                return
438                        key = ''
439                        if path is not None:
440                                for node in path:
441                                        key += node + '#'
442                        key += name
443                        prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
444                gajim.config.foreach(get_prefs)
445                return prefs_dict
446
447        @dbus.service.method(INTERFACE, in_signature='', out_signature='b')
448        def prefs_store(self):
449                try:
450                        gajim.interface.save_config()
451                except Exception, e:
452                        return DBUS_BOOLEAN(False)
453                return DBUS_BOOLEAN(True)
454
455        @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
456        def prefs_del(self, key):
457                if not key:
458                        return DBUS_BOOLEAN(False)
459                key_path = key.split('#', 2)
460                if len(key_path) != 3:
461                        return DBUS_BOOLEAN(False)
462                if key_path[2] == '*':
463                        gajim.config.del_per(key_path[0], key_path[1])
464                else:
465                        gajim.config.del_per(key_path[0], key_path[1], key_path[2])
466                return DBUS_BOOLEAN(True)
467
468        @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
469        def prefs_put(self, key):
470                if not key:
471                        return DBUS_BOOLEAN(False)
472                key_path = key.split('#', 2)
473                if len(key_path) < 3:
474                        subname, value = key.split('=', 1)
475                        gajim.config.set(subname, value)
476                        return DBUS_BOOLEAN(True)
477                subname, value = key_path[2].split('=', 1)
478                gajim.config.set_per(key_path[0], key_path[1], subname, value)
479                return DBUS_BOOLEAN(True)
480
481        @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
482        def add_contact(self, jid, account):
483                if account:
484                        if account in gajim.connections and \
485                                gajim.connections[account].connected > 1:
486                                # if given account is active, use it
487                                AddNewContactWindow(account = account, jid = jid)
488                        else:
489                                # wrong account
490                                return DBUS_BOOLEAN(False)
491                else:
492                        # if account is not given, show account combobox
493                        AddNewContactWindow(account = None, jid = jid)
494                return DBUS_BOOLEAN(True)
495
496        @dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
497        def remove_contact(self, jid, account):
498                jid = self._get_real_jid(jid, account)
499                accounts = gajim.contacts.get_accounts()
500
501                # if there is only one account in roster, take it as default
502                if account:
503                        accounts = [account]
504                contact_exists = False
505                for account in accounts:
506                        contacts = gajim.contacts.get_contact(account, jid)
507                        if contacts:
508                                gajim.connections[account].unsubscribe(jid)
509                                for contact in contacts:
510                                        gajim.interface.roster.remove_contact(contact, account)
511                                gajim.contacts.remove_jid(account, jid)
512                                contact_exists = True
513                return DBUS_BOOLEAN(contact_exists)
514
515        def _is_first(self):
516                if self.first_show:
517                        self.first_show = False
518                        return True
519                return False
520
521        def _get_real_jid(self, jid, account = None):
522                '''get the real jid from the given one: removes xmpp: or get jid from nick
523                if account is specified, search only in this account
524                '''
525                if account:
526                        accounts = [account]
527                else:
528                        accounts = gajim.connections.keys()
529                if jid.startswith('xmpp:'):
530                        return jid[5:] # len('xmpp:') = 5
531                nick_in_roster = None # Is jid a nick ?
532                for account in accounts:
533                        # Does jid exists in roster of one account ?
534                        if gajim.contacts.get_contacts_from_jid(account, jid):
535                                return jid
536                        if not nick_in_roster:
537                                # look in all contact if one has jid as nick
538                                for jid_ in gajim.contacts.get_jid_list(account):
539                                        c = gajim.contacts.get_contacts_from_jid(account, jid_)
540                                        if c[0].name == jid:
541                                                nick_in_roster = jid_
542                                                break
543                if nick_in_roster:
544                        # We have not found jid in roster, but we found is as a nick
545                        return nick_in_roster
546                # We have not found it as jid nor as nick, probably a not in roster jid
547                return jid
548
549        def _contacts_as_dbus_structure(self, contacts):
550                ''' get info from list of Contact objects and create dbus dict '''
551                if not contacts:
552                        return None
553                prim_contact = None # primary contact
554                for contact in contacts:
555                        if prim_contact == None or contact.priority > prim_contact.priority:
556                                prim_contact = contact
557                contact_dict = DBUS_DICT_SV()
558                contact_dict['name'] = DBUS_STRING(prim_contact.name)
559                contact_dict['show'] = DBUS_STRING(prim_contact.show)
560                contact_dict['jid'] = DBUS_STRING(prim_contact.jid)
561                if prim_contact.keyID:
562                        keyID = None
563                        if len(prim_contact.keyID) == 8:
564                                keyID = prim_contact.keyID
565                        elif len(prim_contact.keyID) == 16:
566                                keyID = prim_contact.keyID[8:]
567                        if keyID:
568                                contact_dict['openpgp'] = keyID
569                contact_dict['resources'] = dbus.Array([], signature='(sis)')
570                for contact in contacts:
571                        resource_props = dbus.Struct((DBUS_STRING(contact.resource),
572                                dbus.Int32(contact.priority), DBUS_STRING(contact.status)))
573                        contact_dict['resources'].append(resource_props)
574                return contact_dict
575
576        @dbus.service.method(INTERFACE, in_signature='', out_signature='s')
577        def get_unread_msgs_number(self):
578                return DBUS_STRING(str(gajim.events.get_nb_events()))
579
580        @dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
581        def start_chat(self, account):
582                if not account:
583                        # error is shown in gajim-remote check_arguments(..)
584                        return DBUS_BOOLEAN(False)
585                NewChatDialog(account)
586                return DBUS_BOOLEAN(True)
587
588        @dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
589        def send_xml(self, xml, account):
590                if account:
591                        gajim.connections[account].send_stanza(xml)
592                else:
593                        for acc in gajim.contacts.get_accounts():
594                                gajim.connections[acc].send_stanza(xml)
595
596        @dbus.service.method(INTERFACE, in_signature='ssss', out_signature='')
597        def join_room(self, room_jid, nick, password, account):
598                if not account:
599                        # get the first connected account
600                        accounts = gajim.connections.keys()
601                        for acct in accounts:
602                                if gajim.account_is_connected(acct):
603                                        account = acct
604                                        break
605                        if not account:
606                                return
607                if not nick:
608                        nick = ''
609                        gajim.interface.instances[account]['join_gc'] = \
610                                        JoinGroupchatWindow(account, room_jid, nick)
611                else:
612                        gajim.connections[account].join_gc(nick, room_jid, password)
Note: See TracBrowser for help on using the browser.