remote_control.py 36.7 KB
Newer Older
roidelapluie's avatar
roidelapluie committed
1
# -*- coding:utf-8 -*-
roidelapluie's avatar
roidelapluie committed
2
## src/remote_control.py
3
##
roidelapluie's avatar
roidelapluie committed
4
5
6
## Copyright (C) 2005-2006 Andrew Sayman <lorien420 AT myrealbox.com>
##                         Dimitur Kirov <dkirov AT gmail.com>
##                         Nikos Kouremenos <kourem AT gmail.com>
Dicson's avatar
Dicson committed
7
## Copyright (C) 2005-2014 Yann Leboulanger <asterix AT lagaule.org>
roidelapluie's avatar
roidelapluie committed
8
9
10
11
12
## Copyright (C) 2006-2007 Travis Shirk <travis AT pobox.com>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
##                    Julien Pivotto <roidelapluie AT gmail.com>
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
13
##
14
15
16
## This file is part of Gajim.
##
## 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.
19
##
20
## Gajim is distributed in the hope that it will be useful,
21
## but WITHOUT ANY WARRANTY; without even the implied warranty of
roidelapluie's avatar
roidelapluie committed
22
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
24
## GNU General Public License for more details.
##
25
## You should have received a copy of the GNU General Public License
roidelapluie's avatar
roidelapluie committed
26
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
27
##
28

29
import os
30
31
import base64
import mimetypes
André's avatar
André committed
32
33
34

from gi.repository import GLib
from gi.repository import Gio
35

36
from gajim.common import app
André's avatar
André committed
37
from gajim.common import helpers
38
from gajim.gtk import AddNewContactWindow
André's avatar
André committed
39
40
41
from gajim.common import ged
from gajim.common.connection_handlers_events import MessageOutgoingEvent
from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
42

43

44
def get_dbus_struct(obj):
45
46
47
48
49
    """
    Recursively go through all the items and replace them with their casted dbus
    equivalents
    """
    if obj is None:
André's avatar
André committed
50
        return None
Yann Leboulanger's avatar
Yann Leboulanger committed
51
    if isinstance(obj, str):
André's avatar
André committed
52
        return GLib.Variant('s', obj)
53
    if isinstance(obj, int):
André's avatar
André committed
54
        return GLib.Variant('i', obj)
55
    if isinstance(obj, float):
André's avatar
André committed
56
        return GLib.Variant('d', obj)
57
    if isinstance(obj, bool):
André's avatar
André committed
58
        return GLib.Variant('b', obj)
59
    if isinstance(obj, (list, tuple)):
André's avatar
André committed
60
61
        lst = [get_dbus_struct(i) for i in obj if i is not None]
        result = GLib.Variant('av', lst)
62
63
        return result
    if isinstance(obj, dict):
André's avatar
André committed
64
        result = GLib.VariantDict()
65
        for key, value in obj.items():
André's avatar
André committed
66
67
            result.insert_value(key, get_dbus_struct(value))
        return result.end()
68
    # unknown type
André's avatar
André committed
69
    return None
70

71

André's avatar
André committed
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
class Server:
    def __init__(self, con, path):
        method_outargs = {}
        method_inargs = {}
        for interface in Gio.DBusNodeInfo.new_for_xml(self.__doc__).interfaces:

            for method in interface.methods:
                method_outargs[method.name] = '(' + ''.join(
                    [arg.signature for arg in method.out_args]) + ')'
                method_inargs[method.name] = tuple(
                    arg.signature for arg in method.in_args)

            con.register_object(
                object_path=path,
                interface_info=interface,
                method_call_closure=self.on_method_call)

        self.method_inargs = method_inargs
        self.method_outargs = method_outargs

    def on_method_call(self, connection, sender, object_path, interface_name,
                       method_name, parameters, invocation):

        args = list(parameters.unpack())
        for i, sig in enumerate(self.method_inargs[method_name]):
            if sig is 'h':
                msg = invocation.get_message()
                fd_list = msg.get_unix_fd_list()
                args[i] = fd_list.get(args[i])

        result = getattr(self, method_name)(*args)

        # out_args is atleast (signature1). We therefore always wrap the result
        # as a tuple. Refer to https://bugzilla.gnome.org/show_bug.cgi?id=765603
        result = (result, )

        out_args = self.method_outargs[method_name]
        if out_args != '()':
            variant = GLib.Variant(out_args, result)
            invocation.return_value(variant)
        else:
            invocation.return_value(None)


class GajimRemote(Server):
    '''
    <!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
    'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
    <node>
        <interface name='org.freedesktop.DBus.Introspectable'>
            <method name='Introspect'>
                <arg name='data' direction='out' type='s'/>
            </method>
        </interface>
        <interface name='org.gajim.dbus.RemoteInterface'>
            <method name='account_info'>
                <arg name='account' type='s' />
                <arg direction='out' type='a{ss}' />
            </method>
            <method name='add_contact'>
                <arg name='jid' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='change_avatar'>
                <arg name='picture' type='s' />
                <arg name='account' type='s' />
            </method>
            <method name='change_status'>
                <arg name='status' type='s' />
                <arg name='message' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='get_status'>
                <arg name='account' type='s' />
                <arg direction='out' type='s' />
            </method>
            <method name='get_status_message'>
                <arg name='account' type='s' />
                <arg direction='out' type='s' />
            </method>
            <method name='get_unread_msgs_number'>
                <arg direction='out' type='s' />
            </method>
            <method name='join_room'>
                <arg name='room_jid' type='s' />
                <arg name='nick' type='s' />
                <arg name='password' type='s' />
                <arg name='account' type='s' />
            </method>
            <method name='list_accounts'>
                <arg direction='out' type='as' />
            </method>
            <method name='list_contacts'>
                <arg name='account' type='s' />
                <arg direction='out' type='aa{sv}' />
            </method>
            <method name='open_chat'>
                <arg name='jid' type='s' />
                <arg name='account' type='s' />
                <arg name='message' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='prefs_del'>
                <arg name='key' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='prefs_list'>
                <arg direction='out' type='a{ss}' />
            </method>
            <method name='prefs_put'>
                <arg name='key' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='prefs_store'>
                <arg direction='out' type='b' />
            </method>
            <method name='remove_contact'>
                <arg name='jid' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='send_chat_message'>
                <arg name='jid' type='s' />
                <arg name='message' type='s' />
                <arg name='keyID' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='send_file'>
                <arg name='file_path' type='s' />
                <arg name='jid' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='send_groupchat_message'>
                <arg name='room_jid' type='s' />
                <arg name='message' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='send_single_message'>
                <arg name='jid' type='s' />
                <arg name='subject' type='s' />
                <arg name='message' type='s' />
                <arg name='keyID' type='s' />
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='send_xml'>
                <arg name='xml' type='s' />
                <arg name='account' type='s' />
            </method>
            <method name='set_priority'>
                <arg name='prio' type='s' />
                <arg name='account' type='s' />
            </method>
            <method name='show_next_pending_event' />
            <method name='show_roster' />
            <method name='start_chat'>
                <arg name='account' type='s' />
                <arg direction='out' type='b' />
            </method>
            <method name='toggle_ipython' />
            <method name='toggle_roster_appearance' />
            <signal name='AccountPresence'>
                <arg type='av' />
            </signal>
            <signal name='ChatState'>
                <arg type='av' />
            </signal>
            <signal name='ContactAbsence'>
                <arg type='av' />
            </signal>
            <signal name='ContactPresence'>
                <arg type='av' />
            </signal>
            <signal name='ContactStatus'>
                <arg type='av' />
            </signal>
            <signal name='EntityTime'>
                <arg type='av' />
            </signal>
            <signal name='GCMessage'>
                <arg type='av' />
            </signal>
            <signal name='GCPresence'>
                <arg type='av' />
            </signal>
            <signal name='MessageSent'>
                <arg type='av' />
            </signal>
            <signal name='NewAccount'>
                <arg type='av' />
            </signal>
            <signal name='NewMessage'>
                <arg type='av' />
            </signal>
            <signal name='OsInfo'>
                <arg type='av' />
            </signal>
            <signal name='Roster'>
                <arg type='av' />
            </signal>
            <signal name='RosterInfo'>
                <arg type='av' />
            </signal>
            <signal name='Subscribe'>
                <arg type='av' />
            </signal>
            <signal name='Subscribed'>
                <arg type='av' />
            </signal>
            <signal name='Unsubscribed'>
                <arg type='av' />
            </signal>
            <signal name='VcardInfo'>
                <arg type='av' />
            </signal>
        </interface>
    </node>
    '''

    def __init__(self):
        self.con = Gio.bus_get_sync(Gio.BusType.SESSION, None)
        Gio.bus_own_name_on_connection(self.con, 'org.gajim.Gajim',
                                       Gio.BusNameOwnerFlags.NONE, None, None)
        super().__init__(self.con, '/org/gajim/dbus/RemoteObject')
        self.first_show = True
302

303
        app.ged.register_event_handler('version-result-received', ged.POSTGUI,
304
            self.on_os_info)
305
        app.ged.register_event_handler('time-result-received', ged.POSTGUI,
306
            self.on_time)
307
        app.ged.register_event_handler('roster-info', ged.POSTGUI,
308
            self.on_roster_info)
309
        app.ged.register_event_handler('presence-received', ged.POSTGUI,
310
            self.on_presence_received)
311
        app.ged.register_event_handler('subscribe-presence-received',
312
            ged.POSTGUI, self.on_subscribe_presence_received)
313
        app.ged.register_event_handler('subscribed-presence-received',
314
            ged.POSTGUI, self.on_subscribed_presence_received)
315
        app.ged.register_event_handler('unsubscribed-presence-received',
316
            ged.POSTGUI, self.on_unsubscribed_presence_received)
317
        app.ged.register_event_handler('gc-message-received',
318
            ged.POSTGUI, self.on_gc_message_received)
319
        app.ged.register_event_handler('our-show', ged.POSTGUI,
320
            self.on_our_status)
321
        app.ged.register_event_handler('account-created', ged.POSTGUI,
322
            self.on_account_created)
323
        app.ged.register_event_handler('vcard-received', ged.POSTGUI,
324
            self.on_vcard_received)
325
        app.ged.register_event_handler('chatstate-received', ged.POSTGUI,
326
            self.on_chatstate_received)
327
        app.ged.register_event_handler('message-sent', ged.POSTGUI,
328
329
330
331
            self.on_message_sent)

    def on_chatstate_received(self, obj):
        self.raise_signal('ChatState', (obj.conn.name, [
332
            obj.jid, obj.fjid, obj.stanza, obj.resource, obj.chatstate]))
333
334
335
336
337

    def on_message_sent(self, obj):
        try:
            chatstate = obj.chatstate
        except AttributeError:
André's avatar
André committed
338
            chatstate = ''
339
340
        self.raise_signal('MessageSent', (obj.conn.name, [
            obj.jid, obj.message, obj.keyID, chatstate]))
341
342

    def on_os_info(self, obj):
343
        self.raise_signal('OsInfo', (obj.conn.name, [obj.jid.getStripped(),
344
                                                     obj.jid.getResource(),
345
346
                                                     obj.client_info,
                                                     obj.os_info]))
347
348

    def on_time(self, obj):
349
350
351
        self.raise_signal('EntityTime', (obj.conn.name, [obj.jid.getStripped(),
                                                         obj.jid.getResource(),
                                                         obj.time_info]))
352

353
    def on_roster_info(self, obj):
Yann Leboulanger's avatar
Yann Leboulanger committed
354
        self.raise_signal('RosterInfo', (obj.conn.name, [obj.jid, obj.nickname,
355
356
            obj.sub, obj.ask, obj.groups]))

357
358
359
360
361
362
363
    def on_presence_received(self, obj):
        if obj.old_show < 2 and obj.new_show > 1:
            event = 'ContactPresence'
        elif obj.old_show > 1 and obj.new_show < 2:
            event = 'ContactAbsence'
        elif obj.new_show > 1:
            event = 'ContactStatus'
André's avatar
André committed
364
365
366
        else:
            return
        self.raise_signal(event, (obj.conn.name, [obj.jid, obj.show,
367
368
369
                obj.status, obj.resource, obj.prio, obj.keyID, obj.timestamp,
                obj.contact_nickname]))

370
371
372
373
374
375
376
377
378
379
380
    def on_subscribe_presence_received(self, obj):
        self.raise_signal('Subscribe', (obj.conn.name, [obj.jid, obj.status,
            obj.user_nick]))

    def on_subscribed_presence_received(self, obj):
        self.raise_signal('Subscribed', (obj.conn.name, [obj.jid,
            obj.resource]))

    def on_unsubscribed_presence_received(self, obj):
        self.raise_signal('Unsubscribed', (obj.conn.name, obj.jid))

381
    def on_gc_message_received(self, obj):
382
383
384
        if not hasattr(obj, 'needs_highlight'):
            # event has not been handled at GUI level
            return
385
386
        self.raise_signal('GCMessage', (obj.conn.name, [obj.fjid, obj.msgtxt,
            obj.timestamp, obj.has_timestamp, obj.xhtml_msgtxt, obj.status_code,
387
            obj.displaymarking, obj.captcha_form, obj.needs_highlight]))
388

389
390
391
    def on_our_status(self, obj):
        self.raise_signal('AccountPresence', (obj.show, obj.conn.name))

392
393
394
    def on_account_created(self, obj):
        self.raise_signal('NewAccount', (obj.conn.name, obj.account_info))

395
396
397
    def on_vcard_received(self, obj):
        self.raise_signal('VcardInfo', (obj.conn.name, obj.vcard_dict))

André's avatar
André committed
398
399
400
401
402
403
    def raise_signal(self, event_name, data):
        self.con.emit_signal(None,
                             '/org/gajim/dbus/RemoteObject',
                             'org.gajim.dbus.RemoteInterface',
                             event_name,
                             GLib.Variant.new_tuple(get_dbus_struct(data)))
404
405
406

    def get_status(self, account):
        """
André's avatar
André committed
407
408
        Return status (show to be exact) which is the global one unless
        account is given
409
410
411
        """
        if not account:
            # If user did not ask for account, returns the global status
André's avatar
André committed
412
            return helpers.get_global_show()
413
        # return show for the given account
414
        index = app.connections[account].connected
André's avatar
André committed
415
        return app.SHOW_LIST[index]
416
417
418
419
420
421
422

    def get_status_message(self, account):
        """
        Return status which is the global one unless account is given
        """
        if not account:
            # If user did not ask for account, returns the global status
André's avatar
André committed
423
            return str(helpers.get_global_status())
424
        # return show for the given account
425
        status = app.connections[account].status
André's avatar
André committed
426
        return status
427
428
429
430
431
432
433

    def _get_account_and_contact(self, account, jid):
        """
        Get the account (if not given) and contact instance from jid
        """
        connected_account = None
        contact = None
434
        accounts = app.contacts.get_accounts()
435
436
437
438
439
        # if there is only one account in roster, take it as default
        # if user did not ask for account
        if not account and len(accounts) == 1:
            account = accounts[0]
        if account:
André's avatar
André committed
440
            if app.connections[account].connected > 1:  # account is connected
441
                connected_account = account
André's avatar
André committed
442
443
                contact = app.contacts.get_contact_with_highest_priority(
                    account, jid)
444
445
        else:
            for account in accounts:
André's avatar
André committed
446
447
                contact = app.contacts.get_contact_with_highest_priority(
                    account, jid)
448
                if contact and app.connections[account].connected > 1:
449
450
451
452
453
454
455
456
457
458
459
460
461
462
                    # account is connected
                    connected_account = account
                    break
        if not contact:
            contact = jid

        return connected_account, contact

    def _get_account_for_groupchat(self, account, room_jid):
        """
        Get the account which is connected to groupchat (if not given)
        or check if the given account is connected to the groupchat
        """
        connected_account = None
463
        accounts = app.contacts.get_accounts()
464
465
466
467
468
        # if there is only one account in roster, take it as default
        # if user did not ask for account
        if not account and len(accounts) == 1:
            account = accounts[0]
        if account:
469
            if app.connections[account].connected > 1 and \
André's avatar
André committed
470
471
                    room_jid in app.gc_connected[account] and \
                    app.gc_connected[account][room_jid]:
472
473
474
475
                # account and groupchat are connected
                connected_account = account
        else:
            for account in accounts:
476
                if app.connections[account].connected > 1 and \
André's avatar
André committed
477
478
                        room_jid in app.gc_connected[account] and \
                        app.gc_connected[account][room_jid]:
479
480
481
482
483
484
485
486
487
488
489
                    # account and groupchat are connected
                    connected_account = account
                    break
        return connected_account

    def send_file(self, file_path, jid, account):
        """
        Send file, located at 'file_path' to 'jid', using account (optional)
        'account'
        """
        jid = self._get_real_jid(jid, account)
André's avatar
André committed
490
491
        connected_account, contact = self._get_account_and_contact(
            account, jid)
492
493
494

        if connected_account:
            if file_path.startswith('file://'):
André's avatar
André committed
495
496
                file_path = file_path[7:]
            if os.path.isfile(file_path):  # is it file?
497
                app.interface.instances['file_transfers'].send_file(
André's avatar
André committed
498
499
500
                    connected_account, contact, file_path)
                return True
        return False
501

André's avatar
André committed
502
503
504
505
506
507
508
    def _send_message(self,
                      jid,
                      message,
                      keyID,
                      account,
                      type_='chat',
                      subject=None):
509
510
511
512
513
        """
        Can be called from send_chat_message (default when send_message) or
        send_single_message
        """
        if not jid or not message:
André's avatar
André committed
514
            return False
515
516
517
        if not keyID:
            keyID = ''

André's avatar
André committed
518
519
        connected_account, contact = self._get_account_and_contact(
            account, jid)
520
        if connected_account:
521
            connection = app.connections[connected_account]
522
523
524
525
526
            sessions = connection.get_sessions(jid)
            if sessions:
                session = sessions[0]
            else:
                session = connection.make_new_session(jid)
André's avatar
André committed
527
528
            ctrl = app.interface.msg_win_mgr.search_control(
                jid, connected_account)
529
            if ctrl:
530
531
                ctrl.send_message(message)
            else:
André's avatar
André committed
532
533
534
535
536
537
538
539
540
                app.nec.push_outgoing_event(
                    MessageOutgoingEvent(
                        None,
                        account=connected_account,
                        jid=jid,
                        message=message,
                        keyID=keyID,
                        type_=type_,
                        control=ctrl))
541

André's avatar
André committed
542
543
            return True
        return False
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558

    def send_chat_message(self, jid, message, keyID, account):
        """
        Send chat 'message' to 'jid', using account (optional) 'account'. If keyID
        is specified, encrypt the message with the pgp key
        """
        jid = self._get_real_jid(jid, account)
        return self._send_message(jid, message, keyID, account)

    def send_single_message(self, jid, subject, message, keyID, account):
        """
        Send single 'message' to 'jid', using account (optional) 'account'. If
        keyID is specified, encrypt the message with the pgp key
        """
        jid = self._get_real_jid(jid, account)
André's avatar
André committed
559
560
        return self._send_message(jid, message, keyID, account, 'normal',
                                  subject)
561
562
563
564
565
566

    def send_groupchat_message(self, room_jid, message, account):
        """
        Send 'message' to groupchat 'room_jid', using account (optional) 'account'
        """
        if not room_jid or not message:
André's avatar
André committed
567
            return False
568
569
        connected_account = self._get_account_for_groupchat(account, room_jid)
        if connected_account:
570
            connection = app.connections[connected_account]
André's avatar
André committed
571
572
573
574
575
576
577
578
            app.nec.push_outgoing_event(
                GcMessageOutgoingEvent(
                    None,
                    account=connected_account,
                    jid=room_jid,
                    message=message))
            return True
        return False
579
580
581
582
583
584
585

    def open_chat(self, jid, account, message):
        """
        Shows the tabbed window for new message to 'jid', using account (optional)
        'account'
        """
        if not jid:
586
            raise ValueError('jid is missing')
587
588
589
590
591
        jid = self._get_real_jid(jid, account)
        try:
            jid = helpers.parse_jid(jid)
        except Exception:
            # Jid is not conform, ignore it
André's avatar
André committed
592
            return False
593

Dicson's avatar
Dicson committed
594
        minimized_control = None
595
596
597
        if account:
            accounts = [account]
        else:
598
            accounts = app.connections.keys()
599
600
601
602
603
            if len(accounts) == 1:
                account = accounts[0]
        connected_account = None
        first_connected_acct = None
        for acct in accounts:
André's avatar
André committed
604
            if app.connections[acct].connected > 1:  # account is  online
605
606
                contact = app.contacts.get_first_contact_from_jid(acct, jid)
                if app.interface.msg_win_mgr.has_window(jid, acct):
607
608
609
610
                    connected_account = acct
                    break
                # jid is in roster
                elif contact:
611
                    minimized_control = \
612
                        jid in app.interface.minimized_controls[acct]
613
614
615
616
617
618
619
620
621
                    connected_account = acct
                    break
                # we send the message to jid not in roster, because account is
                # specified, or there is only one account
                elif account:
                    connected_account = acct
                elif first_connected_acct is None:
                    first_connected_acct = acct

Alexander Krotov's avatar
Alexander Krotov committed
622
        # if jid is not a contact, open-chat with first connected account
623
624
625
        if connected_account is None and first_connected_acct:
            connected_account = first_connected_acct

626
        if minimized_control:
André's avatar
André committed
627
628
            app.interface.roster.on_groupchat_maximized(
                None, jid, connected_account)
629

630
        if connected_account:
631
            app.interface.new_chat_from_jid(connected_account, jid, message)
632
            # preserve the 'steal focus preservation'
André's avatar
André committed
633
634
            win = app.interface.msg_win_mgr.get_window(
                jid, connected_account).window
635
            if win.get_property('visible'):
636
                win.window.present()
André's avatar
André committed
637
638
            return True
        return False
639
640
641
642
643
644

    def change_status(self, status, message, account):
        """
        change_status(status, message, account). Account is optional - if not
        specified status is changed for all accounts
        """
André's avatar
André committed
645
646
        if status not in ('offline', 'online', 'chat', 'away', 'xa', 'dnd',
                          'invisible'):
647
648
649
            status = ''
        if account:
            if not status:
650
                if account not in app.connections:
André's avatar
André committed
651
                    return False
652
653
                status = app.SHOW_LIST[app.connections[account].connected]
            GLib.idle_add(app.interface.roster.send_status, account, status,
André's avatar
André committed
654
                          message)
655
656
        else:
            # account not specified, so change the status of all accounts
657
658
            for acc in app.contacts.get_accounts():
                if not app.config.get_per('accounts', acc,
André's avatar
André committed
659
                                          'sync_with_global_status'):
660
661
662
663
                    continue
                if status:
                    status_ = status
                else:
664
                    if acc not in app.connections:
665
                        continue
666
667
                    status_ = app.SHOW_LIST[app.connections[acc].connected]
                GLib.idle_add(app.interface.roster.send_status, acc, status_,
André's avatar
André committed
668
669
                              message)
        return False
670
671
672
673
674
675
676

    def set_priority(self, prio, account):
        """
        set_priority(prio, account). Account is optional - if not specified
        priority is changed for all accounts. That are synced with global status
        """
        if account:
677
678
679
            app.config.set_per('accounts', account, 'priority', prio)
            show = app.SHOW_LIST[app.connections[account].connected]
            status = app.connections[account].status
André's avatar
André committed
680
            GLib.idle_add(app.connections[account].change_status, show, status)
681
682
        else:
            # account not specified, so change prio of all accounts
683
684
            for acc in app.contacts.get_accounts():
                if not app.account_is_connected(acc):
685
                    continue
686
                if not app.config.get_per('accounts', acc,
André's avatar
André committed
687
                                          'sync_with_global_status'):
688
                    continue
689
690
691
                app.config.set_per('accounts', acc, 'priority', prio)
                show = app.SHOW_LIST[app.connections[acc].connected]
                status = app.connections[acc].status
André's avatar
André committed
692
                GLib.idle_add(app.connections[acc].change_status, show, status)
693
694
695
696
697

    def show_next_pending_event(self):
        """
        Show the window(s) with next pending event in tabbed/group chats
        """
698
699
        if app.events.get_nb_events():
            account, jid, event = app.events.get_first_systray_event()
700
701
            if not event:
                return
702
            app.interface.handle_event(account, jid, event.type_)
703
704
705
706
707

    def list_accounts(self):
        """
        List register accounts
        """
708
        result = app.contacts.get_accounts()
André's avatar
André committed
709
710
        result_array = []
        if result:
711
            for account in result:
André's avatar
André committed
712
                result_array.append(account)
713
714
715
716
717
718
        return result_array

    def account_info(self, account):
        """
        Show info on account: resource, jid, nick, prio, message
        """
André's avatar
André committed
719
        result = {}
720
        if account in app.connections:
721
            # account is valid
722
            con = app.connections[account]
723
            index = con.connected
André's avatar
André committed
724
725
726
727
728
729
730
            result['status'] = app.SHOW_LIST[index]
            result['name'] = con.name
            result['jid'] = app.get_jid_from_account(con.name)
            result['message'] = con.status
            result['priority'] = str(con.priority)
            result['resource'] = app.config.get_per('accounts', con.name,
                                                    'resource')
731
732
733
734
        return result

    def list_contacts(self, account):
        """
André's avatar
André committed
735
736
        List all contacts in the roster. If the first argument is specified,
        then return the contacts for the specified account
737
        """
André's avatar
André committed
738
        result = []
739
        accounts = app.contacts.get_accounts()
André's avatar
André committed
740
        if not accounts:
741
742
743
744
745
746
747
            return result
        if account:
            accounts_to_search = [account]
        else:
            accounts_to_search = accounts
        for acct in accounts_to_search:
            if acct in accounts:
748
                for jid in app.contacts.get_jid_list(acct):
749
                    item = self._contacts_as_dbus_structure(
750
                            app.contacts.get_contacts(acct, jid))
751
752
753
754
755
                    if item:
                        result.append(item)
        return result

    def prefs_list(self):
André's avatar
André committed
756
757
        prefs_dict = {}

758
759
760
761
762
763
764
765
        def get_prefs(data, name, path, value):
            if value is None:
                return
            key = ''
            if path is not None:
                for node in path:
                    key += node + '#'
            key += name
André's avatar
André committed
766
767
            prefs_dict[key] = str(value)

768
        app.config.foreach(get_prefs)
769
770
771
772
        return prefs_dict

    def prefs_store(self):
        try:
773
            app.interface.save_config()
Yann Leboulanger's avatar
Yann Leboulanger committed
774
        except Exception:
André's avatar
André committed
775
776
            return False
        return True
777
778
779

    def prefs_del(self, key):
        if not key:
André's avatar
André committed
780
            return False
781
782
        key_path = key.split('#', 2)
        if len(key_path) != 3:
André's avatar
André committed
783
            return False
784
        if key_path[2] == '*':
785
            app.config.del_per(key_path[0], key_path[1])
786
        else:
787
            app.config.del_per(key_path[0], key_path[1], key_path[2])
André's avatar
André committed
788
        return True
789
790
791

    def prefs_put(self, key):
        if not key:
André's avatar
André committed
792
            return False
793
794
795
        key_path = key.split('#', 2)
        if len(key_path) < 3:
            subname, value = key.split('=', 1)
796
            app.config.set(subname, value)
André's avatar
André committed
797
            return True
798
        subname, value = key_path[2].split('=', 1)
799
        app.config.set_per(key_path[0], key_path[1], subname, value)
André's avatar
André committed
800
        return True
801
802
803

    def add_contact(self, jid, account):
        if account:
804
805
            if account in app.connections and \
                    app.connections[account].connected > 1:
806
                # if given account is active, use it
André's avatar
André committed
807
                AddNewContactWindow(account=account, jid=jid)
808
809
            else:
                # wrong account
André's avatar
André committed
810
                return False
811
812
        else:
            # if account is not given, show account combobox
André's avatar
André committed
813
814
            AddNewContactWindow(account=None, jid=jid)
        return True
815
816
817

    def remove_contact(self, jid, account):
        jid = self._get_real_jid(jid, account)
818
        accounts = app.contacts.get_accounts()
819
820
821
822
823
824

        # if there is only one account in roster, take it as default
        if account:
            accounts = [account]
        contact_exists = False
        for account in accounts:
825
            contacts = app.contacts.get_contacts(account, jid)
826
            if contacts:
827
                app.connections[account].get_module('Presence').unsubscribe(jid)
828
                for contact in contacts:
829
830
                    app.interface.roster.remove_contact(contact, account)
                app.contacts.remove_jid(account, jid)
831
                contact_exists = True
André's avatar
André committed
832
        return contact_exists
833
834
835
836
837
838
839

    def _is_first(self):
        if self.first_show:
            self.first_show = False
            return True
        return False

André's avatar
André committed
840
    def _get_real_jid(self, jid, account=None):
841
842
843
844
845
846
847
        """
        Get the real jid from the given one: removes xmpp: or get jid from nick if
        account is specified, search only in this account
        """
        if account:
            accounts = [account]
        else:
848
            accounts = app.connections.keys()
849
        if jid.startswith('xmpp:'):
André's avatar
André committed
850
851
            return jid[5:]  # len('xmpp:') = 5
        nick_in_roster = None  # Is jid a nick ?
852
853
        for account in accounts:
            # Does jid exists in roster of one account ?
854
            if app.contacts.get_contacts(account, jid):
855
856
857
                return jid
            if not nick_in_roster:
                # look in all contact if one has jid as nick
858
859
                for jid_ in app.contacts.get_jid_list(account):
                    c = app.contacts.get_contacts(account, jid_)
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
                    if c[0].name == jid:
                        nick_in_roster = jid_
                        break
        if nick_in_roster:
            # We have not found jid in roster, but we found is as a nick
            return nick_in_roster
        # We have not found it as jid nor as nick, probably a not in roster jid
        return jid

    def _contacts_as_dbus_structure(self, contacts):
        """
        Get info from list of Contact objects and create dbus dict
        """
        if not contacts:
            return None
André's avatar
André committed
875
        prim_contact = None  # primary contact
876
877
878
        for contact in contacts:
            if prim_contact is None or contact.priority > prim_contact.priority:
                prim_contact = contact
André's avatar
André committed
879
        contact_dict = {}
André's avatar
André committed
880
881
        name = prim_contact.name if prim_contact.name is not None else ''
        contact_dict['name'] = GLib.Variant('s', name)
André's avatar
André committed
882
883
        contact_dict['show'] = GLib.Variant('s', prim_contact.show)
        contact_dict['jid'] = GLib.Variant('s', prim_contact.jid)
884
885
886
887
888
889
890
        if prim_contact.keyID:
            keyID = None
            if len(prim_contact.keyID) == 8:
                keyID = prim_contact.keyID
            elif len(prim_contact.keyID) == 16:
                keyID = prim_contact.keyID[8:]
            if keyID:
André's avatar
André committed
891
892
                contact_dict['openpgp'] = GLib.Variant('s', keyID)
        resources = GLib.VariantBuilder(GLib.VariantType('a(sis)'))
893
        for contact in contacts:
André's avatar
André committed
894
895
            resource_props = (contact.resource, int(contact.priority),
                              contact.status)
André's avatar
André committed
896
            resources.add_value(GLib.Variant('(sis)', resource_props))
André's avatar
André committed
897
        contact_dict['resources'] = resources.end()
André's avatar
André committed
898
899
900
901
902

        groups = GLib.VariantBuilder(GLib.VariantType('as'))
        for group in prim_contact.groups:
            groups.add_value(GLib.Variant('s', group))
        contact_dict['groups'] = groups.end()
903
904
905
        return contact_dict

    def get_unread_msgs_number(self):
André's avatar
André committed
906
        return str(app.events.get_nb_events())
907
908
909
910

    def start_chat(self, account):
        if not account:
            # error is shown in gajim-remote check_arguments(..)
André's avatar
André committed
911
            return False
Philipp Hörist's avatar
Philipp Hörist committed
912
        app.app.activate_action('start-chat')
André's avatar
André committed
913
        return True
914
915
916

    def send_xml(self, xml, account):
        if account:
917
            app.connections[account].send_stanza(str(xml))
918
        else:
919
920
            for acc in app.contacts.get_accounts():
                app.connections[acc].send_stanza(str(xml))
921
922
923
924
925
926
927
928
929
930
931

    def change_avatar(self, picture, account):
        filesize = os.path.getsize(picture)
        invalid_file = False
        if os.path.isfile(picture):
            stat = os.stat(picture)
            if stat[6] == 0:
                invalid_file = True
        else:
            invalid_file = True
        if not invalid_file and filesize < 16384:
Philipp Hörist's avatar
Philipp Hörist committed
932
            sha = app.interface.save_avatar(picture, publish=True)
Philipp Hörist's avatar
Philipp Hörist committed
933
934
935
936
            if sha is None:
                return
            app.config.set_per('accounts', self.name, 'avatar_sha', sha)
            data = app.interface.get_avatar(sha, publish=True)
Dicson's avatar
Dicson committed
937
            avatar = base64.b64encode(data).decode('utf-8')
938
            avatar_mime_type = mimetypes.guess_type(picture)[0]
Philipp Hörist's avatar
Philipp Hörist committed
939
            vcard = {}
940
941
942
943
            vcard['PHOTO'] = {'BINVAL': avatar}
            if avatar_mime_type:
                vcard['PHOTO']['TYPE'] = avatar_mime_type
            if account:
944
945
                app.connections[account].get_module('VCardTemp').send_vcard(
                    vcard, sha)
946
            else:
947
                for acc in app.connections:
948
949
                    app.connections[acc].get_module('VCardTemp').send_vcard(
                        vcard, sha)
950
951
952
953

    def join_room(self, room_jid, nick, password, account):
        if not account:
            # get the first connected account
954
            accounts = app.connections.keys()
955
            for acct in accounts:
956
957
                if app.account_is_connected(acct):
                    if not app.connections[acct].is_zeroconf:
958
959
                        account = acct
                        break
960
961
            if not account:
                return
962

963
        if app.connections[account].is_zeroconf:
964
965
966
            # zeroconf not support groupchats
            return

967
        if not nick:
968
            app.interface.join_gc_minimal(account, room_jid)
969
        else:
970
            app.interface.join_gc_room(account, room_jid, nick, password)
André's avatar
André committed
971
972
973

    def Introspect(self):
        return self.__doc__