root/branches/jingle/src/remote_control.py

Revision 8408, 21.3 kB (checked in by roidelapluie, 16 months ago)

Fix a name error in [8407]

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