1/*
2 kimproxy.cpp
3
4 IM service library for KDE
5
6 Copyright (c) 2004 Will Stephenson <lists@stevello.free-online.co.uk>
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22*/
23
24#include "kimproxy.h"
25
26#include <QtCore/QBool>
27#include <QtGui/QPixmapCache>
28
29#include <kapplication.h>
30#include <kdbusservicestarter.h>
31#include <kdebug.h>
32#include <kmessagebox.h>
33#include <kconfig.h>
34#include <kconfiggroup.h>
35#include <kiconloader.h>
36#include <kservice.h>
37#include <kservicetypetrader.h>
38
39#include "kimiface.h"
40
41#include <kservicetype.h>
42
43struct AppPresenceCurrent
44{
45 QString appId;
46 int presence;
47};
48
49static int debugArea() {
50 static int s_area = KDebug::registerArea("kimproxy (kdelibs)");
51 return s_area;
52}
53
54class ContactPresenceListCurrent : public QList<AppPresenceCurrent>
55{
56 public:
57 // return value indicates if the supplied parameter was better than any existing presence
58 bool update( const AppPresenceCurrent );
59 AppPresenceCurrent best();
60};
61
62
63class KIMProxy::Private
64{
65public:
66 // list of the strings in use by KIMIface
67 QStringList presence_strings;
68 // list of the icon names in use by KIMIface
69 QStringList presence_icons;
70 // map of presences
71 PresenceStringMap presence_map;
72};
73
74bool ContactPresenceListCurrent::update( AppPresenceCurrent ap )
75{
76 if ( isEmpty() )
77 {
78 append( ap );
79 return true;
80 }
81
82 bool bestChanged = false;
83 AppPresenceCurrent best;
84 best.presence = -1;
85 ContactPresenceListCurrent::iterator it = begin();
86 const ContactPresenceListCurrent::iterator itEnd = end();
87 ContactPresenceListCurrent::iterator existing = itEnd;
88
89 while ( it != itEnd )
90 {
91 if ( (*it).presence > best.presence )
92 best = (*it);
93 if ( (*it).appId == ap.appId )
94 existing = it;
95 ++it;
96 }
97
98 if ( ap.presence > best.presence ||
99 best.appId == ap.appId )
100 bestChanged = true;
101
102 if ( existing != itEnd )
103 {
104 erase( existing );
105 append( ap );
106 }
107 return bestChanged;
108}
109
110AppPresenceCurrent ContactPresenceListCurrent::best()
111{
112 AppPresenceCurrent best;
113 best.presence = -1;
114 ContactPresenceListCurrent::iterator it = begin();
115 const ContactPresenceListCurrent::iterator itEnd = end();
116 while ( it != itEnd )
117 {
118 if ( (*it).presence > best.presence )
119 best = (*it);
120 ++it;
121 }
122 // if it's still -1 here, we have no presence data, so we return Unknown
123 if ( best.presence == -1 )
124 best.presence = 0;
125 return best;
126}
127
128// int bestPresence( AppPresence* ap )
129// {
130// Q_ASSERT( ap );
131// AppPresence::const_iterator it;
132// it = ap->begin();
133// int best = 0; // unknown
134// if ( it != ap->end() )
135// {
136// best = it.data();
137// ++it;
138// for ( ; it != ap->end(); ++it )
139// {
140// if ( it.data() > best )
141// best = it.data();
142// }
143// }
144// return best;
145// }
146//
147// QCString bestAppId( AppPresence* ap )
148// {
149// Q_ASSERT( ap );
150// AppPresence::const_iterator it;
151// QCString bestAppId;
152// it = ap->begin();
153// if ( it != ap->end() )
154// {
155// int best = it.data();
156// bestAppId = it.key();
157// ++it;
158// for ( ; it != ap->end(); ++it )
159// {
160// if ( it.data() > best )
161// {
162// best = it.data();
163// bestAppId = it.key();
164// }
165// }
166// }
167// return bestAppId;
168// }
169
170OrgKdeKIMInterface * findInterface( const QString & app )
171{
172 return new OrgKdeKIMInterface( app, "/KIMIface", QDBusConnection::sessionBus() );
173}
174
175KIMProxy * KIMProxy::instance()
176{
177 K_GLOBAL_STATIC(KIMProxy, s_instance)
178 return s_instance;
179}
180
181KIMProxy::KIMProxy() : QObject(), d( new Private )
182{
183 //QDBus::sessionBus().registerObject( "/KIMProxy", this);
184 m_initialized = false;
185 connect( QDBusConnection::sessionBus().interface(),
186 SIGNAL(serviceOwnerChanged(QString,QString,QString)),
187 SLOT(nameOwnerChanged(QString,QString,QString)) );
188
189 d->presence_strings.append( "Unknown" );
190 d->presence_strings.append( "Offline" );
191 d->presence_strings.append( "Connecting" );
192 d->presence_strings.append( "Away" );
193 d->presence_strings.append( "Online" );
194
195 d->presence_icons.append( "presence_unknown" );
196 d->presence_icons.append( "presence_offline" );
197 d->presence_icons.append( "presence_connecting" );
198 d->presence_icons.append( "presence_away" );
199 d->presence_icons.append( "presence_online" );
200
201 //QCString senderApp = "Kopete";
202 //QCString senderObjectId = "KIMIface";
203 //DCOPCString method = "contactPresenceChanged( QString, QCString, int )";
204 //QCString receiverObjectId = "KIMProxyIface";
205
206 QDBusConnection::sessionBus().connect( QString(), "/KIMIface", "org.kde.KIM", "contactPresenceChanged",
207 this, SLOT(contactPresenceChanged(QString,QString,int)) );
208}
209
210KIMProxy::~KIMProxy( )
211{
212 qDeleteAll(m_im_client_stubs);
213}
214
215bool KIMProxy::initialize()
216{
217 if ( !m_initialized )
218 {
219 m_initialized = true; // we should only do this once, as registeredToDCOP() will catch any new starts
220 // So there is no error from a failed query when using kdelibs 3.2, which don't have this servicetype
221 if ( KServiceType::serviceType( IM_SERVICE_TYPE ) )
222 {
223 // see what apps implementing our service type are out there
224 const KService::List offers = KServiceTypeTrader::self()->query( IM_SERVICE_TYPE );
225 KService::List::const_iterator offer;
226 QStringList registeredApps = QDBusConnection::sessionBus().interface()->registeredServiceNames();
227 foreach (const QString &app, registeredApps)
228 {
229 //kDebug( debugArea() ) << " considering: " << *app;
230 //for each offer
231 for ( offer = offers.begin(); offer != offers.end(); ++offer )
232 {
233 QString dbusService = (*offer)->property("X-DBUS-ServiceName").toString();
234 if ( !dbusService.isEmpty() )
235 {
236 //kDebug( debugArea() ) << " is it: " << dbusService << "?";
237 // if the application implements the dcop service, add it
238 if ( app.startsWith( dbusService ) )
239 {
240 m_apps_available = true;
241 //kDebug( debugArea() ) << " app name: " << (*offer)->name() << ", has instance " << *app << ", dbusService: " << dbusService;
242 if ( !m_im_client_stubs.contains( dbusService ) )
243 {
244 kDebug( debugArea() ) << "App " << app << ", found, using it for presence info.";
245 m_im_client_stubs.insert( app, findInterface( app ) );
246 pollApp( app );
247 }
248 }
249 }
250 }
251 }
252 }
253 }
254 return !m_im_client_stubs.isEmpty();
255}
256
257void KIMProxy::nameOwnerChanged( const QString & appId, const QString &, const QString & newOwner )
258{
259 // unregister...
260 if ( m_im_client_stubs.contains( appId ) )
261 {
262 kDebug( debugArea() ) << appId << " quit, removing its presence info.";
263
264 PresenceStringMap::Iterator it = d->presence_map.begin();
265 const PresenceStringMap::Iterator end = d->presence_map.end();
266 for ( ; it != end; ++it )
267 {
268 ContactPresenceListCurrent list = it.value();
269 ContactPresenceListCurrent::iterator cpIt = list.begin();
270 while( cpIt != list.end() )
271 {
272 ContactPresenceListCurrent::iterator gone = cpIt++;
273 if ( (*gone).appId == appId )
274 {
275 list.erase( gone );
276 }
277 }
278 }
279 delete m_im_client_stubs.take( appId );
280 emit sigPresenceInfoExpired();
281 }
282
283 // reregister...
284 if ( !newOwner.isEmpty() ) { // application registered
285 bool newApp = false;
286 // get an up to date list of offers in case a new app was installed
287 // and check each of the offers that implement the service type we're looking for,
288 // to see if any of them are the app that just registered
289 const KService::List offers = KServiceTypeTrader::self()->query( IM_SERVICE_TYPE );
290 KService::List::const_iterator it;
291 for ( it = offers.begin(); it != offers.end(); ++it )
292 {
293 QString dbusService = (*it)->property("X-DBUS-ServiceName").toString();
294 if ( appId.startsWith( dbusService ) )
295 {
296 // if it's not already known, insert it
297 if ( !m_im_client_stubs.contains( appId ) )
298 {
299 newApp = true;
300 kDebug( debugArea() ) << "App: " << appId << ", dbusService: " << dbusService << " started, using it for presence info.";
301 m_im_client_stubs.insert( appId, findInterface( appId ) );
302 }
303 }
304 //else
305 // kDebug( debugArea() ) << "App doesn't implement our ServiceType";
306 }
307 //if ( newApp )
308 // emit sigPresenceInfoExpired();
309 }
310}
311
312void KIMProxy::contactPresenceChanged( const QString& uid, const QString& appId, int presence )
313{
314 // update the presence map
315 //kDebug( debugArea() ) << "uid: " << uid << " appId: " << appId << " presence " << presence;
316 ContactPresenceListCurrent current;
317 current = d->presence_map[ uid ];
318 //kDebug( debugArea() ) << "current best presence from : " << current.best().appId << " is: " << current.best().presence;
319 AppPresenceCurrent newPresence;
320 newPresence.appId = appId;
321 newPresence.presence = presence;
322
323 if ( current.update( newPresence ) )
324 {
325 d->presence_map.insert( uid, current );
326 emit sigContactPresenceChanged( uid );
327 }
328}
329
330int KIMProxy::presenceNumeric( const QString& uid )
331{
332 AppPresenceCurrent ap;
333 ap.presence = 0;
334 if ( initialize() )
335 {
336 ContactPresenceListCurrent presence = d->presence_map[ uid ];
337 ap = presence.best();
338 }
339 return ap.presence;
340}
341
342QString KIMProxy::presenceString( const QString& uid )
343{
344 AppPresenceCurrent ap;
345 ap.presence = 0;
346 if ( initialize() )
347 {
348 ContactPresenceListCurrent presence = d->presence_map[ uid ];
349 ap = presence.best();
350 }
351 if ( ap.appId.isEmpty() )
352 return QString();
353 else
354 return d->presence_strings[ ap.presence ];
355}
356
357QPixmap KIMProxy::presenceIcon( const QString& uid )
358{
359 AppPresenceCurrent ap;
360 ap.presence = 0;
361 if ( initialize() )
362 {
363 ContactPresenceListCurrent presence = d->presence_map[ uid ];
364 ap = presence.best();
365 }
366 if ( ap.appId.isEmpty() )
367 {
368 //kDebug( debugArea() ) << "returning a null QPixmap because we were asked for an icon for a uid we know nothing about";
369 return QPixmap();
370 }
371 else
372 {
373 //kDebug( debugArea() ) << "returning this: " << d->presence_icons[ ap.presence ];
374 return SmallIcon( d->presence_icons[ ap.presence ]);
375 }
376}
377
378QStringList KIMProxy::allContacts()
379{
380 QStringList value = d->presence_map.keys();
381 return value;
382}
383
384QStringList KIMProxy::reachableContacts()
385{
386 QStringList value;
387
388 if ( initialize() )
389 {
390 QHashIterator<QString, OrgKdeKIMInterface*> it( m_im_client_stubs );
391 while (it.hasNext())
392 {
393 it.next();
394 value += it.value()->reachableContacts( );
395 }
396 }
397 return value;
398}
399
400QStringList KIMProxy::onlineContacts()
401{
402 QStringList value;
403 PresenceStringMap::iterator it = d->presence_map.begin();
404 const PresenceStringMap::iterator end= d->presence_map.end();
405 for ( ; it != end; ++it )
406 if ( it.value().best().presence > 2 /*Better than Connecting, ie Away or Online*/ )
407 value.append( it.key() );
408
409 return value;
410}
411
412QStringList KIMProxy::fileTransferContacts()
413{
414 QStringList value;
415
416 if ( initialize() )
417 {
418 QHashIterator<QString, OrgKdeKIMInterface*> it( m_im_client_stubs );
419 while (it.hasNext())
420 {
421 it.next();
422 value += it.value()->fileTransferContacts( );
423 }
424 }
425 return value;
426}
427
428bool KIMProxy::isPresent( const QString& uid )
429{
430 return ( !d->presence_map[ uid ].isEmpty() );
431}
432
433QString KIMProxy::displayName( const QString& uid )
434{
435 QString name;
436 if ( initialize() )
437 {
438 if ( OrgKdeKIMInterface* s = stubForUid( uid ) )
439 name = s->displayName( uid );
440 }
441 //kDebug( debugArea() ) << name;
442 return name;
443}
444
445bool KIMProxy::canReceiveFiles( const QString & uid )
446{
447 if ( initialize() )
448 {
449 if ( OrgKdeKIMInterface* s = stubForUid( uid ) )
450 return s->canReceiveFiles( uid );
451 }
452 return false;
453}
454
455bool KIMProxy::canRespond( const QString & uid )
456{
457 if ( initialize() )
458 {
459 if ( OrgKdeKIMInterface* s = stubForUid( uid ) )
460 return s->canRespond( uid );
461 }
462 return false;
463}
464
465QString KIMProxy::context( const QString & uid )
466{
467 if ( initialize() )
468 {
469 if ( OrgKdeKIMInterface* s = stubForUid( uid ) )
470 return s->context( uid );
471 }
472 return QString();
473}
474
475void KIMProxy::chatWithContact( const QString& uid )
476{
477 if ( initialize() )
478 {
479 if ( OrgKdeKIMInterface* s = stubForUid( uid ) )
480 {
481 kapp->updateRemoteUserTimestamp( s->service() );
482 s->chatWithContact( uid );
483 }
484 }
485 return;
486}
487
488void KIMProxy::messageContact( const QString& uid, const QString& message )
489{
490 if ( initialize() )
491 {
492 if ( OrgKdeKIMInterface* s = stubForUid( uid ) )
493 {
494 kapp->updateRemoteUserTimestamp( s->service() );
495 s->messageContact( uid, message );
496 }
497 }
498 return;
499}
500
501void KIMProxy::sendFile(const QString &uid, const QString &sourceURL, const QString &altFileName, uint fileSize )
502{
503 if ( initialize() )
504 {
505 QHashIterator<QString,OrgKdeKIMInterface*> it( m_im_client_stubs );
506 while ( it.hasNext() )
507 {
508 it.next();
509 if ( it.value()->canReceiveFiles( uid ) )
510 {
511 kapp->updateRemoteUserTimestamp( it.value()->service() );
512 it.value()->sendFile( uid, sourceURL, altFileName, fileSize );
513 break;
514 }
515 }
516 }
517 return;
518}
519
520bool KIMProxy::addContact( const QString &contactId, const QString &protocol )
521{
522 if ( initialize() )
523 {
524 if ( OrgKdeKIMInterface* s = stubForProtocol( protocol ) )
525 return s->addContact( contactId, protocol );
526 }
527 return false;
528}
529
530QString KIMProxy::locate( const QString & contactId, const QString & protocol )
531{
532 if ( initialize() )
533 {
534 if ( OrgKdeKIMInterface* s = stubForProtocol( protocol ) )
535 return s->locate( contactId, protocol );
536 }
537 return QString();
538}
539
540bool KIMProxy::imAppsAvailable()
541{
542 return ( !m_im_client_stubs.isEmpty() );
543}
544
545bool KIMProxy::startPreferredApp()
546{
547#ifdef __GNUC__
548# warning "unused variable: preferences"
549#endif
550 QString preferences = QString("[X-DBUS-ServiceName] = '%1'").arg( preferredApp() );
551 // start/find an instance of DBUS/InstantMessenger
552 QString error;
553 QString dbusService;
554 // Get a preferred IM client.
555 // The app will notify itself to us using nameOwnerChanged, so we don't need to record a stub for it here
556 // FIXME: error in preferences, see debug output
557 preferences.clear();
558 int result = KDBusServiceStarter::self()->findServiceFor( IM_SERVICE_TYPE, QString("Application"), &error, &dbusService );
559
560 kDebug( debugArea() ) << "error was: " << error << ", dbusService: " << dbusService;
561
562 return ( result == 0 );
563}
564
565
566void KIMProxy::pollAll( const QString &uid )
567{
568 Q_UNUSED(uid);
569/* // We only need to call this function if we don't have any data at all
570 // otherwise, the data will be kept fresh by received presence change
571 // DCOP signals
572 if ( !d->presence_map.contains( uid ) )
573 {
574 AppPresence *presence = new AppPresence();
575 // record current presence from known clients
576 QDictIterator<OrgKdeKIMInterface> it( m_im_client_stubs );
577 for ( ; it.current(); ++it )
578 {
579 presence->insert( it.currentKey().toLatin1().constData(), it.current()->presenceStatus( uid ) ); // m_im_client_stubs has qstring keys...
580 }
581 d->presence_map.insert( uid, presence );
582 }*/
583}
584
585void KIMProxy::pollApp( const QString & appId )
586{
587 //kDebug( debugArea() ) ;
588 OrgKdeKIMInterface * appStub = m_im_client_stubs.value( appId );
589 QStringList contacts = m_im_client_stubs.value( appId )->allContacts();
590 QStringList::iterator it = contacts.begin();
591 QStringList::iterator end = contacts.end();
592 for ( ; it != end; ++it )
593 {
594 ContactPresenceListCurrent current = d->presence_map[ *it ];
595 AppPresenceCurrent ap;
596 ap.appId = appId;
597#ifdef __GNUC__
598# warning "KIMProxy::pollApp( const QString & appId ).presenceStatus() function doesn't exist Need to fix it"
599#endif
600 //ap.presence = appStub->presenceStatus( *it );
601 current.append( ap );
602
603 d->presence_map.insert( *it, current );
604 if ( current.update( ap ) )
605 emit sigContactPresenceChanged( *it );
606 //kDebug( debugArea() ) << " uid: " << *it << " presence: " << ap.presence;
607 }
608}
609
610OrgKdeKIMInterface * KIMProxy::stubForUid( const QString &uid )
611{
612 // get best appPresence
613 AppPresenceCurrent ap = d->presence_map[ uid ].best();
614 // look up the presence string from that app
615 return m_im_client_stubs.value( ap.appId );
616}
617
618OrgKdeKIMInterface * KIMProxy::stubForProtocol( const QString &protocol)
619{
620 Q_UNUSED(protocol)
621#ifdef __GNUC__
622# warning "KIMProxy::stubForProtocol( const QString &protocol) code disabled: protocols() function doesn't exist. Need to fix it"
623#endif
624#if 0
625 OrgKdeKIMInterface * app;
626 // see if the preferred client supports this protocol
627 QString preferred = preferredApp();
628 if ( ( app = m_im_client_stubs.value( preferred ) ) )
629 {
630 if ( app->protocols().value().filter( protocol ).count() > 0 )
631 return app;
632 }
633 // preferred doesn't do this protocol, try the first of the others that says it does
634 QHashIterator<QString, OrgKdeKIMInterface*> it( m_im_client_stubs );
635 while ( it.hasNext() )
636 {
637 it.next();
638 if ( it.value()->protocols().value().filter( protocol ).count() > 0 )
639 return it.value();
640 }
641#endif
642 return 0L;
643}
644
645QString KIMProxy::preferredApp()
646{
647 KConfig cfg( IM_CLIENT_PREFERENCES_FILE, KConfig::SimpleConfig );
648 KConfigGroup cg(&cfg, IM_CLIENT_PREFERENCES_SECTION );
649 QString preferredApp = cg.readEntry( IM_CLIENT_PREFERENCES_ENTRY );
650 //kDebug( debugArea() ) << "found preferred app: " << preferredApp;
651 return preferredApp;
652}
653
654#include "kimproxy.moc"
655