2 ## src/remote_control.py
4 ## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com>
5 ## Dimitur Kirov <dkirov AT gmail.com>
6 ## Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2005-2008 Yann Leboulanger <asterix AT lagaule.org>
8 ## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
9 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
10 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
11 ## Julien Pivotto <roidelapluie AT gmail.com>
12 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
14 ## This file is part of Gajim.
16 ## Gajim is free software; you can redistribute it and/or modify
17 ## it under the terms of the GNU General Public License as published
18 ## by the Free Software Foundation; version 3 only.
20 ## Gajim is distributed in the hope that it will be useful,
21 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ## GNU General Public License for more details.
25 ## You should have received a copy of the GNU General Public License
26 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
32 from common
import gajim
33 from common
import helpers
35 from dialogs
import AddNewContactWindow
, NewChatDialog
, JoinGroupchatWindow
37 from common
import dbus_support
38 if dbus_support
.supported
:
44 INTERFACE
= 'org.gajim.dbus.RemoteInterface'
45 OBJ_PATH
= '/org/gajim/dbus/RemoteObject'
46 SERVICE
= 'org.gajim.dbus'
50 # in most cases it is a utf-8 string
51 DBUS_STRING
= dbus
.String
53 # general type (for use in dicts, where all values should have the same type)
54 DBUS_BOOLEAN
= dbus
.Boolean
55 DBUS_DOUBLE
= dbus
.Double
56 DBUS_INT32
= dbus
.Int32
57 # dictionary with string key and binary value
58 DBUS_DICT_SV
= lambda : dbus
.Dictionary({}, signature
="sv")
59 # dictionary with string key and value
60 DBUS_DICT_SS
= lambda : dbus
.Dictionary({}, signature
="ss")
61 # empty type (there is no equivalent of None on D-Bus, but historically gajim
63 DBUS_NONE
= lambda : dbus
.Int32(0)
65 def get_dbus_struct(obj
):
66 ''' recursively go through all the items and replace
67 them with their casted dbus equivalents
71 if isinstance(obj
, (unicode, str)):
72 return DBUS_STRING(obj
)
73 if isinstance(obj
, int):
74 return DBUS_INT32(obj
)
75 if isinstance(obj
, float):
76 return DBUS_DOUBLE(obj
)
77 if isinstance(obj
, bool):
78 return DBUS_BOOLEAN(obj
)
79 if isinstance(obj
, (list, tuple)):
80 result
= dbus
.Array([get_dbus_struct(i
) for i
in obj
],
85 if isinstance(obj
, dict):
86 result
= DBUS_DICT_SV()
87 for key
, value
in obj
.items():
88 result
[DBUS_STRING(key
)] = get_dbus_struct(value
)
97 self
.signal_object
= None
98 session_bus
= dbus_support
.session_bus
.SessionBus()
100 bus_name
= dbus
.service
.BusName(SERVICE
, bus
=session_bus
)
101 self
.signal_object
= SignalObject(bus_name
)
103 def raise_signal(self
, signal
, arg
):
104 if self
.signal_object
:
106 getattr(self
.signal_object
, signal
)(get_dbus_struct(arg
))
107 except UnicodeDecodeError:
108 pass # ignore error when we fail to announce on dbus
111 class SignalObject(dbus
.service
.Object
):
112 ''' Local object definition for /org/gajim/dbus/RemoteObject.
113 (This docstring is not be visible, because the clients can access only the remote object.)'''
115 def __init__(self
, bus_name
):
116 self
.first_show
= True
117 self
.vcard_account
= None
119 # register our dbus API
120 dbus
.service
.Object
.__init
__(self
, bus_name
, OBJ_PATH
)
122 @dbus.service
.signal(INTERFACE
, signature
='av')
123 def Roster(self
, account_and_data
):
126 @dbus.service
.signal(INTERFACE
, signature
='av')
127 def AccountPresence(self
, status_and_account
):
130 @dbus.service
.signal(INTERFACE
, signature
='av')
131 def ContactPresence(self
, account_and_array
):
134 @dbus.service
.signal(INTERFACE
, signature
='av')
135 def ContactAbsence(self
, account_and_array
):
138 @dbus.service
.signal(INTERFACE
, signature
='av')
139 def ContactStatus(self
, account_and_array
):
142 @dbus.service
.signal(INTERFACE
, signature
='av')
143 def NewMessage(self
, account_and_array
):
146 @dbus.service
.signal(INTERFACE
, signature
='av')
147 def Subscribe(self
, account_and_array
):
150 @dbus.service
.signal(INTERFACE
, signature
='av')
151 def Subscribed(self
, account_and_array
):
154 @dbus.service
.signal(INTERFACE
, signature
='av')
155 def Unsubscribed(self
, account_and_jid
):
158 @dbus.service
.signal(INTERFACE
, signature
='av')
159 def NewAccount(self
, account_and_array
):
162 @dbus.service
.signal(INTERFACE
, signature
='av')
163 def VcardInfo(self
, account_and_vcard
):
166 @dbus.service
.signal(INTERFACE
, signature
='av')
167 def LastStatusTime(self
, account_and_array
):
170 @dbus.service
.signal(INTERFACE
, signature
='av')
171 def OsInfo(self
, account_and_array
):
174 @dbus.service
.signal(INTERFACE
, signature
='av')
175 def EntityTime(self
, account_and_array
):
178 @dbus.service
.signal(INTERFACE
, signature
='av')
179 def GCPresence(self
, account_and_array
):
182 @dbus.service
.signal(INTERFACE
, signature
='av')
183 def GCMessage(self
, account_and_array
):
186 @dbus.service
.signal(INTERFACE
, signature
='av')
187 def RosterInfo(self
, account_and_array
):
190 @dbus.service
.signal(INTERFACE
, signature
='av')
191 def NewGmail(self
, account_and_array
):
194 def raise_signal(self
, signal
, arg
):
195 '''raise a signal, with a single argument of unspecified type
196 Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).'''
197 getattr(self
, signal
)(arg
)
199 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='s')
200 def get_status(self
, account
):
201 '''Returns status (show to be exact) which is the global one
202 unless account is given'''
204 # If user did not ask for account, returns the global status
205 return DBUS_STRING(helpers
.get_global_show())
206 # return show for the given account
207 index
= gajim
.connections
[account
].connected
208 return DBUS_STRING(gajim
.SHOW_LIST
[index
])
210 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='s')
211 def get_status_message(self
, account
):
212 '''Returns status which is the global one
213 unless account is given'''
215 # If user did not ask for account, returns the global status
216 return DBUS_STRING(str(helpers
.get_global_status()))
217 # return show for the given account
218 status
= gajim
.connections
[account
].status
219 return DBUS_STRING(status
)
221 def _get_account_and_contact(self
, account
, jid
):
222 '''get the account (if not given) and contact instance from jid'''
223 connected_account
= None
225 accounts
= gajim
.contacts
.get_accounts()
226 # if there is only one account in roster, take it as default
227 # if user did not ask for account
228 if not account
and len(accounts
) == 1:
229 account
= accounts
[0]
231 if gajim
.connections
[account
].connected
> 1: # account is connected
232 connected_account
= account
233 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
236 for account
in accounts
:
237 contact
= gajim
.contacts
.get_contact_with_highest_priority(account
,
239 if contact
and gajim
.connections
[account
].connected
> 1:
240 # account is connected
241 connected_account
= account
246 return connected_account
, contact
248 def _get_account_for_groupchat(self
, account
, room_jid
):
249 '''get the account which is connected to groupchat (if not given)
250 or check if the given account is connected to the groupchat'''
251 connected_account
= None
252 accounts
= gajim
.contacts
.get_accounts()
253 # if there is only one account in roster, take it as default
254 # if user did not ask for account
255 if not account
and len(accounts
) == 1:
256 account
= accounts
[0]
258 if gajim
.connections
[account
].connected
> 1 and \
259 room_jid
in gajim
.gc_connected
[account
] and \
260 gajim
.gc_connected
[account
][room_jid
]:
261 # account and groupchat are connected
262 connected_account
= account
264 for account
in accounts
:
265 if gajim
.connections
[account
].connected
> 1 and \
266 room_jid
in gajim
.gc_connected
[account
] and \
267 gajim
.gc_connected
[account
][room_jid
]:
268 # account and groupchat are connected
269 connected_account
= account
271 return connected_account
273 @dbus.service
.method(INTERFACE
, in_signature
='sss', out_signature
='b')
274 def send_file(self
, file_path
, jid
, account
):
275 '''send file, located at 'file_path' to 'jid', using account
276 (optional) 'account' '''
277 jid
= self
._get
_real
_jid
(jid
, account
)
278 connected_account
, contact
= self
._get
_account
_and
_contact
(account
, jid
)
280 if connected_account
:
281 if file_path
.startswith('file://'):
282 file_path
=file_path
[7:]
283 if os
.path
.isfile(file_path
): # is it file?
284 gajim
.interface
.instances
['file_transfers'].send_file(
285 connected_account
, contact
, file_path
)
286 return DBUS_BOOLEAN(True)
287 return DBUS_BOOLEAN(False)
289 def _send_message(self
, jid
, message
, keyID
, account
, type_
= 'chat',
291 '''can be called from send_chat_message (default when send_message)
292 or send_single_message'''
293 if not jid
or not message
:
294 return DBUS_BOOLEAN(False)
298 connected_account
, contact
= self
._get
_account
_and
_contact
(account
, jid
)
299 if connected_account
:
300 connection
= gajim
.connections
[connected_account
]
301 connection
.send_message(jid
, message
, keyID
, type_
, subject
)
302 return DBUS_BOOLEAN(True)
303 return DBUS_BOOLEAN(False)
305 @dbus.service
.method(INTERFACE
, in_signature
='ssss', out_signature
='b')
306 def send_chat_message(self
, jid
, message
, keyID
, account
):
307 '''Send chat 'message' to 'jid', using account (optional) 'account'.
308 if keyID is specified, encrypt the message with the pgp key '''
309 jid
= self
._get
_real
_jid
(jid
, account
)
310 return self
._send
_message
(jid
, message
, keyID
, account
)
312 @dbus.service
.method(INTERFACE
, in_signature
='sssss', out_signature
='b')
313 def send_single_message(self
, jid
, subject
, message
, keyID
, account
):
314 '''Send single 'message' to 'jid', using account (optional) 'account'.
315 if keyID is specified, encrypt the message with the pgp key '''
316 jid
= self
._get
_real
_jid
(jid
, account
)
317 return self
._send
_message
(jid
, message
, keyID
, account
, type, subject
)
319 @dbus.service
.method(INTERFACE
, in_signature
='sss', out_signature
='b')
320 def send_groupchat_message(self
, room_jid
, message
, account
):
321 '''Send 'message' to groupchat 'room_jid',
322 using account (optional) 'account'.'''
323 if not room_jid
or not message
:
324 return DBUS_BOOLEAN(False)
325 connected_account
= self
._get
_account
_for
_groupchat
(account
, room_jid
)
326 if connected_account
:
327 connection
= gajim
.connections
[connected_account
]
328 connection
.send_gc_message(room_jid
, message
)
329 return DBUS_BOOLEAN(True)
330 return DBUS_BOOLEAN(False)
332 @dbus.service
.method(INTERFACE
, in_signature
='sss', out_signature
='b')
333 def open_chat(self
, jid
, account
, message
):
334 '''Shows the tabbed window for new message to 'jid', using account
335 (optional) 'account' '''
337 raise dbus_support
.MissingArgument()
338 jid
= self
._get
_real
_jid
(jid
, account
)
340 jid
= helpers
.parse_jid(jid
)
342 # Jid is not conform, ignore it
343 return DBUS_BOOLEAN(False)
348 accounts
= gajim
.connections
.keys()
349 if len(accounts
) == 1:
350 account
= accounts
[0]
351 connected_account
= None
352 first_connected_acct
= None
353 for acct
in accounts
:
354 if gajim
.connections
[acct
].connected
> 1: # account is online
355 contact
= gajim
.contacts
.get_first_contact_from_jid(acct
, jid
)
356 if gajim
.interface
.msg_win_mgr
.has_window(jid
, acct
):
357 connected_account
= acct
361 connected_account
= acct
363 # we send the message to jid not in roster, because account is
364 # specified, or there is only one account
366 connected_account
= acct
367 elif first_connected_acct
is None:
368 first_connected_acct
= acct
370 # if jid is not a conntact, open-chat with first connected account
371 if connected_account
is None and first_connected_acct
:
372 connected_account
= first_connected_acct
374 if connected_account
:
375 gajim
.interface
.new_chat_from_jid(connected_account
, jid
, message
)
376 # preserve the 'steal focus preservation'
377 win
= gajim
.interface
.msg_win_mgr
.get_window(jid
,
378 connected_account
).window
379 if win
.get_property('visible'):
381 return DBUS_BOOLEAN(True)
382 return DBUS_BOOLEAN(False)
384 @dbus.service
.method(INTERFACE
, in_signature
='sss', out_signature
='b')
385 def change_status(self
, status
, message
, account
):
386 ''' change_status(status, message, account). account is optional -
387 if not specified status is changed for all accounts. '''
388 if status
not in ('offline', 'online', 'chat',
389 'away', 'xa', 'dnd', 'invisible'):
390 return DBUS_BOOLEAN(False)
392 gobject
.idle_add(gajim
.interface
.roster
.send_status
, account
,
395 # account not specified, so change the status of all accounts
396 for acc
in gajim
.contacts
.get_accounts():
397 if not gajim
.config
.get_per('accounts', acc
,
398 'sync_with_global_status'):
400 gobject
.idle_add(gajim
.interface
.roster
.send_status
, acc
,
402 return DBUS_BOOLEAN(False)
404 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='')
405 def show_next_pending_event(self
):
406 '''Show the window(s) with next pending event in tabbed/group chats.'''
407 if gajim
.events
.get_nb_events():
408 gajim
.interface
.systray
.handle_first_event()
410 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='a{sv}')
411 def contact_info(self
, jid
):
412 '''get vcard info for a contact. Return cached value of the vcard.
414 if not isinstance(jid
, unicode):
417 raise dbus_support
.MissingArgument()
418 jid
= self
._get
_real
_jid
(jid
)
420 cached_vcard
= gajim
.connections
.values()[0].get_cached_vcard(jid
)
422 return get_dbus_struct(cached_vcard
)
425 return DBUS_DICT_SV()
427 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='as')
428 def list_accounts(self
):
429 '''list register accounts'''
430 result
= gajim
.contacts
.get_accounts()
431 result_array
= dbus
.Array([], signature
='s')
432 if result
and len(result
) > 0:
433 for account
in result
:
434 result_array
.append(DBUS_STRING(account
))
437 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='a{ss}')
438 def account_info(self
, account
):
439 '''show info on account: resource, jid, nick, prio, message'''
440 result
= DBUS_DICT_SS()
441 if account
in gajim
.connections
:
443 con
= gajim
.connections
[account
]
444 index
= con
.connected
445 result
['status'] = DBUS_STRING(gajim
.SHOW_LIST
[index
])
446 result
['name'] = DBUS_STRING(con
.name
)
447 result
['jid'] = DBUS_STRING(gajim
.get_jid_from_account(con
.name
))
448 result
['message'] = DBUS_STRING(con
.status
)
449 result
['priority'] = DBUS_STRING(unicode(con
.priority
))
450 result
['resource'] = DBUS_STRING(unicode(gajim
.config
.get_per(
451 'accounts', con
.name
, 'resource')))
454 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='aa{sv}')
455 def list_contacts(self
, account
):
456 '''list all contacts in the roster. If the first argument is specified,
457 then return the contacts for the specified account'''
458 result
= dbus
.Array([], signature
='aa{sv}')
459 accounts
= gajim
.contacts
.get_accounts()
460 if len(accounts
) == 0:
463 accounts_to_search
= [account
]
465 accounts_to_search
= accounts
466 for acct
in accounts_to_search
:
468 for jid
in gajim
.contacts
.get_jid_list(acct
):
469 item
= self
._contacts
_as
_dbus
_structure
(
470 gajim
.contacts
.get_contacts(acct
, jid
))
475 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='')
476 def toggle_roster_appearance(self
):
477 ''' shows/hides the roster window '''
478 win
= gajim
.interface
.roster
.window
479 if win
.get_property('visible'):
480 gobject
.idle_add(win
.hide
)
483 # preserve the 'steal focus preservation'
487 win
.window
.focus(long(time()))
489 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='')
490 def toggle_ipython(self
):
491 ''' shows/hides the ipython window '''
492 win
= gajim
.ipython_window
494 if win
.window
.is_visible():
495 gobject
.idle_add(win
.hide
)
500 gajim
.interface
.create_ipython_window()
502 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='a{ss}')
503 def prefs_list(self
):
504 prefs_dict
= DBUS_DICT_SS()
505 def get_prefs(data
, name
, path
, value
):
513 prefs_dict
[DBUS_STRING(key
)] = DBUS_STRING(value
[1])
514 gajim
.config
.foreach(get_prefs
)
517 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='b')
518 def prefs_store(self
):
520 gajim
.interface
.save_config()
522 return DBUS_BOOLEAN(False)
523 return DBUS_BOOLEAN(True)
525 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='b')
526 def prefs_del(self
, key
):
528 return DBUS_BOOLEAN(False)
529 key_path
= key
.split('#', 2)
530 if len(key_path
) != 3:
531 return DBUS_BOOLEAN(False)
532 if key_path
[2] == '*':
533 gajim
.config
.del_per(key_path
[0], key_path
[1])
535 gajim
.config
.del_per(key_path
[0], key_path
[1], key_path
[2])
536 return DBUS_BOOLEAN(True)
538 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='b')
539 def prefs_put(self
, key
):
541 return DBUS_BOOLEAN(False)
542 key_path
= key
.split('#', 2)
543 if len(key_path
) < 3:
544 subname
, value
= key
.split('=', 1)
545 gajim
.config
.set(subname
, value
)
546 return DBUS_BOOLEAN(True)
547 subname
, value
= key_path
[2].split('=', 1)
548 gajim
.config
.set_per(key_path
[0], key_path
[1], subname
, value
)
549 return DBUS_BOOLEAN(True)
551 @dbus.service
.method(INTERFACE
, in_signature
='ss', out_signature
='b')
552 def add_contact(self
, jid
, account
):
554 if account
in gajim
.connections
and \
555 gajim
.connections
[account
].connected
> 1:
556 # if given account is active, use it
557 AddNewContactWindow(account
= account
, jid
= jid
)
560 return DBUS_BOOLEAN(False)
562 # if account is not given, show account combobox
563 AddNewContactWindow(account
= None, jid
= jid
)
564 return DBUS_BOOLEAN(True)
566 @dbus.service
.method(INTERFACE
, in_signature
='ss', out_signature
='b')
567 def remove_contact(self
, jid
, account
):
568 jid
= self
._get
_real
_jid
(jid
, account
)
569 accounts
= gajim
.contacts
.get_accounts()
571 # if there is only one account in roster, take it as default
574 contact_exists
= False
575 for account
in accounts
:
576 contacts
= gajim
.contacts
.get_contacts(account
, jid
)
578 gajim
.connections
[account
].unsubscribe(jid
)
579 for contact
in contacts
:
580 gajim
.interface
.roster
.remove_contact(contact
, account
)
581 gajim
.contacts
.remove_jid(account
, jid
)
582 contact_exists
= True
583 return DBUS_BOOLEAN(contact_exists
)
587 self
.first_show
= False
591 def _get_real_jid(self
, jid
, account
= None):
592 '''get the real jid from the given one: removes xmpp: or get jid from nick
593 if account is specified, search only in this account
598 accounts
= gajim
.connections
.keys()
599 if jid
.startswith('xmpp:'):
600 return jid
[5:] # len('xmpp:') = 5
601 nick_in_roster
= None # Is jid a nick ?
602 for account
in accounts
:
603 # Does jid exists in roster of one account ?
604 if gajim
.contacts
.get_contacts(account
, jid
):
606 if not nick_in_roster
:
607 # look in all contact if one has jid as nick
608 for jid_
in gajim
.contacts
.get_jid_list(account
):
609 c
= gajim
.contacts
.get_contacts(account
, jid_
)
611 nick_in_roster
= jid_
614 # We have not found jid in roster, but we found is as a nick
615 return nick_in_roster
616 # We have not found it as jid nor as nick, probably a not in roster jid
619 def _contacts_as_dbus_structure(self
, contacts
):
620 ''' get info from list of Contact objects and create dbus dict '''
623 prim_contact
= None # primary contact
624 for contact
in contacts
:
625 if prim_contact
is None or contact
.priority
> prim_contact
.priority
:
626 prim_contact
= contact
627 contact_dict
= DBUS_DICT_SV()
628 contact_dict
['name'] = DBUS_STRING(prim_contact
.name
)
629 contact_dict
['show'] = DBUS_STRING(prim_contact
.show
)
630 contact_dict
['jid'] = DBUS_STRING(prim_contact
.jid
)
631 if prim_contact
.keyID
:
633 if len(prim_contact
.keyID
) == 8:
634 keyID
= prim_contact
.keyID
635 elif len(prim_contact
.keyID
) == 16:
636 keyID
= prim_contact
.keyID
[8:]
638 contact_dict
['openpgp'] = keyID
639 contact_dict
['resources'] = dbus
.Array([], signature
='(sis)')
640 for contact
in contacts
:
641 resource_props
= dbus
.Struct((DBUS_STRING(contact
.resource
),
642 dbus
.Int32(contact
.priority
), DBUS_STRING(contact
.status
)))
643 contact_dict
['resources'].append(resource_props
)
644 contact_dict
['groups'] = dbus
.Array([], signature
='(s)')
645 for group
in prim_contact
.groups
:
646 contact_dict
['groups'].append((DBUS_STRING(group
),))
649 @dbus.service
.method(INTERFACE
, in_signature
='', out_signature
='s')
650 def get_unread_msgs_number(self
):
651 return DBUS_STRING(str(gajim
.events
.get_nb_events()))
653 @dbus.service
.method(INTERFACE
, in_signature
='s', out_signature
='b')
654 def start_chat(self
, account
):
656 # error is shown in gajim-remote check_arguments(..)
657 return DBUS_BOOLEAN(False)
658 NewChatDialog(account
)
659 return DBUS_BOOLEAN(True)
661 @dbus.service
.method(INTERFACE
, in_signature
='ss', out_signature
='')
662 def send_xml(self
, xml
, account
):
664 gajim
.connections
[account
].send_stanza(str(xml
))
666 for acc
in gajim
.contacts
.get_accounts():
667 gajim
.connections
[acc
].send_stanza(str(xml
))
669 @dbus.service
.method(INTERFACE
, in_signature
='ssss', out_signature
='')
670 def join_room(self
, room_jid
, nick
, password
, account
):
672 # get the first connected account
673 accounts
= gajim
.connections
.keys()
674 for acct
in accounts
:
675 if gajim
.account_is_connected(acct
):
682 gajim
.interface
.instances
[account
]['join_gc'] = \
683 JoinGroupchatWindow(account
, room_jid
, nick
)
685 gajim
.interface
.join_gc_room(account
, room_jid
, nick
, password
)